From 3b9b6d0b8e7f798023c9d109c490449d528fde80 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:59:48 +0200 Subject: Adding upstream version 1:9.18.19. Signed-off-by: Daniel Baumann --- lib/dns/Makefile.am | 325 + lib/dns/Makefile.in | 2238 +++ lib/dns/acl.c | 863 ++ lib/dns/adb.c | 4749 +++++++ lib/dns/badcache.c | 522 + lib/dns/byaddr.c | 282 + lib/dns/cache.c | 1444 ++ lib/dns/callbacks.c | 107 + lib/dns/catz.c | 2698 ++++ lib/dns/client.c | 1326 ++ lib/dns/clientinfo.c | 42 + lib/dns/compress.c | 580 + lib/dns/db.c | 1121 ++ lib/dns/dbiterator.c | 135 + lib/dns/diff.c | 686 + lib/dns/dispatch.c | 2296 ++++ lib/dns/dlz.c | 537 + lib/dns/dns64.c | 484 + lib/dns/dnsrps.c | 1004 ++ lib/dns/dnssec.c | 2533 ++++ lib/dns/dnstap.c | 1386 ++ lib/dns/dnstap.proto | 289 + lib/dns/ds.c | 135 + lib/dns/dst_api.c | 2831 ++++ lib/dns/dst_internal.h | 254 + lib/dns/dst_openssl.h | 67 + lib/dns/dst_parse.c | 798 ++ lib/dns/dst_parse.h | 131 + lib/dns/dyndb.c | 325 + lib/dns/ecs.c | 113 + lib/dns/fixedname.c | 39 + lib/dns/forward.c | 217 + lib/dns/gen.c | 1062 ++ lib/dns/geoip2.c | 382 + lib/dns/gssapi_link.c | 364 + lib/dns/gssapictx.c | 962 ++ lib/dns/hmac_link.c | 527 + lib/dns/include/dns/acl.h | 337 + lib/dns/include/dns/adb.h | 805 ++ lib/dns/include/dns/badcache.h | 149 + lib/dns/include/dns/bit.h | 25 + lib/dns/include/dns/byaddr.h | 149 + lib/dns/include/dns/cache.h | 302 + lib/dns/include/dns/callbacks.h | 93 + lib/dns/include/dns/catz.h | 461 + lib/dns/include/dns/cert.h | 60 + lib/dns/include/dns/client.h | 293 + lib/dns/include/dns/clientinfo.h | 102 + lib/dns/include/dns/compress.h | 299 + lib/dns/include/dns/db.h | 1759 +++ lib/dns/include/dns/dbiterator.h | 290 + lib/dns/include/dns/diff.h | 279 + lib/dns/include/dns/dispatch.h | 390 + lib/dns/include/dns/dlz.h | 333 + lib/dns/include/dns/dlz_dlopen.h | 156 + lib/dns/include/dns/dns64.h | 192 + lib/dns/include/dns/dnsrps.h | 112 + lib/dns/include/dns/dnssec.h | 399 + lib/dns/include/dns/dnstap.h | 393 + lib/dns/include/dns/ds.h | 65 + lib/dns/include/dns/dsdigest.h | 69 + lib/dns/include/dns/dyndb.h | 159 + lib/dns/include/dns/ecs.h | 81 + lib/dns/include/dns/edns.h | 26 + lib/dns/include/dns/events.h | 87 + lib/dns/include/dns/fixedname.h | 82 + lib/dns/include/dns/forward.h | 123 + lib/dns/include/dns/geoip.h | 111 + lib/dns/include/dns/ipkeylist.h | 87 + lib/dns/include/dns/iptable.h | 67 + lib/dns/include/dns/journal.h | 338 + lib/dns/include/dns/kasp.h | 716 + lib/dns/include/dns/keydata.h | 48 + lib/dns/include/dns/keyflags.h | 45 + lib/dns/include/dns/keymgr.h | 131 + lib/dns/include/dns/keytable.h | 351 + lib/dns/include/dns/keyvalues.h | 104 + lib/dns/include/dns/librpz.h | 945 ++ lib/dns/include/dns/log.h | 112 + lib/dns/include/dns/lookup.h | 128 + lib/dns/include/dns/master.h | 259 + lib/dns/include/dns/masterdump.h | 359 + lib/dns/include/dns/message.h | 1567 +++ lib/dns/include/dns/name.h | 1381 ++ lib/dns/include/dns/ncache.h | 184 + lib/dns/include/dns/nsec.h | 126 + lib/dns/include/dns/nsec3.h | 269 + lib/dns/include/dns/nta.h | 209 + lib/dns/include/dns/opcode.h | 43 + lib/dns/include/dns/order.h | 90 + lib/dns/include/dns/peer.h | 208 + lib/dns/include/dns/private.h | 66 + lib/dns/include/dns/rbt.h | 995 ++ lib/dns/include/dns/rcode.h | 107 + lib/dns/include/dns/rdata.h | 809 ++ lib/dns/include/dns/rdataclass.h | 91 + lib/dns/include/dns/rdatalist.h | 120 + lib/dns/include/dns/rdataset.h | 621 + lib/dns/include/dns/rdatasetiter.h | 161 + lib/dns/include/dns/rdataslab.h | 170 + lib/dns/include/dns/rdatatype.h | 94 + lib/dns/include/dns/request.h | 321 + lib/dns/include/dns/resolver.h | 709 + lib/dns/include/dns/result.h | 31 + lib/dns/include/dns/rootns.h | 36 + lib/dns/include/dns/rpz.h | 441 + lib/dns/include/dns/rriterator.h | 179 + lib/dns/include/dns/rrl.h | 269 + lib/dns/include/dns/sdb.h | 211 + lib/dns/include/dns/sdlz.h | 356 + lib/dns/include/dns/secalg.h | 69 + lib/dns/include/dns/secproto.h | 62 + lib/dns/include/dns/soa.h | 93 + lib/dns/include/dns/ssu.h | 261 + lib/dns/include/dns/stats.h | 825 ++ lib/dns/include/dns/time.h | 70 + lib/dns/include/dns/tkey.h | 243 + lib/dns/include/dns/transport.h | 168 + lib/dns/include/dns/tsec.h | 129 + lib/dns/include/dns/tsig.h | 295 + lib/dns/include/dns/ttl.h | 77 + lib/dns/include/dns/types.h | 430 + lib/dns/include/dns/update.h | 72 + lib/dns/include/dns/validator.h | 240 + lib/dns/include/dns/view.h | 1416 ++ lib/dns/include/dns/xfrin.h | 96 + lib/dns/include/dns/zone.h | 2657 ++++ lib/dns/include/dns/zonekey.h | 35 + lib/dns/include/dns/zoneverify.h | 50 + lib/dns/include/dns/zt.h | 220 + lib/dns/include/dst/dst.h | 1245 ++ lib/dns/include/dst/gssapi.h | 192 + lib/dns/ipkeylist.c | 226 + lib/dns/iptable.c | 174 + lib/dns/journal.c | 2856 ++++ lib/dns/kasp.c | 515 + lib/dns/key.c | 192 + lib/dns/keydata.c | 76 + lib/dns/keymgr.c | 2647 ++++ lib/dns/keytable.c | 956 ++ lib/dns/log.c | 71 + lib/dns/lookup.c | 445 + lib/dns/master.c | 3200 +++++ lib/dns/masterdump.c | 2094 +++ lib/dns/message.c | 4849 +++++++ lib/dns/name.c | 2652 ++++ lib/dns/ncache.c | 777 ++ lib/dns/nsec.c | 533 + lib/dns/nsec3.c | 2195 +++ lib/dns/nta.c | 703 + lib/dns/openssl_link.c | 223 + lib/dns/openssl_shim.c | 252 + lib/dns/openssl_shim.h | 87 + lib/dns/openssldh_link.c | 1336 ++ lib/dns/opensslecdsa_link.c | 1453 ++ lib/dns/openssleddsa_link.c | 702 + lib/dns/opensslrsa_link.c | 1816 +++ lib/dns/order.c | 150 + lib/dns/peer.c | 898 ++ lib/dns/private.c | 417 + lib/dns/rbt.c | 3124 +++++ lib/dns/rbtdb.c | 10241 ++++++++++++++ lib/dns/rbtdb.h | 49 + lib/dns/rcode.c | 585 + lib/dns/rdata.c | 2367 ++++ lib/dns/rdata/any_255/tsig_250.c | 622 + lib/dns/rdata/any_255/tsig_250.h | 29 + lib/dns/rdata/ch_3/a_1.c | 326 + lib/dns/rdata/ch_3/a_1.h | 28 + lib/dns/rdata/generic/afsdb_18.c | 318 + lib/dns/rdata/generic/afsdb_18.h | 24 + lib/dns/rdata/generic/amtrelay_260.c | 472 + lib/dns/rdata/generic/amtrelay_260.h | 27 + lib/dns/rdata/generic/avc_258.c | 145 + lib/dns/rdata/generic/avc_258.h | 30 + lib/dns/rdata/generic/caa_257.c | 628 + lib/dns/rdata/generic/caa_257.h | 24 + lib/dns/rdata/generic/cdnskey_60.c | 164 + lib/dns/rdata/generic/cdnskey_60.h | 17 + lib/dns/rdata/generic/cds_59.c | 167 + lib/dns/rdata/generic/cds_59.h | 17 + lib/dns/rdata/generic/cert_37.c | 285 + lib/dns/rdata/generic/cert_37.h | 25 + lib/dns/rdata/generic/cname_5.c | 231 + lib/dns/rdata/generic/cname_5.h | 20 + lib/dns/rdata/generic/csync_62.c | 274 + lib/dns/rdata/generic/csync_62.h | 27 + lib/dns/rdata/generic/dlv_32769.c | 163 + lib/dns/rdata/generic/dlv_32769.h | 17 + lib/dns/rdata/generic/dname_39.c | 231 + lib/dns/rdata/generic/dname_39.h | 23 + lib/dns/rdata/generic/dnskey_48.c | 165 + lib/dns/rdata/generic/dnskey_48.h | 20 + lib/dns/rdata/generic/doa_259.c | 362 + lib/dns/rdata/generic/doa_259.h | 26 + lib/dns/rdata/generic/ds_43.c | 386 + lib/dns/rdata/generic/ds_43.h | 26 + lib/dns/rdata/generic/eui48_108.c | 212 + lib/dns/rdata/generic/eui48_108.h | 20 + lib/dns/rdata/generic/eui64_109.c | 215 + lib/dns/rdata/generic/eui64_109.h | 20 + lib/dns/rdata/generic/gpos_27.c | 257 + lib/dns/rdata/generic/gpos_27.h | 28 + lib/dns/rdata/generic/hinfo_13.c | 218 + lib/dns/rdata/generic/hinfo_13.h | 23 + lib/dns/rdata/generic/hip_55.c | 523 + lib/dns/rdata/generic/hip_55.h | 39 + lib/dns/rdata/generic/ipseckey_45.c | 527 + lib/dns/rdata/generic/ipseckey_45.h | 27 + lib/dns/rdata/generic/isdn_20.c | 248 + lib/dns/rdata/generic/isdn_20.h | 26 + lib/dns/rdata/generic/key_25.c | 469 + lib/dns/rdata/generic/key_25.h | 27 + lib/dns/rdata/generic/keydata_65533.c | 463 + lib/dns/rdata/generic/keydata_65533.h | 27 + lib/dns/rdata/generic/l32_105.c | 231 + lib/dns/rdata/generic/l32_105.h | 21 + lib/dns/rdata/generic/l64_106.c | 225 + lib/dns/rdata/generic/l64_106.h | 21 + lib/dns/rdata/generic/loc_29.c | 839 ++ lib/dns/rdata/generic/loc_29.h | 34 + lib/dns/rdata/generic/lp_107.c | 278 + lib/dns/rdata/generic/lp_107.h | 22 + lib/dns/rdata/generic/mb_7.c | 234 + lib/dns/rdata/generic/mb_7.h | 21 + lib/dns/rdata/generic/md_3.c | 236 + lib/dns/rdata/generic/md_3.h | 21 + lib/dns/rdata/generic/mf_4.c | 235 + lib/dns/rdata/generic/mf_4.h | 21 + lib/dns/rdata/generic/mg_8.c | 229 + lib/dns/rdata/generic/mg_8.h | 21 + lib/dns/rdata/generic/minfo_14.c | 323 + lib/dns/rdata/generic/minfo_14.h | 22 + lib/dns/rdata/generic/mr_9.c | 230 + lib/dns/rdata/generic/mr_9.h | 21 + lib/dns/rdata/generic/mx_15.c | 359 + lib/dns/rdata/generic/mx_15.h | 22 + lib/dns/rdata/generic/naptr_35.c | 738 + lib/dns/rdata/generic/naptr_35.h | 31 + lib/dns/rdata/generic/nid_104.c | 225 + lib/dns/rdata/generic/nid_104.h | 21 + lib/dns/rdata/generic/ninfo_56.c | 170 + lib/dns/rdata/generic/ninfo_56.h | 33 + lib/dns/rdata/generic/ns_2.c | 256 + lib/dns/rdata/generic/ns_2.h | 21 + lib/dns/rdata/generic/nsec3_50.c | 425 + lib/dns/rdata/generic/nsec3_50.h | 109 + lib/dns/rdata/generic/nsec3param_51.c | 322 + lib/dns/rdata/generic/nsec3param_51.h | 29 + lib/dns/rdata/generic/nsec_47.c | 291 + lib/dns/rdata/generic/nsec_47.h | 25 + lib/dns/rdata/generic/null_10.c | 187 + lib/dns/rdata/generic/null_10.h | 22 + lib/dns/rdata/generic/nxt_30.c | 351 + lib/dns/rdata/generic/nxt_30.h | 25 + lib/dns/rdata/generic/openpgpkey_61.c | 250 + lib/dns/rdata/generic/openpgpkey_61.h | 21 + lib/dns/rdata/generic/opt_41.c | 473 + lib/dns/rdata/generic/opt_41.h | 46 + lib/dns/rdata/generic/proforma.c | 165 + lib/dns/rdata/generic/proforma.h | 22 + lib/dns/rdata/generic/ptr_12.c | 279 + lib/dns/rdata/generic/ptr_12.h | 21 + lib/dns/rdata/generic/rkey_57.c | 161 + lib/dns/rdata/generic/rkey_57.h | 16 + lib/dns/rdata/generic/rp_17.c | 310 + lib/dns/rdata/generic/rp_17.h | 24 + lib/dns/rdata/generic/rrsig_46.c | 640 + lib/dns/rdata/generic/rrsig_46.h | 31 + lib/dns/rdata/generic/rt_21.c | 324 + lib/dns/rdata/generic/rt_21.h | 24 + lib/dns/rdata/generic/sig_24.c | 591 + lib/dns/rdata/generic/sig_24.h | 32 + lib/dns/rdata/generic/sink_40.c | 292 + lib/dns/rdata/generic/sink_40.h | 24 + lib/dns/rdata/generic/smimea_53.c | 153 + lib/dns/rdata/generic/smimea_53.h | 16 + lib/dns/rdata/generic/soa_6.c | 443 + lib/dns/rdata/generic/soa_6.h | 27 + lib/dns/rdata/generic/spf_99.c | 146 + lib/dns/rdata/generic/spf_99.h | 33 + lib/dns/rdata/generic/sshfp_44.c | 297 + lib/dns/rdata/generic/sshfp_44.h | 26 + lib/dns/rdata/generic/ta_32768.c | 163 + lib/dns/rdata/generic/ta_32768.h | 19 + lib/dns/rdata/generic/talink_58.c | 258 + lib/dns/rdata/generic/talink_58.h | 25 + lib/dns/rdata/generic/tkey_249.c | 581 + lib/dns/rdata/generic/tkey_249.h | 31 + lib/dns/rdata/generic/tlsa_52.c | 340 + lib/dns/rdata/generic/tlsa_52.h | 27 + lib/dns/rdata/generic/txt_16.c | 360 + lib/dns/rdata/generic/txt_16.h | 43 + lib/dns/rdata/generic/uri_256.c | 319 + lib/dns/rdata/generic/uri_256.h | 23 + lib/dns/rdata/generic/x25_19.c | 233 + lib/dns/rdata/generic/x25_19.h | 24 + lib/dns/rdata/generic/zonemd_63.c | 351 + lib/dns/rdata/generic/zonemd_63.h | 31 + lib/dns/rdata/hs_4/a_1.c | 233 + lib/dns/rdata/hs_4/a_1.h | 20 + lib/dns/rdata/in_1/a6_38.c | 487 + lib/dns/rdata/in_1/a6_38.h | 25 + lib/dns/rdata/in_1/a_1.c | 279 + lib/dns/rdata/in_1/a_1.h | 20 + lib/dns/rdata/in_1/aaaa_28.c | 266 + lib/dns/rdata/in_1/aaaa_28.h | 22 + lib/dns/rdata/in_1/apl_42.c | 483 + lib/dns/rdata/in_1/apl_42.h | 50 + lib/dns/rdata/in_1/atma_34.c | 318 + lib/dns/rdata/in_1/atma_34.h | 25 + lib/dns/rdata/in_1/dhcid_49.c | 236 + lib/dns/rdata/in_1/dhcid_49.h | 22 + lib/dns/rdata/in_1/eid_31.c | 225 + lib/dns/rdata/in_1/eid_31.h | 25 + lib/dns/rdata/in_1/https_65.c | 183 + lib/dns/rdata/in_1/https_65.h | 32 + lib/dns/rdata/in_1/kx_36.c | 291 + lib/dns/rdata/in_1/kx_36.h | 24 + lib/dns/rdata/in_1/nimloc_32.c | 225 + lib/dns/rdata/in_1/nimloc_32.h | 25 + lib/dns/rdata/in_1/nsap-ptr_23.c | 244 + lib/dns/rdata/in_1/nsap-ptr_23.h | 23 + lib/dns/rdata/in_1/nsap_22.c | 260 + lib/dns/rdata/in_1/nsap_22.h | 24 + lib/dns/rdata/in_1/px_26.c | 372 + lib/dns/rdata/in_1/px_26.h | 25 + lib/dns/rdata/in_1/srv_33.c | 413 + lib/dns/rdata/in_1/srv_33.h | 26 + lib/dns/rdata/in_1/svcb_64.c | 1338 ++ lib/dns/rdata/in_1/svcb_64.h | 37 + lib/dns/rdata/in_1/wks_11.c | 404 + lib/dns/rdata/in_1/wks_11.h | 23 + lib/dns/rdata/rdatastructpre.h | 35 + lib/dns/rdata/rdatastructsuf.h | 14 + lib/dns/rdatalist.c | 448 + lib/dns/rdatalist_p.h | 62 + lib/dns/rdataset.c | 749 + lib/dns/rdatasetiter.c | 71 + lib/dns/rdataslab.c | 1005 ++ lib/dns/request.c | 1216 ++ lib/dns/resolver.c | 11753 ++++++++++++++++ lib/dns/result.c | 121 + lib/dns/rootns.c | 566 + lib/dns/rpz.c | 2734 ++++ lib/dns/rriterator.c | 220 + lib/dns/rrl.c | 1367 ++ lib/dns/sdb.c | 1598 +++ lib/dns/sdlz.c | 2086 +++ lib/dns/soa.c | 137 + lib/dns/ssu.c | 715 + lib/dns/ssu_external.c | 255 + lib/dns/stats.c | 653 + lib/dns/time.c | 216 + lib/dns/tkey.c | 1605 +++ lib/dns/transport.c | 474 + lib/dns/tsec.c | 149 + lib/dns/tsig.c | 1919 +++ lib/dns/tsig_p.h | 40 + lib/dns/ttl.c | 225 + lib/dns/update.c | 2279 +++ lib/dns/validator.c | 3394 +++++ lib/dns/view.c | 2761 ++++ lib/dns/xfrin.c | 2034 +++ lib/dns/zone.c | 23706 ++++++++++++++++++++++++++++++++ lib/dns/zone_p.h | 50 + lib/dns/zonekey.c | 54 + lib/dns/zoneverify.c | 2037 +++ lib/dns/zt.c | 615 + 369 files changed, 210813 insertions(+) create mode 100644 lib/dns/Makefile.am create mode 100644 lib/dns/Makefile.in create mode 100644 lib/dns/acl.c create mode 100644 lib/dns/adb.c create mode 100644 lib/dns/badcache.c create mode 100644 lib/dns/byaddr.c create mode 100644 lib/dns/cache.c create mode 100644 lib/dns/callbacks.c create mode 100644 lib/dns/catz.c create mode 100644 lib/dns/client.c create mode 100644 lib/dns/clientinfo.c create mode 100644 lib/dns/compress.c create mode 100644 lib/dns/db.c create mode 100644 lib/dns/dbiterator.c create mode 100644 lib/dns/diff.c create mode 100644 lib/dns/dispatch.c create mode 100644 lib/dns/dlz.c create mode 100644 lib/dns/dns64.c create mode 100644 lib/dns/dnsrps.c create mode 100644 lib/dns/dnssec.c create mode 100644 lib/dns/dnstap.c create mode 100644 lib/dns/dnstap.proto create mode 100644 lib/dns/ds.c create mode 100644 lib/dns/dst_api.c create mode 100644 lib/dns/dst_internal.h create mode 100644 lib/dns/dst_openssl.h create mode 100644 lib/dns/dst_parse.c create mode 100644 lib/dns/dst_parse.h create mode 100644 lib/dns/dyndb.c create mode 100644 lib/dns/ecs.c create mode 100644 lib/dns/fixedname.c create mode 100644 lib/dns/forward.c create mode 100644 lib/dns/gen.c create mode 100644 lib/dns/geoip2.c create mode 100644 lib/dns/gssapi_link.c create mode 100644 lib/dns/gssapictx.c create mode 100644 lib/dns/hmac_link.c create mode 100644 lib/dns/include/dns/acl.h create mode 100644 lib/dns/include/dns/adb.h create mode 100644 lib/dns/include/dns/badcache.h create mode 100644 lib/dns/include/dns/bit.h create mode 100644 lib/dns/include/dns/byaddr.h create mode 100644 lib/dns/include/dns/cache.h create mode 100644 lib/dns/include/dns/callbacks.h create mode 100644 lib/dns/include/dns/catz.h create mode 100644 lib/dns/include/dns/cert.h create mode 100644 lib/dns/include/dns/client.h create mode 100644 lib/dns/include/dns/clientinfo.h create mode 100644 lib/dns/include/dns/compress.h create mode 100644 lib/dns/include/dns/db.h create mode 100644 lib/dns/include/dns/dbiterator.h create mode 100644 lib/dns/include/dns/diff.h create mode 100644 lib/dns/include/dns/dispatch.h create mode 100644 lib/dns/include/dns/dlz.h create mode 100644 lib/dns/include/dns/dlz_dlopen.h create mode 100644 lib/dns/include/dns/dns64.h create mode 100644 lib/dns/include/dns/dnsrps.h create mode 100644 lib/dns/include/dns/dnssec.h create mode 100644 lib/dns/include/dns/dnstap.h create mode 100644 lib/dns/include/dns/ds.h create mode 100644 lib/dns/include/dns/dsdigest.h create mode 100644 lib/dns/include/dns/dyndb.h create mode 100644 lib/dns/include/dns/ecs.h create mode 100644 lib/dns/include/dns/edns.h create mode 100644 lib/dns/include/dns/events.h create mode 100644 lib/dns/include/dns/fixedname.h create mode 100644 lib/dns/include/dns/forward.h create mode 100644 lib/dns/include/dns/geoip.h create mode 100644 lib/dns/include/dns/ipkeylist.h create mode 100644 lib/dns/include/dns/iptable.h create mode 100644 lib/dns/include/dns/journal.h create mode 100644 lib/dns/include/dns/kasp.h create mode 100644 lib/dns/include/dns/keydata.h create mode 100644 lib/dns/include/dns/keyflags.h create mode 100644 lib/dns/include/dns/keymgr.h create mode 100644 lib/dns/include/dns/keytable.h create mode 100644 lib/dns/include/dns/keyvalues.h create mode 100644 lib/dns/include/dns/librpz.h create mode 100644 lib/dns/include/dns/log.h create mode 100644 lib/dns/include/dns/lookup.h create mode 100644 lib/dns/include/dns/master.h create mode 100644 lib/dns/include/dns/masterdump.h create mode 100644 lib/dns/include/dns/message.h create mode 100644 lib/dns/include/dns/name.h create mode 100644 lib/dns/include/dns/ncache.h create mode 100644 lib/dns/include/dns/nsec.h create mode 100644 lib/dns/include/dns/nsec3.h create mode 100644 lib/dns/include/dns/nta.h create mode 100644 lib/dns/include/dns/opcode.h create mode 100644 lib/dns/include/dns/order.h create mode 100644 lib/dns/include/dns/peer.h create mode 100644 lib/dns/include/dns/private.h create mode 100644 lib/dns/include/dns/rbt.h create mode 100644 lib/dns/include/dns/rcode.h create mode 100644 lib/dns/include/dns/rdata.h create mode 100644 lib/dns/include/dns/rdataclass.h create mode 100644 lib/dns/include/dns/rdatalist.h create mode 100644 lib/dns/include/dns/rdataset.h create mode 100644 lib/dns/include/dns/rdatasetiter.h create mode 100644 lib/dns/include/dns/rdataslab.h create mode 100644 lib/dns/include/dns/rdatatype.h create mode 100644 lib/dns/include/dns/request.h create mode 100644 lib/dns/include/dns/resolver.h create mode 100644 lib/dns/include/dns/result.h create mode 100644 lib/dns/include/dns/rootns.h create mode 100644 lib/dns/include/dns/rpz.h create mode 100644 lib/dns/include/dns/rriterator.h create mode 100644 lib/dns/include/dns/rrl.h create mode 100644 lib/dns/include/dns/sdb.h create mode 100644 lib/dns/include/dns/sdlz.h create mode 100644 lib/dns/include/dns/secalg.h create mode 100644 lib/dns/include/dns/secproto.h create mode 100644 lib/dns/include/dns/soa.h create mode 100644 lib/dns/include/dns/ssu.h create mode 100644 lib/dns/include/dns/stats.h create mode 100644 lib/dns/include/dns/time.h create mode 100644 lib/dns/include/dns/tkey.h create mode 100644 lib/dns/include/dns/transport.h create mode 100644 lib/dns/include/dns/tsec.h create mode 100644 lib/dns/include/dns/tsig.h create mode 100644 lib/dns/include/dns/ttl.h create mode 100644 lib/dns/include/dns/types.h create mode 100644 lib/dns/include/dns/update.h create mode 100644 lib/dns/include/dns/validator.h create mode 100644 lib/dns/include/dns/view.h create mode 100644 lib/dns/include/dns/xfrin.h create mode 100644 lib/dns/include/dns/zone.h create mode 100644 lib/dns/include/dns/zonekey.h create mode 100644 lib/dns/include/dns/zoneverify.h create mode 100644 lib/dns/include/dns/zt.h create mode 100644 lib/dns/include/dst/dst.h create mode 100644 lib/dns/include/dst/gssapi.h create mode 100644 lib/dns/ipkeylist.c create mode 100644 lib/dns/iptable.c create mode 100644 lib/dns/journal.c create mode 100644 lib/dns/kasp.c create mode 100644 lib/dns/key.c create mode 100644 lib/dns/keydata.c create mode 100644 lib/dns/keymgr.c create mode 100644 lib/dns/keytable.c create mode 100644 lib/dns/log.c create mode 100644 lib/dns/lookup.c create mode 100644 lib/dns/master.c create mode 100644 lib/dns/masterdump.c create mode 100644 lib/dns/message.c create mode 100644 lib/dns/name.c create mode 100644 lib/dns/ncache.c create mode 100644 lib/dns/nsec.c create mode 100644 lib/dns/nsec3.c create mode 100644 lib/dns/nta.c create mode 100644 lib/dns/openssl_link.c create mode 100644 lib/dns/openssl_shim.c create mode 100644 lib/dns/openssl_shim.h create mode 100644 lib/dns/openssldh_link.c create mode 100644 lib/dns/opensslecdsa_link.c create mode 100644 lib/dns/openssleddsa_link.c create mode 100644 lib/dns/opensslrsa_link.c create mode 100644 lib/dns/order.c create mode 100644 lib/dns/peer.c create mode 100644 lib/dns/private.c create mode 100644 lib/dns/rbt.c create mode 100644 lib/dns/rbtdb.c create mode 100644 lib/dns/rbtdb.h create mode 100644 lib/dns/rcode.c create mode 100644 lib/dns/rdata.c create mode 100644 lib/dns/rdata/any_255/tsig_250.c create mode 100644 lib/dns/rdata/any_255/tsig_250.h create mode 100644 lib/dns/rdata/ch_3/a_1.c create mode 100644 lib/dns/rdata/ch_3/a_1.h create mode 100644 lib/dns/rdata/generic/afsdb_18.c create mode 100644 lib/dns/rdata/generic/afsdb_18.h create mode 100644 lib/dns/rdata/generic/amtrelay_260.c create mode 100644 lib/dns/rdata/generic/amtrelay_260.h create mode 100644 lib/dns/rdata/generic/avc_258.c create mode 100644 lib/dns/rdata/generic/avc_258.h create mode 100644 lib/dns/rdata/generic/caa_257.c create mode 100644 lib/dns/rdata/generic/caa_257.h create mode 100644 lib/dns/rdata/generic/cdnskey_60.c create mode 100644 lib/dns/rdata/generic/cdnskey_60.h create mode 100644 lib/dns/rdata/generic/cds_59.c create mode 100644 lib/dns/rdata/generic/cds_59.h create mode 100644 lib/dns/rdata/generic/cert_37.c create mode 100644 lib/dns/rdata/generic/cert_37.h create mode 100644 lib/dns/rdata/generic/cname_5.c create mode 100644 lib/dns/rdata/generic/cname_5.h create mode 100644 lib/dns/rdata/generic/csync_62.c create mode 100644 lib/dns/rdata/generic/csync_62.h create mode 100644 lib/dns/rdata/generic/dlv_32769.c create mode 100644 lib/dns/rdata/generic/dlv_32769.h create mode 100644 lib/dns/rdata/generic/dname_39.c create mode 100644 lib/dns/rdata/generic/dname_39.h create mode 100644 lib/dns/rdata/generic/dnskey_48.c create mode 100644 lib/dns/rdata/generic/dnskey_48.h create mode 100644 lib/dns/rdata/generic/doa_259.c create mode 100644 lib/dns/rdata/generic/doa_259.h create mode 100644 lib/dns/rdata/generic/ds_43.c create mode 100644 lib/dns/rdata/generic/ds_43.h create mode 100644 lib/dns/rdata/generic/eui48_108.c create mode 100644 lib/dns/rdata/generic/eui48_108.h create mode 100644 lib/dns/rdata/generic/eui64_109.c create mode 100644 lib/dns/rdata/generic/eui64_109.h create mode 100644 lib/dns/rdata/generic/gpos_27.c create mode 100644 lib/dns/rdata/generic/gpos_27.h create mode 100644 lib/dns/rdata/generic/hinfo_13.c create mode 100644 lib/dns/rdata/generic/hinfo_13.h create mode 100644 lib/dns/rdata/generic/hip_55.c create mode 100644 lib/dns/rdata/generic/hip_55.h create mode 100644 lib/dns/rdata/generic/ipseckey_45.c create mode 100644 lib/dns/rdata/generic/ipseckey_45.h create mode 100644 lib/dns/rdata/generic/isdn_20.c create mode 100644 lib/dns/rdata/generic/isdn_20.h create mode 100644 lib/dns/rdata/generic/key_25.c create mode 100644 lib/dns/rdata/generic/key_25.h create mode 100644 lib/dns/rdata/generic/keydata_65533.c create mode 100644 lib/dns/rdata/generic/keydata_65533.h create mode 100644 lib/dns/rdata/generic/l32_105.c create mode 100644 lib/dns/rdata/generic/l32_105.h create mode 100644 lib/dns/rdata/generic/l64_106.c create mode 100644 lib/dns/rdata/generic/l64_106.h create mode 100644 lib/dns/rdata/generic/loc_29.c create mode 100644 lib/dns/rdata/generic/loc_29.h create mode 100644 lib/dns/rdata/generic/lp_107.c create mode 100644 lib/dns/rdata/generic/lp_107.h create mode 100644 lib/dns/rdata/generic/mb_7.c create mode 100644 lib/dns/rdata/generic/mb_7.h create mode 100644 lib/dns/rdata/generic/md_3.c create mode 100644 lib/dns/rdata/generic/md_3.h create mode 100644 lib/dns/rdata/generic/mf_4.c create mode 100644 lib/dns/rdata/generic/mf_4.h create mode 100644 lib/dns/rdata/generic/mg_8.c create mode 100644 lib/dns/rdata/generic/mg_8.h create mode 100644 lib/dns/rdata/generic/minfo_14.c create mode 100644 lib/dns/rdata/generic/minfo_14.h create mode 100644 lib/dns/rdata/generic/mr_9.c create mode 100644 lib/dns/rdata/generic/mr_9.h create mode 100644 lib/dns/rdata/generic/mx_15.c create mode 100644 lib/dns/rdata/generic/mx_15.h create mode 100644 lib/dns/rdata/generic/naptr_35.c create mode 100644 lib/dns/rdata/generic/naptr_35.h create mode 100644 lib/dns/rdata/generic/nid_104.c create mode 100644 lib/dns/rdata/generic/nid_104.h create mode 100644 lib/dns/rdata/generic/ninfo_56.c create mode 100644 lib/dns/rdata/generic/ninfo_56.h create mode 100644 lib/dns/rdata/generic/ns_2.c create mode 100644 lib/dns/rdata/generic/ns_2.h create mode 100644 lib/dns/rdata/generic/nsec3_50.c create mode 100644 lib/dns/rdata/generic/nsec3_50.h create mode 100644 lib/dns/rdata/generic/nsec3param_51.c create mode 100644 lib/dns/rdata/generic/nsec3param_51.h create mode 100644 lib/dns/rdata/generic/nsec_47.c create mode 100644 lib/dns/rdata/generic/nsec_47.h create mode 100644 lib/dns/rdata/generic/null_10.c create mode 100644 lib/dns/rdata/generic/null_10.h create mode 100644 lib/dns/rdata/generic/nxt_30.c create mode 100644 lib/dns/rdata/generic/nxt_30.h create mode 100644 lib/dns/rdata/generic/openpgpkey_61.c create mode 100644 lib/dns/rdata/generic/openpgpkey_61.h create mode 100644 lib/dns/rdata/generic/opt_41.c create mode 100644 lib/dns/rdata/generic/opt_41.h create mode 100644 lib/dns/rdata/generic/proforma.c create mode 100644 lib/dns/rdata/generic/proforma.h create mode 100644 lib/dns/rdata/generic/ptr_12.c create mode 100644 lib/dns/rdata/generic/ptr_12.h create mode 100644 lib/dns/rdata/generic/rkey_57.c create mode 100644 lib/dns/rdata/generic/rkey_57.h create mode 100644 lib/dns/rdata/generic/rp_17.c create mode 100644 lib/dns/rdata/generic/rp_17.h create mode 100644 lib/dns/rdata/generic/rrsig_46.c create mode 100644 lib/dns/rdata/generic/rrsig_46.h create mode 100644 lib/dns/rdata/generic/rt_21.c create mode 100644 lib/dns/rdata/generic/rt_21.h create mode 100644 lib/dns/rdata/generic/sig_24.c create mode 100644 lib/dns/rdata/generic/sig_24.h create mode 100644 lib/dns/rdata/generic/sink_40.c create mode 100644 lib/dns/rdata/generic/sink_40.h create mode 100644 lib/dns/rdata/generic/smimea_53.c create mode 100644 lib/dns/rdata/generic/smimea_53.h create mode 100644 lib/dns/rdata/generic/soa_6.c create mode 100644 lib/dns/rdata/generic/soa_6.h create mode 100644 lib/dns/rdata/generic/spf_99.c create mode 100644 lib/dns/rdata/generic/spf_99.h create mode 100644 lib/dns/rdata/generic/sshfp_44.c create mode 100644 lib/dns/rdata/generic/sshfp_44.h create mode 100644 lib/dns/rdata/generic/ta_32768.c create mode 100644 lib/dns/rdata/generic/ta_32768.h create mode 100644 lib/dns/rdata/generic/talink_58.c create mode 100644 lib/dns/rdata/generic/talink_58.h create mode 100644 lib/dns/rdata/generic/tkey_249.c create mode 100644 lib/dns/rdata/generic/tkey_249.h create mode 100644 lib/dns/rdata/generic/tlsa_52.c create mode 100644 lib/dns/rdata/generic/tlsa_52.h create mode 100644 lib/dns/rdata/generic/txt_16.c create mode 100644 lib/dns/rdata/generic/txt_16.h create mode 100644 lib/dns/rdata/generic/uri_256.c create mode 100644 lib/dns/rdata/generic/uri_256.h create mode 100644 lib/dns/rdata/generic/x25_19.c create mode 100644 lib/dns/rdata/generic/x25_19.h create mode 100644 lib/dns/rdata/generic/zonemd_63.c create mode 100644 lib/dns/rdata/generic/zonemd_63.h create mode 100644 lib/dns/rdata/hs_4/a_1.c create mode 100644 lib/dns/rdata/hs_4/a_1.h create mode 100644 lib/dns/rdata/in_1/a6_38.c create mode 100644 lib/dns/rdata/in_1/a6_38.h create mode 100644 lib/dns/rdata/in_1/a_1.c create mode 100644 lib/dns/rdata/in_1/a_1.h create mode 100644 lib/dns/rdata/in_1/aaaa_28.c create mode 100644 lib/dns/rdata/in_1/aaaa_28.h create mode 100644 lib/dns/rdata/in_1/apl_42.c create mode 100644 lib/dns/rdata/in_1/apl_42.h create mode 100644 lib/dns/rdata/in_1/atma_34.c create mode 100644 lib/dns/rdata/in_1/atma_34.h create mode 100644 lib/dns/rdata/in_1/dhcid_49.c create mode 100644 lib/dns/rdata/in_1/dhcid_49.h create mode 100644 lib/dns/rdata/in_1/eid_31.c create mode 100644 lib/dns/rdata/in_1/eid_31.h create mode 100644 lib/dns/rdata/in_1/https_65.c create mode 100644 lib/dns/rdata/in_1/https_65.h create mode 100644 lib/dns/rdata/in_1/kx_36.c create mode 100644 lib/dns/rdata/in_1/kx_36.h create mode 100644 lib/dns/rdata/in_1/nimloc_32.c create mode 100644 lib/dns/rdata/in_1/nimloc_32.h create mode 100644 lib/dns/rdata/in_1/nsap-ptr_23.c create mode 100644 lib/dns/rdata/in_1/nsap-ptr_23.h create mode 100644 lib/dns/rdata/in_1/nsap_22.c create mode 100644 lib/dns/rdata/in_1/nsap_22.h create mode 100644 lib/dns/rdata/in_1/px_26.c create mode 100644 lib/dns/rdata/in_1/px_26.h create mode 100644 lib/dns/rdata/in_1/srv_33.c create mode 100644 lib/dns/rdata/in_1/srv_33.h create mode 100644 lib/dns/rdata/in_1/svcb_64.c create mode 100644 lib/dns/rdata/in_1/svcb_64.h create mode 100644 lib/dns/rdata/in_1/wks_11.c create mode 100644 lib/dns/rdata/in_1/wks_11.h create mode 100644 lib/dns/rdata/rdatastructpre.h create mode 100644 lib/dns/rdata/rdatastructsuf.h create mode 100644 lib/dns/rdatalist.c create mode 100644 lib/dns/rdatalist_p.h create mode 100644 lib/dns/rdataset.c create mode 100644 lib/dns/rdatasetiter.c create mode 100644 lib/dns/rdataslab.c create mode 100644 lib/dns/request.c create mode 100644 lib/dns/resolver.c create mode 100644 lib/dns/result.c create mode 100644 lib/dns/rootns.c create mode 100644 lib/dns/rpz.c create mode 100644 lib/dns/rriterator.c create mode 100644 lib/dns/rrl.c create mode 100644 lib/dns/sdb.c create mode 100644 lib/dns/sdlz.c create mode 100644 lib/dns/soa.c create mode 100644 lib/dns/ssu.c create mode 100644 lib/dns/ssu_external.c create mode 100644 lib/dns/stats.c create mode 100644 lib/dns/time.c create mode 100644 lib/dns/tkey.c create mode 100644 lib/dns/transport.c create mode 100644 lib/dns/tsec.c create mode 100644 lib/dns/tsig.c create mode 100644 lib/dns/tsig_p.h create mode 100644 lib/dns/ttl.c create mode 100644 lib/dns/update.c create mode 100644 lib/dns/validator.c create mode 100644 lib/dns/view.c create mode 100644 lib/dns/xfrin.c create mode 100644 lib/dns/zone.c create mode 100644 lib/dns/zone_p.h create mode 100644 lib/dns/zonekey.c create mode 100644 lib/dns/zoneverify.c create mode 100644 lib/dns/zt.c (limited to 'lib/dns') diff --git a/lib/dns/Makefile.am b/lib/dns/Makefile.am new file mode 100644 index 0000000..ef8e9f2 --- /dev/null +++ b/lib/dns/Makefile.am @@ -0,0 +1,325 @@ +include $(top_srcdir)/Makefile.top + +lib_LTLIBRARIES = libdns.la + +nodist_libdns_ladir = $(includedir)/dns +nodist_libdns_la_HEADERS = \ + include/dns/enumclass.h \ + include/dns/enumtype.h \ + include/dns/rdatastruct.h + +nodist_libdns_la_SOURCES = \ + $(nodist_libdns_la_HEADERS) \ + code.h + +BUILT_SOURCES = \ + $(nodist_libdns_la_SOURCES) + +CLEANFILES = \ + $(nodist_libdns_la_SOURCES) \ + gen$(BUILD_EXEEXT) + +gen$(BUILD_EXEEXT): gen.c + $(CC_FOR_BUILD) -g -I. $(srcdir)/gen.c -o $@ + +EXTRA_DIST = \ + dnstap.proto \ + gen.c \ + rdata/* + +include/dns/enumtype.h: gen Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -t > $@ + +include/dns/enumclass.h: gen Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -c > $@ + +include/dns/rdatastruct.h: gen rdata/rdatastructpre.h rdata/rdatastructsuf.h Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -i \ + -P $(srcdir)/rdata/rdatastructpre.h \ + -S $(srcdir)/rdata/rdatastructsuf.h > $@ + +code.h: gen Makefile + $(builddir)/gen -s $(srcdir) > $@ + +libdns_ladir = $(includedir)/dns +libdns_la_HEADERS = \ + include/dns/acl.h \ + include/dns/adb.h \ + include/dns/badcache.h \ + include/dns/bit.h \ + include/dns/byaddr.h \ + include/dns/cache.h \ + include/dns/callbacks.h \ + include/dns/catz.h \ + include/dns/cert.h \ + include/dns/client.h \ + include/dns/clientinfo.h \ + include/dns/compress.h \ + include/dns/db.h \ + include/dns/dbiterator.h \ + include/dns/diff.h \ + include/dns/dispatch.h \ + include/dns/dlz.h \ + include/dns/dlz_dlopen.h \ + include/dns/dns64.h \ + include/dns/dnsrps.h \ + include/dns/dnssec.h \ + include/dns/ds.h \ + include/dns/dsdigest.h \ + include/dns/dnstap.h \ + include/dns/dyndb.h \ + include/dns/ecs.h \ + include/dns/edns.h \ + include/dns/events.h \ + include/dns/fixedname.h \ + include/dns/forward.h \ + include/dns/geoip.h \ + include/dns/ipkeylist.h \ + include/dns/iptable.h \ + include/dns/journal.h \ + include/dns/kasp.h \ + include/dns/keydata.h \ + include/dns/keyflags.h \ + include/dns/keymgr.h \ + include/dns/keytable.h \ + include/dns/keyvalues.h \ + include/dns/librpz.h \ + include/dns/lookup.h \ + include/dns/log.h \ + include/dns/master.h \ + include/dns/masterdump.h \ + include/dns/message.h \ + include/dns/name.h \ + include/dns/ncache.h \ + include/dns/nsec.h \ + include/dns/nsec3.h \ + include/dns/nta.h \ + include/dns/opcode.h \ + include/dns/order.h \ + include/dns/peer.h \ + include/dns/private.h \ + include/dns/rbt.h \ + include/dns/rcode.h \ + include/dns/rdata.h \ + include/dns/rdataclass.h \ + include/dns/rdatalist.h \ + include/dns/rdataset.h \ + include/dns/rdatasetiter.h \ + include/dns/rdataslab.h \ + include/dns/rdatatype.h \ + include/dns/request.h \ + include/dns/resolver.h \ + include/dns/result.h \ + include/dns/rootns.h \ + include/dns/rpz.h \ + include/dns/rriterator.h \ + include/dns/rrl.h \ + include/dns/sdb.h \ + include/dns/sdlz.h \ + include/dns/secalg.h \ + include/dns/secproto.h \ + include/dns/soa.h \ + include/dns/ssu.h \ + include/dns/stats.h \ + include/dns/time.h \ + include/dns/transport.h \ + include/dns/tkey.h \ + include/dns/tsec.h \ + include/dns/tsig.h \ + include/dns/ttl.h \ + include/dns/types.h \ + include/dns/update.h \ + include/dns/validator.h \ + include/dns/view.h \ + include/dns/xfrin.h \ + include/dns/zone.h \ + include/dns/zonekey.h \ + include/dns/zoneverify.h \ + include/dns/zt.h + +dstdir = $(includedir)/dst +dst_HEADERS = \ + include/dst/dst.h \ + include/dst/gssapi.h + +libdns_la_SOURCES = \ + $(libdns_la_HEADERS) \ + $(dst_HEADERS) \ + acl.c \ + adb.c \ + badcache.c \ + byaddr.c \ + cache.c \ + callbacks.c \ + catz.c \ + clientinfo.c \ + compress.c \ + db.c \ + dbiterator.c \ + diff.c \ + dispatch.c \ + dlz.c \ + dns64.c \ + dnsrps.c \ + dnssec.c \ + ds.c \ + dst_api.c \ + dst_internal.h \ + dst_openssl.h \ + dst_parse.c \ + dst_parse.h \ + dyndb.c \ + ecs.c \ + fixedname.c \ + forward.c \ + gssapictx.c \ + hmac_link.c \ + ipkeylist.c \ + iptable.c \ + journal.c \ + kasp.c \ + key.c \ + keydata.c \ + keymgr.c \ + keytable.c \ + log.c \ + lookup.c \ + master.c \ + masterdump.c \ + message.c \ + name.c \ + ncache.c \ + nsec.c \ + nsec3.c \ + nta.c \ + openssl_link.c \ + openssl_shim.c \ + openssl_shim.h \ + openssldh_link.c \ + opensslecdsa_link.c \ + openssleddsa_link.c \ + opensslrsa_link.c \ + order.c \ + peer.c \ + private.c \ + rbt.c \ + rbtdb.h \ + rbtdb.c \ + rcode.c \ + rdata.c \ + rdatalist.c \ + rdataset.c \ + rdatasetiter.c \ + rdataslab.c \ + request.c \ + resolver.c \ + result.c \ + rootns.c \ + rpz.c \ + rrl.c \ + rriterator.c \ + sdb.c \ + sdlz.c \ + soa.c \ + ssu.c \ + ssu_external.c \ + stats.c \ + time.c \ + transport.c \ + tkey.c \ + tsec.c \ + tsig.c \ + ttl.c \ + update.c \ + validator.c \ + view.c \ + xfrin.c \ + zone.c \ + zoneverify.c \ + zonekey.c \ + zt.c \ + client.c \ + rdatalist_p.h \ + tsig_p.h \ + zone_p.h + +if HAVE_GSSAPI +libdns_la_SOURCES += \ + gssapi_link.c +endif + +if HAVE_GEOIP2 +libdns_la_SOURCES += \ + geoip2.c +endif + +libdns_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LIBDNS_CFLAGS) \ + $(LIBISC_CFLAGS) \ + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) + +libdns_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -release "$(PACKAGE_VERSION)" + +libdns_la_LIBADD = \ + $(LIBISC_LIBS) \ + $(LIBUV_LIBS) \ + $(OPENSSL_LIBS) + +if HAVE_JSON_C +libdns_la_CPPFLAGS += \ + $(JSON_C_CFLAGS) + +libdns_la_LIBADD += \ + $(JSON_C_LIBS) +endif HAVE_JSON_C + +if HAVE_LIBXML2 +libdns_la_CPPFLAGS += \ + $(LIBXML2_CFLAGS) + +libdns_la_LIBADD += \ + $(LIBXML2_LIBS) +endif HAVE_LIBXML2 + +if HAVE_GSSAPI +libdns_la_CPPFLAGS += \ + $(GSSAPI_CFLAGS) \ + $(KRB5_CFLAGS) +libdns_la_LIBADD += \ + $(GSSAPI_LIBS) \ + $(KRB5_LIBS) +endif + +if HAVE_GEOIP2 +libdns_la_CPPFLAGS += \ + $(MAXMINDDB_CFLAGS) +libdns_la_LIBADD += \ + $(MAXMINDDB_LIBS) +endif + +if HAVE_DNSTAP +nodist_libdns_la_SOURCES += \ + dnstap.pb-c.h \ + dnstap.pb-c.c + +libdns_la_SOURCES += \ + dnstap.c + +dnstap.pb-c.h dnstap.pb-c.c: dnstap.proto + $(PROTOC_C) --proto_path=$(srcdir) --c_out=. dnstap.proto + +libdns_la_CPPFLAGS += $(DNSTAP_CFLAGS) +libdns_la_LIBADD += $(DNSTAP_LIBS) +endif + +if HAVE_LMDB +libdns_la_CPPFLAGS += $(LMDB_CFLAGS) +libdns_la_LIBADD += $(LMDB_LIBS) +endif diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in new file mode 100644 index 0000000..7d71f13 --- /dev/null +++ b/lib/dns/Makefile.in @@ -0,0 +1,2238 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Hey Emacs, this is -*- makefile-automake -*- file! +# vim: filetype=automake + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@HOST_MACOS_TRUE@am__append_1 = \ +@HOST_MACOS_TRUE@ -Wl,-flat_namespace + +@HAVE_GSSAPI_TRUE@am__append_2 = \ +@HAVE_GSSAPI_TRUE@ gssapi_link.c + +@HAVE_GEOIP2_TRUE@am__append_3 = \ +@HAVE_GEOIP2_TRUE@ geoip2.c + +@HAVE_JSON_C_TRUE@am__append_4 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_CFLAGS) + +@HAVE_JSON_C_TRUE@am__append_5 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_LIBS) + +@HAVE_LIBXML2_TRUE@am__append_6 = \ +@HAVE_LIBXML2_TRUE@ $(LIBXML2_CFLAGS) + +@HAVE_LIBXML2_TRUE@am__append_7 = \ +@HAVE_LIBXML2_TRUE@ $(LIBXML2_LIBS) + +@HAVE_GSSAPI_TRUE@am__append_8 = \ +@HAVE_GSSAPI_TRUE@ $(GSSAPI_CFLAGS) \ +@HAVE_GSSAPI_TRUE@ $(KRB5_CFLAGS) + +@HAVE_GSSAPI_TRUE@am__append_9 = \ +@HAVE_GSSAPI_TRUE@ $(GSSAPI_LIBS) \ +@HAVE_GSSAPI_TRUE@ $(KRB5_LIBS) + +@HAVE_GEOIP2_TRUE@am__append_10 = \ +@HAVE_GEOIP2_TRUE@ $(MAXMINDDB_CFLAGS) + +@HAVE_GEOIP2_TRUE@am__append_11 = \ +@HAVE_GEOIP2_TRUE@ $(MAXMINDDB_LIBS) + +@HAVE_DNSTAP_TRUE@am__append_12 = \ +@HAVE_DNSTAP_TRUE@ dnstap.pb-c.h \ +@HAVE_DNSTAP_TRUE@ dnstap.pb-c.c + +@HAVE_DNSTAP_TRUE@am__append_13 = \ +@HAVE_DNSTAP_TRUE@ dnstap.c + +@HAVE_DNSTAP_TRUE@am__append_14 = $(DNSTAP_CFLAGS) +@HAVE_DNSTAP_TRUE@am__append_15 = $(DNSTAP_LIBS) +@HAVE_LMDB_TRUE@am__append_16 = $(LMDB_CFLAGS) +@HAVE_LMDB_TRUE@am__append_17 = $(LMDB_LIBS) +subdir = lib/dns +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4/ax_jemalloc.m4 \ + $(top_srcdir)/m4/ax_lib_lmdb.m4 \ + $(top_srcdir)/m4/ax_perl_module.m4 \ + $(top_srcdir)/m4/ax_posix_shell.m4 \ + $(top_srcdir)/m4/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_module.m4 \ + $(top_srcdir)/m4/ax_restore_flags.m4 \ + $(top_srcdir)/m4/ax_save_flags.m4 $(top_srcdir)/m4/ax_tls.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dst_HEADERS) \ + $(libdns_la_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(dstdir)" \ + "$(DESTDIR)$(libdns_ladir)" "$(DESTDIR)$(libdns_ladir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +@HAVE_JSON_C_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +@HAVE_LIBXML2_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) +@HAVE_GSSAPI_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) \ +@HAVE_GSSAPI_TRUE@ $(am__DEPENDENCIES_1) +@HAVE_GEOIP2_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) +@HAVE_DNSTAP_TRUE@am__DEPENDENCIES_6 = $(am__DEPENDENCIES_1) +@HAVE_LMDB_TRUE@am__DEPENDENCIES_7 = $(am__DEPENDENCIES_1) +libdns_la_DEPENDENCIES = $(LIBISC_LIBS) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_4) \ + $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_6) \ + $(am__DEPENDENCIES_7) +am__libdns_la_SOURCES_DIST = include/dns/acl.h include/dns/adb.h \ + include/dns/badcache.h include/dns/bit.h include/dns/byaddr.h \ + include/dns/cache.h include/dns/callbacks.h include/dns/catz.h \ + include/dns/cert.h include/dns/client.h \ + include/dns/clientinfo.h include/dns/compress.h \ + include/dns/db.h include/dns/dbiterator.h include/dns/diff.h \ + include/dns/dispatch.h include/dns/dlz.h \ + include/dns/dlz_dlopen.h include/dns/dns64.h \ + include/dns/dnsrps.h include/dns/dnssec.h include/dns/ds.h \ + include/dns/dsdigest.h include/dns/dnstap.h \ + include/dns/dyndb.h include/dns/ecs.h include/dns/edns.h \ + include/dns/events.h include/dns/fixedname.h \ + include/dns/forward.h include/dns/geoip.h \ + include/dns/ipkeylist.h include/dns/iptable.h \ + include/dns/journal.h include/dns/kasp.h include/dns/keydata.h \ + include/dns/keyflags.h include/dns/keymgr.h \ + include/dns/keytable.h include/dns/keyvalues.h \ + include/dns/librpz.h include/dns/lookup.h include/dns/log.h \ + include/dns/master.h include/dns/masterdump.h \ + include/dns/message.h include/dns/name.h include/dns/ncache.h \ + include/dns/nsec.h include/dns/nsec3.h include/dns/nta.h \ + include/dns/opcode.h include/dns/order.h include/dns/peer.h \ + include/dns/private.h include/dns/rbt.h include/dns/rcode.h \ + include/dns/rdata.h include/dns/rdataclass.h \ + include/dns/rdatalist.h include/dns/rdataset.h \ + include/dns/rdatasetiter.h include/dns/rdataslab.h \ + include/dns/rdatatype.h include/dns/request.h \ + include/dns/resolver.h include/dns/result.h \ + include/dns/rootns.h include/dns/rpz.h \ + include/dns/rriterator.h include/dns/rrl.h include/dns/sdb.h \ + include/dns/sdlz.h include/dns/secalg.h include/dns/secproto.h \ + include/dns/soa.h include/dns/ssu.h include/dns/stats.h \ + include/dns/time.h include/dns/transport.h include/dns/tkey.h \ + include/dns/tsec.h include/dns/tsig.h include/dns/ttl.h \ + include/dns/types.h include/dns/update.h \ + include/dns/validator.h include/dns/view.h include/dns/xfrin.h \ + include/dns/zone.h include/dns/zonekey.h \ + include/dns/zoneverify.h include/dns/zt.h include/dst/dst.h \ + include/dst/gssapi.h acl.c adb.c badcache.c byaddr.c cache.c \ + callbacks.c catz.c clientinfo.c compress.c db.c dbiterator.c \ + diff.c dispatch.c dlz.c dns64.c dnsrps.c dnssec.c ds.c \ + dst_api.c dst_internal.h dst_openssl.h dst_parse.c dst_parse.h \ + dyndb.c ecs.c fixedname.c forward.c gssapictx.c hmac_link.c \ + ipkeylist.c iptable.c journal.c kasp.c key.c keydata.c \ + keymgr.c keytable.c log.c lookup.c master.c masterdump.c \ + message.c name.c ncache.c nsec.c nsec3.c nta.c openssl_link.c \ + openssl_shim.c openssl_shim.h openssldh_link.c \ + opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \ + order.c peer.c private.c rbt.c rbtdb.h rbtdb.c rcode.c rdata.c \ + rdatalist.c rdataset.c rdatasetiter.c rdataslab.c request.c \ + resolver.c result.c rootns.c rpz.c rrl.c rriterator.c sdb.c \ + sdlz.c soa.c ssu.c ssu_external.c stats.c time.c transport.c \ + tkey.c tsec.c tsig.c ttl.c update.c validator.c view.c xfrin.c \ + zone.c zoneverify.c zonekey.c zt.c client.c rdatalist_p.h \ + tsig_p.h zone_p.h gssapi_link.c geoip2.c dnstap.c +am__objects_1 = +@HAVE_GSSAPI_TRUE@am__objects_2 = libdns_la-gssapi_link.lo +@HAVE_GEOIP2_TRUE@am__objects_3 = libdns_la-geoip2.lo +@HAVE_DNSTAP_TRUE@am__objects_4 = libdns_la-dnstap.lo +am_libdns_la_OBJECTS = $(am__objects_1) $(am__objects_1) \ + libdns_la-acl.lo libdns_la-adb.lo libdns_la-badcache.lo \ + libdns_la-byaddr.lo libdns_la-cache.lo libdns_la-callbacks.lo \ + libdns_la-catz.lo libdns_la-clientinfo.lo \ + libdns_la-compress.lo libdns_la-db.lo libdns_la-dbiterator.lo \ + libdns_la-diff.lo libdns_la-dispatch.lo libdns_la-dlz.lo \ + libdns_la-dns64.lo libdns_la-dnsrps.lo libdns_la-dnssec.lo \ + libdns_la-ds.lo libdns_la-dst_api.lo libdns_la-dst_parse.lo \ + libdns_la-dyndb.lo libdns_la-ecs.lo libdns_la-fixedname.lo \ + libdns_la-forward.lo libdns_la-gssapictx.lo \ + libdns_la-hmac_link.lo libdns_la-ipkeylist.lo \ + libdns_la-iptable.lo libdns_la-journal.lo libdns_la-kasp.lo \ + libdns_la-key.lo libdns_la-keydata.lo libdns_la-keymgr.lo \ + libdns_la-keytable.lo libdns_la-log.lo libdns_la-lookup.lo \ + libdns_la-master.lo libdns_la-masterdump.lo \ + libdns_la-message.lo libdns_la-name.lo libdns_la-ncache.lo \ + libdns_la-nsec.lo libdns_la-nsec3.lo libdns_la-nta.lo \ + libdns_la-openssl_link.lo libdns_la-openssl_shim.lo \ + libdns_la-openssldh_link.lo libdns_la-opensslecdsa_link.lo \ + libdns_la-openssleddsa_link.lo libdns_la-opensslrsa_link.lo \ + libdns_la-order.lo libdns_la-peer.lo libdns_la-private.lo \ + libdns_la-rbt.lo libdns_la-rbtdb.lo libdns_la-rcode.lo \ + libdns_la-rdata.lo libdns_la-rdatalist.lo \ + libdns_la-rdataset.lo libdns_la-rdatasetiter.lo \ + libdns_la-rdataslab.lo libdns_la-request.lo \ + libdns_la-resolver.lo libdns_la-result.lo libdns_la-rootns.lo \ + libdns_la-rpz.lo libdns_la-rrl.lo libdns_la-rriterator.lo \ + libdns_la-sdb.lo libdns_la-sdlz.lo libdns_la-soa.lo \ + libdns_la-ssu.lo libdns_la-ssu_external.lo libdns_la-stats.lo \ + libdns_la-time.lo libdns_la-transport.lo libdns_la-tkey.lo \ + libdns_la-tsec.lo libdns_la-tsig.lo libdns_la-ttl.lo \ + libdns_la-update.lo libdns_la-validator.lo libdns_la-view.lo \ + libdns_la-xfrin.lo libdns_la-zone.lo libdns_la-zoneverify.lo \ + libdns_la-zonekey.lo libdns_la-zt.lo libdns_la-client.lo \ + $(am__objects_2) $(am__objects_3) $(am__objects_4) +@HAVE_DNSTAP_TRUE@am__objects_5 = libdns_la-dnstap.pb-c.lo +nodist_libdns_la_OBJECTS = $(am__objects_1) $(am__objects_5) +libdns_la_OBJECTS = $(am_libdns_la_OBJECTS) \ + $(nodist_libdns_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libdns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libdns_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/libdns_la-acl.Plo \ + ./$(DEPDIR)/libdns_la-adb.Plo \ + ./$(DEPDIR)/libdns_la-badcache.Plo \ + ./$(DEPDIR)/libdns_la-byaddr.Plo \ + ./$(DEPDIR)/libdns_la-cache.Plo \ + ./$(DEPDIR)/libdns_la-callbacks.Plo \ + ./$(DEPDIR)/libdns_la-catz.Plo \ + ./$(DEPDIR)/libdns_la-client.Plo \ + ./$(DEPDIR)/libdns_la-clientinfo.Plo \ + ./$(DEPDIR)/libdns_la-compress.Plo \ + ./$(DEPDIR)/libdns_la-db.Plo \ + ./$(DEPDIR)/libdns_la-dbiterator.Plo \ + ./$(DEPDIR)/libdns_la-diff.Plo \ + ./$(DEPDIR)/libdns_la-dispatch.Plo \ + ./$(DEPDIR)/libdns_la-dlz.Plo ./$(DEPDIR)/libdns_la-dns64.Plo \ + ./$(DEPDIR)/libdns_la-dnsrps.Plo \ + ./$(DEPDIR)/libdns_la-dnssec.Plo \ + ./$(DEPDIR)/libdns_la-dnstap.Plo \ + ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo \ + ./$(DEPDIR)/libdns_la-ds.Plo ./$(DEPDIR)/libdns_la-dst_api.Plo \ + ./$(DEPDIR)/libdns_la-dst_parse.Plo \ + ./$(DEPDIR)/libdns_la-dyndb.Plo ./$(DEPDIR)/libdns_la-ecs.Plo \ + ./$(DEPDIR)/libdns_la-fixedname.Plo \ + ./$(DEPDIR)/libdns_la-forward.Plo \ + ./$(DEPDIR)/libdns_la-geoip2.Plo \ + ./$(DEPDIR)/libdns_la-gssapi_link.Plo \ + ./$(DEPDIR)/libdns_la-gssapictx.Plo \ + ./$(DEPDIR)/libdns_la-hmac_link.Plo \ + ./$(DEPDIR)/libdns_la-ipkeylist.Plo \ + ./$(DEPDIR)/libdns_la-iptable.Plo \ + ./$(DEPDIR)/libdns_la-journal.Plo \ + ./$(DEPDIR)/libdns_la-kasp.Plo ./$(DEPDIR)/libdns_la-key.Plo \ + ./$(DEPDIR)/libdns_la-keydata.Plo \ + ./$(DEPDIR)/libdns_la-keymgr.Plo \ + ./$(DEPDIR)/libdns_la-keytable.Plo \ + ./$(DEPDIR)/libdns_la-log.Plo ./$(DEPDIR)/libdns_la-lookup.Plo \ + ./$(DEPDIR)/libdns_la-master.Plo \ + ./$(DEPDIR)/libdns_la-masterdump.Plo \ + ./$(DEPDIR)/libdns_la-message.Plo \ + ./$(DEPDIR)/libdns_la-name.Plo \ + ./$(DEPDIR)/libdns_la-ncache.Plo \ + ./$(DEPDIR)/libdns_la-nsec.Plo ./$(DEPDIR)/libdns_la-nsec3.Plo \ + ./$(DEPDIR)/libdns_la-nta.Plo \ + ./$(DEPDIR)/libdns_la-openssl_link.Plo \ + ./$(DEPDIR)/libdns_la-openssl_shim.Plo \ + ./$(DEPDIR)/libdns_la-openssldh_link.Plo \ + ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo \ + ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo \ + ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo \ + ./$(DEPDIR)/libdns_la-order.Plo ./$(DEPDIR)/libdns_la-peer.Plo \ + ./$(DEPDIR)/libdns_la-private.Plo \ + ./$(DEPDIR)/libdns_la-rbt.Plo ./$(DEPDIR)/libdns_la-rbtdb.Plo \ + ./$(DEPDIR)/libdns_la-rcode.Plo \ + ./$(DEPDIR)/libdns_la-rdata.Plo \ + ./$(DEPDIR)/libdns_la-rdatalist.Plo \ + ./$(DEPDIR)/libdns_la-rdataset.Plo \ + ./$(DEPDIR)/libdns_la-rdatasetiter.Plo \ + ./$(DEPDIR)/libdns_la-rdataslab.Plo \ + ./$(DEPDIR)/libdns_la-request.Plo \ + ./$(DEPDIR)/libdns_la-resolver.Plo \ + ./$(DEPDIR)/libdns_la-result.Plo \ + ./$(DEPDIR)/libdns_la-rootns.Plo ./$(DEPDIR)/libdns_la-rpz.Plo \ + ./$(DEPDIR)/libdns_la-rriterator.Plo \ + ./$(DEPDIR)/libdns_la-rrl.Plo ./$(DEPDIR)/libdns_la-sdb.Plo \ + ./$(DEPDIR)/libdns_la-sdlz.Plo ./$(DEPDIR)/libdns_la-soa.Plo \ + ./$(DEPDIR)/libdns_la-ssu.Plo \ + ./$(DEPDIR)/libdns_la-ssu_external.Plo \ + ./$(DEPDIR)/libdns_la-stats.Plo ./$(DEPDIR)/libdns_la-time.Plo \ + ./$(DEPDIR)/libdns_la-tkey.Plo \ + ./$(DEPDIR)/libdns_la-transport.Plo \ + ./$(DEPDIR)/libdns_la-tsec.Plo ./$(DEPDIR)/libdns_la-tsig.Plo \ + ./$(DEPDIR)/libdns_la-ttl.Plo ./$(DEPDIR)/libdns_la-update.Plo \ + ./$(DEPDIR)/libdns_la-validator.Plo \ + ./$(DEPDIR)/libdns_la-view.Plo ./$(DEPDIR)/libdns_la-xfrin.Plo \ + ./$(DEPDIR)/libdns_la-zone.Plo \ + ./$(DEPDIR)/libdns_la-zonekey.Plo \ + ./$(DEPDIR)/libdns_la-zoneverify.Plo \ + ./$(DEPDIR)/libdns_la-zt.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libdns_la_SOURCES) $(nodist_libdns_la_SOURCES) +DIST_SOURCES = $(am__libdns_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(dst_HEADERS) $(libdns_la_HEADERS) \ + $(nodist_libdns_la_HEADERS) +am__extra_recursive_targets = test-recursive unit-recursive \ + doc-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top \ + $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_EXEEXT = @BUILD_EXEEXT@ +BUILD_OBJEXT = @BUILD_OBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CMOCKA_CFLAGS = @CMOCKA_CFLAGS@ +CMOCKA_LIBS = @CMOCKA_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CPP_FOR_BUILD = @CPP_FOR_BUILD@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CURL = @CURL@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEVELOPER_MODE = @DEVELOPER_MODE@ +DLLTOOL = @DLLTOOL@ +DNSTAP_CFLAGS = @DNSTAP_CFLAGS@ +DNSTAP_LIBS = @DNSTAP_LIBS@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +FSTRM_CAPTURE = @FSTRM_CAPTURE@ +FUZZ_LDFLAGS = @FUZZ_LDFLAGS@ +FUZZ_LOG_COMPILER = @FUZZ_LOG_COMPILER@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JEMALLOC_CFLAGS = @JEMALLOC_CFLAGS@ +JEMALLOC_LIBS = @JEMALLOC_LIBS@ +JSON_C_CFLAGS = @JSON_C_CFLAGS@ +JSON_C_LIBS = @JSON_C_LIBS@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_CONFIG = @KRB5_CONFIG@ +KRB5_LIBS = @KRB5_LIBS@ +LATEXMK = @LATEXMK@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBIDN2_CFLAGS = @LIBIDN2_CFLAGS@ +LIBIDN2_LIBS = @LIBIDN2_LIBS@ +LIBNGHTTP2_CFLAGS = @LIBNGHTTP2_CFLAGS@ +LIBNGHTTP2_LIBS = @LIBNGHTTP2_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUV_CFLAGS = @LIBUV_CFLAGS@ +LIBUV_LIBS = @LIBUV_LIBS@ +LIBXML2_CFLAGS = @LIBXML2_CFLAGS@ +LIBXML2_LIBS = @LIBXML2_LIBS@ +LIPO = @LIPO@ +LMDB_CFLAGS = @LMDB_CFLAGS@ +LMDB_LIBS = @LMDB_LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MAXMINDDB_CFLAGS = @MAXMINDDB_CFLAGS@ +MAXMINDDB_LIBS = @MAXMINDDB_LIBS@ +MAXMINDDB_PREFIX = @MAXMINDDB_PREFIX@ +MKDIR_P = @MKDIR_P@ +NC = @NC@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROTOC_C = @PROTOC_C@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_CXX = @PTHREAD_CXX@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTEST = @PYTEST@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +READLINE_CFLAGS = @READLINE_CFLAGS@ +READLINE_LIBS = @READLINE_LIBS@ +RELEASE_DATE = @RELEASE_DATE@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX_BUILD = @SPHINX_BUILD@ +STD_CFLAGS = @STD_CFLAGS@ +STD_CPPFLAGS = @STD_CPPFLAGS@ +STD_LDFLAGS = @STD_LDFLAGS@ +STRIP = @STRIP@ +TEST_CFLAGS = @TEST_CFLAGS@ +VERSION = @VERSION@ +XELATEX = @XELATEX@ +XSLTPROC = @XSLTPROC@ +ZLIB_CFLAGS = @ZLIB_CFLAGS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4 +AM_CFLAGS = \ + $(STD_CFLAGS) + +AM_CPPFLAGS = \ + $(STD_CPPFLAGS) \ + -include $(top_builddir)/config.h \ + -I$(srcdir)/include + +AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1) +LDADD = +LIBISC_CFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/lib/isc/include \ + -I$(top_builddir)/lib/isc/include + +LIBISC_LIBS = $(top_builddir)/lib/isc/libisc.la +LIBDNS_CFLAGS = \ + -I$(top_srcdir)/lib/dns/include \ + -I$(top_builddir)/lib/dns/include + +LIBDNS_LIBS = \ + $(top_builddir)/lib/dns/libdns.la + +LIBNS_CFLAGS = \ + -I$(top_srcdir)/lib/ns/include + +LIBNS_LIBS = \ + $(top_builddir)/lib/ns/libns.la + +LIBIRS_CFLAGS = \ + -I$(top_srcdir)/lib/irs/include + +LIBIRS_LIBS = \ + $(top_builddir)/lib/irs/libirs.la + +LIBISCCFG_CFLAGS = \ + -I$(top_srcdir)/lib/isccfg/include + +LIBISCCFG_LIBS = \ + $(top_builddir)/lib/isccfg/libisccfg.la + +LIBISCCC_CFLAGS = \ + -I$(top_srcdir)/lib/isccc/include/ + +LIBISCCC_LIBS = \ + $(top_builddir)/lib/isccc/libisccc.la + +LIBBIND9_CFLAGS = \ + -I$(top_srcdir)/lib/bind9/include + +LIBBIND9_LIBS = \ + $(top_builddir)/lib/bind9/libbind9.la + +lib_LTLIBRARIES = libdns.la +nodist_libdns_ladir = $(includedir)/dns +nodist_libdns_la_HEADERS = \ + include/dns/enumclass.h \ + include/dns/enumtype.h \ + include/dns/rdatastruct.h + +nodist_libdns_la_SOURCES = $(nodist_libdns_la_HEADERS) code.h \ + $(am__append_12) +BUILT_SOURCES = \ + $(nodist_libdns_la_SOURCES) + +CLEANFILES = \ + $(nodist_libdns_la_SOURCES) \ + gen$(BUILD_EXEEXT) + +EXTRA_DIST = \ + dnstap.proto \ + gen.c \ + rdata/* + +libdns_ladir = $(includedir)/dns +libdns_la_HEADERS = \ + include/dns/acl.h \ + include/dns/adb.h \ + include/dns/badcache.h \ + include/dns/bit.h \ + include/dns/byaddr.h \ + include/dns/cache.h \ + include/dns/callbacks.h \ + include/dns/catz.h \ + include/dns/cert.h \ + include/dns/client.h \ + include/dns/clientinfo.h \ + include/dns/compress.h \ + include/dns/db.h \ + include/dns/dbiterator.h \ + include/dns/diff.h \ + include/dns/dispatch.h \ + include/dns/dlz.h \ + include/dns/dlz_dlopen.h \ + include/dns/dns64.h \ + include/dns/dnsrps.h \ + include/dns/dnssec.h \ + include/dns/ds.h \ + include/dns/dsdigest.h \ + include/dns/dnstap.h \ + include/dns/dyndb.h \ + include/dns/ecs.h \ + include/dns/edns.h \ + include/dns/events.h \ + include/dns/fixedname.h \ + include/dns/forward.h \ + include/dns/geoip.h \ + include/dns/ipkeylist.h \ + include/dns/iptable.h \ + include/dns/journal.h \ + include/dns/kasp.h \ + include/dns/keydata.h \ + include/dns/keyflags.h \ + include/dns/keymgr.h \ + include/dns/keytable.h \ + include/dns/keyvalues.h \ + include/dns/librpz.h \ + include/dns/lookup.h \ + include/dns/log.h \ + include/dns/master.h \ + include/dns/masterdump.h \ + include/dns/message.h \ + include/dns/name.h \ + include/dns/ncache.h \ + include/dns/nsec.h \ + include/dns/nsec3.h \ + include/dns/nta.h \ + include/dns/opcode.h \ + include/dns/order.h \ + include/dns/peer.h \ + include/dns/private.h \ + include/dns/rbt.h \ + include/dns/rcode.h \ + include/dns/rdata.h \ + include/dns/rdataclass.h \ + include/dns/rdatalist.h \ + include/dns/rdataset.h \ + include/dns/rdatasetiter.h \ + include/dns/rdataslab.h \ + include/dns/rdatatype.h \ + include/dns/request.h \ + include/dns/resolver.h \ + include/dns/result.h \ + include/dns/rootns.h \ + include/dns/rpz.h \ + include/dns/rriterator.h \ + include/dns/rrl.h \ + include/dns/sdb.h \ + include/dns/sdlz.h \ + include/dns/secalg.h \ + include/dns/secproto.h \ + include/dns/soa.h \ + include/dns/ssu.h \ + include/dns/stats.h \ + include/dns/time.h \ + include/dns/transport.h \ + include/dns/tkey.h \ + include/dns/tsec.h \ + include/dns/tsig.h \ + include/dns/ttl.h \ + include/dns/types.h \ + include/dns/update.h \ + include/dns/validator.h \ + include/dns/view.h \ + include/dns/xfrin.h \ + include/dns/zone.h \ + include/dns/zonekey.h \ + include/dns/zoneverify.h \ + include/dns/zt.h + +dstdir = $(includedir)/dst +dst_HEADERS = \ + include/dst/dst.h \ + include/dst/gssapi.h + +libdns_la_SOURCES = $(libdns_la_HEADERS) $(dst_HEADERS) acl.c adb.c \ + badcache.c byaddr.c cache.c callbacks.c catz.c clientinfo.c \ + compress.c db.c dbiterator.c diff.c dispatch.c dlz.c dns64.c \ + dnsrps.c dnssec.c ds.c dst_api.c dst_internal.h dst_openssl.h \ + dst_parse.c dst_parse.h dyndb.c ecs.c fixedname.c forward.c \ + gssapictx.c hmac_link.c ipkeylist.c iptable.c journal.c kasp.c \ + key.c keydata.c keymgr.c keytable.c log.c lookup.c master.c \ + masterdump.c message.c name.c ncache.c nsec.c nsec3.c nta.c \ + openssl_link.c openssl_shim.c openssl_shim.h openssldh_link.c \ + opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \ + order.c peer.c private.c rbt.c rbtdb.h rbtdb.c rcode.c rdata.c \ + rdatalist.c rdataset.c rdatasetiter.c rdataslab.c request.c \ + resolver.c result.c rootns.c rpz.c rrl.c rriterator.c sdb.c \ + sdlz.c soa.c ssu.c ssu_external.c stats.c time.c transport.c \ + tkey.c tsec.c tsig.c ttl.c update.c validator.c view.c xfrin.c \ + zone.c zoneverify.c zonekey.c zt.c client.c rdatalist_p.h \ + tsig_p.h zone_p.h $(am__append_2) $(am__append_3) \ + $(am__append_13) +libdns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBDNS_CFLAGS) $(LIBISC_CFLAGS) \ + $(LIBUV_CFLAGS) $(OPENSSL_CFLAGS) $(am__append_4) \ + $(am__append_6) $(am__append_8) $(am__append_10) \ + $(am__append_14) $(am__append_16) +libdns_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -release "$(PACKAGE_VERSION)" + +libdns_la_LIBADD = $(LIBISC_LIBS) $(LIBUV_LIBS) $(OPENSSL_LIBS) \ + $(am__append_5) $(am__append_7) $(am__append_9) \ + $(am__append_11) $(am__append_15) $(am__append_17) +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/dns/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign lib/dns/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/Makefile.top $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libdns.la: $(libdns_la_OBJECTS) $(libdns_la_DEPENDENCIES) $(EXTRA_libdns_la_DEPENDENCIES) + $(AM_V_CCLD)$(libdns_la_LINK) -rpath $(libdir) $(libdns_la_OBJECTS) $(libdns_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-acl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-adb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-badcache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-byaddr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-callbacks.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-catz.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-clientinfo.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-compress.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-db.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dbiterator.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-diff.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dispatch.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dlz.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dns64.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnsrps.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnssec.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnstap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ds.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dst_api.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dst_parse.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-dyndb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ecs.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-fixedname.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-forward.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-geoip2.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-gssapi_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-gssapictx.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-hmac_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ipkeylist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-iptable.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-journal.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-kasp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-key.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keydata.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keymgr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-keytable.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-lookup.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-master.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-masterdump.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-message.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-name.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ncache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nsec.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nsec3.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-nta.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssl_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssl_shim.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssldh_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-openssleddsa_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-opensslrsa_link.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-order.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-peer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-private.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rbt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rbtdb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rcode.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdata.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdatalist.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdataset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdatasetiter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rdataslab.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-request.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-resolver.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-result.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rootns.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rpz.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rriterator.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-rrl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-sdb.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-sdlz.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-soa.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ssu.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ssu_external.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-stats.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-time.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tkey.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-transport.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tsec.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-tsig.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-ttl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-update.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-validator.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-view.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-xfrin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zone.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zonekey.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zoneverify.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdns_la-zt.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +libdns_la-acl.lo: acl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-acl.lo -MD -MP -MF $(DEPDIR)/libdns_la-acl.Tpo -c -o libdns_la-acl.lo `test -f 'acl.c' || echo '$(srcdir)/'`acl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-acl.Tpo $(DEPDIR)/libdns_la-acl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='acl.c' object='libdns_la-acl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-acl.lo `test -f 'acl.c' || echo '$(srcdir)/'`acl.c + +libdns_la-adb.lo: adb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-adb.lo -MD -MP -MF $(DEPDIR)/libdns_la-adb.Tpo -c -o libdns_la-adb.lo `test -f 'adb.c' || echo '$(srcdir)/'`adb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-adb.Tpo $(DEPDIR)/libdns_la-adb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='adb.c' object='libdns_la-adb.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-adb.lo `test -f 'adb.c' || echo '$(srcdir)/'`adb.c + +libdns_la-badcache.lo: badcache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-badcache.lo -MD -MP -MF $(DEPDIR)/libdns_la-badcache.Tpo -c -o libdns_la-badcache.lo `test -f 'badcache.c' || echo '$(srcdir)/'`badcache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-badcache.Tpo $(DEPDIR)/libdns_la-badcache.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='badcache.c' object='libdns_la-badcache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-badcache.lo `test -f 'badcache.c' || echo '$(srcdir)/'`badcache.c + +libdns_la-byaddr.lo: byaddr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-byaddr.lo -MD -MP -MF $(DEPDIR)/libdns_la-byaddr.Tpo -c -o libdns_la-byaddr.lo `test -f 'byaddr.c' || echo '$(srcdir)/'`byaddr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-byaddr.Tpo $(DEPDIR)/libdns_la-byaddr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='byaddr.c' object='libdns_la-byaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-byaddr.lo `test -f 'byaddr.c' || echo '$(srcdir)/'`byaddr.c + +libdns_la-cache.lo: cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-cache.lo -MD -MP -MF $(DEPDIR)/libdns_la-cache.Tpo -c -o libdns_la-cache.lo `test -f 'cache.c' || echo '$(srcdir)/'`cache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-cache.Tpo $(DEPDIR)/libdns_la-cache.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='libdns_la-cache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-cache.lo `test -f 'cache.c' || echo '$(srcdir)/'`cache.c + +libdns_la-callbacks.lo: callbacks.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-callbacks.lo -MD -MP -MF $(DEPDIR)/libdns_la-callbacks.Tpo -c -o libdns_la-callbacks.lo `test -f 'callbacks.c' || echo '$(srcdir)/'`callbacks.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-callbacks.Tpo $(DEPDIR)/libdns_la-callbacks.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='callbacks.c' object='libdns_la-callbacks.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-callbacks.lo `test -f 'callbacks.c' || echo '$(srcdir)/'`callbacks.c + +libdns_la-catz.lo: catz.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-catz.lo -MD -MP -MF $(DEPDIR)/libdns_la-catz.Tpo -c -o libdns_la-catz.lo `test -f 'catz.c' || echo '$(srcdir)/'`catz.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-catz.Tpo $(DEPDIR)/libdns_la-catz.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='catz.c' object='libdns_la-catz.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-catz.lo `test -f 'catz.c' || echo '$(srcdir)/'`catz.c + +libdns_la-clientinfo.lo: clientinfo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-clientinfo.lo -MD -MP -MF $(DEPDIR)/libdns_la-clientinfo.Tpo -c -o libdns_la-clientinfo.lo `test -f 'clientinfo.c' || echo '$(srcdir)/'`clientinfo.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-clientinfo.Tpo $(DEPDIR)/libdns_la-clientinfo.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='clientinfo.c' object='libdns_la-clientinfo.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-clientinfo.lo `test -f 'clientinfo.c' || echo '$(srcdir)/'`clientinfo.c + +libdns_la-compress.lo: compress.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-compress.lo -MD -MP -MF $(DEPDIR)/libdns_la-compress.Tpo -c -o libdns_la-compress.lo `test -f 'compress.c' || echo '$(srcdir)/'`compress.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-compress.Tpo $(DEPDIR)/libdns_la-compress.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compress.c' object='libdns_la-compress.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-compress.lo `test -f 'compress.c' || echo '$(srcdir)/'`compress.c + +libdns_la-db.lo: db.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-db.lo -MD -MP -MF $(DEPDIR)/libdns_la-db.Tpo -c -o libdns_la-db.lo `test -f 'db.c' || echo '$(srcdir)/'`db.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-db.Tpo $(DEPDIR)/libdns_la-db.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db.c' object='libdns_la-db.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-db.lo `test -f 'db.c' || echo '$(srcdir)/'`db.c + +libdns_la-dbiterator.lo: dbiterator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dbiterator.lo -MD -MP -MF $(DEPDIR)/libdns_la-dbiterator.Tpo -c -o libdns_la-dbiterator.lo `test -f 'dbiterator.c' || echo '$(srcdir)/'`dbiterator.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dbiterator.Tpo $(DEPDIR)/libdns_la-dbiterator.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dbiterator.c' object='libdns_la-dbiterator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dbiterator.lo `test -f 'dbiterator.c' || echo '$(srcdir)/'`dbiterator.c + +libdns_la-diff.lo: diff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-diff.lo -MD -MP -MF $(DEPDIR)/libdns_la-diff.Tpo -c -o libdns_la-diff.lo `test -f 'diff.c' || echo '$(srcdir)/'`diff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-diff.Tpo $(DEPDIR)/libdns_la-diff.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='diff.c' object='libdns_la-diff.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-diff.lo `test -f 'diff.c' || echo '$(srcdir)/'`diff.c + +libdns_la-dispatch.lo: dispatch.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dispatch.lo -MD -MP -MF $(DEPDIR)/libdns_la-dispatch.Tpo -c -o libdns_la-dispatch.lo `test -f 'dispatch.c' || echo '$(srcdir)/'`dispatch.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dispatch.Tpo $(DEPDIR)/libdns_la-dispatch.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dispatch.c' object='libdns_la-dispatch.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dispatch.lo `test -f 'dispatch.c' || echo '$(srcdir)/'`dispatch.c + +libdns_la-dlz.lo: dlz.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dlz.lo -MD -MP -MF $(DEPDIR)/libdns_la-dlz.Tpo -c -o libdns_la-dlz.lo `test -f 'dlz.c' || echo '$(srcdir)/'`dlz.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dlz.Tpo $(DEPDIR)/libdns_la-dlz.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dlz.c' object='libdns_la-dlz.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dlz.lo `test -f 'dlz.c' || echo '$(srcdir)/'`dlz.c + +libdns_la-dns64.lo: dns64.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dns64.lo -MD -MP -MF $(DEPDIR)/libdns_la-dns64.Tpo -c -o libdns_la-dns64.lo `test -f 'dns64.c' || echo '$(srcdir)/'`dns64.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dns64.Tpo $(DEPDIR)/libdns_la-dns64.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns64.c' object='libdns_la-dns64.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dns64.lo `test -f 'dns64.c' || echo '$(srcdir)/'`dns64.c + +libdns_la-dnsrps.lo: dnsrps.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnsrps.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnsrps.Tpo -c -o libdns_la-dnsrps.lo `test -f 'dnsrps.c' || echo '$(srcdir)/'`dnsrps.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnsrps.Tpo $(DEPDIR)/libdns_la-dnsrps.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnsrps.c' object='libdns_la-dnsrps.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnsrps.lo `test -f 'dnsrps.c' || echo '$(srcdir)/'`dnsrps.c + +libdns_la-dnssec.lo: dnssec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnssec.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnssec.Tpo -c -o libdns_la-dnssec.lo `test -f 'dnssec.c' || echo '$(srcdir)/'`dnssec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnssec.Tpo $(DEPDIR)/libdns_la-dnssec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnssec.c' object='libdns_la-dnssec.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnssec.lo `test -f 'dnssec.c' || echo '$(srcdir)/'`dnssec.c + +libdns_la-ds.lo: ds.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ds.lo -MD -MP -MF $(DEPDIR)/libdns_la-ds.Tpo -c -o libdns_la-ds.lo `test -f 'ds.c' || echo '$(srcdir)/'`ds.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ds.Tpo $(DEPDIR)/libdns_la-ds.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ds.c' object='libdns_la-ds.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ds.lo `test -f 'ds.c' || echo '$(srcdir)/'`ds.c + +libdns_la-dst_api.lo: dst_api.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dst_api.lo -MD -MP -MF $(DEPDIR)/libdns_la-dst_api.Tpo -c -o libdns_la-dst_api.lo `test -f 'dst_api.c' || echo '$(srcdir)/'`dst_api.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dst_api.Tpo $(DEPDIR)/libdns_la-dst_api.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dst_api.c' object='libdns_la-dst_api.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dst_api.lo `test -f 'dst_api.c' || echo '$(srcdir)/'`dst_api.c + +libdns_la-dst_parse.lo: dst_parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dst_parse.lo -MD -MP -MF $(DEPDIR)/libdns_la-dst_parse.Tpo -c -o libdns_la-dst_parse.lo `test -f 'dst_parse.c' || echo '$(srcdir)/'`dst_parse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dst_parse.Tpo $(DEPDIR)/libdns_la-dst_parse.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dst_parse.c' object='libdns_la-dst_parse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dst_parse.lo `test -f 'dst_parse.c' || echo '$(srcdir)/'`dst_parse.c + +libdns_la-dyndb.lo: dyndb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dyndb.lo -MD -MP -MF $(DEPDIR)/libdns_la-dyndb.Tpo -c -o libdns_la-dyndb.lo `test -f 'dyndb.c' || echo '$(srcdir)/'`dyndb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dyndb.Tpo $(DEPDIR)/libdns_la-dyndb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dyndb.c' object='libdns_la-dyndb.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dyndb.lo `test -f 'dyndb.c' || echo '$(srcdir)/'`dyndb.c + +libdns_la-ecs.lo: ecs.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ecs.lo -MD -MP -MF $(DEPDIR)/libdns_la-ecs.Tpo -c -o libdns_la-ecs.lo `test -f 'ecs.c' || echo '$(srcdir)/'`ecs.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ecs.Tpo $(DEPDIR)/libdns_la-ecs.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ecs.c' object='libdns_la-ecs.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ecs.lo `test -f 'ecs.c' || echo '$(srcdir)/'`ecs.c + +libdns_la-fixedname.lo: fixedname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-fixedname.lo -MD -MP -MF $(DEPDIR)/libdns_la-fixedname.Tpo -c -o libdns_la-fixedname.lo `test -f 'fixedname.c' || echo '$(srcdir)/'`fixedname.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-fixedname.Tpo $(DEPDIR)/libdns_la-fixedname.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fixedname.c' object='libdns_la-fixedname.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-fixedname.lo `test -f 'fixedname.c' || echo '$(srcdir)/'`fixedname.c + +libdns_la-forward.lo: forward.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-forward.lo -MD -MP -MF $(DEPDIR)/libdns_la-forward.Tpo -c -o libdns_la-forward.lo `test -f 'forward.c' || echo '$(srcdir)/'`forward.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-forward.Tpo $(DEPDIR)/libdns_la-forward.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='forward.c' object='libdns_la-forward.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-forward.lo `test -f 'forward.c' || echo '$(srcdir)/'`forward.c + +libdns_la-gssapictx.lo: gssapictx.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-gssapictx.lo -MD -MP -MF $(DEPDIR)/libdns_la-gssapictx.Tpo -c -o libdns_la-gssapictx.lo `test -f 'gssapictx.c' || echo '$(srcdir)/'`gssapictx.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-gssapictx.Tpo $(DEPDIR)/libdns_la-gssapictx.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gssapictx.c' object='libdns_la-gssapictx.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-gssapictx.lo `test -f 'gssapictx.c' || echo '$(srcdir)/'`gssapictx.c + +libdns_la-hmac_link.lo: hmac_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-hmac_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-hmac_link.Tpo -c -o libdns_la-hmac_link.lo `test -f 'hmac_link.c' || echo '$(srcdir)/'`hmac_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-hmac_link.Tpo $(DEPDIR)/libdns_la-hmac_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hmac_link.c' object='libdns_la-hmac_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-hmac_link.lo `test -f 'hmac_link.c' || echo '$(srcdir)/'`hmac_link.c + +libdns_la-ipkeylist.lo: ipkeylist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ipkeylist.lo -MD -MP -MF $(DEPDIR)/libdns_la-ipkeylist.Tpo -c -o libdns_la-ipkeylist.lo `test -f 'ipkeylist.c' || echo '$(srcdir)/'`ipkeylist.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ipkeylist.Tpo $(DEPDIR)/libdns_la-ipkeylist.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ipkeylist.c' object='libdns_la-ipkeylist.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ipkeylist.lo `test -f 'ipkeylist.c' || echo '$(srcdir)/'`ipkeylist.c + +libdns_la-iptable.lo: iptable.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-iptable.lo -MD -MP -MF $(DEPDIR)/libdns_la-iptable.Tpo -c -o libdns_la-iptable.lo `test -f 'iptable.c' || echo '$(srcdir)/'`iptable.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-iptable.Tpo $(DEPDIR)/libdns_la-iptable.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='iptable.c' object='libdns_la-iptable.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-iptable.lo `test -f 'iptable.c' || echo '$(srcdir)/'`iptable.c + +libdns_la-journal.lo: journal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-journal.lo -MD -MP -MF $(DEPDIR)/libdns_la-journal.Tpo -c -o libdns_la-journal.lo `test -f 'journal.c' || echo '$(srcdir)/'`journal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-journal.Tpo $(DEPDIR)/libdns_la-journal.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='journal.c' object='libdns_la-journal.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-journal.lo `test -f 'journal.c' || echo '$(srcdir)/'`journal.c + +libdns_la-kasp.lo: kasp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-kasp.lo -MD -MP -MF $(DEPDIR)/libdns_la-kasp.Tpo -c -o libdns_la-kasp.lo `test -f 'kasp.c' || echo '$(srcdir)/'`kasp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-kasp.Tpo $(DEPDIR)/libdns_la-kasp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kasp.c' object='libdns_la-kasp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-kasp.lo `test -f 'kasp.c' || echo '$(srcdir)/'`kasp.c + +libdns_la-key.lo: key.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-key.lo -MD -MP -MF $(DEPDIR)/libdns_la-key.Tpo -c -o libdns_la-key.lo `test -f 'key.c' || echo '$(srcdir)/'`key.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-key.Tpo $(DEPDIR)/libdns_la-key.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='key.c' object='libdns_la-key.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-key.lo `test -f 'key.c' || echo '$(srcdir)/'`key.c + +libdns_la-keydata.lo: keydata.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keydata.lo -MD -MP -MF $(DEPDIR)/libdns_la-keydata.Tpo -c -o libdns_la-keydata.lo `test -f 'keydata.c' || echo '$(srcdir)/'`keydata.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keydata.Tpo $(DEPDIR)/libdns_la-keydata.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keydata.c' object='libdns_la-keydata.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keydata.lo `test -f 'keydata.c' || echo '$(srcdir)/'`keydata.c + +libdns_la-keymgr.lo: keymgr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keymgr.lo -MD -MP -MF $(DEPDIR)/libdns_la-keymgr.Tpo -c -o libdns_la-keymgr.lo `test -f 'keymgr.c' || echo '$(srcdir)/'`keymgr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keymgr.Tpo $(DEPDIR)/libdns_la-keymgr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keymgr.c' object='libdns_la-keymgr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keymgr.lo `test -f 'keymgr.c' || echo '$(srcdir)/'`keymgr.c + +libdns_la-keytable.lo: keytable.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-keytable.lo -MD -MP -MF $(DEPDIR)/libdns_la-keytable.Tpo -c -o libdns_la-keytable.lo `test -f 'keytable.c' || echo '$(srcdir)/'`keytable.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-keytable.Tpo $(DEPDIR)/libdns_la-keytable.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='keytable.c' object='libdns_la-keytable.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-keytable.lo `test -f 'keytable.c' || echo '$(srcdir)/'`keytable.c + +libdns_la-log.lo: log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-log.lo -MD -MP -MF $(DEPDIR)/libdns_la-log.Tpo -c -o libdns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-log.Tpo $(DEPDIR)/libdns_la-log.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libdns_la-log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c + +libdns_la-lookup.lo: lookup.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-lookup.lo -MD -MP -MF $(DEPDIR)/libdns_la-lookup.Tpo -c -o libdns_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-lookup.Tpo $(DEPDIR)/libdns_la-lookup.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lookup.c' object='libdns_la-lookup.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c + +libdns_la-master.lo: master.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-master.lo -MD -MP -MF $(DEPDIR)/libdns_la-master.Tpo -c -o libdns_la-master.lo `test -f 'master.c' || echo '$(srcdir)/'`master.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-master.Tpo $(DEPDIR)/libdns_la-master.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='master.c' object='libdns_la-master.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-master.lo `test -f 'master.c' || echo '$(srcdir)/'`master.c + +libdns_la-masterdump.lo: masterdump.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-masterdump.lo -MD -MP -MF $(DEPDIR)/libdns_la-masterdump.Tpo -c -o libdns_la-masterdump.lo `test -f 'masterdump.c' || echo '$(srcdir)/'`masterdump.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-masterdump.Tpo $(DEPDIR)/libdns_la-masterdump.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='masterdump.c' object='libdns_la-masterdump.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-masterdump.lo `test -f 'masterdump.c' || echo '$(srcdir)/'`masterdump.c + +libdns_la-message.lo: message.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-message.lo -MD -MP -MF $(DEPDIR)/libdns_la-message.Tpo -c -o libdns_la-message.lo `test -f 'message.c' || echo '$(srcdir)/'`message.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-message.Tpo $(DEPDIR)/libdns_la-message.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='message.c' object='libdns_la-message.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-message.lo `test -f 'message.c' || echo '$(srcdir)/'`message.c + +libdns_la-name.lo: name.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-name.lo -MD -MP -MF $(DEPDIR)/libdns_la-name.Tpo -c -o libdns_la-name.lo `test -f 'name.c' || echo '$(srcdir)/'`name.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-name.Tpo $(DEPDIR)/libdns_la-name.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='name.c' object='libdns_la-name.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-name.lo `test -f 'name.c' || echo '$(srcdir)/'`name.c + +libdns_la-ncache.lo: ncache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ncache.lo -MD -MP -MF $(DEPDIR)/libdns_la-ncache.Tpo -c -o libdns_la-ncache.lo `test -f 'ncache.c' || echo '$(srcdir)/'`ncache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ncache.Tpo $(DEPDIR)/libdns_la-ncache.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ncache.c' object='libdns_la-ncache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ncache.lo `test -f 'ncache.c' || echo '$(srcdir)/'`ncache.c + +libdns_la-nsec.lo: nsec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nsec.lo -MD -MP -MF $(DEPDIR)/libdns_la-nsec.Tpo -c -o libdns_la-nsec.lo `test -f 'nsec.c' || echo '$(srcdir)/'`nsec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nsec.Tpo $(DEPDIR)/libdns_la-nsec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsec.c' object='libdns_la-nsec.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nsec.lo `test -f 'nsec.c' || echo '$(srcdir)/'`nsec.c + +libdns_la-nsec3.lo: nsec3.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nsec3.lo -MD -MP -MF $(DEPDIR)/libdns_la-nsec3.Tpo -c -o libdns_la-nsec3.lo `test -f 'nsec3.c' || echo '$(srcdir)/'`nsec3.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nsec3.Tpo $(DEPDIR)/libdns_la-nsec3.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsec3.c' object='libdns_la-nsec3.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nsec3.lo `test -f 'nsec3.c' || echo '$(srcdir)/'`nsec3.c + +libdns_la-nta.lo: nta.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-nta.lo -MD -MP -MF $(DEPDIR)/libdns_la-nta.Tpo -c -o libdns_la-nta.lo `test -f 'nta.c' || echo '$(srcdir)/'`nta.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-nta.Tpo $(DEPDIR)/libdns_la-nta.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nta.c' object='libdns_la-nta.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-nta.lo `test -f 'nta.c' || echo '$(srcdir)/'`nta.c + +libdns_la-openssl_link.lo: openssl_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssl_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssl_link.Tpo -c -o libdns_la-openssl_link.lo `test -f 'openssl_link.c' || echo '$(srcdir)/'`openssl_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssl_link.Tpo $(DEPDIR)/libdns_la-openssl_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_link.c' object='libdns_la-openssl_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssl_link.lo `test -f 'openssl_link.c' || echo '$(srcdir)/'`openssl_link.c + +libdns_la-openssl_shim.lo: openssl_shim.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssl_shim.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssl_shim.Tpo -c -o libdns_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssl_shim.Tpo $(DEPDIR)/libdns_la-openssl_shim.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_shim.c' object='libdns_la-openssl_shim.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c + +libdns_la-openssldh_link.lo: openssldh_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssldh_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssldh_link.Tpo -c -o libdns_la-openssldh_link.lo `test -f 'openssldh_link.c' || echo '$(srcdir)/'`openssldh_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssldh_link.Tpo $(DEPDIR)/libdns_la-openssldh_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssldh_link.c' object='libdns_la-openssldh_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssldh_link.lo `test -f 'openssldh_link.c' || echo '$(srcdir)/'`openssldh_link.c + +libdns_la-opensslecdsa_link.lo: opensslecdsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-opensslecdsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-opensslecdsa_link.Tpo -c -o libdns_la-opensslecdsa_link.lo `test -f 'opensslecdsa_link.c' || echo '$(srcdir)/'`opensslecdsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-opensslecdsa_link.Tpo $(DEPDIR)/libdns_la-opensslecdsa_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='opensslecdsa_link.c' object='libdns_la-opensslecdsa_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-opensslecdsa_link.lo `test -f 'opensslecdsa_link.c' || echo '$(srcdir)/'`opensslecdsa_link.c + +libdns_la-openssleddsa_link.lo: openssleddsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-openssleddsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-openssleddsa_link.Tpo -c -o libdns_la-openssleddsa_link.lo `test -f 'openssleddsa_link.c' || echo '$(srcdir)/'`openssleddsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-openssleddsa_link.Tpo $(DEPDIR)/libdns_la-openssleddsa_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssleddsa_link.c' object='libdns_la-openssleddsa_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-openssleddsa_link.lo `test -f 'openssleddsa_link.c' || echo '$(srcdir)/'`openssleddsa_link.c + +libdns_la-opensslrsa_link.lo: opensslrsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-opensslrsa_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-opensslrsa_link.Tpo -c -o libdns_la-opensslrsa_link.lo `test -f 'opensslrsa_link.c' || echo '$(srcdir)/'`opensslrsa_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-opensslrsa_link.Tpo $(DEPDIR)/libdns_la-opensslrsa_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='opensslrsa_link.c' object='libdns_la-opensslrsa_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-opensslrsa_link.lo `test -f 'opensslrsa_link.c' || echo '$(srcdir)/'`opensslrsa_link.c + +libdns_la-order.lo: order.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-order.lo -MD -MP -MF $(DEPDIR)/libdns_la-order.Tpo -c -o libdns_la-order.lo `test -f 'order.c' || echo '$(srcdir)/'`order.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-order.Tpo $(DEPDIR)/libdns_la-order.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='order.c' object='libdns_la-order.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-order.lo `test -f 'order.c' || echo '$(srcdir)/'`order.c + +libdns_la-peer.lo: peer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-peer.lo -MD -MP -MF $(DEPDIR)/libdns_la-peer.Tpo -c -o libdns_la-peer.lo `test -f 'peer.c' || echo '$(srcdir)/'`peer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-peer.Tpo $(DEPDIR)/libdns_la-peer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='peer.c' object='libdns_la-peer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-peer.lo `test -f 'peer.c' || echo '$(srcdir)/'`peer.c + +libdns_la-private.lo: private.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-private.lo -MD -MP -MF $(DEPDIR)/libdns_la-private.Tpo -c -o libdns_la-private.lo `test -f 'private.c' || echo '$(srcdir)/'`private.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-private.Tpo $(DEPDIR)/libdns_la-private.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='private.c' object='libdns_la-private.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-private.lo `test -f 'private.c' || echo '$(srcdir)/'`private.c + +libdns_la-rbt.lo: rbt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rbt.lo -MD -MP -MF $(DEPDIR)/libdns_la-rbt.Tpo -c -o libdns_la-rbt.lo `test -f 'rbt.c' || echo '$(srcdir)/'`rbt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rbt.Tpo $(DEPDIR)/libdns_la-rbt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rbt.c' object='libdns_la-rbt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rbt.lo `test -f 'rbt.c' || echo '$(srcdir)/'`rbt.c + +libdns_la-rbtdb.lo: rbtdb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rbtdb.lo -MD -MP -MF $(DEPDIR)/libdns_la-rbtdb.Tpo -c -o libdns_la-rbtdb.lo `test -f 'rbtdb.c' || echo '$(srcdir)/'`rbtdb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rbtdb.Tpo $(DEPDIR)/libdns_la-rbtdb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rbtdb.c' object='libdns_la-rbtdb.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rbtdb.lo `test -f 'rbtdb.c' || echo '$(srcdir)/'`rbtdb.c + +libdns_la-rcode.lo: rcode.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rcode.lo -MD -MP -MF $(DEPDIR)/libdns_la-rcode.Tpo -c -o libdns_la-rcode.lo `test -f 'rcode.c' || echo '$(srcdir)/'`rcode.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rcode.Tpo $(DEPDIR)/libdns_la-rcode.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rcode.c' object='libdns_la-rcode.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rcode.lo `test -f 'rcode.c' || echo '$(srcdir)/'`rcode.c + +libdns_la-rdata.lo: rdata.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdata.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdata.Tpo -c -o libdns_la-rdata.lo `test -f 'rdata.c' || echo '$(srcdir)/'`rdata.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdata.Tpo $(DEPDIR)/libdns_la-rdata.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdata.c' object='libdns_la-rdata.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdata.lo `test -f 'rdata.c' || echo '$(srcdir)/'`rdata.c + +libdns_la-rdatalist.lo: rdatalist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdatalist.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdatalist.Tpo -c -o libdns_la-rdatalist.lo `test -f 'rdatalist.c' || echo '$(srcdir)/'`rdatalist.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdatalist.Tpo $(DEPDIR)/libdns_la-rdatalist.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdatalist.c' object='libdns_la-rdatalist.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdatalist.lo `test -f 'rdatalist.c' || echo '$(srcdir)/'`rdatalist.c + +libdns_la-rdataset.lo: rdataset.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdataset.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdataset.Tpo -c -o libdns_la-rdataset.lo `test -f 'rdataset.c' || echo '$(srcdir)/'`rdataset.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdataset.Tpo $(DEPDIR)/libdns_la-rdataset.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdataset.c' object='libdns_la-rdataset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdataset.lo `test -f 'rdataset.c' || echo '$(srcdir)/'`rdataset.c + +libdns_la-rdatasetiter.lo: rdatasetiter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdatasetiter.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdatasetiter.Tpo -c -o libdns_la-rdatasetiter.lo `test -f 'rdatasetiter.c' || echo '$(srcdir)/'`rdatasetiter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdatasetiter.Tpo $(DEPDIR)/libdns_la-rdatasetiter.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdatasetiter.c' object='libdns_la-rdatasetiter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdatasetiter.lo `test -f 'rdatasetiter.c' || echo '$(srcdir)/'`rdatasetiter.c + +libdns_la-rdataslab.lo: rdataslab.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rdataslab.lo -MD -MP -MF $(DEPDIR)/libdns_la-rdataslab.Tpo -c -o libdns_la-rdataslab.lo `test -f 'rdataslab.c' || echo '$(srcdir)/'`rdataslab.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rdataslab.Tpo $(DEPDIR)/libdns_la-rdataslab.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rdataslab.c' object='libdns_la-rdataslab.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rdataslab.lo `test -f 'rdataslab.c' || echo '$(srcdir)/'`rdataslab.c + +libdns_la-request.lo: request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-request.lo -MD -MP -MF $(DEPDIR)/libdns_la-request.Tpo -c -o libdns_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-request.Tpo $(DEPDIR)/libdns_la-request.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='request.c' object='libdns_la-request.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c + +libdns_la-resolver.lo: resolver.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-resolver.lo -MD -MP -MF $(DEPDIR)/libdns_la-resolver.Tpo -c -o libdns_la-resolver.lo `test -f 'resolver.c' || echo '$(srcdir)/'`resolver.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-resolver.Tpo $(DEPDIR)/libdns_la-resolver.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='resolver.c' object='libdns_la-resolver.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-resolver.lo `test -f 'resolver.c' || echo '$(srcdir)/'`resolver.c + +libdns_la-result.lo: result.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-result.lo -MD -MP -MF $(DEPDIR)/libdns_la-result.Tpo -c -o libdns_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-result.Tpo $(DEPDIR)/libdns_la-result.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='result.c' object='libdns_la-result.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c + +libdns_la-rootns.lo: rootns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rootns.lo -MD -MP -MF $(DEPDIR)/libdns_la-rootns.Tpo -c -o libdns_la-rootns.lo `test -f 'rootns.c' || echo '$(srcdir)/'`rootns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rootns.Tpo $(DEPDIR)/libdns_la-rootns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rootns.c' object='libdns_la-rootns.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rootns.lo `test -f 'rootns.c' || echo '$(srcdir)/'`rootns.c + +libdns_la-rpz.lo: rpz.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rpz.lo -MD -MP -MF $(DEPDIR)/libdns_la-rpz.Tpo -c -o libdns_la-rpz.lo `test -f 'rpz.c' || echo '$(srcdir)/'`rpz.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rpz.Tpo $(DEPDIR)/libdns_la-rpz.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rpz.c' object='libdns_la-rpz.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rpz.lo `test -f 'rpz.c' || echo '$(srcdir)/'`rpz.c + +libdns_la-rrl.lo: rrl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rrl.lo -MD -MP -MF $(DEPDIR)/libdns_la-rrl.Tpo -c -o libdns_la-rrl.lo `test -f 'rrl.c' || echo '$(srcdir)/'`rrl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rrl.Tpo $(DEPDIR)/libdns_la-rrl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rrl.c' object='libdns_la-rrl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rrl.lo `test -f 'rrl.c' || echo '$(srcdir)/'`rrl.c + +libdns_la-rriterator.lo: rriterator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-rriterator.lo -MD -MP -MF $(DEPDIR)/libdns_la-rriterator.Tpo -c -o libdns_la-rriterator.lo `test -f 'rriterator.c' || echo '$(srcdir)/'`rriterator.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-rriterator.Tpo $(DEPDIR)/libdns_la-rriterator.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rriterator.c' object='libdns_la-rriterator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-rriterator.lo `test -f 'rriterator.c' || echo '$(srcdir)/'`rriterator.c + +libdns_la-sdb.lo: sdb.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-sdb.lo -MD -MP -MF $(DEPDIR)/libdns_la-sdb.Tpo -c -o libdns_la-sdb.lo `test -f 'sdb.c' || echo '$(srcdir)/'`sdb.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-sdb.Tpo $(DEPDIR)/libdns_la-sdb.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sdb.c' object='libdns_la-sdb.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-sdb.lo `test -f 'sdb.c' || echo '$(srcdir)/'`sdb.c + +libdns_la-sdlz.lo: sdlz.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-sdlz.lo -MD -MP -MF $(DEPDIR)/libdns_la-sdlz.Tpo -c -o libdns_la-sdlz.lo `test -f 'sdlz.c' || echo '$(srcdir)/'`sdlz.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-sdlz.Tpo $(DEPDIR)/libdns_la-sdlz.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sdlz.c' object='libdns_la-sdlz.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-sdlz.lo `test -f 'sdlz.c' || echo '$(srcdir)/'`sdlz.c + +libdns_la-soa.lo: soa.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-soa.lo -MD -MP -MF $(DEPDIR)/libdns_la-soa.Tpo -c -o libdns_la-soa.lo `test -f 'soa.c' || echo '$(srcdir)/'`soa.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-soa.Tpo $(DEPDIR)/libdns_la-soa.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='soa.c' object='libdns_la-soa.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-soa.lo `test -f 'soa.c' || echo '$(srcdir)/'`soa.c + +libdns_la-ssu.lo: ssu.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ssu.lo -MD -MP -MF $(DEPDIR)/libdns_la-ssu.Tpo -c -o libdns_la-ssu.lo `test -f 'ssu.c' || echo '$(srcdir)/'`ssu.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ssu.Tpo $(DEPDIR)/libdns_la-ssu.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ssu.c' object='libdns_la-ssu.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ssu.lo `test -f 'ssu.c' || echo '$(srcdir)/'`ssu.c + +libdns_la-ssu_external.lo: ssu_external.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ssu_external.lo -MD -MP -MF $(DEPDIR)/libdns_la-ssu_external.Tpo -c -o libdns_la-ssu_external.lo `test -f 'ssu_external.c' || echo '$(srcdir)/'`ssu_external.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ssu_external.Tpo $(DEPDIR)/libdns_la-ssu_external.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ssu_external.c' object='libdns_la-ssu_external.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ssu_external.lo `test -f 'ssu_external.c' || echo '$(srcdir)/'`ssu_external.c + +libdns_la-stats.lo: stats.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-stats.lo -MD -MP -MF $(DEPDIR)/libdns_la-stats.Tpo -c -o libdns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-stats.Tpo $(DEPDIR)/libdns_la-stats.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stats.c' object='libdns_la-stats.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c + +libdns_la-time.lo: time.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-time.lo -MD -MP -MF $(DEPDIR)/libdns_la-time.Tpo -c -o libdns_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-time.Tpo $(DEPDIR)/libdns_la-time.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='time.c' object='libdns_la-time.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c + +libdns_la-transport.lo: transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-transport.lo -MD -MP -MF $(DEPDIR)/libdns_la-transport.Tpo -c -o libdns_la-transport.lo `test -f 'transport.c' || echo '$(srcdir)/'`transport.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-transport.Tpo $(DEPDIR)/libdns_la-transport.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='transport.c' object='libdns_la-transport.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-transport.lo `test -f 'transport.c' || echo '$(srcdir)/'`transport.c + +libdns_la-tkey.lo: tkey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tkey.lo -MD -MP -MF $(DEPDIR)/libdns_la-tkey.Tpo -c -o libdns_la-tkey.lo `test -f 'tkey.c' || echo '$(srcdir)/'`tkey.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tkey.Tpo $(DEPDIR)/libdns_la-tkey.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tkey.c' object='libdns_la-tkey.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tkey.lo `test -f 'tkey.c' || echo '$(srcdir)/'`tkey.c + +libdns_la-tsec.lo: tsec.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tsec.lo -MD -MP -MF $(DEPDIR)/libdns_la-tsec.Tpo -c -o libdns_la-tsec.lo `test -f 'tsec.c' || echo '$(srcdir)/'`tsec.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tsec.Tpo $(DEPDIR)/libdns_la-tsec.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tsec.c' object='libdns_la-tsec.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tsec.lo `test -f 'tsec.c' || echo '$(srcdir)/'`tsec.c + +libdns_la-tsig.lo: tsig.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-tsig.lo -MD -MP -MF $(DEPDIR)/libdns_la-tsig.Tpo -c -o libdns_la-tsig.lo `test -f 'tsig.c' || echo '$(srcdir)/'`tsig.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-tsig.Tpo $(DEPDIR)/libdns_la-tsig.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tsig.c' object='libdns_la-tsig.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-tsig.lo `test -f 'tsig.c' || echo '$(srcdir)/'`tsig.c + +libdns_la-ttl.lo: ttl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-ttl.lo -MD -MP -MF $(DEPDIR)/libdns_la-ttl.Tpo -c -o libdns_la-ttl.lo `test -f 'ttl.c' || echo '$(srcdir)/'`ttl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-ttl.Tpo $(DEPDIR)/libdns_la-ttl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ttl.c' object='libdns_la-ttl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-ttl.lo `test -f 'ttl.c' || echo '$(srcdir)/'`ttl.c + +libdns_la-update.lo: update.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-update.lo -MD -MP -MF $(DEPDIR)/libdns_la-update.Tpo -c -o libdns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-update.Tpo $(DEPDIR)/libdns_la-update.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='update.c' object='libdns_la-update.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c + +libdns_la-validator.lo: validator.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-validator.lo -MD -MP -MF $(DEPDIR)/libdns_la-validator.Tpo -c -o libdns_la-validator.lo `test -f 'validator.c' || echo '$(srcdir)/'`validator.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-validator.Tpo $(DEPDIR)/libdns_la-validator.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='validator.c' object='libdns_la-validator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-validator.lo `test -f 'validator.c' || echo '$(srcdir)/'`validator.c + +libdns_la-view.lo: view.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-view.lo -MD -MP -MF $(DEPDIR)/libdns_la-view.Tpo -c -o libdns_la-view.lo `test -f 'view.c' || echo '$(srcdir)/'`view.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-view.Tpo $(DEPDIR)/libdns_la-view.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='view.c' object='libdns_la-view.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-view.lo `test -f 'view.c' || echo '$(srcdir)/'`view.c + +libdns_la-xfrin.lo: xfrin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-xfrin.lo -MD -MP -MF $(DEPDIR)/libdns_la-xfrin.Tpo -c -o libdns_la-xfrin.lo `test -f 'xfrin.c' || echo '$(srcdir)/'`xfrin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-xfrin.Tpo $(DEPDIR)/libdns_la-xfrin.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xfrin.c' object='libdns_la-xfrin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-xfrin.lo `test -f 'xfrin.c' || echo '$(srcdir)/'`xfrin.c + +libdns_la-zone.lo: zone.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zone.lo -MD -MP -MF $(DEPDIR)/libdns_la-zone.Tpo -c -o libdns_la-zone.lo `test -f 'zone.c' || echo '$(srcdir)/'`zone.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zone.Tpo $(DEPDIR)/libdns_la-zone.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zone.c' object='libdns_la-zone.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zone.lo `test -f 'zone.c' || echo '$(srcdir)/'`zone.c + +libdns_la-zoneverify.lo: zoneverify.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zoneverify.lo -MD -MP -MF $(DEPDIR)/libdns_la-zoneverify.Tpo -c -o libdns_la-zoneverify.lo `test -f 'zoneverify.c' || echo '$(srcdir)/'`zoneverify.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zoneverify.Tpo $(DEPDIR)/libdns_la-zoneverify.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zoneverify.c' object='libdns_la-zoneverify.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zoneverify.lo `test -f 'zoneverify.c' || echo '$(srcdir)/'`zoneverify.c + +libdns_la-zonekey.lo: zonekey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zonekey.lo -MD -MP -MF $(DEPDIR)/libdns_la-zonekey.Tpo -c -o libdns_la-zonekey.lo `test -f 'zonekey.c' || echo '$(srcdir)/'`zonekey.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zonekey.Tpo $(DEPDIR)/libdns_la-zonekey.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zonekey.c' object='libdns_la-zonekey.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zonekey.lo `test -f 'zonekey.c' || echo '$(srcdir)/'`zonekey.c + +libdns_la-zt.lo: zt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-zt.lo -MD -MP -MF $(DEPDIR)/libdns_la-zt.Tpo -c -o libdns_la-zt.lo `test -f 'zt.c' || echo '$(srcdir)/'`zt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-zt.Tpo $(DEPDIR)/libdns_la-zt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zt.c' object='libdns_la-zt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-zt.lo `test -f 'zt.c' || echo '$(srcdir)/'`zt.c + +libdns_la-client.lo: client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-client.lo -MD -MP -MF $(DEPDIR)/libdns_la-client.Tpo -c -o libdns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-client.Tpo $(DEPDIR)/libdns_la-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='libdns_la-client.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c + +libdns_la-gssapi_link.lo: gssapi_link.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-gssapi_link.lo -MD -MP -MF $(DEPDIR)/libdns_la-gssapi_link.Tpo -c -o libdns_la-gssapi_link.lo `test -f 'gssapi_link.c' || echo '$(srcdir)/'`gssapi_link.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-gssapi_link.Tpo $(DEPDIR)/libdns_la-gssapi_link.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gssapi_link.c' object='libdns_la-gssapi_link.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-gssapi_link.lo `test -f 'gssapi_link.c' || echo '$(srcdir)/'`gssapi_link.c + +libdns_la-geoip2.lo: geoip2.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-geoip2.lo -MD -MP -MF $(DEPDIR)/libdns_la-geoip2.Tpo -c -o libdns_la-geoip2.lo `test -f 'geoip2.c' || echo '$(srcdir)/'`geoip2.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-geoip2.Tpo $(DEPDIR)/libdns_la-geoip2.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='geoip2.c' object='libdns_la-geoip2.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-geoip2.lo `test -f 'geoip2.c' || echo '$(srcdir)/'`geoip2.c + +libdns_la-dnstap.lo: dnstap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnstap.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnstap.Tpo -c -o libdns_la-dnstap.lo `test -f 'dnstap.c' || echo '$(srcdir)/'`dnstap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnstap.Tpo $(DEPDIR)/libdns_la-dnstap.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnstap.c' object='libdns_la-dnstap.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnstap.lo `test -f 'dnstap.c' || echo '$(srcdir)/'`dnstap.c + +libdns_la-dnstap.pb-c.lo: dnstap.pb-c.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdns_la-dnstap.pb-c.lo -MD -MP -MF $(DEPDIR)/libdns_la-dnstap.pb-c.Tpo -c -o libdns_la-dnstap.pb-c.lo `test -f 'dnstap.pb-c.c' || echo '$(srcdir)/'`dnstap.pb-c.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdns_la-dnstap.pb-c.Tpo $(DEPDIR)/libdns_la-dnstap.pb-c.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnstap.pb-c.c' object='libdns_la-dnstap.pb-c.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdns_la-dnstap.pb-c.lo `test -f 'dnstap.pb-c.c' || echo '$(srcdir)/'`dnstap.pb-c.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-dstHEADERS: $(dst_HEADERS) + @$(NORMAL_INSTALL) + @list='$(dst_HEADERS)'; test -n "$(dstdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dstdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dstdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(dstdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(dstdir)" || exit $$?; \ + done + +uninstall-dstHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(dst_HEADERS)'; test -n "$(dstdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(dstdir)'; $(am__uninstall_files_from_dir) +install-libdns_laHEADERS: $(libdns_la_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdns_ladir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdns_ladir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libdns_ladir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns_ladir)" || exit $$?; \ + done + +uninstall-libdns_laHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libdns_ladir)'; $(am__uninstall_files_from_dir) +install-nodist_libdns_laHEADERS: $(nodist_libdns_la_HEADERS) + @$(NORMAL_INSTALL) + @list='$(nodist_libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdns_ladir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdns_ladir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libdns_ladir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns_ladir)" || exit $$?; \ + done + +uninstall-nodist_libdns_laHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nodist_libdns_la_HEADERS)'; test -n "$(libdns_ladir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libdns_ladir)'; $(am__uninstall_files_from_dir) +test-local: +unit-local: +doc-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(dstdir)" "$(DESTDIR)$(libdns_ladir)" "$(DESTDIR)$(libdns_ladir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/libdns_la-acl.Plo + -rm -f ./$(DEPDIR)/libdns_la-adb.Plo + -rm -f ./$(DEPDIR)/libdns_la-badcache.Plo + -rm -f ./$(DEPDIR)/libdns_la-byaddr.Plo + -rm -f ./$(DEPDIR)/libdns_la-cache.Plo + -rm -f ./$(DEPDIR)/libdns_la-callbacks.Plo + -rm -f ./$(DEPDIR)/libdns_la-catz.Plo + -rm -f ./$(DEPDIR)/libdns_la-client.Plo + -rm -f ./$(DEPDIR)/libdns_la-clientinfo.Plo + -rm -f ./$(DEPDIR)/libdns_la-compress.Plo + -rm -f ./$(DEPDIR)/libdns_la-db.Plo + -rm -f ./$(DEPDIR)/libdns_la-dbiterator.Plo + -rm -f ./$(DEPDIR)/libdns_la-diff.Plo + -rm -f ./$(DEPDIR)/libdns_la-dispatch.Plo + -rm -f ./$(DEPDIR)/libdns_la-dlz.Plo + -rm -f ./$(DEPDIR)/libdns_la-dns64.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnsrps.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnssec.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnstap.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo + -rm -f ./$(DEPDIR)/libdns_la-ds.Plo + -rm -f ./$(DEPDIR)/libdns_la-dst_api.Plo + -rm -f ./$(DEPDIR)/libdns_la-dst_parse.Plo + -rm -f ./$(DEPDIR)/libdns_la-dyndb.Plo + -rm -f ./$(DEPDIR)/libdns_la-ecs.Plo + -rm -f ./$(DEPDIR)/libdns_la-fixedname.Plo + -rm -f ./$(DEPDIR)/libdns_la-forward.Plo + -rm -f ./$(DEPDIR)/libdns_la-geoip2.Plo + -rm -f ./$(DEPDIR)/libdns_la-gssapi_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-gssapictx.Plo + -rm -f ./$(DEPDIR)/libdns_la-hmac_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-ipkeylist.Plo + -rm -f ./$(DEPDIR)/libdns_la-iptable.Plo + -rm -f ./$(DEPDIR)/libdns_la-journal.Plo + -rm -f ./$(DEPDIR)/libdns_la-kasp.Plo + -rm -f ./$(DEPDIR)/libdns_la-key.Plo + -rm -f ./$(DEPDIR)/libdns_la-keydata.Plo + -rm -f ./$(DEPDIR)/libdns_la-keymgr.Plo + -rm -f ./$(DEPDIR)/libdns_la-keytable.Plo + -rm -f ./$(DEPDIR)/libdns_la-log.Plo + -rm -f ./$(DEPDIR)/libdns_la-lookup.Plo + -rm -f ./$(DEPDIR)/libdns_la-master.Plo + -rm -f ./$(DEPDIR)/libdns_la-masterdump.Plo + -rm -f ./$(DEPDIR)/libdns_la-message.Plo + -rm -f ./$(DEPDIR)/libdns_la-name.Plo + -rm -f ./$(DEPDIR)/libdns_la-ncache.Plo + -rm -f ./$(DEPDIR)/libdns_la-nsec.Plo + -rm -f ./$(DEPDIR)/libdns_la-nsec3.Plo + -rm -f ./$(DEPDIR)/libdns_la-nta.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssl_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssl_shim.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssldh_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-order.Plo + -rm -f ./$(DEPDIR)/libdns_la-peer.Plo + -rm -f ./$(DEPDIR)/libdns_la-private.Plo + -rm -f ./$(DEPDIR)/libdns_la-rbt.Plo + -rm -f ./$(DEPDIR)/libdns_la-rbtdb.Plo + -rm -f ./$(DEPDIR)/libdns_la-rcode.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdata.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdatalist.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdataset.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdatasetiter.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdataslab.Plo + -rm -f ./$(DEPDIR)/libdns_la-request.Plo + -rm -f ./$(DEPDIR)/libdns_la-resolver.Plo + -rm -f ./$(DEPDIR)/libdns_la-result.Plo + -rm -f ./$(DEPDIR)/libdns_la-rootns.Plo + -rm -f ./$(DEPDIR)/libdns_la-rpz.Plo + -rm -f ./$(DEPDIR)/libdns_la-rriterator.Plo + -rm -f ./$(DEPDIR)/libdns_la-rrl.Plo + -rm -f ./$(DEPDIR)/libdns_la-sdb.Plo + -rm -f ./$(DEPDIR)/libdns_la-sdlz.Plo + -rm -f ./$(DEPDIR)/libdns_la-soa.Plo + -rm -f ./$(DEPDIR)/libdns_la-ssu.Plo + -rm -f ./$(DEPDIR)/libdns_la-ssu_external.Plo + -rm -f ./$(DEPDIR)/libdns_la-stats.Plo + -rm -f ./$(DEPDIR)/libdns_la-time.Plo + -rm -f ./$(DEPDIR)/libdns_la-tkey.Plo + -rm -f ./$(DEPDIR)/libdns_la-transport.Plo + -rm -f ./$(DEPDIR)/libdns_la-tsec.Plo + -rm -f ./$(DEPDIR)/libdns_la-tsig.Plo + -rm -f ./$(DEPDIR)/libdns_la-ttl.Plo + -rm -f ./$(DEPDIR)/libdns_la-update.Plo + -rm -f ./$(DEPDIR)/libdns_la-validator.Plo + -rm -f ./$(DEPDIR)/libdns_la-view.Plo + -rm -f ./$(DEPDIR)/libdns_la-xfrin.Plo + -rm -f ./$(DEPDIR)/libdns_la-zone.Plo + -rm -f ./$(DEPDIR)/libdns_la-zonekey.Plo + -rm -f ./$(DEPDIR)/libdns_la-zoneverify.Plo + -rm -f ./$(DEPDIR)/libdns_la-zt.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +doc: doc-am + +doc-am: doc-local + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-dstHEADERS install-libdns_laHEADERS \ + install-nodist_libdns_laHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/libdns_la-acl.Plo + -rm -f ./$(DEPDIR)/libdns_la-adb.Plo + -rm -f ./$(DEPDIR)/libdns_la-badcache.Plo + -rm -f ./$(DEPDIR)/libdns_la-byaddr.Plo + -rm -f ./$(DEPDIR)/libdns_la-cache.Plo + -rm -f ./$(DEPDIR)/libdns_la-callbacks.Plo + -rm -f ./$(DEPDIR)/libdns_la-catz.Plo + -rm -f ./$(DEPDIR)/libdns_la-client.Plo + -rm -f ./$(DEPDIR)/libdns_la-clientinfo.Plo + -rm -f ./$(DEPDIR)/libdns_la-compress.Plo + -rm -f ./$(DEPDIR)/libdns_la-db.Plo + -rm -f ./$(DEPDIR)/libdns_la-dbiterator.Plo + -rm -f ./$(DEPDIR)/libdns_la-diff.Plo + -rm -f ./$(DEPDIR)/libdns_la-dispatch.Plo + -rm -f ./$(DEPDIR)/libdns_la-dlz.Plo + -rm -f ./$(DEPDIR)/libdns_la-dns64.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnsrps.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnssec.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnstap.Plo + -rm -f ./$(DEPDIR)/libdns_la-dnstap.pb-c.Plo + -rm -f ./$(DEPDIR)/libdns_la-ds.Plo + -rm -f ./$(DEPDIR)/libdns_la-dst_api.Plo + -rm -f ./$(DEPDIR)/libdns_la-dst_parse.Plo + -rm -f ./$(DEPDIR)/libdns_la-dyndb.Plo + -rm -f ./$(DEPDIR)/libdns_la-ecs.Plo + -rm -f ./$(DEPDIR)/libdns_la-fixedname.Plo + -rm -f ./$(DEPDIR)/libdns_la-forward.Plo + -rm -f ./$(DEPDIR)/libdns_la-geoip2.Plo + -rm -f ./$(DEPDIR)/libdns_la-gssapi_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-gssapictx.Plo + -rm -f ./$(DEPDIR)/libdns_la-hmac_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-ipkeylist.Plo + -rm -f ./$(DEPDIR)/libdns_la-iptable.Plo + -rm -f ./$(DEPDIR)/libdns_la-journal.Plo + -rm -f ./$(DEPDIR)/libdns_la-kasp.Plo + -rm -f ./$(DEPDIR)/libdns_la-key.Plo + -rm -f ./$(DEPDIR)/libdns_la-keydata.Plo + -rm -f ./$(DEPDIR)/libdns_la-keymgr.Plo + -rm -f ./$(DEPDIR)/libdns_la-keytable.Plo + -rm -f ./$(DEPDIR)/libdns_la-log.Plo + -rm -f ./$(DEPDIR)/libdns_la-lookup.Plo + -rm -f ./$(DEPDIR)/libdns_la-master.Plo + -rm -f ./$(DEPDIR)/libdns_la-masterdump.Plo + -rm -f ./$(DEPDIR)/libdns_la-message.Plo + -rm -f ./$(DEPDIR)/libdns_la-name.Plo + -rm -f ./$(DEPDIR)/libdns_la-ncache.Plo + -rm -f ./$(DEPDIR)/libdns_la-nsec.Plo + -rm -f ./$(DEPDIR)/libdns_la-nsec3.Plo + -rm -f ./$(DEPDIR)/libdns_la-nta.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssl_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssl_shim.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssldh_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-opensslecdsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-openssleddsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-opensslrsa_link.Plo + -rm -f ./$(DEPDIR)/libdns_la-order.Plo + -rm -f ./$(DEPDIR)/libdns_la-peer.Plo + -rm -f ./$(DEPDIR)/libdns_la-private.Plo + -rm -f ./$(DEPDIR)/libdns_la-rbt.Plo + -rm -f ./$(DEPDIR)/libdns_la-rbtdb.Plo + -rm -f ./$(DEPDIR)/libdns_la-rcode.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdata.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdatalist.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdataset.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdatasetiter.Plo + -rm -f ./$(DEPDIR)/libdns_la-rdataslab.Plo + -rm -f ./$(DEPDIR)/libdns_la-request.Plo + -rm -f ./$(DEPDIR)/libdns_la-resolver.Plo + -rm -f ./$(DEPDIR)/libdns_la-result.Plo + -rm -f ./$(DEPDIR)/libdns_la-rootns.Plo + -rm -f ./$(DEPDIR)/libdns_la-rpz.Plo + -rm -f ./$(DEPDIR)/libdns_la-rriterator.Plo + -rm -f ./$(DEPDIR)/libdns_la-rrl.Plo + -rm -f ./$(DEPDIR)/libdns_la-sdb.Plo + -rm -f ./$(DEPDIR)/libdns_la-sdlz.Plo + -rm -f ./$(DEPDIR)/libdns_la-soa.Plo + -rm -f ./$(DEPDIR)/libdns_la-ssu.Plo + -rm -f ./$(DEPDIR)/libdns_la-ssu_external.Plo + -rm -f ./$(DEPDIR)/libdns_la-stats.Plo + -rm -f ./$(DEPDIR)/libdns_la-time.Plo + -rm -f ./$(DEPDIR)/libdns_la-tkey.Plo + -rm -f ./$(DEPDIR)/libdns_la-transport.Plo + -rm -f ./$(DEPDIR)/libdns_la-tsec.Plo + -rm -f ./$(DEPDIR)/libdns_la-tsig.Plo + -rm -f ./$(DEPDIR)/libdns_la-ttl.Plo + -rm -f ./$(DEPDIR)/libdns_la-update.Plo + -rm -f ./$(DEPDIR)/libdns_la-validator.Plo + -rm -f ./$(DEPDIR)/libdns_la-view.Plo + -rm -f ./$(DEPDIR)/libdns_la-xfrin.Plo + -rm -f ./$(DEPDIR)/libdns_la-zone.Plo + -rm -f ./$(DEPDIR)/libdns_la-zonekey.Plo + -rm -f ./$(DEPDIR)/libdns_la-zoneverify.Plo + -rm -f ./$(DEPDIR)/libdns_la-zt.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +test: test-am + +test-am: test-local + +uninstall-am: uninstall-dstHEADERS uninstall-libLTLIBRARIES \ + uninstall-libdns_laHEADERS uninstall-nodist_libdns_laHEADERS + +unit: unit-am + +unit-am: unit-local + +.MAKE: all check install install-am install-exec install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir doc-am doc-local dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dstHEADERS install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libLTLIBRARIES install-libdns_laHEADERS install-man \ + install-nodist_libdns_laHEADERS install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am test-am test-local uninstall uninstall-am \ + uninstall-dstHEADERS uninstall-libLTLIBRARIES \ + uninstall-libdns_laHEADERS uninstall-nodist_libdns_laHEADERS \ + unit-am unit-local + +.PRECIOUS: Makefile + + +gen$(BUILD_EXEEXT): gen.c + $(CC_FOR_BUILD) -g -I. $(srcdir)/gen.c -o $@ + +include/dns/enumtype.h: gen Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -t > $@ + +include/dns/enumclass.h: gen Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -c > $@ + +include/dns/rdatastruct.h: gen rdata/rdatastructpre.h rdata/rdatastructsuf.h Makefile + mkdir -p include/dns + $(builddir)/gen -s $(srcdir) -i \ + -P $(srcdir)/rdata/rdatastructpre.h \ + -S $(srcdir)/rdata/rdatastructsuf.h > $@ + +code.h: gen Makefile + $(builddir)/gen -s $(srcdir) > $@ + +@HAVE_DNSTAP_TRUE@dnstap.pb-c.h dnstap.pb-c.c: dnstap.proto +@HAVE_DNSTAP_TRUE@ $(PROTOC_C) --proto_path=$(srcdir) --c_out=. dnstap.proto + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lib/dns/acl.c b/lib/dns/acl.c new file mode 100644 index 0000000..cea74f7 --- /dev/null +++ b/lib/dns/acl.c @@ -0,0 +1,863 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define DNS_ACLENV_MAGIC ISC_MAGIC('a', 'c', 'n', 'v') +#define VALID_ACLENV(a) ISC_MAGIC_VALID(a, DNS_ACLENV_MAGIC) + +/* + * Create a new ACL, including an IP table and an array with room + * for 'n' ACL elements. The elements are uninitialized and the + * length is 0. + */ +isc_result_t +dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) { + isc_result_t result; + dns_acl_t *acl; + + /* + * Work around silly limitation of isc_mem_get(). + */ + if (n == 0) { + n = 1; + } + + acl = isc_mem_get(mctx, sizeof(*acl)); + + acl->mctx = NULL; + isc_mem_attach(mctx, &acl->mctx); + + acl->name = NULL; + + isc_refcount_init(&acl->refcount, 1); + + result = dns_iptable_create(mctx, &acl->iptable); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, acl, sizeof(*acl)); + return (result); + } + + acl->elements = NULL; + acl->alloc = 0; + acl->length = 0; + acl->has_negatives = false; + + ISC_LINK_INIT(acl, nextincache); + /* + * Must set magic early because we use dns_acl_detach() to clean up. + */ + acl->magic = DNS_ACL_MAGIC; + + acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t)); + acl->alloc = n; + memset(acl->elements, 0, n * sizeof(dns_aclelement_t)); + ISC_LIST_INIT(acl->ports_and_transports); + acl->port_proto_entries = 0; + + *target = acl; + return (ISC_R_SUCCESS); +} + +/* + * Create a new ACL and initialize it with the value "any" or "none", + * depending on the value of the "neg" parameter. + * "any" is a positive iptable entry with bit length 0. + * "none" is the same as "!any". + */ +static isc_result_t +dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) { + isc_result_t result; + dns_acl_t *acl = NULL; + + result = dns_acl_create(mctx, 0, &acl); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg); + if (result != ISC_R_SUCCESS) { + dns_acl_detach(&acl); + return (result); + } + + *target = acl; + return (result); +} + +/* + * Create a new ACL that matches everything. + */ +isc_result_t +dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) { + return (dns_acl_anyornone(mctx, false, target)); +} + +/* + * Create a new ACL that matches nothing. + */ +isc_result_t +dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) { + return (dns_acl_anyornone(mctx, true, target)); +} + +/* + * If pos is true, test whether acl is set to "{ any; }" + * If pos is false, test whether acl is set to "{ none; }" + */ +static bool +dns_acl_isanyornone(dns_acl_t *acl, bool pos) { + /* Should never happen but let's be safe */ + if (acl == NULL || acl->iptable == NULL || + acl->iptable->radix == NULL || acl->iptable->radix->head == NULL || + acl->iptable->radix->head->prefix == NULL) + { + return (false); + } + + if (acl->length != 0 || dns_acl_node_count(acl) != 1) { + return (false); + } + + if (acl->iptable->radix->head->prefix->bitlen == 0 && + acl->iptable->radix->head->data[0] != NULL && + acl->iptable->radix->head->data[0] == + acl->iptable->radix->head->data[1] && + *(bool *)(acl->iptable->radix->head->data[0]) == pos) + { + return (true); + } + + return (false); /* All others */ +} + +/* + * Test whether acl is set to "{ any; }" + */ +bool +dns_acl_isany(dns_acl_t *acl) { + return (dns_acl_isanyornone(acl, true)); +} + +/* + * Test whether acl is set to "{ none; }" + */ +bool +dns_acl_isnone(dns_acl_t *acl) { + return (dns_acl_isanyornone(acl, false)); +} + +/* + * Determine whether a given address or signer matches a given ACL. + * For a match with a positive ACL element or iptable radix entry, + * return with a positive value in match; for a match with a negated ACL + * element or radix entry, return with a negative value in match. + */ + +isc_result_t +dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, + const dns_acl_t *acl, dns_aclenv_t *env, int *match, + const dns_aclelement_t **matchelt) { + uint16_t bitlen; + isc_prefix_t pfx; + isc_radix_node_t *node = NULL; + const isc_netaddr_t *addr = reqaddr; + isc_netaddr_t v4addr; + isc_result_t result; + int match_num = -1; + unsigned int i; + + REQUIRE(reqaddr != NULL); + REQUIRE(matchelt == NULL || *matchelt == NULL); + + if (env != NULL && env->match_mapped && addr->family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&addr->type.in6)) + { + isc_netaddr_fromv4mapped(&v4addr, addr); + addr = &v4addr; + } + + /* Always match with host addresses. */ + bitlen = (addr->family == AF_INET6) ? 128 : 32; + NETADDR_TO_PREFIX_T(addr, pfx, bitlen); + + /* Assume no match. */ + *match = 0; + + /* Search radix. */ + result = isc_radix_search(acl->iptable->radix, &node, &pfx); + + /* Found a match. */ + if (result == ISC_R_SUCCESS && node != NULL) { + int fam = ISC_RADIX_FAMILY(&pfx); + match_num = node->node_num[fam]; + if (*(bool *)node->data[fam]) { + *match = match_num; + } else { + *match = -match_num; + } + } + + isc_refcount_destroy(&pfx.refcount); + + /* Now search non-radix elements for a match with a lower node_num. */ + for (i = 0; i < acl->length; i++) { + dns_aclelement_t *e = &acl->elements[i]; + + /* Already found a better match? */ + if (match_num != -1 && match_num < e->node_num) { + break; + } + + if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt)) + { + if (match_num == -1 || e->node_num < match_num) { + if (e->negative) { + *match = -e->node_num; + } else { + *match = e->node_num; + } + } + break; + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_acl_match_port_transport(const isc_netaddr_t *reqaddr, + const in_port_t local_port, + const isc_nmsocket_type_t transport, + const bool encrypted, const dns_name_t *reqsigner, + const dns_acl_t *acl, dns_aclenv_t *env, + int *match, const dns_aclelement_t **matchelt) { + isc_result_t result = ISC_R_SUCCESS; + dns_acl_port_transports_t *next; + + REQUIRE(reqaddr != NULL); + REQUIRE(DNS_ACL_VALID(acl)); + + if (!ISC_LIST_EMPTY(acl->ports_and_transports)) { + result = ISC_R_FAILURE; + for (next = ISC_LIST_HEAD(acl->ports_and_transports); + next != NULL; next = ISC_LIST_NEXT(next, link)) + { + bool match_port = true; + bool match_transport = true; + + if (next->port != 0) { + /* Port is specified. */ + match_port = (local_port == next->port); + } + if (next->transports != 0) { + /* Transport protocol is specified. */ + match_transport = + ((transport & next->transports) == + transport && + next->encrypted == encrypted); + } + + if (match_port && match_transport) { + result = next->negative ? ISC_R_FAILURE + : ISC_R_SUCCESS; + break; + } + } + } + + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (dns_acl_match(reqaddr, reqsigner, acl, env, match, matchelt)); +} + +/* + * Merge the contents of one ACL into another. Call dns_iptable_merge() + * for the IP tables, then concatenate the element arrays. + * + * If pos is set to false, then the nested ACL is to be negated. This + * means reverse the sense of each *positive* element or IP table node, + * but leave negatives alone, so as to prevent a double-negative causing + * an unexpected positive match in the parent ACL. + */ +isc_result_t +dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) { + isc_result_t result; + unsigned int newalloc, nelem, i; + int max_node = 0, nodes; + + /* Resize the element array if needed. */ + if (dest->length + source->length > dest->alloc) { + void *newmem; + + newalloc = dest->alloc + source->alloc; + if (newalloc < 4) { + newalloc = 4; + } + + newmem = isc_mem_get(dest->mctx, + newalloc * sizeof(dns_aclelement_t)); + + /* Zero. */ + memset(newmem, 0, newalloc * sizeof(dns_aclelement_t)); + + /* Copy in the original elements */ + memmove(newmem, dest->elements, + dest->length * sizeof(dns_aclelement_t)); + + /* Release the memory for the old elements array */ + isc_mem_put(dest->mctx, dest->elements, + dest->alloc * sizeof(dns_aclelement_t)); + dest->elements = newmem; + dest->alloc = newalloc; + } + + /* + * Now copy in the new elements, increasing their node_num + * values so as to keep the new ACL consistent. If we're + * negating, then negate positive elements, but keep negative + * elements the same for security reasons. + */ + nelem = dest->length; + dest->length += source->length; + for (i = 0; i < source->length; i++) { + if (source->elements[i].node_num > max_node) { + max_node = source->elements[i].node_num; + } + + /* Copy type. */ + dest->elements[nelem + i].type = source->elements[i].type; + + /* Adjust node numbering. */ + dest->elements[nelem + i].node_num = + source->elements[i].node_num + dns_acl_node_count(dest); + + /* Duplicate nested acl. */ + if (source->elements[i].type == dns_aclelementtype_nestedacl && + source->elements[i].nestedacl != NULL) + { + dns_acl_attach(source->elements[i].nestedacl, + &dest->elements[nelem + i].nestedacl); + } + + /* Duplicate key name. */ + if (source->elements[i].type == dns_aclelementtype_keyname) { + dns_name_init(&dest->elements[nelem + i].keyname, NULL); + dns_name_dup(&source->elements[i].keyname, dest->mctx, + &dest->elements[nelem + i].keyname); + } + +#if defined(HAVE_GEOIP2) + /* Duplicate GeoIP data */ + if (source->elements[i].type == dns_aclelementtype_geoip) { + dest->elements[nelem + i].geoip_elem = + source->elements[i].geoip_elem; + } +#endif /* if defined(HAVE_GEOIP2) */ + + /* reverse sense of positives if this is a negative acl */ + if (!pos && !source->elements[i].negative) { + dest->elements[nelem + i].negative = true; + } else { + dest->elements[nelem + i].negative = + source->elements[i].negative; + } + } + + /* + * Merge the iptables. Make sure the destination ACL's + * node_count value is set correctly afterward. + */ + nodes = max_node + dns_acl_node_count(dest); + result = dns_iptable_merge(dest->iptable, source->iptable, pos); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (nodes > dns_acl_node_count(dest)) { + dns_acl_node_count(dest) = nodes; + } + + /* + * Merge ports and transports + */ + dns_acl_merge_ports_transports(dest, source, pos); + + return (ISC_R_SUCCESS); +} + +/* + * Like dns_acl_match, but matches against the single ACL element 'e' + * rather than a complete ACL, and returns true iff it matched. + * + * To determine whether the match was positive or negative, the + * caller should examine e->negative. Since the element 'e' may be + * a reference to a named ACL or a nested ACL, a matching element + * returned through 'matchelt' is not necessarily 'e' itself. + */ + +bool +dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, + const dns_aclelement_t *e, dns_aclenv_t *env, + const dns_aclelement_t **matchelt) { + dns_acl_t *inner = NULL; + int indirectmatch; + isc_result_t result; + + switch (e->type) { + case dns_aclelementtype_keyname: + if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname)) + { + if (matchelt != NULL) { + *matchelt = e; + } + return (true); + } else { + return (false); + } + + case dns_aclelementtype_nestedacl: + dns_acl_attach(e->nestedacl, &inner); + break; + + case dns_aclelementtype_localhost: + if (env == NULL) { + return (false); + } + RWLOCK(&env->rwlock, isc_rwlocktype_read); + if (env->localhost == NULL) { + RWUNLOCK(&env->rwlock, isc_rwlocktype_read); + return (false); + } + dns_acl_attach(env->localhost, &inner); + RWUNLOCK(&env->rwlock, isc_rwlocktype_read); + break; + + case dns_aclelementtype_localnets: + if (env == NULL) { + return (false); + } + RWLOCK(&env->rwlock, isc_rwlocktype_read); + if (env->localnets == NULL) { + RWUNLOCK(&env->rwlock, isc_rwlocktype_read); + return (false); + } + dns_acl_attach(env->localnets, &inner); + RWUNLOCK(&env->rwlock, isc_rwlocktype_read); + break; + +#if defined(HAVE_GEOIP2) + case dns_aclelementtype_geoip: + if (env == NULL || env->geoip == NULL) { + return (false); + } + return (dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem)); +#endif /* if defined(HAVE_GEOIP2) */ + default: + UNREACHABLE(); + } + + result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch, + matchelt); + INSIST(result == ISC_R_SUCCESS); + + dns_acl_detach(&inner); + + /* + * Treat negative matches in indirect ACLs as "no match". + * That way, a negated indirect ACL will never become a + * surprise positive match through double negation. + * XXXDCL this should be documented. + */ + if (indirectmatch > 0) { + if (matchelt != NULL) { + *matchelt = e; + } + return (true); + } + + /* + * A negative indirect match may have set *matchelt, but we don't + * want it set when we return. + */ + if (matchelt != NULL) { + *matchelt = NULL; + } + + return (false); +} + +void +dns_acl_attach(dns_acl_t *source, dns_acl_t **target) { + REQUIRE(DNS_ACL_VALID(source)); + + isc_refcount_increment(&source->refcount); + *target = source; +} + +static void +destroy(dns_acl_t *dacl) { + unsigned int i; + dns_acl_port_transports_t *port_proto; + + INSIST(!ISC_LINK_LINKED(dacl, nextincache)); + + for (i = 0; i < dacl->length; i++) { + dns_aclelement_t *de = &dacl->elements[i]; + if (de->type == dns_aclelementtype_keyname) { + dns_name_free(&de->keyname, dacl->mctx); + } else if (de->type == dns_aclelementtype_nestedacl) { + dns_acl_detach(&de->nestedacl); + } + } + if (dacl->elements != NULL) { + isc_mem_put(dacl->mctx, dacl->elements, + dacl->alloc * sizeof(dns_aclelement_t)); + } + if (dacl->name != NULL) { + isc_mem_free(dacl->mctx, dacl->name); + } + if (dacl->iptable != NULL) { + dns_iptable_detach(&dacl->iptable); + } + + port_proto = ISC_LIST_HEAD(dacl->ports_and_transports); + while (port_proto != NULL) { + dns_acl_port_transports_t *next = NULL; + + next = ISC_LIST_NEXT(port_proto, link); + ISC_LIST_DEQUEUE(dacl->ports_and_transports, port_proto, link); + isc_mem_put(dacl->mctx, port_proto, sizeof(*port_proto)); + port_proto = next; + } + + isc_refcount_destroy(&dacl->refcount); + dacl->magic = 0; + isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl)); +} + +void +dns_acl_detach(dns_acl_t **aclp) { + REQUIRE(aclp != NULL && DNS_ACL_VALID(*aclp)); + dns_acl_t *acl = *aclp; + *aclp = NULL; + + if (isc_refcount_decrement(&acl->refcount) == 1) { + destroy(acl); + } +} + +static isc_once_t insecure_prefix_once = ISC_ONCE_INIT; +static isc_mutex_t insecure_prefix_lock; +static bool insecure_prefix_found; + +static void +initialize_action(void) { + isc_mutex_init(&insecure_prefix_lock); +} + +/* + * Called via isc_radix_process() to find IP table nodes that are + * insecure. + */ +static void +is_insecure(isc_prefix_t *prefix, void **data) { + /* + * If all nonexistent or negative then this node is secure. + */ + if ((data[0] == NULL || !*(bool *)data[0]) && + (data[1] == NULL || !*(bool *)data[1])) + { + return; + } + + /* + * If a loopback address found and the other family + * entry doesn't exist or is negative, return. + */ + if (prefix->bitlen == 32 && + htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK && + (data[1] == NULL || !*(bool *)data[1])) + { + return; + } + + if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) && + (data[0] == NULL || !*(bool *)data[0])) + { + return; + } + + /* Non-negated, non-loopback */ + insecure_prefix_found = true; /* LOCKED */ + return; +} + +/* + * Return true iff the acl 'a' is considered insecure, that is, + * if it contains IP addresses other than those of the local host. + * This is intended for applications such as printing warning + * messages for suspect ACLs; it is not intended for making access + * control decisions. We make no guarantee that an ACL for which + * this function returns false is safe. + */ +bool +dns_acl_isinsecure(const dns_acl_t *a) { + unsigned int i; + bool insecure; + + RUNTIME_CHECK(isc_once_do(&insecure_prefix_once, initialize_action) == + ISC_R_SUCCESS); + + /* + * Walk radix tree to find out if there are any non-negated, + * non-loopback prefixes. + */ + LOCK(&insecure_prefix_lock); + insecure_prefix_found = false; + isc_radix_process(a->iptable->radix, is_insecure); + insecure = insecure_prefix_found; + UNLOCK(&insecure_prefix_lock); + if (insecure) { + return (true); + } + + /* Now check non-radix elements */ + for (i = 0; i < a->length; i++) { + dns_aclelement_t *e = &a->elements[i]; + + /* A negated match can never be insecure. */ + if (e->negative) { + continue; + } + + switch (e->type) { + case dns_aclelementtype_keyname: + case dns_aclelementtype_localhost: + continue; + + case dns_aclelementtype_nestedacl: + if (dns_acl_isinsecure(e->nestedacl)) { + return (true); + } + continue; + +#if defined(HAVE_GEOIP2) + case dns_aclelementtype_geoip: +#endif /* if defined(HAVE_GEOIP2) */ + case dns_aclelementtype_localnets: + return (true); + + default: + UNREACHABLE(); + } + } + + /* No insecure elements were found. */ + return (false); +} + +/*% + * Check whether an address/signer is allowed by a given acl/aclenv. + */ +bool +dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl, + dns_aclenv_t *aclenv) { + int match; + isc_result_t result; + + if (acl == NULL) { + return (true); + } + result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL); + if (result == ISC_R_SUCCESS && match > 0) { + return (true); + } + return (false); +} + +/* + * Initialize ACL environment, setting up localhost and localnets ACLs + */ +isc_result_t +dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp) { + isc_result_t result; + dns_aclenv_t *env = isc_mem_get(mctx, sizeof(*env)); + *env = (dns_aclenv_t){ 0 }; + + isc_mem_attach(mctx, &env->mctx); + isc_refcount_init(&env->references, 1); + isc_rwlock_init(&env->rwlock, 0, 0); + + result = dns_acl_create(mctx, 0, &env->localhost); + if (result != ISC_R_SUCCESS) { + goto cleanup_rwlock; + } + result = dns_acl_create(mctx, 0, &env->localnets); + if (result != ISC_R_SUCCESS) { + goto cleanup_localhost; + } + env->match_mapped = false; +#if defined(HAVE_GEOIP2) + env->geoip = NULL; +#endif /* if defined(HAVE_GEOIP2) */ + + env->magic = DNS_ACLENV_MAGIC; + + *envp = env; + + return (ISC_R_SUCCESS); + +cleanup_localhost: + dns_acl_detach(&env->localhost); +cleanup_rwlock: + isc_rwlock_destroy(&env->rwlock); + isc_mem_putanddetach(&env->mctx, env, sizeof(*env)); + return (result); +} + +void +dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets) { + REQUIRE(VALID_ACLENV(env)); + + RWLOCK(&env->rwlock, isc_rwlocktype_write); + dns_acl_detach(&env->localhost); + dns_acl_attach(localhost, &env->localhost); + dns_acl_detach(&env->localnets); + dns_acl_attach(localnets, &env->localnets); + RWUNLOCK(&env->rwlock, isc_rwlocktype_write); +} + +void +dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) { + REQUIRE(VALID_ACLENV(s)); + REQUIRE(VALID_ACLENV(t)); + + RWLOCK(&t->rwlock, isc_rwlocktype_write); + RWLOCK(&s->rwlock, isc_rwlocktype_read); + dns_acl_detach(&t->localhost); + dns_acl_attach(s->localhost, &t->localhost); + dns_acl_detach(&t->localnets); + dns_acl_attach(s->localnets, &t->localnets); + + t->match_mapped = s->match_mapped; +#if defined(HAVE_GEOIP2) + t->geoip = s->geoip; +#endif /* if defined(HAVE_GEOIP2) */ + + RWUNLOCK(&s->rwlock, isc_rwlocktype_read); + RWUNLOCK(&t->rwlock, isc_rwlocktype_write); +} + +static void +dns__aclenv_destroy(dns_aclenv_t *aclenv) { + REQUIRE(VALID_ACLENV(aclenv)); + + aclenv->magic = 0; + + isc_refcount_destroy(&aclenv->references); + dns_acl_detach(&aclenv->localhost); + dns_acl_detach(&aclenv->localnets); + isc_rwlock_destroy(&aclenv->rwlock); + + isc_mem_putanddetach(&aclenv->mctx, aclenv, sizeof(*aclenv)); +} + +void +dns_aclenv_attach(dns_aclenv_t *source, dns_aclenv_t **targetp) { + REQUIRE(VALID_ACLENV(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + *targetp = source; +} + +void +dns_aclenv_detach(dns_aclenv_t **aclenvp) { + dns_aclenv_t *aclenv = NULL; + + REQUIRE(aclenvp != NULL && VALID_ACLENV(*aclenvp)); + + aclenv = *aclenvp; + *aclenvp = NULL; + + if (isc_refcount_decrement(&aclenv->references) == 1) { + dns__aclenv_destroy(aclenv); + } +} + +void +dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port, + const uint32_t transports, const bool encrypted, + const bool negative) { + dns_acl_port_transports_t *port_proto; + REQUIRE(DNS_ACL_VALID(acl)); + REQUIRE(port != 0 || transports != 0); + + port_proto = isc_mem_get(acl->mctx, sizeof(*port_proto)); + *port_proto = (dns_acl_port_transports_t){ .port = port, + .transports = transports, + .encrypted = encrypted, + .negative = negative }; + + ISC_LINK_INIT(port_proto, link); + + ISC_LIST_APPEND(acl->ports_and_transports, port_proto, link); + acl->port_proto_entries++; +} + +void +dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos) { + dns_acl_port_transports_t *next; + + REQUIRE(DNS_ACL_VALID(dest)); + REQUIRE(DNS_ACL_VALID(source)); + + const bool negative = !pos; + + /* + * Merge ports and transports + */ + for (next = ISC_LIST_HEAD(source->ports_and_transports); next != NULL; + next = ISC_LIST_NEXT(next, link)) + { + const bool next_positive = !next->negative; + bool add_negative; + + /* + * Reverse sense of positives if this is a negative acl. The + * logic is used (and, thus, enforced) by dns_acl_merge(), + * from which dns_acl_merge_ports_transports() is called. + */ + if (negative && next_positive) { + add_negative = true; + } else { + add_negative = next->negative; + } + + dns_acl_add_port_transports(dest, next->port, next->transports, + next->encrypted, add_negative); + } +} diff --git a/lib/dns/adb.c b/lib/dns/adb.c new file mode 100644 index 0000000..449fe34 --- /dev/null +++ b/lib/dns/adb.c @@ -0,0 +1,4749 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file + * + * \note + * In finds, if task == NULL, no events will be generated, and no events + * have been sent. If task != NULL but taskaction == NULL, an event has been + * posted but not yet freed. If neither are NULL, no event was posted. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DNS_ADB_MAGIC ISC_MAGIC('D', 'a', 'd', 'b') +#define DNS_ADB_VALID(x) ISC_MAGIC_VALID(x, DNS_ADB_MAGIC) +#define DNS_ADBNAME_MAGIC ISC_MAGIC('a', 'd', 'b', 'N') +#define DNS_ADBNAME_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAME_MAGIC) +#define DNS_ADBNAMEHOOK_MAGIC ISC_MAGIC('a', 'd', 'N', 'H') +#define DNS_ADBNAMEHOOK_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBNAMEHOOK_MAGIC) +#define DNS_ADBLAMEINFO_MAGIC ISC_MAGIC('a', 'd', 'b', 'Z') +#define DNS_ADBLAMEINFO_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBLAMEINFO_MAGIC) +#define DNS_ADBENTRY_MAGIC ISC_MAGIC('a', 'd', 'b', 'E') +#define DNS_ADBENTRY_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBENTRY_MAGIC) +#define DNS_ADBFETCH_MAGIC ISC_MAGIC('a', 'd', 'F', '4') +#define DNS_ADBFETCH_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH_MAGIC) +#define DNS_ADBFETCH6_MAGIC ISC_MAGIC('a', 'd', 'F', '6') +#define DNS_ADBFETCH6_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFETCH6_MAGIC) + +/*! + * For type 3 negative cache entries, we will remember that the address is + * broken for this long. XXXMLG This is also used for actual addresses, too. + * The intent is to keep us from constantly asking about A/AAAA records + * if the zone has extremely low TTLs. + */ +#define ADB_CACHE_MINIMUM 10 /*%< seconds */ +#define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */ +#define ADB_ENTRY_WINDOW 1800 /*%< seconds */ + +/*% + * The period in seconds after which an ADB name entry is regarded as stale + * and forced to be cleaned up. + * TODO: This should probably be configurable at run-time. + */ +#ifndef ADB_STALE_MARGIN +#define ADB_STALE_MARGIN 1800 +#endif /* ifndef ADB_STALE_MARGIN */ + +#define FREE_ITEMS 64 /*%< free count for memory pools */ +#define FILL_COUNT 16 /*%< fill count for memory pools */ + +#define DNS_ADB_INVALIDBUCKET (-1) /*%< invalid bucket address */ + +#define DNS_ADB_MINADBSIZE (1024U * 1024U) /*%< 1 Megabyte */ + +typedef ISC_LIST(dns_adbname_t) dns_adbnamelist_t; +typedef struct dns_adbnamehook dns_adbnamehook_t; +typedef ISC_LIST(dns_adbnamehook_t) dns_adbnamehooklist_t; +typedef struct dns_adblameinfo dns_adblameinfo_t; +typedef ISC_LIST(dns_adbentry_t) dns_adbentrylist_t; +typedef struct dns_adbfetch dns_adbfetch_t; +typedef struct dns_adbfetch6 dns_adbfetch6_t; + +/*% dns adb structure */ +struct dns_adb { + unsigned int magic; + + isc_mutex_t lock; + isc_mutex_t reflock; /*%< Covers irefcnt, erefcnt */ + isc_mutex_t overmemlock; /*%< Covers overmem */ + isc_mem_t *mctx; + isc_mem_t *hmctx; + dns_view_t *view; + + isc_taskmgr_t *taskmgr; + isc_task_t *task; + isc_task_t *excl; + + isc_interval_t tick_interval; + int next_cleanbucket; + + unsigned int irefcnt; + unsigned int erefcnt; + + isc_refcount_t ahrefcnt; + isc_refcount_t nhrefcnt; + + /*! + * Bucketized locks and lists for names. + * + * XXXRTH Have a per-bucket structure that contains all of these? + */ + unsigned int nnames; + isc_mutex_t namescntlock; + unsigned int namescnt; + dns_adbnamelist_t *names; + dns_adbnamelist_t *deadnames; + isc_mutex_t *namelocks; + bool *name_sd; + unsigned int *name_refcnt; + + /*! + * Bucketized locks and lists for entries. + * + * XXXRTH Have a per-bucket structure that contains all of these? + */ + unsigned int nentries; + isc_mutex_t entriescntlock; + unsigned int entriescnt; + dns_adbentrylist_t *entries; + dns_adbentrylist_t *deadentries; + isc_mutex_t *entrylocks; + bool *entry_sd; /*%< shutting down */ + unsigned int *entry_refcnt; + + isc_event_t cevent; + bool cevent_out; + atomic_bool shutting_down; + isc_eventlist_t whenshutdown; + isc_event_t growentries; + bool growentries_sent; + isc_event_t grownames; + bool grownames_sent; + + uint32_t quota; + uint32_t atr_freq; + double atr_low; + double atr_high; + double atr_discount; +}; + +/* + * XXXMLG Document these structures. + */ + +/*% dns_adbname structure */ +struct dns_adbname { + unsigned int magic; + dns_name_t name; + dns_adb_t *adb; + unsigned int partial_result; + unsigned int flags; + int lock_bucket; + dns_name_t target; + isc_stdtime_t expire_target; + isc_stdtime_t expire_v4; + isc_stdtime_t expire_v6; + unsigned int chains; + dns_adbnamehooklist_t v4; + dns_adbnamehooklist_t v6; + dns_adbfetch_t *fetch_a; + dns_adbfetch_t *fetch_aaaa; + unsigned int fetch_err; + unsigned int fetch6_err; + dns_adbfindlist_t finds; + /* for LRU-based management */ + isc_stdtime_t last_used; + + ISC_LINK(dns_adbname_t) plink; +}; + +/*% The adbfetch structure */ +struct dns_adbfetch { + unsigned int magic; + dns_fetch_t *fetch; + dns_rdataset_t rdataset; + unsigned int depth; +}; + +/*% + * This is a small widget that dangles off a dns_adbname_t. It contains a + * pointer to the address information about this host, and a link to the next + * namehook that will contain the next address this host has. + */ +struct dns_adbnamehook { + unsigned int magic; + dns_adbentry_t *entry; + ISC_LINK(dns_adbnamehook_t) plink; +}; + +/*% + * This is a small widget that holds qname-specific information about an + * address. Currently limited to lameness, but could just as easily be + * extended to other types of information about zones. + */ +struct dns_adblameinfo { + unsigned int magic; + + dns_name_t qname; + dns_rdatatype_t qtype; + isc_stdtime_t lame_timer; + + ISC_LINK(dns_adblameinfo_t) plink; +}; + +/*% + * An address entry. It holds quite a bit of information about addresses, + * including edns state (in "flags"), rtt, and of course the address of + * the host. + */ +struct dns_adbentry { + unsigned int magic; + + int lock_bucket; + unsigned int refcnt; + unsigned int nh; + + unsigned int flags; + unsigned int srtt; + uint16_t udpsize; + unsigned int completed; + unsigned int timeouts; + unsigned char plain; + unsigned char plainto; + unsigned char edns; + unsigned char ednsto; + + uint8_t mode; + atomic_uint_fast32_t quota; + atomic_uint_fast32_t active; + double atr; + + isc_sockaddr_t sockaddr; + unsigned char *cookie; + uint16_t cookielen; + + isc_stdtime_t expires; + isc_stdtime_t lastage; + /*%< + * A nonzero 'expires' field indicates that the entry should + * persist until that time. This allows entries found + * using dns_adb_findaddrinfo() to persist for a limited time + * even though they are not necessarily associated with a + * name. + */ + + ISC_LIST(dns_adblameinfo_t) lameinfo; + ISC_LINK(dns_adbentry_t) plink; +}; + +/* + * Internal functions (and prototypes). + */ +static dns_adbname_t * +new_adbname(dns_adb_t *, const dns_name_t *); +static void +free_adbname(dns_adb_t *, dns_adbname_t **); +static dns_adbnamehook_t * +new_adbnamehook(dns_adb_t *, dns_adbentry_t *); +static void +free_adbnamehook(dns_adb_t *, dns_adbnamehook_t **); +static dns_adblameinfo_t * +new_adblameinfo(dns_adb_t *, const dns_name_t *, dns_rdatatype_t); +static void +free_adblameinfo(dns_adb_t *, dns_adblameinfo_t **); +static dns_adbentry_t * +new_adbentry(dns_adb_t *); +static void +free_adbentry(dns_adb_t *, dns_adbentry_t **); +static dns_adbfind_t * +new_adbfind(dns_adb_t *); +static bool +free_adbfind(dns_adb_t *, dns_adbfind_t **); +static dns_adbaddrinfo_t * +new_adbaddrinfo(dns_adb_t *, dns_adbentry_t *, in_port_t); +static dns_adbfetch_t * +new_adbfetch(dns_adb_t *); +static void +free_adbfetch(dns_adb_t *, dns_adbfetch_t **); +static dns_adbname_t * +find_name_and_lock(dns_adb_t *, const dns_name_t *, unsigned int, int *); +static dns_adbentry_t * +find_entry_and_lock(dns_adb_t *, const isc_sockaddr_t *, int *, isc_stdtime_t); +static void +dump_adb(dns_adb_t *, FILE *, bool debug, isc_stdtime_t); +static void +print_dns_name(FILE *, const dns_name_t *); +static void +print_namehook_list(FILE *, const char *legend, dns_adb_t *adb, + dns_adbnamehooklist_t *list, bool debug, isc_stdtime_t now); +static void +print_find_list(FILE *, dns_adbname_t *); +static void +print_fetch_list(FILE *, dns_adbname_t *); +static bool +dec_adb_irefcnt(dns_adb_t *); +static void +inc_adb_irefcnt(dns_adb_t *); +static void +inc_adb_erefcnt(dns_adb_t *); +static void +inc_entry_refcnt(dns_adb_t *, dns_adbentry_t *, bool); +static bool +dec_entry_refcnt(dns_adb_t *, bool, dns_adbentry_t *, bool, isc_stdtime_t); +static void +violate_locking_hierarchy(isc_mutex_t *, isc_mutex_t *); +static bool +clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *); +static void +clean_target(dns_adb_t *, dns_name_t *); +static void +clean_finds_at_name(dns_adbname_t *, isc_eventtype_t, unsigned int); +static bool +check_expire_namehooks(dns_adbname_t *, isc_stdtime_t); +static bool +check_expire_entry(dns_adb_t *, dns_adbentry_t **, isc_stdtime_t); +static void +cancel_fetches_at_name(dns_adbname_t *); +static isc_result_t +dbfind_name(dns_adbname_t *, isc_stdtime_t, dns_rdatatype_t); +static isc_result_t +fetch_name(dns_adbname_t *, bool, unsigned int, isc_counter_t *qc, + dns_rdatatype_t); +static void +check_exit(dns_adb_t *); +static void +destroy(dns_adb_t *); +static bool +shutdown_names(dns_adb_t *); +static bool +shutdown_entries(dns_adb_t *); +static void +link_name(dns_adb_t *, int, dns_adbname_t *); +static bool +unlink_name(dns_adb_t *, dns_adbname_t *); +static void +link_entry(dns_adb_t *, int, dns_adbentry_t *); +static bool +unlink_entry(dns_adb_t *, dns_adbentry_t *); +static bool +kill_name(dns_adbname_t **, isc_eventtype_t); +static void +water(void *, int); +static void +dump_entry(FILE *, dns_adb_t *, dns_adbentry_t *, bool, isc_stdtime_t); +static void +adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor, + isc_stdtime_t now); +static void +shutdown_task(isc_task_t *task, isc_event_t *ev); +static void +log_quota(dns_adbentry_t *entry, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); + +/* + * Private flag(s) for adbfind objects. These are used internally and + * are not meant to be seen or used by the caller; however, we use the + * same flags field as for DNS_ADBFIND_xxx flags, so we must be careful + * that there is no overlap between these values and those. To make it + * easier, we will number these starting from the most significant bit + * instead of the least significant. + */ +enum { + FIND_EVENT_SENT = 1 << 31, + FIND_EVENT_FREED = 1 << 30, +}; +#define FIND_EVENTSENT(h) (((h)->flags & FIND_EVENT_SENT) != 0) +#define FIND_EVENTFREED(h) (((h)->flags & FIND_EVENT_FREED) != 0) + +/* + * Private flag(s) for adbname objects. + */ +enum { + NAME_IS_DEAD = 1 << 31, + NAME_NEEDS_POKE = 1 << 30, +}; +#define NAME_DEAD(n) (((n)->flags & NAME_IS_DEAD) != 0) +#define NAME_NEEDSPOKE(n) (((n)->flags & NAME_NEEDS_POKE) != 0) +#define NAME_GLUEOK(n) (((n)->flags & DNS_ADBFIND_GLUEOK) != 0) +#define NAME_HINTOK(n) (((n)->flags & DNS_ADBFIND_HINTOK) != 0) + +/* + * Private flag(s) for adbentry objects. Note that these will also + * be used for addrinfo flags, and in resolver.c we'll use the same + * field for FCTX_ADDRINFO_xxx flags to store information about remote + * servers, so we must be careful that there is no overlap between + * these values and those. To make it easier, we will number these + * starting from the most significant bit instead of the least + * significant. + */ +enum { + ENTRY_IS_DEAD = 1 << 31, +}; + +/* + * To the name, address classes are all that really exist. If it has a + * V6 address it doesn't care if it came from a AAAA query. + */ +#define NAME_HAS_V4(n) (!ISC_LIST_EMPTY((n)->v4)) +#define NAME_HAS_V6(n) (!ISC_LIST_EMPTY((n)->v6)) +#define NAME_HAS_ADDRS(n) (NAME_HAS_V4(n) || NAME_HAS_V6(n)) + +/* + * Fetches are broken out into A and AAAA types. In some cases, + * however, it makes more sense to test for a particular class of fetches, + * like V4 or V6 above. + */ +#define NAME_FETCH_A(n) ((n)->fetch_a != NULL) +#define NAME_FETCH_AAAA(n) ((n)->fetch_aaaa != NULL) +#define NAME_FETCH(n) (NAME_FETCH_A(n) || NAME_FETCH_AAAA(n)) + +/* + * Find options and tests to see if there are addresses on the list. + */ +#define FIND_WANTEVENT(fn) (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0) +#define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0) +#define FIND_AVOIDFETCHES(fn) (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0) +#define FIND_STARTATZONE(fn) (((fn)->options & DNS_ADBFIND_STARTATZONE) != 0) +#define FIND_HINTOK(fn) (((fn)->options & DNS_ADBFIND_HINTOK) != 0) +#define FIND_GLUEOK(fn) (((fn)->options & DNS_ADBFIND_GLUEOK) != 0) +#define FIND_HAS_ADDRS(fn) (!ISC_LIST_EMPTY((fn)->list)) +#define FIND_RETURNLAME(fn) (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0) +#define FIND_NOFETCH(fn) (((fn)->options & DNS_ADBFIND_NOFETCH) != 0) + +/* + * These are currently used on simple unsigned ints, so they are + * not really associated with any particular type. + */ +#define WANT_INET(x) (((x)&DNS_ADBFIND_INET) != 0) +#define WANT_INET6(x) (((x)&DNS_ADBFIND_INET6) != 0) + +#define EXPIRE_OK(exp, now) ((exp == INT_MAX) || (exp < now)) + +/* + * Find out if the flags on a name (nf) indicate if it is a hint or + * glue, and compare this to the appropriate bits set in o, to see if + * this is ok. + */ +#define GLUE_OK(nf, o) (!NAME_GLUEOK(nf) || (((o)&DNS_ADBFIND_GLUEOK) != 0)) +#define HINT_OK(nf, o) (!NAME_HINTOK(nf) || (((o)&DNS_ADBFIND_HINTOK) != 0)) +#define GLUEHINT_OK(nf, o) (GLUE_OK(nf, o) || HINT_OK(nf, o)) +#define STARTATZONE_MATCHES(nf, o) \ + (((nf)->flags & DNS_ADBFIND_STARTATZONE) == \ + ((o)&DNS_ADBFIND_STARTATZONE)) + +#define ENTER_LEVEL ISC_LOG_DEBUG(50) +#define EXIT_LEVEL ENTER_LEVEL +#define CLEAN_LEVEL ISC_LOG_DEBUG(100) +#define DEF_LEVEL ISC_LOG_DEBUG(5) +#define NCACHE_LEVEL ISC_LOG_DEBUG(20) + +#define NCACHE_RESULT(r) \ + ((r) == DNS_R_NCACHENXDOMAIN || (r) == DNS_R_NCACHENXRRSET) +#define AUTH_NX(r) ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NXRRSET) +#define NXDOMAIN_RESULT(r) \ + ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN) +#define NXRRSET_RESULT(r) \ + ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \ + (r) == DNS_R_HINTNXRRSET) + +/* + * Error state rankings. + */ + +#define FIND_ERR_SUCCESS 0 /* highest rank */ +#define FIND_ERR_CANCELED 1 +#define FIND_ERR_FAILURE 2 +#define FIND_ERR_NXDOMAIN 3 +#define FIND_ERR_NXRRSET 4 +#define FIND_ERR_UNEXPECTED 5 +#define FIND_ERR_NOTFOUND 6 +#define FIND_ERR_MAX 7 + +static const char *errnames[] = { "success", "canceled", "failure", + "nxdomain", "nxrrset", "unexpected", + "not_found" }; + +#define NEWERR(old, new) (ISC_MIN((old), (new))) + +static isc_result_t find_err_map[FIND_ERR_MAX] = { + ISC_R_SUCCESS, ISC_R_CANCELED, ISC_R_FAILURE, DNS_R_NXDOMAIN, + DNS_R_NXRRSET, ISC_R_UNEXPECTED, ISC_R_NOTFOUND /* not YET found */ +}; + +static void +DP(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3); + +static void +DP(int level, const char *format, ...) { + va_list args; + + va_start(args, format); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB, + level, format, args); + va_end(args); +} + +/*% + * Increment resolver-related statistics counters. + */ +static void +inc_stats(dns_adb_t *adb, isc_statscounter_t counter) { + if (adb->view->resstats != NULL) { + isc_stats_increment(adb->view->resstats, counter); + } +} + +/*% + * Set adb-related statistics counters. + */ +static void +set_adbstat(dns_adb_t *adb, uint64_t val, isc_statscounter_t counter) { + if (adb->view->adbstats != NULL) { + isc_stats_set(adb->view->adbstats, val, counter); + } +} + +static void +dec_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { + if (adb->view->adbstats != NULL) { + isc_stats_decrement(adb->view->adbstats, counter); + } +} + +static void +inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { + if (adb->view->adbstats != NULL) { + isc_stats_increment(adb->view->adbstats, counter); + } +} + +static dns_ttl_t +ttlclamp(dns_ttl_t ttl) { + if (ttl < ADB_CACHE_MINIMUM) { + ttl = ADB_CACHE_MINIMUM; + } + if (ttl > ADB_CACHE_MAXIMUM) { + ttl = ADB_CACHE_MAXIMUM; + } + + return (ttl); +} + +/* + * Hashing is most efficient if the number of buckets is prime. + * The sequence below is the closest previous primes to 2^n and + * 1.5 * 2^n, for values of n from 10 to 28. (The tables will + * no longer grow beyond 2^28 entries.) + */ +static const unsigned nbuckets[] = { + 1021, 1531, 2039, 3067, 4093, 6143, + 8191, 12281, 16381, 24571, 32749, 49193, + 65521, 98299, 131071, 199603, 262139, 393209, + 524287, 768431, 1048573, 1572853, 2097143, 3145721, + 4194301, 6291449, 8388593, 12582893, 16777213, 25165813, + 33554393, 50331599, 67108859, 100663291, 134217689, 201326557, + 268535431, 0 +}; + +static void +grow_entries(isc_task_t *task, isc_event_t *ev) { + dns_adb_t *adb; + dns_adbentry_t *e; + dns_adbentrylist_t *newdeadentries = NULL; + dns_adbentrylist_t *newentries = NULL; + bool *newentry_sd = NULL; + isc_mutex_t *newentrylocks = NULL; + isc_result_t result; + unsigned int *newentry_refcnt = NULL; + unsigned int i, n, bucket; + + adb = ev->ev_arg; + INSIST(DNS_ADB_VALID(adb)); + + isc_event_free(&ev); + + result = isc_task_beginexclusive(task); + if (result != ISC_R_SUCCESS) { + goto check_exit; + } + + i = 0; + while (nbuckets[i] != 0 && adb->nentries >= nbuckets[i]) { + i++; + } + if (nbuckets[i] != 0) { + n = nbuckets[i]; + } else { + goto done; + } + + DP(ISC_LOG_INFO, "adb: grow_entries to %u starting", n); + + /* + * Are we shutting down? + */ + for (i = 0; i < adb->nentries; i++) { + if (adb->entry_sd[i]) { + goto cleanup; + + /* + * Grab all the resources we need. + */ + } + } + + /* + * Grab all the resources we need. + */ + newentries = isc_mem_get(adb->hmctx, sizeof(*newentries) * n); + newdeadentries = isc_mem_get(adb->hmctx, sizeof(*newdeadentries) * n); + newentrylocks = isc_mem_get(adb->hmctx, sizeof(*newentrylocks) * n); + newentry_sd = isc_mem_get(adb->hmctx, sizeof(*newentry_sd) * n); + newentry_refcnt = isc_mem_get(adb->hmctx, sizeof(*newentry_refcnt) * n); + + /* + * Initialise the new resources. + */ + isc_mutexblock_init(newentrylocks, n); + + for (i = 0; i < n; i++) { + ISC_LIST_INIT(newentries[i]); + ISC_LIST_INIT(newdeadentries[i]); + newentry_sd[i] = false; + newentry_refcnt[i] = 0; + adb->irefcnt++; + } + + /* + * Move entries to new arrays. + */ + for (i = 0; i < adb->nentries; i++) { + e = ISC_LIST_HEAD(adb->entries[i]); + while (e != NULL) { + ISC_LIST_UNLINK(adb->entries[i], e, plink); + bucket = isc_sockaddr_hash(&e->sockaddr, true) % n; + e->lock_bucket = bucket; + ISC_LIST_APPEND(newentries[bucket], e, plink); + INSIST(adb->entry_refcnt[i] > 0); + adb->entry_refcnt[i]--; + newentry_refcnt[bucket]++; + e = ISC_LIST_HEAD(adb->entries[i]); + } + e = ISC_LIST_HEAD(adb->deadentries[i]); + while (e != NULL) { + ISC_LIST_UNLINK(adb->deadentries[i], e, plink); + bucket = isc_sockaddr_hash(&e->sockaddr, true) % n; + e->lock_bucket = bucket; + ISC_LIST_APPEND(newdeadentries[bucket], e, plink); + INSIST(adb->entry_refcnt[i] > 0); + adb->entry_refcnt[i]--; + newentry_refcnt[bucket]++; + e = ISC_LIST_HEAD(adb->deadentries[i]); + } + INSIST(adb->entry_refcnt[i] == 0); + adb->irefcnt--; + } + + /* + * Cleanup old resources. + */ + isc_mutexblock_destroy(adb->entrylocks, adb->nentries); + isc_mem_put(adb->hmctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + isc_mem_put(adb->hmctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entry_refcnt, + sizeof(*adb->entry_refcnt) * adb->nentries); + + /* + * Install new resources. + */ + adb->entries = newentries; + adb->deadentries = newdeadentries; + adb->entrylocks = newentrylocks; + adb->entry_sd = newentry_sd; + adb->entry_refcnt = newentry_refcnt; + adb->nentries = n; + + set_adbstat(adb, adb->nentries, dns_adbstats_nentries); + + /* + * Only on success do we set adb->growentries_sent to false. + * This will prevent us being continuously being called on error. + */ + adb->growentries_sent = false; + goto done; + +cleanup: + if (newentries != NULL) { + isc_mem_put(adb->mctx, newentries, sizeof(*newentries) * n); + } + if (newdeadentries != NULL) { + isc_mem_put(adb->mctx, newdeadentries, + sizeof(*newdeadentries) * n); + } + if (newentrylocks != NULL) { + isc_mem_put(adb->mctx, newentrylocks, + sizeof(*newentrylocks) * n); + } + if (newentry_sd != NULL) { + isc_mem_put(adb->mctx, newentry_sd, sizeof(*newentry_sd) * n); + } + if (newentry_refcnt != NULL) { + isc_mem_put(adb->mctx, newentry_refcnt, + sizeof(*newentry_refcnt) * n); + } +done: + isc_task_endexclusive(task); + +check_exit: + LOCK(&adb->lock); + if (dec_adb_irefcnt(adb)) { + check_exit(adb); + } + UNLOCK(&adb->lock); + DP(ISC_LOG_INFO, "adb: grow_entries finished"); +} + +static void +grow_names(isc_task_t *task, isc_event_t *ev) { + dns_adb_t *adb; + dns_adbname_t *name; + dns_adbnamelist_t *newdeadnames = NULL; + dns_adbnamelist_t *newnames = NULL; + bool *newname_sd = NULL; + isc_mutex_t *newnamelocks = NULL; + isc_result_t result; + unsigned int *newname_refcnt = NULL; + unsigned int i, n; + unsigned int bucket; + + adb = ev->ev_arg; + INSIST(DNS_ADB_VALID(adb)); + + isc_event_free(&ev); + + result = isc_task_beginexclusive(task); + if (result != ISC_R_SUCCESS) { + goto check_exit; + } + + i = 0; + while (nbuckets[i] != 0 && adb->nnames >= nbuckets[i]) { + i++; + } + if (nbuckets[i] != 0) { + n = nbuckets[i]; + } else { + goto done; + } + + DP(ISC_LOG_INFO, "adb: grow_names to %u starting", n); + + /* + * Are we shutting down? + */ + for (i = 0; i < adb->nnames; i++) { + if (adb->name_sd[i]) { + goto cleanup; + + /* + * Grab all the resources we need. + */ + } + } + + /* + * Grab all the resources we need. + */ + newnames = isc_mem_get(adb->hmctx, sizeof(*newnames) * n); + newdeadnames = isc_mem_get(adb->hmctx, sizeof(*newdeadnames) * n); + newnamelocks = isc_mem_get(adb->hmctx, sizeof(*newnamelocks) * n); + newname_sd = isc_mem_get(adb->hmctx, sizeof(*newname_sd) * n); + newname_refcnt = isc_mem_get(adb->hmctx, sizeof(*newname_refcnt) * n); + + /* + * Initialise the new resources. + */ + isc_mutexblock_init(newnamelocks, n); + + for (i = 0; i < n; i++) { + ISC_LIST_INIT(newnames[i]); + ISC_LIST_INIT(newdeadnames[i]); + newname_sd[i] = false; + newname_refcnt[i] = 0; + adb->irefcnt++; + } + + /* + * Move names to new arrays. + */ + for (i = 0; i < adb->nnames; i++) { + name = ISC_LIST_HEAD(adb->names[i]); + while (name != NULL) { + ISC_LIST_UNLINK(adb->names[i], name, plink); + bucket = dns_name_fullhash(&name->name, true) % n; + name->lock_bucket = bucket; + ISC_LIST_APPEND(newnames[bucket], name, plink); + INSIST(adb->name_refcnt[i] > 0); + adb->name_refcnt[i]--; + newname_refcnt[bucket]++; + name = ISC_LIST_HEAD(adb->names[i]); + } + name = ISC_LIST_HEAD(adb->deadnames[i]); + while (name != NULL) { + ISC_LIST_UNLINK(adb->deadnames[i], name, plink); + bucket = dns_name_fullhash(&name->name, true) % n; + name->lock_bucket = bucket; + ISC_LIST_APPEND(newdeadnames[bucket], name, plink); + INSIST(adb->name_refcnt[i] > 0); + adb->name_refcnt[i]--; + newname_refcnt[bucket]++; + name = ISC_LIST_HEAD(adb->deadnames[i]); + } + INSIST(adb->name_refcnt[i] == 0); + adb->irefcnt--; + } + + /* + * Cleanup old resources. + */ + isc_mutexblock_destroy(adb->namelocks, adb->nnames); + isc_mem_put(adb->hmctx, adb->names, sizeof(*adb->names) * adb->nnames); + isc_mem_put(adb->hmctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + isc_mem_put(adb->hmctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + isc_mem_put(adb->hmctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + isc_mem_put(adb->hmctx, adb->name_refcnt, + sizeof(*adb->name_refcnt) * adb->nnames); + + /* + * Install new resources. + */ + adb->names = newnames; + adb->deadnames = newdeadnames; + adb->namelocks = newnamelocks; + adb->name_sd = newname_sd; + adb->name_refcnt = newname_refcnt; + adb->nnames = n; + + set_adbstat(adb, adb->nnames, dns_adbstats_nnames); + + /* + * Only on success do we set adb->grownames_sent to false. + * This will prevent us being continuously being called on error. + */ + adb->grownames_sent = false; + goto done; + +cleanup: + if (newnames != NULL) { + isc_mem_put(adb->hmctx, newnames, sizeof(*newnames) * n); + } + if (newdeadnames != NULL) { + isc_mem_put(adb->hmctx, newdeadnames, + sizeof(*newdeadnames) * n); + } + if (newnamelocks != NULL) { + isc_mem_put(adb->hmctx, newnamelocks, + sizeof(*newnamelocks) * n); + } + if (newname_sd != NULL) { + isc_mem_put(adb->hmctx, newname_sd, sizeof(*newname_sd) * n); + } + if (newname_refcnt != NULL) { + isc_mem_put(adb->hmctx, newname_refcnt, + sizeof(*newname_refcnt) * n); + } +done: + isc_task_endexclusive(task); + +check_exit: + LOCK(&adb->lock); + if (dec_adb_irefcnt(adb)) { + check_exit(adb); + } + UNLOCK(&adb->lock); + DP(ISC_LOG_INFO, "adb: grow_names finished"); +} + +/* + * Requires the adbname bucket be locked and that no entry buckets be locked. + * + * This code handles A and AAAA rdatasets only. + */ +static isc_result_t +import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset, + isc_stdtime_t now) { + isc_result_t result; + dns_adb_t *adb = NULL; + dns_adbnamehook_t *nh = NULL; + dns_adbnamehook_t *anh = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct in_addr ina; + struct in6_addr in6a; + isc_sockaddr_t sockaddr; + dns_adbentry_t *foundentry = NULL; /* NO CLEAN UP! */ + int addr_bucket; + bool new_addresses_added; + dns_rdatatype_t rdtype; + dns_adbnamehooklist_t *hookhead = NULL; + + INSIST(DNS_ADBNAME_VALID(adbname)); + adb = adbname->adb; + INSIST(DNS_ADB_VALID(adb)); + + rdtype = rdataset->type; + INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa)); + + addr_bucket = DNS_ADB_INVALIDBUCKET; + new_addresses_added = false; + + result = dns_rdataset_first(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + if (rdtype == dns_rdatatype_a) { + INSIST(rdata.length == 4); + memmove(&ina.s_addr, rdata.data, 4); + isc_sockaddr_fromin(&sockaddr, &ina, 0); + hookhead = &adbname->v4; + } else { + INSIST(rdata.length == 16); + memmove(in6a.s6_addr, rdata.data, 16); + isc_sockaddr_fromin6(&sockaddr, &in6a, 0); + hookhead = &adbname->v6; + } + + INSIST(nh == NULL); + nh = new_adbnamehook(adb, NULL); + foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket, + now); + if (foundentry == NULL) { + dns_adbentry_t *entry; + + entry = new_adbentry(adb); + entry->sockaddr = sockaddr; + entry->refcnt = 1; + entry->nh = 1; + + nh->entry = entry; + + link_entry(adb, addr_bucket, entry); + } else { + for (anh = ISC_LIST_HEAD(*hookhead); anh != NULL; + anh = ISC_LIST_NEXT(anh, plink)) + { + if (anh->entry == foundentry) { + break; + } + } + if (anh == NULL) { + foundentry->refcnt++; + foundentry->nh++; + nh->entry = foundentry; + } else { + free_adbnamehook(adb, &nh); + } + } + + new_addresses_added = true; + if (nh != NULL) { + ISC_LIST_APPEND(*hookhead, nh, plink); + } + nh = NULL; + result = dns_rdataset_next(rdataset); + } + + if (addr_bucket != DNS_ADB_INVALIDBUCKET) { + UNLOCK(&adb->entrylocks[addr_bucket]); + } + + if (rdataset->trust == dns_trust_glue || + rdataset->trust == dns_trust_additional) + { + rdataset->ttl = ADB_CACHE_MINIMUM; + } else if (rdataset->trust == dns_trust_ultimate) { + rdataset->ttl = 0; + } else { + rdataset->ttl = ttlclamp(rdataset->ttl); + } + + if (rdtype == dns_rdatatype_a) { + DP(NCACHE_LEVEL, "expire_v4 set to MIN(%u,%u) import_rdataset", + adbname->expire_v4, now + rdataset->ttl); + adbname->expire_v4 = ISC_MIN( + adbname->expire_v4, + ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl)); + } else { + DP(NCACHE_LEVEL, "expire_v6 set to MIN(%u,%u) import_rdataset", + adbname->expire_v6, now + rdataset->ttl); + adbname->expire_v6 = ISC_MIN( + adbname->expire_v6, + ISC_MIN(now + ADB_ENTRY_WINDOW, now + rdataset->ttl)); + } + + if (new_addresses_added) { + /* + * Lie a little here. This is more or less so code that cares + * can find out if any new information was added or not. + */ + return (ISC_R_SUCCESS); + } + + return (result); +} + +/* + * Requires the name's bucket be locked. + */ +static bool +kill_name(dns_adbname_t **n, isc_eventtype_t ev) { + dns_adbname_t *name; + bool result = false; + bool result4, result6; + int bucket; + dns_adb_t *adb; + + INSIST(n != NULL); + name = *n; + *n = NULL; + INSIST(DNS_ADBNAME_VALID(name)); + adb = name->adb; + INSIST(DNS_ADB_VALID(adb)); + + DP(DEF_LEVEL, "killing name %p", name); + + /* + * If we're dead already, just check to see if we should go + * away now or not. + */ + if (NAME_DEAD(name) && !NAME_FETCH(name)) { + result = unlink_name(adb, name); + free_adbname(adb, &name); + if (result) { + result = dec_adb_irefcnt(adb); + } + return (result); + } + + /* + * Clean up the name's various lists. These two are destructive + * in that they will always empty the list. + */ + clean_finds_at_name(name, ev, DNS_ADBFIND_ADDRESSMASK); + result4 = clean_namehooks(adb, &name->v4); + result6 = clean_namehooks(adb, &name->v6); + clean_target(adb, &name->target); + result = (result4 || result6); + + /* + * If fetches are running, cancel them. If none are running, we can + * just kill the name here. + */ + if (!NAME_FETCH(name)) { + INSIST(!result); + result = unlink_name(adb, name); + free_adbname(adb, &name); + if (result) { + result = dec_adb_irefcnt(adb); + } + } else { + cancel_fetches_at_name(name); + if (!NAME_DEAD(name)) { + bucket = name->lock_bucket; + ISC_LIST_UNLINK(adb->names[bucket], name, plink); + ISC_LIST_APPEND(adb->deadnames[bucket], name, plink); + name->flags |= NAME_IS_DEAD; + } + } + return (result); +} + +/* + * Requires the name's bucket be locked and no entry buckets be locked. + */ +static bool +check_expire_namehooks(dns_adbname_t *name, isc_stdtime_t now) { + dns_adb_t *adb; + bool result4 = false; + bool result6 = false; + + INSIST(DNS_ADBNAME_VALID(name)); + adb = name->adb; + INSIST(DNS_ADB_VALID(adb)); + + /* + * Check to see if we need to remove the v4 addresses + */ + if (!NAME_FETCH_A(name) && EXPIRE_OK(name->expire_v4, now)) { + if (NAME_HAS_V4(name)) { + DP(DEF_LEVEL, "expiring v4 for name %p", name); + result4 = clean_namehooks(adb, &name->v4); + name->partial_result &= ~DNS_ADBFIND_INET; + } + name->expire_v4 = INT_MAX; + name->fetch_err = FIND_ERR_UNEXPECTED; + } + + /* + * Check to see if we need to remove the v6 addresses + */ + if (!NAME_FETCH_AAAA(name) && EXPIRE_OK(name->expire_v6, now)) { + if (NAME_HAS_V6(name)) { + DP(DEF_LEVEL, "expiring v6 for name %p", name); + result6 = clean_namehooks(adb, &name->v6); + name->partial_result &= ~DNS_ADBFIND_INET6; + } + name->expire_v6 = INT_MAX; + name->fetch6_err = FIND_ERR_UNEXPECTED; + } + + /* + * Check to see if we need to remove the alias target. + */ + if (EXPIRE_OK(name->expire_target, now)) { + clean_target(adb, &name->target); + name->expire_target = INT_MAX; + } + return (result4 || result6); +} + +/* + * Requires the name's bucket be locked. + */ +static void +link_name(dns_adb_t *adb, int bucket, dns_adbname_t *name) { + INSIST(name->lock_bucket == DNS_ADB_INVALIDBUCKET); + + ISC_LIST_PREPEND(adb->names[bucket], name, plink); + name->lock_bucket = bucket; + adb->name_refcnt[bucket]++; +} + +/* + * Requires the name's bucket be locked. + */ +static bool +unlink_name(dns_adb_t *adb, dns_adbname_t *name) { + int bucket; + bool result = false; + + bucket = name->lock_bucket; + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + + if (NAME_DEAD(name)) { + ISC_LIST_UNLINK(adb->deadnames[bucket], name, plink); + } else { + ISC_LIST_UNLINK(adb->names[bucket], name, plink); + } + name->lock_bucket = DNS_ADB_INVALIDBUCKET; + INSIST(adb->name_refcnt[bucket] > 0); + adb->name_refcnt[bucket]--; + if (adb->name_sd[bucket] && adb->name_refcnt[bucket] == 0) { + result = true; + } + return (result); +} + +/* + * Requires the entry's bucket be locked. + */ +static void +link_entry(dns_adb_t *adb, int bucket, dns_adbentry_t *entry) { + int i; + dns_adbentry_t *e; + + if (isc_mem_isovermem(adb->mctx)) { + for (i = 0; i < 2; i++) { + e = ISC_LIST_TAIL(adb->entries[bucket]); + if (e == NULL) { + break; + } + if (e->refcnt == 0) { + unlink_entry(adb, e); + free_adbentry(adb, &e); + continue; + } + INSIST((e->flags & ENTRY_IS_DEAD) == 0); + e->flags |= ENTRY_IS_DEAD; + ISC_LIST_UNLINK(adb->entries[bucket], e, plink); + ISC_LIST_PREPEND(adb->deadentries[bucket], e, plink); + } + } + + ISC_LIST_PREPEND(adb->entries[bucket], entry, plink); + entry->lock_bucket = bucket; + adb->entry_refcnt[bucket]++; +} + +/* + * Requires the entry's bucket be locked. + */ +static bool +unlink_entry(dns_adb_t *adb, dns_adbentry_t *entry) { + int bucket; + bool result = false; + + bucket = entry->lock_bucket; + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + + if ((entry->flags & ENTRY_IS_DEAD) != 0) { + ISC_LIST_UNLINK(adb->deadentries[bucket], entry, plink); + } else { + ISC_LIST_UNLINK(adb->entries[bucket], entry, plink); + } + entry->lock_bucket = DNS_ADB_INVALIDBUCKET; + INSIST(adb->entry_refcnt[bucket] > 0); + adb->entry_refcnt[bucket]--; + if (adb->entry_sd[bucket] && adb->entry_refcnt[bucket] == 0) { + result = true; + } + return (result); +} + +static void +violate_locking_hierarchy(isc_mutex_t *have, isc_mutex_t *want) { + if (isc_mutex_trylock(want) != ISC_R_SUCCESS) { + UNLOCK(have); + LOCK(want); + LOCK(have); + } +} + +/* + * The ADB _MUST_ be locked before calling. Also, exit conditions must be + * checked after calling this function. + */ +static bool +shutdown_names(dns_adb_t *adb) { + unsigned int bucket; + bool result = false; + dns_adbname_t *name; + dns_adbname_t *next_name; + + for (bucket = 0; bucket < adb->nnames; bucket++) { + LOCK(&adb->namelocks[bucket]); + adb->name_sd[bucket] = true; + + name = ISC_LIST_HEAD(adb->names[bucket]); + if (name == NULL) { + /* + * This bucket has no names. We must decrement the + * irefcnt ourselves, since it will not be + * automatically triggered by a name being unlinked. + */ + INSIST(!result); + result = dec_adb_irefcnt(adb); + } else { + /* + * Run through the list. For each name, clean up finds + * found there, and cancel any fetches running. When + * all the fetches are canceled, the name will destroy + * itself. + */ + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, plink); + INSIST(!result); + result = kill_name(&name, + DNS_EVENT_ADBSHUTDOWN); + name = next_name; + } + } + + UNLOCK(&adb->namelocks[bucket]); + } + return (result); +} + +/* + * The ADB _MUST_ be locked before calling. Also, exit conditions must be + * checked after calling this function. + */ +static bool +shutdown_entries(dns_adb_t *adb) { + unsigned int bucket; + bool result = false; + dns_adbentry_t *entry; + dns_adbentry_t *next_entry; + + for (bucket = 0; bucket < adb->nentries; bucket++) { + LOCK(&adb->entrylocks[bucket]); + adb->entry_sd[bucket] = true; + + entry = ISC_LIST_HEAD(adb->entries[bucket]); + if (adb->entry_refcnt[bucket] == 0) { + /* + * This bucket has no entries. We must decrement the + * irefcnt ourselves, since it will not be + * automatically triggered by an entry being unlinked. + */ + result = dec_adb_irefcnt(adb); + } else { + /* + * Run through the list. Cleanup any entries not + * associated with names, and which are not in use. + */ + while (entry != NULL) { + next_entry = ISC_LIST_NEXT(entry, plink); + if (entry->refcnt == 0 && entry->expires != 0) { + result = unlink_entry(adb, entry); + free_adbentry(adb, &entry); + if (result) { + result = dec_adb_irefcnt(adb); + } + } + entry = next_entry; + } + } + + UNLOCK(&adb->entrylocks[bucket]); + } + return (result); +} + +/* + * Name bucket must be locked + */ +static void +cancel_fetches_at_name(dns_adbname_t *name) { + if (NAME_FETCH_A(name)) { + dns_resolver_cancelfetch(name->fetch_a->fetch); + } + + if (NAME_FETCH_AAAA(name)) { + dns_resolver_cancelfetch(name->fetch_aaaa->fetch); + } +} + +/* + * Assumes the name bucket is locked. + */ +static bool +clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks) { + dns_adbentry_t *entry; + dns_adbnamehook_t *namehook; + int addr_bucket; + bool result = false; + bool overmem = isc_mem_isovermem(adb->mctx); + + addr_bucket = DNS_ADB_INVALIDBUCKET; + namehook = ISC_LIST_HEAD(*namehooks); + while (namehook != NULL) { + INSIST(DNS_ADBNAMEHOOK_VALID(namehook)); + + /* + * Clean up the entry if needed. + */ + entry = namehook->entry; + if (entry != NULL) { + INSIST(DNS_ADBENTRY_VALID(entry)); + + if (addr_bucket != entry->lock_bucket) { + if (addr_bucket != DNS_ADB_INVALIDBUCKET) { + UNLOCK(&adb->entrylocks[addr_bucket]); + } + addr_bucket = entry->lock_bucket; + INSIST(addr_bucket != DNS_ADB_INVALIDBUCKET); + LOCK(&adb->entrylocks[addr_bucket]); + } + + entry->nh--; + result = dec_entry_refcnt(adb, overmem, entry, false, + INT_MAX); + } + + /* + * Free the namehook + */ + namehook->entry = NULL; + ISC_LIST_UNLINK(*namehooks, namehook, plink); + free_adbnamehook(adb, &namehook); + + namehook = ISC_LIST_HEAD(*namehooks); + } + + if (addr_bucket != DNS_ADB_INVALIDBUCKET) { + UNLOCK(&adb->entrylocks[addr_bucket]); + } + return (result); +} + +static void +clean_target(dns_adb_t *adb, dns_name_t *target) { + if (dns_name_countlabels(target) > 0) { + dns_name_free(target, adb->mctx); + dns_name_init(target, NULL); + } +} + +static isc_result_t +set_target(dns_adb_t *adb, const dns_name_t *name, const dns_name_t *fname, + dns_rdataset_t *rdataset, dns_name_t *target) { + isc_result_t result; + dns_namereln_t namereln; + unsigned int nlabels; + int order; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_fixedname_t fixed1, fixed2; + dns_name_t *prefix, *new_target; + + REQUIRE(dns_name_countlabels(target) == 0); + + if (rdataset->type == dns_rdatatype_cname) { + dns_rdata_cname_t cname; + + /* + * Copy the CNAME's target into the target name. + */ + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_name_dup(&cname.cname, adb->mctx, target); + dns_rdata_freestruct(&cname); + } else { + dns_rdata_dname_t dname; + + INSIST(rdataset->type == dns_rdatatype_dname); + namereln = dns_name_fullcompare(name, fname, &order, &nlabels); + INSIST(namereln == dns_namereln_subdomain); + /* + * Get the target name of the DNAME. + */ + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dname, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* + * Construct the new target name. + */ + prefix = dns_fixedname_initname(&fixed1); + new_target = dns_fixedname_initname(&fixed2); + dns_name_split(name, nlabels, prefix, NULL); + result = dns_name_concatenate(prefix, &dname.dname, new_target, + NULL); + dns_rdata_freestruct(&dname); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_name_dup(new_target, adb->mctx, target); + } + + return (ISC_R_SUCCESS); +} + +/* + * Assumes nothing is locked, since this is called by the client. + */ +static void +event_free(isc_event_t *event) { + dns_adbfind_t *find; + + INSIST(event != NULL); + find = event->ev_destroy_arg; + INSIST(DNS_ADBFIND_VALID(find)); + + LOCK(&find->lock); + find->flags |= FIND_EVENT_FREED; + event->ev_destroy_arg = NULL; + UNLOCK(&find->lock); +} + +/* + * Assumes the name bucket is locked. + */ +static void +clean_finds_at_name(dns_adbname_t *name, isc_eventtype_t evtype, + unsigned int addrs) { + isc_event_t *ev; + isc_task_t *task; + dns_adbfind_t *find; + dns_adbfind_t *next_find; + bool process; + unsigned int wanted, notify; + + DP(ENTER_LEVEL, + "ENTER clean_finds_at_name, name %p, evtype %08x, addrs %08x", name, + evtype, addrs); + + find = ISC_LIST_HEAD(name->finds); + while (find != NULL) { + LOCK(&find->lock); + next_find = ISC_LIST_NEXT(find, plink); + + process = false; + wanted = find->flags & DNS_ADBFIND_ADDRESSMASK; + notify = wanted & addrs; + + switch (evtype) { + case DNS_EVENT_ADBMOREADDRESSES: + DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBMOREADDRESSES"); + if ((notify) != 0) { + find->flags &= ~addrs; + process = true; + } + break; + case DNS_EVENT_ADBNOMOREADDRESSES: + DP(ISC_LOG_DEBUG(3), "DNS_EVENT_ADBNOMOREADDRESSES"); + find->flags &= ~addrs; + wanted = find->flags & DNS_ADBFIND_ADDRESSMASK; + if (wanted == 0) { + process = true; + } + break; + default: + find->flags &= ~addrs; + process = true; + } + + if (process) { + DP(DEF_LEVEL, "cfan: processing find %p", find); + /* + * Unlink the find from the name, letting the caller + * call dns_adb_destroyfind() on it to clean it up + * later. + */ + ISC_LIST_UNLINK(name->finds, find, plink); + find->adbname = NULL; + find->name_bucket = DNS_ADB_INVALIDBUCKET; + + INSIST(!FIND_EVENTSENT(find)); + + ev = &find->event; + task = ev->ev_sender; + ev->ev_sender = find; + find->result_v4 = find_err_map[name->fetch_err]; + find->result_v6 = find_err_map[name->fetch6_err]; + ev->ev_type = evtype; + ev->ev_destroy = event_free; + ev->ev_destroy_arg = find; + + DP(DEF_LEVEL, "sending event %p to task %p for find %p", + ev, task, find); + + isc_task_sendanddetach(&task, (isc_event_t **)&ev); + find->flags |= FIND_EVENT_SENT; + } else { + DP(DEF_LEVEL, "cfan: skipping find %p", find); + } + + UNLOCK(&find->lock); + find = next_find; + } + DP(ENTER_LEVEL, "EXIT clean_finds_at_name, name %p", name); +} + +static void +check_exit(dns_adb_t *adb) { + isc_event_t *event; + /* + * The caller must be holding the adb lock. + */ + if (atomic_load(&adb->shutting_down)) { + /* + * If there aren't any external references either, we're + * done. Send the control event to initiate shutdown. + */ + INSIST(!adb->cevent_out); /* Sanity check. */ + ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, + DNS_EVENT_ADBCONTROL, shutdown_task, adb, adb, + NULL, NULL); + event = &adb->cevent; + isc_task_send(adb->task, &event); + adb->cevent_out = true; + } +} + +static bool +dec_adb_irefcnt(dns_adb_t *adb) { + isc_event_t *event; + isc_task_t *etask; + bool result = false; + + LOCK(&adb->reflock); + + INSIST(adb->irefcnt > 0); + adb->irefcnt--; + + if (adb->irefcnt == 0) { + event = ISC_LIST_HEAD(adb->whenshutdown); + while (event != NULL) { + ISC_LIST_UNLINK(adb->whenshutdown, event, ev_link); + etask = event->ev_sender; + event->ev_sender = adb; + isc_task_sendanddetach(&etask, &event); + event = ISC_LIST_HEAD(adb->whenshutdown); + } + } + + if (adb->irefcnt == 0 && adb->erefcnt == 0) { + result = true; + } + UNLOCK(&adb->reflock); + return (result); +} + +static void +inc_adb_irefcnt(dns_adb_t *adb) { + LOCK(&adb->reflock); + adb->irefcnt++; + UNLOCK(&adb->reflock); +} + +static void +inc_adb_erefcnt(dns_adb_t *adb) { + LOCK(&adb->reflock); + adb->erefcnt++; + UNLOCK(&adb->reflock); +} + +static void +inc_entry_refcnt(dns_adb_t *adb, dns_adbentry_t *entry, bool lock) { + int bucket; + + bucket = entry->lock_bucket; + + if (lock) { + LOCK(&adb->entrylocks[bucket]); + } + + entry->refcnt++; + + if (lock) { + UNLOCK(&adb->entrylocks[bucket]); + } +} + +static bool +dec_entry_refcnt(dns_adb_t *adb, bool overmem, dns_adbentry_t *entry, bool lock, + isc_stdtime_t now) { + int bucket; + bool destroy_entry = false; + bool result = false; + + bucket = entry->lock_bucket; + + if (lock) { + LOCK(&adb->entrylocks[bucket]); + } + + INSIST(entry->refcnt > 0); + entry->refcnt--; + + if (entry->refcnt == 0 && + (adb->entry_sd[bucket] || entry->expires == 0 || + (overmem && entry->expires + ADB_CACHE_MINIMUM < now) || + (entry->flags & ENTRY_IS_DEAD) != 0)) + { + destroy_entry = true; + result = unlink_entry(adb, entry); + } + + if (lock) { + UNLOCK(&adb->entrylocks[bucket]); + } + + if (!destroy_entry) { + return (result); + } + + entry->lock_bucket = DNS_ADB_INVALIDBUCKET; + + free_adbentry(adb, &entry); + if (result) { + result = dec_adb_irefcnt(adb); + } + + return (result); +} + +static dns_adbname_t * +new_adbname(dns_adb_t *adb, const dns_name_t *dnsname) { + dns_adbname_t *name; + + name = isc_mem_get(adb->mctx, sizeof(*name)); + + dns_name_init(&name->name, NULL); + dns_name_dup(dnsname, adb->mctx, &name->name); + dns_name_init(&name->target, NULL); + name->magic = DNS_ADBNAME_MAGIC; + name->adb = adb; + name->partial_result = 0; + name->flags = 0; + name->expire_v4 = INT_MAX; + name->expire_v6 = INT_MAX; + name->expire_target = INT_MAX; + name->chains = 0; + name->lock_bucket = DNS_ADB_INVALIDBUCKET; + ISC_LIST_INIT(name->v4); + ISC_LIST_INIT(name->v6); + name->fetch_a = NULL; + name->fetch_aaaa = NULL; + name->fetch_err = FIND_ERR_UNEXPECTED; + name->fetch6_err = FIND_ERR_UNEXPECTED; + ISC_LIST_INIT(name->finds); + ISC_LINK_INIT(name, plink); + + LOCK(&adb->namescntlock); + adb->namescnt++; + inc_adbstats(adb, dns_adbstats_namescnt); + if (!adb->grownames_sent && adb->excl != NULL && + adb->namescnt > (adb->nnames * 8)) + { + isc_event_t *event = &adb->grownames; + inc_adb_irefcnt(adb); + isc_task_send(adb->excl, &event); + adb->grownames_sent = true; + } + UNLOCK(&adb->namescntlock); + + return (name); +} + +static void +free_adbname(dns_adb_t *adb, dns_adbname_t **name) { + dns_adbname_t *n; + + INSIST(name != NULL && DNS_ADBNAME_VALID(*name)); + n = *name; + *name = NULL; + + INSIST(!NAME_HAS_V4(n)); + INSIST(!NAME_HAS_V6(n)); + INSIST(!NAME_FETCH(n)); + INSIST(ISC_LIST_EMPTY(n->finds)); + INSIST(!ISC_LINK_LINKED(n, plink)); + INSIST(n->lock_bucket == DNS_ADB_INVALIDBUCKET); + INSIST(n->adb == adb); + + n->magic = 0; + dns_name_free(&n->name, adb->mctx); + + isc_mem_put(adb->mctx, n, sizeof(*n)); + LOCK(&adb->namescntlock); + adb->namescnt--; + dec_adbstats(adb, dns_adbstats_namescnt); + UNLOCK(&adb->namescntlock); +} + +static dns_adbnamehook_t * +new_adbnamehook(dns_adb_t *adb, dns_adbentry_t *entry) { + dns_adbnamehook_t *nh; + + nh = isc_mem_get(adb->mctx, sizeof(*nh)); + isc_refcount_increment0(&adb->nhrefcnt); + + nh->magic = DNS_ADBNAMEHOOK_MAGIC; + nh->entry = entry; + ISC_LINK_INIT(nh, plink); + + return (nh); +} + +static void +free_adbnamehook(dns_adb_t *adb, dns_adbnamehook_t **namehook) { + dns_adbnamehook_t *nh; + + INSIST(namehook != NULL && DNS_ADBNAMEHOOK_VALID(*namehook)); + nh = *namehook; + *namehook = NULL; + + INSIST(nh->entry == NULL); + INSIST(!ISC_LINK_LINKED(nh, plink)); + + nh->magic = 0; + + isc_refcount_decrement(&adb->nhrefcnt); + isc_mem_put(adb->mctx, nh, sizeof(*nh)); +} + +static dns_adblameinfo_t * +new_adblameinfo(dns_adb_t *adb, const dns_name_t *qname, + dns_rdatatype_t qtype) { + dns_adblameinfo_t *li; + + li = isc_mem_get(adb->mctx, sizeof(*li)); + + dns_name_init(&li->qname, NULL); + dns_name_dup(qname, adb->mctx, &li->qname); + li->magic = DNS_ADBLAMEINFO_MAGIC; + li->lame_timer = 0; + li->qtype = qtype; + ISC_LINK_INIT(li, plink); + + return (li); +} + +static void +free_adblameinfo(dns_adb_t *adb, dns_adblameinfo_t **lameinfo) { + dns_adblameinfo_t *li; + + INSIST(lameinfo != NULL && DNS_ADBLAMEINFO_VALID(*lameinfo)); + li = *lameinfo; + *lameinfo = NULL; + + INSIST(!ISC_LINK_LINKED(li, plink)); + + dns_name_free(&li->qname, adb->mctx); + + li->magic = 0; + + isc_mem_put(adb->mctx, li, sizeof(*li)); +} + +static dns_adbentry_t * +new_adbentry(dns_adb_t *adb) { + dns_adbentry_t *e; + + e = isc_mem_get(adb->mctx, sizeof(*e)); + + e->magic = DNS_ADBENTRY_MAGIC; + e->lock_bucket = DNS_ADB_INVALIDBUCKET; + e->refcnt = 0; + e->nh = 0; + e->flags = 0; + e->udpsize = 0; + e->edns = 0; + e->ednsto = 0; + e->completed = 0; + e->timeouts = 0; + e->plain = 0; + e->plainto = 0; + e->cookie = NULL; + e->cookielen = 0; + e->srtt = (isc_random_uniform(0x1f)) + 1; + e->lastage = 0; + e->expires = 0; + atomic_init(&e->active, 0); + e->mode = 0; + atomic_init(&e->quota, adb->quota); + e->atr = 0.0; + ISC_LIST_INIT(e->lameinfo); + ISC_LINK_INIT(e, plink); + LOCK(&adb->entriescntlock); + adb->entriescnt++; + inc_adbstats(adb, dns_adbstats_entriescnt); + if (!adb->growentries_sent && adb->excl != NULL && + adb->entriescnt > (adb->nentries * 8)) + { + isc_event_t *event = &adb->growentries; + inc_adb_irefcnt(adb); + isc_task_send(adb->excl, &event); + adb->growentries_sent = true; + } + UNLOCK(&adb->entriescntlock); + + return (e); +} + +static void +free_adbentry(dns_adb_t *adb, dns_adbentry_t **entry) { + dns_adbentry_t *e; + dns_adblameinfo_t *li; + uint_fast32_t active; + + INSIST(entry != NULL && DNS_ADBENTRY_VALID(*entry)); + e = *entry; + *entry = NULL; + + active = atomic_load_acquire(&e->active); + INSIST(active == 0); + INSIST(e->lock_bucket == DNS_ADB_INVALIDBUCKET); + INSIST(e->refcnt == 0); + INSIST(!ISC_LINK_LINKED(e, plink)); + + e->magic = 0; + + if (e->cookie != NULL) { + isc_mem_put(adb->mctx, e->cookie, e->cookielen); + } + + li = ISC_LIST_HEAD(e->lameinfo); + while (li != NULL) { + ISC_LIST_UNLINK(e->lameinfo, li, plink); + free_adblameinfo(adb, &li); + li = ISC_LIST_HEAD(e->lameinfo); + } + + isc_mem_put(adb->mctx, e, sizeof(*e)); + LOCK(&adb->entriescntlock); + adb->entriescnt--; + dec_adbstats(adb, dns_adbstats_entriescnt); + UNLOCK(&adb->entriescntlock); +} + +static dns_adbfind_t * +new_adbfind(dns_adb_t *adb) { + dns_adbfind_t *h; + + h = isc_mem_get(adb->mctx, sizeof(*h)); + isc_refcount_increment0(&adb->ahrefcnt); + + /* + * Public members. + */ + h->magic = 0; + h->adb = adb; + h->partial_result = 0; + h->options = 0; + h->flags = 0; + h->result_v4 = ISC_R_UNEXPECTED; + h->result_v6 = ISC_R_UNEXPECTED; + ISC_LINK_INIT(h, publink); + ISC_LINK_INIT(h, plink); + ISC_LIST_INIT(h->list); + h->adbname = NULL; + h->name_bucket = DNS_ADB_INVALIDBUCKET; + + /* + * private members + */ + isc_mutex_init(&h->lock); + + ISC_EVENT_INIT(&h->event, sizeof(isc_event_t), 0, 0, 0, NULL, NULL, + NULL, NULL, h); + + inc_adb_irefcnt(adb); + h->magic = DNS_ADBFIND_MAGIC; + return (h); +} + +static dns_adbfetch_t * +new_adbfetch(dns_adb_t *adb) { + dns_adbfetch_t *f; + + f = isc_mem_get(adb->mctx, sizeof(*f)); + + f->magic = 0; + f->fetch = NULL; + + dns_rdataset_init(&f->rdataset); + + f->magic = DNS_ADBFETCH_MAGIC; + + return (f); +} + +static void +free_adbfetch(dns_adb_t *adb, dns_adbfetch_t **fetch) { + dns_adbfetch_t *f; + + INSIST(fetch != NULL && DNS_ADBFETCH_VALID(*fetch)); + f = *fetch; + *fetch = NULL; + + f->magic = 0; + + if (dns_rdataset_isassociated(&f->rdataset)) { + dns_rdataset_disassociate(&f->rdataset); + } + + isc_mem_put(adb->mctx, f, sizeof(*f)); +} + +static bool +free_adbfind(dns_adb_t *adb, dns_adbfind_t **findp) { + dns_adbfind_t *find; + + INSIST(findp != NULL && DNS_ADBFIND_VALID(*findp)); + find = *findp; + *findp = NULL; + + INSIST(!FIND_HAS_ADDRS(find)); + INSIST(!ISC_LINK_LINKED(find, publink)); + INSIST(!ISC_LINK_LINKED(find, plink)); + INSIST(find->name_bucket == DNS_ADB_INVALIDBUCKET); + INSIST(find->adbname == NULL); + + find->magic = 0; + + isc_mutex_destroy(&find->lock); + + isc_refcount_decrement(&adb->ahrefcnt); + isc_mem_put(adb->mctx, find, sizeof(*find)); + return (dec_adb_irefcnt(adb)); +} + +/* + * Copy bits from the entry into the newly allocated addrinfo. The entry + * must be locked, and the reference count must be bumped up by one + * if this function returns a valid pointer. + */ +static dns_adbaddrinfo_t * +new_adbaddrinfo(dns_adb_t *adb, dns_adbentry_t *entry, in_port_t port) { + dns_adbaddrinfo_t *ai; + + ai = isc_mem_get(adb->mctx, sizeof(*ai)); + + ai->magic = DNS_ADBADDRINFO_MAGIC; + ai->sockaddr = entry->sockaddr; + isc_sockaddr_setport(&ai->sockaddr, port); + ai->srtt = entry->srtt; + ai->flags = entry->flags; + ai->entry = entry; + ISC_LINK_INIT(ai, publink); + + return (ai); +} + +static void +free_adbaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **ainfo) { + dns_adbaddrinfo_t *ai; + + INSIST(ainfo != NULL && DNS_ADBADDRINFO_VALID(*ainfo)); + ai = *ainfo; + *ainfo = NULL; + + INSIST(ai->entry == NULL); + INSIST(!ISC_LINK_LINKED(ai, publink)); + + ai->magic = 0; + + isc_mem_put(adb->mctx, ai, sizeof(*ai)); +} + +/* + * Search for the name. NOTE: The bucket is kept locked on both + * success and failure, so it must always be unlocked by the caller! + * + * On the first call to this function, *bucketp must be set to + * DNS_ADB_INVALIDBUCKET. + */ +static dns_adbname_t * +find_name_and_lock(dns_adb_t *adb, const dns_name_t *name, unsigned int options, + int *bucketp) { + dns_adbname_t *adbname; + int bucket; + + bucket = dns_name_fullhash(name, false) % adb->nnames; + + if (*bucketp == DNS_ADB_INVALIDBUCKET) { + LOCK(&adb->namelocks[bucket]); + *bucketp = bucket; + } else if (*bucketp != bucket) { + UNLOCK(&adb->namelocks[*bucketp]); + LOCK(&adb->namelocks[bucket]); + *bucketp = bucket; + } + + adbname = ISC_LIST_HEAD(adb->names[bucket]); + while (adbname != NULL) { + if (!NAME_DEAD(adbname)) { + if (dns_name_equal(name, &adbname->name) && + GLUEHINT_OK(adbname, options) && + STARTATZONE_MATCHES(adbname, options)) + { + return (adbname); + } + } + adbname = ISC_LIST_NEXT(adbname, plink); + } + + return (NULL); +} + +/* + * Search for the address. NOTE: The bucket is kept locked on both + * success and failure, so it must always be unlocked by the caller. + * + * On the first call to this function, *bucketp must be set to + * DNS_ADB_INVALIDBUCKET. This will cause a lock to occur. On + * later calls (within the same "lock path") it can be left alone, so + * if this function is called multiple times locking is only done if + * the bucket changes. + */ +static dns_adbentry_t * +find_entry_and_lock(dns_adb_t *adb, const isc_sockaddr_t *addr, int *bucketp, + isc_stdtime_t now) { + dns_adbentry_t *entry, *entry_next; + int bucket; + + bucket = isc_sockaddr_hash(addr, true) % adb->nentries; + + if (*bucketp == DNS_ADB_INVALIDBUCKET) { + LOCK(&adb->entrylocks[bucket]); + *bucketp = bucket; + } else if (*bucketp != bucket) { + UNLOCK(&adb->entrylocks[*bucketp]); + LOCK(&adb->entrylocks[bucket]); + *bucketp = bucket; + } + + /* Search the list, while cleaning up expired entries. */ + for (entry = ISC_LIST_HEAD(adb->entries[bucket]); entry != NULL; + entry = entry_next) + { + entry_next = ISC_LIST_NEXT(entry, plink); + (void)check_expire_entry(adb, &entry, now); + if (entry != NULL && + (entry->expires == 0 || entry->expires > now) && + isc_sockaddr_equal(addr, &entry->sockaddr)) + { + ISC_LIST_UNLINK(adb->entries[bucket], entry, plink); + ISC_LIST_PREPEND(adb->entries[bucket], entry, plink); + return (entry); + } + } + + return (NULL); +} + +/* + * Entry bucket MUST be locked! + */ +static bool +entry_is_lame(dns_adb_t *adb, dns_adbentry_t *entry, const dns_name_t *qname, + dns_rdatatype_t qtype, isc_stdtime_t now) { + dns_adblameinfo_t *li, *next_li; + bool is_bad; + + is_bad = false; + + li = ISC_LIST_HEAD(entry->lameinfo); + if (li == NULL) { + return (false); + } + while (li != NULL) { + next_li = ISC_LIST_NEXT(li, plink); + + /* + * Has the entry expired? + */ + if (li->lame_timer < now) { + ISC_LIST_UNLINK(entry->lameinfo, li, plink); + free_adblameinfo(adb, &li); + } + + /* + * Order tests from least to most expensive. + * + * We do not break out of the main loop here as + * we use the loop for house keeping. + */ + if (li != NULL && !is_bad && li->qtype == qtype && + dns_name_equal(qname, &li->qname)) + { + is_bad = true; + } + + li = next_li; + } + + return (is_bad); +} + +static void +log_quota(dns_adbentry_t *entry, const char *fmt, ...) { + va_list ap; + char msgbuf[2048]; + char addrbuf[ISC_NETADDR_FORMATSIZE]; + isc_netaddr_t netaddr; + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr); + isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB, + ISC_LOG_INFO, + "adb: quota %s (%" PRIuFAST32 "/%" PRIuFAST32 "): %s", + addrbuf, atomic_load_relaxed(&entry->active), + atomic_load_relaxed(&entry->quota), msgbuf); +} + +static void +copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, + const dns_name_t *qname, dns_rdatatype_t qtype, + dns_adbname_t *name, isc_stdtime_t now) { + dns_adbnamehook_t *namehook; + dns_adbaddrinfo_t *addrinfo; + dns_adbentry_t *entry; + int bucket; + + bucket = DNS_ADB_INVALIDBUCKET; + + if ((find->options & DNS_ADBFIND_INET) != 0) { + namehook = ISC_LIST_HEAD(name->v4); + while (namehook != NULL) { + entry = namehook->entry; + bucket = entry->lock_bucket; + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + LOCK(&adb->entrylocks[bucket]); + + if (dns_adbentry_overquota(entry)) { + find->options |= (DNS_ADBFIND_LAMEPRUNED | + DNS_ADBFIND_OVERQUOTA); + goto nextv4; + } + + if (!FIND_RETURNLAME(find) && + entry_is_lame(adb, entry, qname, qtype, now)) + { + find->options |= DNS_ADBFIND_LAMEPRUNED; + goto nextv4; + } + + addrinfo = new_adbaddrinfo(adb, entry, find->port); + + /* + * Found a valid entry. Add it to the find's list. + */ + inc_entry_refcnt(adb, entry, false); + ISC_LIST_APPEND(find->list, addrinfo, publink); + addrinfo = NULL; + nextv4: + UNLOCK(&adb->entrylocks[bucket]); + bucket = DNS_ADB_INVALIDBUCKET; + namehook = ISC_LIST_NEXT(namehook, plink); + } + } + + if ((find->options & DNS_ADBFIND_INET6) != 0) { + namehook = ISC_LIST_HEAD(name->v6); + while (namehook != NULL) { + entry = namehook->entry; + bucket = entry->lock_bucket; + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + LOCK(&adb->entrylocks[bucket]); + + if (dns_adbentry_overquota(entry)) { + find->options |= (DNS_ADBFIND_LAMEPRUNED | + DNS_ADBFIND_OVERQUOTA); + goto nextv6; + } + + if (!FIND_RETURNLAME(find) && + entry_is_lame(adb, entry, qname, qtype, now)) + { + find->options |= DNS_ADBFIND_LAMEPRUNED; + goto nextv6; + } + addrinfo = new_adbaddrinfo(adb, entry, find->port); + + /* + * Found a valid entry. Add it to the find's list. + */ + inc_entry_refcnt(adb, entry, false); + ISC_LIST_APPEND(find->list, addrinfo, publink); + addrinfo = NULL; + nextv6: + UNLOCK(&adb->entrylocks[bucket]); + bucket = DNS_ADB_INVALIDBUCKET; + namehook = ISC_LIST_NEXT(namehook, plink); + } + } + + if (bucket != DNS_ADB_INVALIDBUCKET) { + UNLOCK(&adb->entrylocks[bucket]); + } +} + +static void +shutdown_task(isc_task_t *task, isc_event_t *ev) { + dns_adb_t *adb; + + UNUSED(task); + + adb = ev->ev_arg; + INSIST(DNS_ADB_VALID(adb)); + + isc_event_free(&ev); + /* + * Wait for lock around check_exit() call to be released. + */ + LOCK(&adb->lock); + UNLOCK(&adb->lock); + destroy(adb); +} + +/* + * Name bucket must be locked; adb may be locked; no other locks held. + */ +static bool +check_expire_name(dns_adbname_t **namep, isc_stdtime_t now) { + dns_adbname_t *name; + bool result = false; + + INSIST(namep != NULL && DNS_ADBNAME_VALID(*namep)); + name = *namep; + + if (NAME_HAS_V4(name) || NAME_HAS_V6(name)) { + return (result); + } + if (NAME_FETCH(name)) { + return (result); + } + if (!EXPIRE_OK(name->expire_v4, now)) { + return (result); + } + if (!EXPIRE_OK(name->expire_v6, now)) { + return (result); + } + if (!EXPIRE_OK(name->expire_target, now)) { + return (result); + } + + /* + * The name is empty. Delete it. + */ + *namep = NULL; + result = kill_name(&name, DNS_EVENT_ADBEXPIRED); + + /* + * Our caller, or one of its callers, will be calling check_exit() at + * some point, so we don't need to do it here. + */ + return (result); +} + +/*% + * Examine the tail entry of the LRU list to see if it expires or is stale + * (unused for some period); if so, the name entry will be freed. If the ADB + * is in the overmem condition, the tail and the next to tail entries + * will be unconditionally removed (unless they have an outstanding fetch). + * We don't care about a race on 'overmem' at the risk of causing some + * collateral damage or a small delay in starting cleanup, so we don't bother + * to lock ADB (if it's not locked). + * + * Name bucket must be locked; adb may be locked; no other locks held. + */ +static void +check_stale_name(dns_adb_t *adb, int bucket, isc_stdtime_t now) { + int victims, max_victims; + dns_adbname_t *victim, *next_victim; + bool overmem = isc_mem_isovermem(adb->mctx); + int scans = 0; + + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + + max_victims = overmem ? 2 : 1; + + /* + * We limit the number of scanned entries to 10 (arbitrary choice) + * in order to avoid examining too many entries when there are many + * tail entries that have fetches (this should be rare, but could + * happen). + */ + victim = ISC_LIST_TAIL(adb->names[bucket]); + for (victims = 0; victim != NULL && victims < max_victims && scans < 10; + victim = next_victim) + { + INSIST(!NAME_DEAD(victim)); + scans++; + next_victim = ISC_LIST_PREV(victim, plink); + (void)check_expire_name(&victim, now); + if (victim == NULL) { + victims++; + goto next; + } + + /* + * Make sure that we are not purging ADB names that has been + * just created. + */ + if (victim->last_used + ADB_CACHE_MINIMUM >= now) { + break; + } + + if (!NAME_FETCH(victim) && + (overmem || victim->last_used + ADB_STALE_MARGIN <= now)) + { + RUNTIME_CHECK( + !kill_name(&victim, DNS_EVENT_ADBCANCELED)); + victims++; + } + + next: + if (!overmem) { + break; + } + } +} + +/* + * Entry bucket must be locked; adb may be locked; no other locks held. + */ +static bool +check_expire_entry(dns_adb_t *adb, dns_adbentry_t **entryp, isc_stdtime_t now) { + dns_adbentry_t *entry; + bool result = false; + + INSIST(entryp != NULL && DNS_ADBENTRY_VALID(*entryp)); + entry = *entryp; + + if (entry->refcnt != 0) { + return (result); + } + + if (entry->expires == 0 || entry->expires > now) { + return (result); + } + + /* + * The entry is not in use. Delete it. + */ + *entryp = NULL; + DP(DEF_LEVEL, "killing entry %p", entry); + INSIST(ISC_LINK_LINKED(entry, plink)); + result = unlink_entry(adb, entry); + free_adbentry(adb, &entry); + if (result) { + dec_adb_irefcnt(adb); + } + return (result); +} + +/* + * ADB must be locked, and no other locks held. + */ +static bool +cleanup_names(dns_adb_t *adb, int bucket, isc_stdtime_t now) { + dns_adbname_t *name; + dns_adbname_t *next_name; + bool result = false; + + DP(CLEAN_LEVEL, "cleaning name bucket %d", bucket); + + LOCK(&adb->namelocks[bucket]); + if (adb->name_sd[bucket]) { + UNLOCK(&adb->namelocks[bucket]); + return (result); + } + + name = ISC_LIST_HEAD(adb->names[bucket]); + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, plink); + INSIST(!result); + result = check_expire_namehooks(name, now); + if (!result) { + result = check_expire_name(&name, now); + } + name = next_name; + } + UNLOCK(&adb->namelocks[bucket]); + return (result); +} + +/* + * ADB must be locked, and no other locks held. + */ +static bool +cleanup_entries(dns_adb_t *adb, int bucket, isc_stdtime_t now) { + dns_adbentry_t *entry, *next_entry; + bool result = false; + + DP(CLEAN_LEVEL, "cleaning entry bucket %d", bucket); + + LOCK(&adb->entrylocks[bucket]); + entry = ISC_LIST_HEAD(adb->entries[bucket]); + while (entry != NULL) { + next_entry = ISC_LIST_NEXT(entry, plink); + INSIST(!result); + result = check_expire_entry(adb, &entry, now); + entry = next_entry; + } + UNLOCK(&adb->entrylocks[bucket]); + return (result); +} + +static void +destroy(dns_adb_t *adb) { + adb->magic = 0; + + isc_task_detach(&adb->task); + if (adb->excl != NULL) { + isc_task_detach(&adb->excl); + } + + isc_mutexblock_destroy(adb->entrylocks, adb->nentries); + isc_mem_put(adb->hmctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + isc_mem_put(adb->hmctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + isc_mem_put(adb->hmctx, adb->entry_refcnt, + sizeof(*adb->entry_refcnt) * adb->nentries); + + isc_mutexblock_destroy(adb->namelocks, adb->nnames); + isc_mem_put(adb->hmctx, adb->names, sizeof(*adb->names) * adb->nnames); + isc_mem_put(adb->hmctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + isc_mem_put(adb->hmctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + isc_mem_put(adb->hmctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + isc_mem_put(adb->hmctx, adb->name_refcnt, + sizeof(*adb->name_refcnt) * adb->nnames); + + isc_mem_destroy(&adb->hmctx); + + isc_mutex_destroy(&adb->reflock); + isc_mutex_destroy(&adb->lock); + isc_mutex_destroy(&adb->overmemlock); + isc_mutex_destroy(&adb->entriescntlock); + isc_mutex_destroy(&adb->namescntlock); + + isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t)); +} + +/* + * Public functions. + */ + +isc_result_t +dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *timermgr, + isc_taskmgr_t *taskmgr, dns_adb_t **newadb) { + dns_adb_t *adb; + isc_result_t result; + unsigned int i; + + REQUIRE(mem != NULL); + REQUIRE(view != NULL); + REQUIRE(timermgr != NULL); /* this is actually unused */ + REQUIRE(taskmgr != NULL); + REQUIRE(newadb != NULL && *newadb == NULL); + + UNUSED(timermgr); + + adb = isc_mem_get(mem, sizeof(dns_adb_t)); + + /* + * Initialize things here that cannot fail, and especially things + * that must be NULL for the error return to work properly. + */ + adb->magic = 0; + adb->erefcnt = 1; + adb->irefcnt = 0; + adb->task = NULL; + adb->excl = NULL; + adb->mctx = NULL; + adb->hmctx = NULL; + adb->view = view; + adb->taskmgr = taskmgr; + adb->next_cleanbucket = 0; + ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, 0, NULL, + NULL, NULL, NULL, NULL); + adb->cevent_out = false; + atomic_init(&adb->shutting_down, false); + ISC_LIST_INIT(adb->whenshutdown); + + adb->nentries = nbuckets[0]; + adb->entriescnt = 0; + adb->entries = NULL; + adb->deadentries = NULL; + adb->entry_sd = NULL; + adb->entry_refcnt = NULL; + adb->entrylocks = NULL; + ISC_EVENT_INIT(&adb->growentries, sizeof(adb->growentries), 0, NULL, + DNS_EVENT_ADBGROWENTRIES, grow_entries, adb, adb, NULL, + NULL); + adb->growentries_sent = false; + + adb->quota = 0; + adb->atr_freq = 0; + adb->atr_low = 0.0; + adb->atr_high = 0.0; + adb->atr_discount = 0.0; + + adb->nnames = nbuckets[0]; + adb->namescnt = 0; + adb->names = NULL; + adb->deadnames = NULL; + adb->name_sd = NULL; + adb->name_refcnt = NULL; + adb->namelocks = NULL; + ISC_EVENT_INIT(&adb->grownames, sizeof(adb->grownames), 0, NULL, + DNS_EVENT_ADBGROWNAMES, grow_names, adb, adb, NULL, + NULL); + adb->grownames_sent = false; + + result = isc_taskmgr_excltask(adb->taskmgr, &adb->excl); + if (result != ISC_R_SUCCESS) { + DP(DEF_LEVEL, + "adb: task-exclusive mode unavailable, " + "initializing table sizes to %u\n", + nbuckets[11]); + adb->nentries = nbuckets[11]; + adb->nnames = nbuckets[11]; + } + + isc_mem_attach(mem, &adb->mctx); + + isc_mutex_init(&adb->lock); + isc_mutex_init(&adb->reflock); + isc_mutex_init(&adb->overmemlock); + isc_mutex_init(&adb->entriescntlock); + isc_mutex_init(&adb->namescntlock); + + isc_mem_create(&adb->hmctx); + isc_mem_setname(adb->hmctx, "ADB_hashmaps"); + +#define ALLOCENTRY(adb, el) \ + do { \ + (adb)->el = isc_mem_get((adb)->hmctx, \ + sizeof(*(adb)->el) * (adb)->nentries); \ + } while (0) + ALLOCENTRY(adb, entries); + ALLOCENTRY(adb, deadentries); + ALLOCENTRY(adb, entrylocks); + ALLOCENTRY(adb, entry_sd); + ALLOCENTRY(adb, entry_refcnt); +#undef ALLOCENTRY + +#define ALLOCNAME(adb, el) \ + do { \ + (adb)->el = isc_mem_get((adb)->hmctx, \ + sizeof(*(adb)->el) * (adb)->nnames); \ + } while (0) + ALLOCNAME(adb, names); + ALLOCNAME(adb, deadnames); + ALLOCNAME(adb, namelocks); + ALLOCNAME(adb, name_sd); + ALLOCNAME(adb, name_refcnt); +#undef ALLOCNAME + + /* + * Initialize the bucket locks for names and elements. + * May as well initialize the list heads, too. + */ + isc_mutexblock_init(adb->namelocks, adb->nnames); + + for (i = 0; i < adb->nnames; i++) { + ISC_LIST_INIT(adb->names[i]); + ISC_LIST_INIT(adb->deadnames[i]); + adb->name_sd[i] = false; + adb->name_refcnt[i] = 0; + adb->irefcnt++; + } + for (i = 0; i < adb->nentries; i++) { + ISC_LIST_INIT(adb->entries[i]); + ISC_LIST_INIT(adb->deadentries[i]); + adb->entry_sd[i] = false; + adb->entry_refcnt[i] = 0; + adb->irefcnt++; + } + isc_mutexblock_init(adb->entrylocks, adb->nentries); + + isc_refcount_init(&adb->ahrefcnt, 0); + isc_refcount_init(&adb->nhrefcnt, 0); + + /* + * Allocate an internal task. + */ + result = isc_task_create(adb->taskmgr, 0, &adb->task); + if (result != ISC_R_SUCCESS) { + goto fail2; + } + + isc_task_setname(adb->task, "ADB", adb); + + result = isc_stats_create(adb->mctx, &view->adbstats, dns_adbstats_max); + if (result != ISC_R_SUCCESS) { + goto fail2; + } + + set_adbstat(adb, adb->nentries, dns_adbstats_nentries); + set_adbstat(adb, adb->nnames, dns_adbstats_nnames); + + /* + * Normal return. + */ + adb->magic = DNS_ADB_MAGIC; + *newadb = adb; + return (ISC_R_SUCCESS); + +fail2: + if (adb->task != NULL) { + isc_task_detach(&adb->task); + } + + /* clean up entrylocks */ + isc_mutexblock_destroy(adb->entrylocks, adb->nentries); + isc_mutexblock_destroy(adb->namelocks, adb->nnames); + + if (adb->entries != NULL) { + isc_mem_put(adb->hmctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + } + if (adb->deadentries != NULL) { + isc_mem_put(adb->hmctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + } + if (adb->entrylocks != NULL) { + isc_mem_put(adb->hmctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + } + if (adb->entry_sd != NULL) { + isc_mem_put(adb->hmctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + } + if (adb->entry_refcnt != NULL) { + isc_mem_put(adb->hmctx, adb->entry_refcnt, + sizeof(*adb->entry_refcnt) * adb->nentries); + } + if (adb->names != NULL) { + isc_mem_put(adb->hmctx, adb->names, + sizeof(*adb->names) * adb->nnames); + } + if (adb->deadnames != NULL) { + isc_mem_put(adb->hmctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + } + if (adb->namelocks != NULL) { + isc_mem_put(adb->hmctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + } + if (adb->name_sd != NULL) { + isc_mem_put(adb->hmctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + } + if (adb->name_refcnt != NULL) { + isc_mem_put(adb->hmctx, adb->name_refcnt, + sizeof(*adb->name_refcnt) * adb->nnames); + } + + isc_mem_destroy(&adb->hmctx); + + isc_mutex_destroy(&adb->namescntlock); + isc_mutex_destroy(&adb->entriescntlock); + isc_mutex_destroy(&adb->overmemlock); + isc_mutex_destroy(&adb->reflock); + isc_mutex_destroy(&adb->lock); + if (adb->excl != NULL) { + isc_task_detach(&adb->excl); + } + isc_mem_putanddetach(&adb->mctx, adb, sizeof(dns_adb_t)); + + return (result); +} + +void +dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbx) { + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(adbx != NULL && *adbx == NULL); + + inc_adb_erefcnt(adb); + *adbx = adb; +} + +void +dns_adb_detach(dns_adb_t **adbx) { + dns_adb_t *adb; + bool need_exit_check; + + REQUIRE(adbx != NULL && DNS_ADB_VALID(*adbx)); + + adb = *adbx; + *adbx = NULL; + + LOCK(&adb->reflock); + INSIST(adb->erefcnt > 0); + adb->erefcnt--; + need_exit_check = (adb->erefcnt == 0 && adb->irefcnt == 0); + UNLOCK(&adb->reflock); + + if (need_exit_check) { + LOCK(&adb->lock); + INSIST(atomic_load(&adb->shutting_down)); + check_exit(adb); + UNLOCK(&adb->lock); + } +} + +void +dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp) { + isc_task_t *tclone; + isc_event_t *event; + bool zeroirefcnt; + + /* + * Send '*eventp' to 'task' when 'adb' has shutdown. + */ + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(eventp != NULL); + + event = *eventp; + *eventp = NULL; + + LOCK(&adb->lock); + LOCK(&adb->reflock); + + zeroirefcnt = (adb->irefcnt == 0); + + if (atomic_load(&adb->shutting_down) && zeroirefcnt && + isc_refcount_current(&adb->ahrefcnt) == 0) + { + /* + * We're already shutdown. Send the event. + */ + event->ev_sender = adb; + isc_task_send(task, &event); + } else { + tclone = NULL; + isc_task_attach(task, &tclone); + event->ev_sender = tclone; + ISC_LIST_APPEND(adb->whenshutdown, event, ev_link); + } + + UNLOCK(&adb->reflock); + UNLOCK(&adb->lock); +} + +static void +shutdown_stage2(isc_task_t *task, isc_event_t *event) { + dns_adb_t *adb; + + UNUSED(task); + + adb = event->ev_arg; + INSIST(DNS_ADB_VALID(adb)); + + LOCK(&adb->lock); + INSIST(atomic_load(&adb->shutting_down)); + adb->cevent_out = false; + (void)shutdown_names(adb); + (void)shutdown_entries(adb); + if (dec_adb_irefcnt(adb)) { + check_exit(adb); + } + UNLOCK(&adb->lock); +} + +void +dns_adb_shutdown(dns_adb_t *adb) { + isc_event_t *event; + + /* + * Shutdown 'adb'. + */ + + LOCK(&adb->lock); + + if (atomic_compare_exchange_strong(&adb->shutting_down, + &(bool){ false }, true)) + { + isc_mem_clearwater(adb->mctx); + /* + * Isolate shutdown_names and shutdown_entries calls. + */ + inc_adb_irefcnt(adb); + ISC_EVENT_INIT(&adb->cevent, sizeof(adb->cevent), 0, NULL, + DNS_EVENT_ADBCONTROL, shutdown_stage2, adb, adb, + NULL, NULL); + adb->cevent_out = true; + event = &adb->cevent; + isc_task_send(adb->task, &event); + } + + UNLOCK(&adb->lock); +} + +isc_result_t +dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action, + void *arg, const dns_name_t *name, const dns_name_t *qname, + dns_rdatatype_t qtype, unsigned int options, + isc_stdtime_t now, dns_name_t *target, in_port_t port, + unsigned int depth, isc_counter_t *qc, + dns_adbfind_t **findp) { + dns_adbfind_t *find = NULL; + dns_adbname_t *adbname = NULL; + int bucket; + bool want_event = true; + bool start_at_zone = false; + bool alias = false; + bool have_address = false; + isc_result_t result; + unsigned int wanted_addresses = (options & DNS_ADBFIND_ADDRESSMASK); + unsigned int wanted_fetches = 0; + unsigned int query_pending = 0; + char namebuf[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_ADB_VALID(adb)); + if (task != NULL) { + REQUIRE(action != NULL); + } + REQUIRE(name != NULL); + REQUIRE(qname != NULL); + REQUIRE(findp != NULL && *findp == NULL); + REQUIRE(target == NULL || dns_name_hasbuffer(target)); + + REQUIRE((options & DNS_ADBFIND_ADDRESSMASK) != 0); + + result = ISC_R_UNEXPECTED; + POST(result); + + if (atomic_load(&adb->shutting_down)) { + DP(DEF_LEVEL, "dns_adb_createfind: returning " + "ISC_R_SHUTTINGDOWN"); + + return (ISC_R_SHUTTINGDOWN); + } + + if (now == 0) { + isc_stdtime_get(&now); + } + + /* + * XXXMLG Move this comment somewhere else! + * + * Look up the name in our internal database. + * + * Possibilities: Note that these are not always exclusive. + * + * No name found. In this case, allocate a new name header and + * an initial namehook or two. + * + * Name found, valid addresses present. Allocate one addrinfo + * structure for each found and append it to the linked list + * of addresses for this header. + * + * Name found, queries pending. In this case, if a task was + * passed in, allocate a job id, attach it to the name's job + * list and remember to tell the caller that there will be + * more info coming later. + */ + + find = new_adbfind(adb); + + find->port = port; + + /* + * Remember what types of addresses we are interested in. + */ + find->options = options; + find->flags |= wanted_addresses; + if (FIND_WANTEVENT(find)) { + REQUIRE(task != NULL); + } + + if (isc_log_wouldlog(dns_lctx, DEF_LEVEL)) { + dns_name_format(name, namebuf, sizeof(namebuf)); + } else { + namebuf[0] = 0; + } + + /* + * Try to see if we know anything about this name at all. + */ + bucket = DNS_ADB_INVALIDBUCKET; + adbname = find_name_and_lock(adb, name, find->options, &bucket); + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + if (adb->name_sd[bucket]) { + DP(DEF_LEVEL, "dns_adb_createfind: returning " + "ISC_R_SHUTTINGDOWN"); + RUNTIME_CHECK(!free_adbfind(adb, &find)); + result = ISC_R_SHUTTINGDOWN; + goto out; + } + + /* + * Nothing found. Allocate a new adbname structure for this name. + */ + if (adbname == NULL) { + /* + * See if there is any stale name at the end of list, and purge + * it if so. + */ + check_stale_name(adb, bucket, now); + + adbname = new_adbname(adb, name); + link_name(adb, bucket, adbname); + if (FIND_HINTOK(find)) { + adbname->flags |= DNS_ADBFIND_HINTOK; + } + if (FIND_GLUEOK(find)) { + adbname->flags |= DNS_ADBFIND_GLUEOK; + } + if (FIND_STARTATZONE(find)) { + adbname->flags |= DNS_ADBFIND_STARTATZONE; + } + } else { + /* Move this name forward in the LRU list */ + ISC_LIST_UNLINK(adb->names[bucket], adbname, plink); + ISC_LIST_PREPEND(adb->names[bucket], adbname, plink); + } + adbname->last_used = now; + + /* + * Expire old entries, etc. + */ + RUNTIME_CHECK(!check_expire_namehooks(adbname, now)); + + /* + * Do we know that the name is an alias? + */ + if (!EXPIRE_OK(adbname->expire_target, now)) { + /* + * Yes, it is. + */ + DP(DEF_LEVEL, + "dns_adb_createfind: name %s (%p) is an alias (cached)", + namebuf, adbname); + alias = true; + goto post_copy; + } + + /* + * Try to populate the name from the database and/or + * start fetches. First try looking for an A record + * in the database. + */ + if (!NAME_HAS_V4(adbname) && EXPIRE_OK(adbname->expire_v4, now) && + WANT_INET(wanted_addresses)) + { + result = dbfind_name(adbname, now, dns_rdatatype_a); + if (result == ISC_R_SUCCESS) { + DP(DEF_LEVEL, + "dns_adb_createfind: found A for name %s (%p) in db", + namebuf, adbname); + goto v6; + } + + /* + * Did we get a CNAME or DNAME? + */ + if (result == DNS_R_ALIAS) { + DP(DEF_LEVEL, + "dns_adb_createfind: name %s (%p) is an alias", + namebuf, adbname); + alias = true; + goto post_copy; + } + + /* + * If the name doesn't exist at all, don't bother with + * v6 queries; they won't work. + * + * If the name does exist but we didn't get our data, go + * ahead and try AAAA. + * + * If the result is neither of these, try a fetch for A. + */ + if (NXDOMAIN_RESULT(result)) { + goto fetch; + } else if (NXRRSET_RESULT(result)) { + goto v6; + } + + if (!NAME_FETCH_A(adbname)) { + wanted_fetches |= DNS_ADBFIND_INET; + } + } + +v6: + if (!NAME_HAS_V6(adbname) && EXPIRE_OK(adbname->expire_v6, now) && + WANT_INET6(wanted_addresses)) + { + result = dbfind_name(adbname, now, dns_rdatatype_aaaa); + if (result == ISC_R_SUCCESS) { + DP(DEF_LEVEL, + "dns_adb_createfind: found AAAA for name %s (%p)", + namebuf, adbname); + goto fetch; + } + + /* + * Did we get a CNAME or DNAME? + */ + if (result == DNS_R_ALIAS) { + DP(DEF_LEVEL, + "dns_adb_createfind: name %s (%p) is an alias", + namebuf, adbname); + alias = true; + goto post_copy; + } + + /* + * Listen to negative cache hints, and don't start + * another query. + */ + if (NCACHE_RESULT(result) || AUTH_NX(result)) { + goto fetch; + } + + if (!NAME_FETCH_AAAA(adbname)) { + wanted_fetches |= DNS_ADBFIND_INET6; + } + } + +fetch: + if ((WANT_INET(wanted_addresses) && NAME_HAS_V4(adbname)) || + (WANT_INET6(wanted_addresses) && NAME_HAS_V6(adbname))) + { + have_address = true; + } else { + have_address = false; + } + if (wanted_fetches != 0 && !(FIND_AVOIDFETCHES(find) && have_address) && + !FIND_NOFETCH(find)) + { + /* + * We're missing at least one address family. Either the + * caller hasn't instructed us to avoid fetches, or we don't + * know anything about any of the address families that would + * be acceptable so we have to launch fetches. + */ + + if (FIND_STARTATZONE(find)) { + start_at_zone = true; + } + + /* + * Start V4. + */ + if (WANT_INET(wanted_fetches) && + fetch_name(adbname, start_at_zone, depth, qc, + dns_rdatatype_a) == ISC_R_SUCCESS) + { + DP(DEF_LEVEL, + "dns_adb_createfind: " + "started A fetch for name %s (%p)", + namebuf, adbname); + } + + /* + * Start V6. + */ + if (WANT_INET6(wanted_fetches) && + fetch_name(adbname, start_at_zone, depth, qc, + dns_rdatatype_aaaa) == ISC_R_SUCCESS) + { + DP(DEF_LEVEL, + "dns_adb_createfind: " + "started AAAA fetch for name %s (%p)", + namebuf, adbname); + } + } + + /* + * Run through the name and copy out the bits we are + * interested in. + */ + copy_namehook_lists(adb, find, qname, qtype, adbname, now); + +post_copy: + if (NAME_FETCH_A(adbname)) { + query_pending |= DNS_ADBFIND_INET; + } + if (NAME_FETCH_AAAA(adbname)) { + query_pending |= DNS_ADBFIND_INET6; + } + + /* + * Attach to the name's query list if there are queries + * already running, and we have been asked to. + */ + if (!FIND_WANTEVENT(find)) { + want_event = false; + } + if (FIND_WANTEMPTYEVENT(find) && FIND_HAS_ADDRS(find)) { + want_event = false; + } + if ((wanted_addresses & query_pending) == 0) { + want_event = false; + } + if (alias) { + want_event = false; + } + if (want_event) { + bool empty; + find->adbname = adbname; + find->name_bucket = bucket; + empty = ISC_LIST_EMPTY(adbname->finds); + ISC_LIST_APPEND(adbname->finds, find, plink); + find->query_pending = (query_pending & wanted_addresses); + find->flags &= ~DNS_ADBFIND_ADDRESSMASK; + find->flags |= (find->query_pending & DNS_ADBFIND_ADDRESSMASK); + DP(DEF_LEVEL, "createfind: attaching find %p to adbname %p %d", + find, adbname, empty); + } else { + /* + * Remove the flag so the caller knows there will never + * be an event, and set internal flags to fake that + * the event was sent and freed, so dns_adb_destroyfind() will + * do the right thing. + */ + find->query_pending = (query_pending & wanted_addresses); + find->options &= ~DNS_ADBFIND_WANTEVENT; + find->flags |= (FIND_EVENT_SENT | FIND_EVENT_FREED); + find->flags &= ~DNS_ADBFIND_ADDRESSMASK; + } + + find->partial_result |= (adbname->partial_result & wanted_addresses); + if (alias) { + if (target != NULL) { + dns_name_copy(&adbname->target, target); + } + result = DNS_R_ALIAS; + } else { + result = ISC_R_SUCCESS; + } + + /* + * Copy out error flags from the name structure into the find. + */ + find->result_v4 = find_err_map[adbname->fetch_err]; + find->result_v6 = find_err_map[adbname->fetch6_err]; + +out: + if (find != NULL) { + if (want_event) { + isc_task_t *taskp = NULL; + + INSIST((find->flags & DNS_ADBFIND_ADDRESSMASK) != 0); + isc_task_attach(task, &taskp); + find->event.ev_sender = taskp; + find->event.ev_action = action; + find->event.ev_arg = arg; + } + + *findp = find; + } + + UNLOCK(&adb->namelocks[bucket]); + return (result); +} + +void +dns_adb_destroyfind(dns_adbfind_t **findp) { + dns_adbfind_t *find; + dns_adbentry_t *entry; + dns_adbaddrinfo_t *ai; + int bucket; + dns_adb_t *adb; + bool overmem; + isc_stdtime_t now; + + REQUIRE(findp != NULL && DNS_ADBFIND_VALID(*findp)); + find = *findp; + *findp = NULL; + + LOCK(&find->lock); + + DP(DEF_LEVEL, "dns_adb_destroyfind on find %p", find); + + adb = find->adb; + REQUIRE(DNS_ADB_VALID(adb)); + + REQUIRE(FIND_EVENTFREED(find)); + + bucket = find->name_bucket; + INSIST(bucket == DNS_ADB_INVALIDBUCKET); + + UNLOCK(&find->lock); + + /* + * The find doesn't exist on any list, and nothing is locked. + * Return the find to the memory pool, and decrement the adb's + * reference count. + */ + isc_stdtime_get(&now); + overmem = isc_mem_isovermem(adb->mctx); + ai = ISC_LIST_HEAD(find->list); + while (ai != NULL) { + ISC_LIST_UNLINK(find->list, ai, publink); + entry = ai->entry; + ai->entry = NULL; + INSIST(DNS_ADBENTRY_VALID(entry)); + RUNTIME_CHECK( + !dec_entry_refcnt(adb, overmem, entry, true, now)); + free_adbaddrinfo(adb, &ai); + ai = ISC_LIST_HEAD(find->list); + } + + /* + * WARNING: The find is freed with the adb locked. This is done + * to avoid a race condition where we free the find, some other + * thread tests to see if it should be destroyed, detects it should + * be, destroys it, and then we try to lock it for our check, but the + * lock is destroyed. + */ + LOCK(&adb->lock); + if (free_adbfind(adb, &find)) { + check_exit(adb); + } + UNLOCK(&adb->lock); +} + +void +dns_adb_cancelfind(dns_adbfind_t *find) { + isc_event_t *ev; + isc_task_t *task; + dns_adb_t *adb; + int bucket; + int unlock_bucket; + + LOCK(&find->lock); + + DP(DEF_LEVEL, "dns_adb_cancelfind on find %p", find); + + adb = find->adb; + REQUIRE(DNS_ADB_VALID(adb)); + + REQUIRE(!FIND_EVENTFREED(find)); + REQUIRE(FIND_WANTEVENT(find)); + + bucket = find->name_bucket; + if (bucket == DNS_ADB_INVALIDBUCKET) { + goto cleanup; + } + + /* + * We need to get the adbname's lock to unlink the find. + */ + unlock_bucket = bucket; + violate_locking_hierarchy(&find->lock, &adb->namelocks[unlock_bucket]); + bucket = find->name_bucket; + if (bucket != DNS_ADB_INVALIDBUCKET) { + ISC_LIST_UNLINK(find->adbname->finds, find, plink); + find->adbname = NULL; + find->name_bucket = DNS_ADB_INVALIDBUCKET; + } + UNLOCK(&adb->namelocks[unlock_bucket]); + bucket = DNS_ADB_INVALIDBUCKET; + POST(bucket); + +cleanup: + + if (!FIND_EVENTSENT(find)) { + ev = &find->event; + task = ev->ev_sender; + ev->ev_sender = find; + ev->ev_type = DNS_EVENT_ADBCANCELED; + ev->ev_destroy = event_free; + ev->ev_destroy_arg = find; + find->result_v4 = ISC_R_CANCELED; + find->result_v6 = ISC_R_CANCELED; + + DP(DEF_LEVEL, "sending event %p to task %p for find %p", ev, + task, find); + + isc_task_sendanddetach(&task, (isc_event_t **)&ev); + } + + UNLOCK(&find->lock); +} + +void +dns_adb_dump(dns_adb_t *adb, FILE *f) { + unsigned int i; + isc_stdtime_t now; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(f != NULL); + + /* + * Lock the adb itself, lock all the name buckets, then lock all + * the entry buckets. This should put the adb into a state where + * nothing can change, so we can iterate through everything and + * print at our leisure. + */ + + LOCK(&adb->lock); + isc_stdtime_get(&now); + + for (i = 0; i < adb->nnames; i++) { + RUNTIME_CHECK(!cleanup_names(adb, i, now)); + } + for (i = 0; i < adb->nentries; i++) { + RUNTIME_CHECK(!cleanup_entries(adb, i, now)); + } + + dump_adb(adb, f, false, now); + UNLOCK(&adb->lock); +} + +static void +dump_ttl(FILE *f, const char *legend, isc_stdtime_t value, isc_stdtime_t now) { + if (value == INT_MAX) { + return; + } + fprintf(f, " [%s TTL %d]", legend, (int)(value - now)); +} + +static void +dump_adb(dns_adb_t *adb, FILE *f, bool debug, isc_stdtime_t now) { + dns_adbname_t *name; + dns_adbentry_t *entry; + + fprintf(f, ";\n; Address database dump\n;\n"); + fprintf(f, "; [edns success/timeout]\n"); + fprintf(f, "; [plain success/timeout]\n;\n"); + if (debug) { + LOCK(&adb->reflock); + fprintf(f, + "; addr %p, erefcnt %u, irefcnt %u, finds out " + "%" PRIuFAST32 "\n", + adb, adb->erefcnt, adb->irefcnt, + isc_refcount_current(&adb->nhrefcnt)); + UNLOCK(&adb->reflock); + } + +/* + * In TSAN mode we need to lock the locks individually, as TSAN + * can't handle more than 64 locks locked by one thread. + * In regular mode we want a consistent dump so we need to + * lock everything. + */ +#ifndef __SANITIZE_THREAD__ + for (size_t i = 0; i < adb->nnames; i++) { + LOCK(&adb->namelocks[i]); + } + for (size_t i = 0; i < adb->nentries; i++) { + LOCK(&adb->entrylocks[i]); + } +#endif /* ifndef __SANITIZE_THREAD__ */ + + /* + * Dump the names + */ + for (size_t i = 0; i < adb->nnames; i++) { +#ifdef __SANITIZE_THREAD__ + LOCK(&adb->namelocks[i]); +#endif /* ifdef __SANITIZE_THREAD__ */ + name = ISC_LIST_HEAD(adb->names[i]); + if (name == NULL) { +#ifdef __SANITIZE_THREAD__ + UNLOCK(&adb->namelocks[i]); +#endif /* ifdef __SANITIZE_THREAD__ */ + continue; + } + if (debug) { + fprintf(f, "; bucket %zu\n", i); + } + for (; name != NULL; name = ISC_LIST_NEXT(name, plink)) { + if (debug) { + fprintf(f, "; name %p (flags %08x)\n", name, + name->flags); + } + fprintf(f, "; "); + print_dns_name(f, &name->name); + if (dns_name_countlabels(&name->target) > 0) { + fprintf(f, " alias "); + print_dns_name(f, &name->target); + } + + dump_ttl(f, "v4", name->expire_v4, now); + dump_ttl(f, "v6", name->expire_v6, now); + dump_ttl(f, "target", name->expire_target, now); + + fprintf(f, " [v4 %s] [v6 %s]", + errnames[name->fetch_err], + errnames[name->fetch6_err]); + + fprintf(f, "\n"); + + print_namehook_list(f, "v4", adb, &name->v4, debug, + now); + print_namehook_list(f, "v6", adb, &name->v6, debug, + now); + + if (debug) { + print_fetch_list(f, name); + print_find_list(f, name); + } + } +#ifdef __SANITIZE_THREAD__ + UNLOCK(&adb->namelocks[i]); +#endif /* ifdef __SANITIZE_THREAD__ */ + } + + fprintf(f, ";\n; Unassociated entries\n;\n"); + + for (size_t i = 0; i < adb->nentries; i++) { +#ifdef __SANITIZE_THREAD__ + LOCK(&adb->entrylocks[i]); +#endif /* ifdef __SANITIZE_THREAD__ */ + entry = ISC_LIST_HEAD(adb->entries[i]); + while (entry != NULL) { + if (entry->nh == 0) { + dump_entry(f, adb, entry, debug, now); + } + entry = ISC_LIST_NEXT(entry, plink); + } +#ifdef __SANITIZE_THREAD__ + UNLOCK(&adb->entrylocks[i]); +#endif /* ifdef __SANITIZE_THREAD__ */ + } + +#ifndef __SANITIZE_THREAD__ + /* + * Unlock everything + */ + for (ssize_t i = adb->nentries - 1; i >= 0; i--) { + UNLOCK(&adb->entrylocks[i]); + } + for (ssize_t i = adb->nnames - 1; i >= 0; i--) { + UNLOCK(&adb->namelocks[i]); + } +#endif /* ifndef __SANITIZE_THREAD__ */ +} + +static void +dump_entry(FILE *f, dns_adb_t *adb, dns_adbentry_t *entry, bool debug, + isc_stdtime_t now) { + char addrbuf[ISC_NETADDR_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + isc_netaddr_t netaddr; + dns_adblameinfo_t *li; + + isc_netaddr_fromsockaddr(&netaddr, &entry->sockaddr); + isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); + + if (debug) { + fprintf(f, ";\t%p: refcnt %u\n", entry, entry->refcnt); + } + + fprintf(f, + ";\t%s [srtt %u] [flags %08x] [edns %u/%u] " + "[plain %u/%u]", + addrbuf, entry->srtt, entry->flags, entry->edns, entry->ednsto, + entry->plain, entry->plainto); + if (entry->udpsize != 0U) { + fprintf(f, " [udpsize %u]", entry->udpsize); + } + if (entry->cookie != NULL) { + unsigned int i; + fprintf(f, " [cookie="); + for (i = 0; i < entry->cookielen; i++) { + fprintf(f, "%02x", entry->cookie[i]); + } + fprintf(f, "]"); + } + if (entry->expires != 0) { + fprintf(f, " [ttl %d]", (int)(entry->expires - now)); + } + + if (adb != NULL && adb->quota != 0 && adb->atr_freq != 0) { + uint_fast32_t quota = atomic_load_relaxed(&entry->quota); + fprintf(f, " [atr %0.2f] [quota %" PRIuFAST32 "]", entry->atr, + quota); + } + + fprintf(f, "\n"); + for (li = ISC_LIST_HEAD(entry->lameinfo); li != NULL; + li = ISC_LIST_NEXT(li, plink)) + { + fprintf(f, ";\t\t"); + print_dns_name(f, &li->qname); + dns_rdatatype_format(li->qtype, typebuf, sizeof(typebuf)); + fprintf(f, " %s [lame TTL %d]\n", typebuf, + (int)(li->lame_timer - now)); + } +} + +void +dns_adb_dumpfind(dns_adbfind_t *find, FILE *f) { + char tmp[512]; + const char *tmpp; + dns_adbaddrinfo_t *ai; + isc_sockaddr_t *sa; + + /* + * Not used currently, in the API Just In Case we + * want to dump out the name and/or entries too. + */ + + LOCK(&find->lock); + + fprintf(f, ";Find %p\n", find); + fprintf(f, ";\tqpending %08x partial %08x options %08x flags %08x\n", + find->query_pending, find->partial_result, find->options, + find->flags); + fprintf(f, ";\tname_bucket %d, name %p, event sender %p\n", + find->name_bucket, find->adbname, find->event.ev_sender); + + ai = ISC_LIST_HEAD(find->list); + if (ai != NULL) { + fprintf(f, "\tAddresses:\n"); + } + while (ai != NULL) { + sa = &ai->sockaddr; + switch (sa->type.sa.sa_family) { + case AF_INET: + tmpp = inet_ntop(AF_INET, &sa->type.sin.sin_addr, tmp, + sizeof(tmp)); + break; + case AF_INET6: + tmpp = inet_ntop(AF_INET6, &sa->type.sin6.sin6_addr, + tmp, sizeof(tmp)); + break; + default: + tmpp = "UnkFamily"; + } + + if (tmpp == NULL) { + tmpp = "BadAddress"; + } + + fprintf(f, + "\t\tentry %p, flags %08x" + " srtt %u addr %s\n", + ai->entry, ai->flags, ai->srtt, tmpp); + + ai = ISC_LIST_NEXT(ai, publink); + } + + UNLOCK(&find->lock); +} + +static void +print_dns_name(FILE *f, const dns_name_t *name) { + char buf[DNS_NAME_FORMATSIZE]; + + INSIST(f != NULL); + + dns_name_format(name, buf, sizeof(buf)); + fprintf(f, "%s", buf); +} + +static void +print_namehook_list(FILE *f, const char *legend, dns_adb_t *adb, + dns_adbnamehooklist_t *list, bool debug, + isc_stdtime_t now) { + dns_adbnamehook_t *nh; + + for (nh = ISC_LIST_HEAD(*list); nh != NULL; + nh = ISC_LIST_NEXT(nh, plink)) + { + if (debug) { + fprintf(f, ";\tHook(%s) %p\n", legend, nh); + } +#ifdef __SANITIZE_THREAD__ + LOCK(&adb->entrylocks[nh->entry->lock_bucket]); +#endif + dump_entry(f, adb, nh->entry, debug, now); +#ifdef __SANITIZE_THREAD__ + UNLOCK(&adb->entrylocks[nh->entry->lock_bucket]); +#endif + } +} + +static void +print_fetch(FILE *f, dns_adbfetch_t *ft, const char *type) { + fprintf(f, "\t\tFetch(%s): %p -> { fetch %p }\n", type, ft, ft->fetch); +} + +static void +print_fetch_list(FILE *f, dns_adbname_t *n) { + if (NAME_FETCH_A(n)) { + print_fetch(f, n->fetch_a, "A"); + } + if (NAME_FETCH_AAAA(n)) { + print_fetch(f, n->fetch_aaaa, "AAAA"); + } +} + +static void +print_find_list(FILE *f, dns_adbname_t *name) { + dns_adbfind_t *find; + + find = ISC_LIST_HEAD(name->finds); + while (find != NULL) { + dns_adb_dumpfind(find, f); + find = ISC_LIST_NEXT(find, plink); + } +} + +static isc_result_t +dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_adb_t *adb; + dns_fixedname_t foundname; + dns_name_t *fname; + + INSIST(DNS_ADBNAME_VALID(adbname)); + adb = adbname->adb; + INSIST(DNS_ADB_VALID(adb)); + INSIST(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa); + + fname = dns_fixedname_initname(&foundname); + dns_rdataset_init(&rdataset); + + if (rdtype == dns_rdatatype_a) { + adbname->fetch_err = FIND_ERR_UNEXPECTED; + } else { + adbname->fetch6_err = FIND_ERR_UNEXPECTED; + } + + /* + * We need to specify whether to search static-stub zones (if + * configured) depending on whether this is a "start at zone" lookup, + * i.e., whether it's a "bailiwick" glue. If it's bailiwick (in which + * case DNS_ADBFIND_STARTATZONE is set) we need to stop the search at + * any matching static-stub zone without looking into the cache to honor + * the configuration on which server we should send queries to. + */ + result = + dns_view_find(adb->view, &adbname->name, rdtype, now, + NAME_GLUEOK(adbname) ? DNS_DBFIND_GLUEOK : 0, + NAME_HINTOK(adbname), + ((adbname->flags & DNS_ADBFIND_STARTATZONE) != 0), + NULL, NULL, fname, &rdataset, NULL); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (result) { + case DNS_R_GLUE: + case DNS_R_HINT: + case ISC_R_SUCCESS: + /* + * Found in the database. Even if we can't copy out + * any information, return success, or else a fetch + * will be made, which will only make things worse. + */ + if (rdtype == dns_rdatatype_a) { + adbname->fetch_err = FIND_ERR_SUCCESS; + } else { + adbname->fetch6_err = FIND_ERR_SUCCESS; + } + result = import_rdataset(adbname, &rdataset, now); + break; + case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + /* + * We're authoritative and the data doesn't exist. + * Make up a negative cache entry so we don't ask again + * for a while. + * + * XXXRTH What time should we use? I'm putting in 30 seconds + * for now. + */ + if (rdtype == dns_rdatatype_a) { + adbname->expire_v4 = now + 30; + DP(NCACHE_LEVEL, + "adb name %p: Caching auth negative entry for A", + adbname); + if (result == DNS_R_NXDOMAIN) { + adbname->fetch_err = FIND_ERR_NXDOMAIN; + } else { + adbname->fetch_err = FIND_ERR_NXRRSET; + } + } else { + DP(NCACHE_LEVEL, + "adb name %p: Caching auth negative entry for AAAA", + adbname); + adbname->expire_v6 = now + 30; + if (result == DNS_R_NXDOMAIN) { + adbname->fetch6_err = FIND_ERR_NXDOMAIN; + } else { + adbname->fetch6_err = FIND_ERR_NXRRSET; + } + } + break; + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + /* + * We found a negative cache entry. Pull the TTL from it + * so we won't ask again for a while. + */ + rdataset.ttl = ttlclamp(rdataset.ttl); + if (rdtype == dns_rdatatype_a) { + adbname->expire_v4 = rdataset.ttl + now; + if (result == DNS_R_NCACHENXDOMAIN) { + adbname->fetch_err = FIND_ERR_NXDOMAIN; + } else { + adbname->fetch_err = FIND_ERR_NXRRSET; + } + DP(NCACHE_LEVEL, + "adb name %p: Caching negative entry for A (ttl %u)", + adbname, rdataset.ttl); + } else { + DP(NCACHE_LEVEL, + "adb name %p: Caching negative entry for AAAA (ttl " + "%u)", + adbname, rdataset.ttl); + adbname->expire_v6 = rdataset.ttl + now; + if (result == DNS_R_NCACHENXDOMAIN) { + adbname->fetch6_err = FIND_ERR_NXDOMAIN; + } else { + adbname->fetch6_err = FIND_ERR_NXRRSET; + } + } + break; + case DNS_R_CNAME: + case DNS_R_DNAME: + /* + * Clear the hint and glue flags, so this will match + * more often. + */ + adbname->flags &= ~(DNS_ADBFIND_GLUEOK | DNS_ADBFIND_HINTOK); + + rdataset.ttl = ttlclamp(rdataset.ttl); + clean_target(adb, &adbname->target); + adbname->expire_target = INT_MAX; + result = set_target(adb, &adbname->name, fname, &rdataset, + &adbname->target); + if (result == ISC_R_SUCCESS) { + result = DNS_R_ALIAS; + DP(NCACHE_LEVEL, "adb name %p: caching alias target", + adbname); + adbname->expire_target = rdataset.ttl + now; + } + if (rdtype == dns_rdatatype_a) { + adbname->fetch_err = FIND_ERR_SUCCESS; + } else { + adbname->fetch6_err = FIND_ERR_SUCCESS; + } + break; + default: + break; + } + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + return (result); +} + +static void +fetch_callback(isc_task_t *task, isc_event_t *ev) { + dns_fetchevent_t *dev; + dns_adbname_t *name; + dns_adb_t *adb; + dns_adbfetch_t *fetch; + int bucket; + isc_eventtype_t ev_status; + isc_stdtime_t now; + isc_result_t result; + unsigned int address_type; + bool want_check_exit = false; + + UNUSED(task); + + INSIST(ev->ev_type == DNS_EVENT_FETCHDONE); + dev = (dns_fetchevent_t *)ev; + name = ev->ev_arg; + INSIST(DNS_ADBNAME_VALID(name)); + adb = name->adb; + INSIST(DNS_ADB_VALID(adb)); + + bucket = name->lock_bucket; + LOCK(&adb->namelocks[bucket]); + + INSIST(NAME_FETCH_A(name) || NAME_FETCH_AAAA(name)); + address_type = 0; + if (NAME_FETCH_A(name) && (name->fetch_a->fetch == dev->fetch)) { + address_type = DNS_ADBFIND_INET; + fetch = name->fetch_a; + name->fetch_a = NULL; + } else if (NAME_FETCH_AAAA(name) && + (name->fetch_aaaa->fetch == dev->fetch)) + { + address_type = DNS_ADBFIND_INET6; + fetch = name->fetch_aaaa; + name->fetch_aaaa = NULL; + } else { + fetch = NULL; + } + + INSIST(address_type != 0 && fetch != NULL); + + dns_resolver_destroyfetch(&fetch->fetch); + dev->fetch = NULL; + + ev_status = DNS_EVENT_ADBNOMOREADDRESSES; + + /* + * Cleanup things we don't care about. + */ + if (dev->node != NULL) { + dns_db_detachnode(dev->db, &dev->node); + } + if (dev->db != NULL) { + dns_db_detach(&dev->db); + } + + /* + * If this name is marked as dead, clean up, throwing away + * potentially good data. + */ + if (NAME_DEAD(name)) { + free_adbfetch(adb, &fetch); + isc_event_free(&ev); + + want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED); + + UNLOCK(&adb->namelocks[bucket]); + + if (want_check_exit) { + LOCK(&adb->lock); + check_exit(adb); + UNLOCK(&adb->lock); + } + + return; + } + + isc_stdtime_get(&now); + + /* + * If we got a negative cache response, remember it. + */ + if (NCACHE_RESULT(dev->result)) { + dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl); + if (address_type == DNS_ADBFIND_INET) { + DP(NCACHE_LEVEL, + "adb fetch name %p: " + "caching negative entry for A (ttl %u)", + name, dev->rdataset->ttl); + name->expire_v4 = ISC_MIN(name->expire_v4, + dev->rdataset->ttl + now); + if (dev->result == DNS_R_NCACHENXDOMAIN) { + name->fetch_err = FIND_ERR_NXDOMAIN; + } else { + name->fetch_err = FIND_ERR_NXRRSET; + } + inc_stats(adb, dns_resstatscounter_gluefetchv4fail); + } else { + DP(NCACHE_LEVEL, + "adb fetch name %p: " + "caching negative entry for AAAA (ttl %u)", + name, dev->rdataset->ttl); + name->expire_v6 = ISC_MIN(name->expire_v6, + dev->rdataset->ttl + now); + if (dev->result == DNS_R_NCACHENXDOMAIN) { + name->fetch6_err = FIND_ERR_NXDOMAIN; + } else { + name->fetch6_err = FIND_ERR_NXRRSET; + } + inc_stats(adb, dns_resstatscounter_gluefetchv6fail); + } + goto out; + } + + /* + * Handle CNAME/DNAME. + */ + if (dev->result == DNS_R_CNAME || dev->result == DNS_R_DNAME) { + dev->rdataset->ttl = ttlclamp(dev->rdataset->ttl); + clean_target(adb, &name->target); + name->expire_target = INT_MAX; + result = set_target(adb, &name->name, dev->foundname, + dev->rdataset, &name->target); + if (result == ISC_R_SUCCESS) { + DP(NCACHE_LEVEL, + "adb fetch name %p: caching alias target", name); + name->expire_target = dev->rdataset->ttl + now; + } + goto check_result; + } + + /* + * Did we get back junk? If so, and there are no more fetches + * sitting out there, tell all the finds about it. + */ + if (dev->result != ISC_R_SUCCESS) { + char buf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&name->name, buf, sizeof(buf)); + DP(DEF_LEVEL, "adb: fetch of '%s' %s failed: %s", buf, + address_type == DNS_ADBFIND_INET ? "A" : "AAAA", + isc_result_totext(dev->result)); + /* + * Don't record a failure unless this is the initial + * fetch of a chain. + */ + if (fetch->depth > 1) { + goto out; + } + /* XXXMLG Don't pound on bad servers. */ + if (address_type == DNS_ADBFIND_INET) { + name->expire_v4 = ISC_MIN(name->expire_v4, now + 10); + name->fetch_err = FIND_ERR_FAILURE; + inc_stats(adb, dns_resstatscounter_gluefetchv4fail); + } else { + name->expire_v6 = ISC_MIN(name->expire_v6, now + 10); + name->fetch6_err = FIND_ERR_FAILURE; + inc_stats(adb, dns_resstatscounter_gluefetchv6fail); + } + goto out; + } + + /* + * We got something potentially useful. + */ + result = import_rdataset(name, &fetch->rdataset, now); + +check_result: + if (result == ISC_R_SUCCESS) { + ev_status = DNS_EVENT_ADBMOREADDRESSES; + if (address_type == DNS_ADBFIND_INET) { + name->fetch_err = FIND_ERR_SUCCESS; + } else { + name->fetch6_err = FIND_ERR_SUCCESS; + } + } + +out: + free_adbfetch(adb, &fetch); + isc_event_free(&ev); + + clean_finds_at_name(name, ev_status, address_type); + + UNLOCK(&adb->namelocks[bucket]); +} + +static isc_result_t +fetch_name(dns_adbname_t *adbname, bool start_at_zone, unsigned int depth, + isc_counter_t *qc, dns_rdatatype_t type) { + isc_result_t result; + dns_adbfetch_t *fetch = NULL; + dns_adb_t *adb; + dns_fixedname_t fixed; + dns_name_t *name; + dns_rdataset_t rdataset; + dns_rdataset_t *nameservers; + unsigned int options; + + INSIST(DNS_ADBNAME_VALID(adbname)); + adb = adbname->adb; + INSIST(DNS_ADB_VALID(adb)); + + INSIST((type == dns_rdatatype_a && !NAME_FETCH_A(adbname)) || + (type == dns_rdatatype_aaaa && !NAME_FETCH_AAAA(adbname))); + + adbname->fetch_err = FIND_ERR_NOTFOUND; + + name = NULL; + nameservers = NULL; + dns_rdataset_init(&rdataset); + + options = DNS_FETCHOPT_NOVALIDATE; + if (start_at_zone) { + DP(ENTER_LEVEL, "fetch_name: starting at zone for name %p", + adbname); + name = dns_fixedname_initname(&fixed); + result = dns_view_findzonecut(adb->view, &adbname->name, name, + NULL, 0, 0, true, false, + &rdataset, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_HINT) { + goto cleanup; + } + nameservers = &rdataset; + options |= DNS_FETCHOPT_UNSHARED; + } + + fetch = new_adbfetch(adb); + fetch->depth = depth; + + /* + * We're not minimizing this query, as nothing user-related should + * be leaked here. + * However, if we'd ever want to change it we'd have to modify + * createfetch to find deepest cached name when we're providing + * domain and nameservers. + */ + result = dns_resolver_createfetch( + adb->view->resolver, &adbname->name, type, name, nameservers, + NULL, NULL, 0, options, depth, qc, adb->task, fetch_callback, + adbname, &fetch->rdataset, NULL, &fetch->fetch); + if (result != ISC_R_SUCCESS) { + DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s", + isc_result_totext(result)); + goto cleanup; + } + + if (type == dns_rdatatype_a) { + adbname->fetch_a = fetch; + inc_stats(adb, dns_resstatscounter_gluefetchv4); + } else { + adbname->fetch_aaaa = fetch; + inc_stats(adb, dns_resstatscounter_gluefetchv6); + } + fetch = NULL; /* Keep us from cleaning this up below. */ + +cleanup: + if (fetch != NULL) { + free_adbfetch(adb, &fetch); + } + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + return (result); +} + +/* + * XXXMLG Needs to take a find argument and an address info, no zone or adb, + * since these can be extracted from the find itself. + */ +isc_result_t +dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + const dns_name_t *qname, dns_rdatatype_t qtype, + isc_stdtime_t expire_time) { + dns_adblameinfo_t *li; + int bucket; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + REQUIRE(qname != NULL); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + li = ISC_LIST_HEAD(addr->entry->lameinfo); + while (li != NULL && + (li->qtype != qtype || !dns_name_equal(qname, &li->qname))) + { + li = ISC_LIST_NEXT(li, plink); + } + if (li != NULL) { + if (expire_time > li->lame_timer) { + li->lame_timer = expire_time; + } + goto unlock; + } + li = new_adblameinfo(adb, qname, qtype); + li->lame_timer = expire_time; + + ISC_LIST_PREPEND(addr->entry->lameinfo, li, plink); +unlock: + UNLOCK(&adb->entrylocks[bucket]); + + return (result); +} + +void +dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt, + unsigned int factor) { + int bucket; + isc_stdtime_t now = 0; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + REQUIRE(factor <= 10); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + if (addr->entry->expires == 0 || factor == DNS_ADB_RTTADJAGE) { + isc_stdtime_get(&now); + } + adjustsrtt(addr, rtt, factor, now); + + UNLOCK(&adb->entrylocks[bucket]); +} + +void +dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + adjustsrtt(addr, 0, DNS_ADB_RTTADJAGE, now); + + UNLOCK(&adb->entrylocks[bucket]); +} + +static void +adjustsrtt(dns_adbaddrinfo_t *addr, unsigned int rtt, unsigned int factor, + isc_stdtime_t now) { + uint64_t new_srtt; + + if (factor == DNS_ADB_RTTADJAGE) { + if (addr->entry->lastage != now) { + new_srtt = addr->entry->srtt; + new_srtt <<= 9; + new_srtt -= addr->entry->srtt; + new_srtt >>= 9; + addr->entry->lastage = now; + } else { + new_srtt = addr->entry->srtt; + } + } else { + new_srtt = ((uint64_t)addr->entry->srtt / 10 * factor) + + ((uint64_t)rtt / 10 * (10 - factor)); + } + + addr->entry->srtt = (unsigned int)new_srtt; + addr->srtt = (unsigned int)new_srtt; + + if (addr->entry->expires == 0) { + addr->entry->expires = now + ADB_ENTRY_WINDOW; + } +} + +void +dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits, + unsigned int mask) { + int bucket; + isc_stdtime_t now; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + REQUIRE((bits & ENTRY_IS_DEAD) == 0); + REQUIRE((mask & ENTRY_IS_DEAD) == 0); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + addr->entry->flags = (addr->entry->flags & ~mask) | (bits & mask); + if (addr->entry->expires == 0) { + isc_stdtime_get(&now); + addr->entry->expires = now + ADB_ENTRY_WINDOW; + } + + /* + * Note that we do not update the other bits in addr->flags with + * the most recent values from addr->entry->flags. + */ + addr->flags = (addr->flags & ~mask) | (bits & mask); + + UNLOCK(&adb->entrylocks[bucket]); +} + +/* + * The polynomial backoff curve (10000 / ((10 + n) / 10)^(3/2)) <0..99> drops + * fairly aggressively at first, then slows down and tails off at around 2-3%. + * + * These will be used to make quota adjustments. + */ +static int quota_adj[] = { + 10000, 8668, 7607, 6747, 6037, 5443, 4941, 4512, 4141, 3818, 3536, + 3286, 3065, 2867, 2690, 2530, 2385, 2254, 2134, 2025, 1925, 1832, + 1747, 1668, 1595, 1527, 1464, 1405, 1350, 1298, 1250, 1205, 1162, + 1121, 1083, 1048, 1014, 981, 922, 894, 868, 843, 820, 797, + 775, 755, 735, 716, 698, 680, 664, 648, 632, 618, 603, + 590, 577, 564, 552, 540, 529, 518, 507, 497, 487, 477, + 468, 459, 450, 442, 434, 426, 418, 411, 404, 397, 390, + 383, 377, 370, 364, 358, 353, 347, 342, 336, 331, 326, + 321, 316, 312, 307, 303, 298, 294, 290, 286, 282, 278 +}; + +#define QUOTA_ADJ_SIZE (sizeof(quota_adj) / sizeof(quota_adj[0])) + +/* + * Caller must hold adbentry lock + */ +static void +maybe_adjust_quota(dns_adb_t *adb, dns_adbaddrinfo_t *addr, bool timeout) { + double tr; + + UNUSED(adb); + + if (adb->quota == 0 || adb->atr_freq == 0) { + return; + } + + if (timeout) { + addr->entry->timeouts++; + } + + if (addr->entry->completed++ <= adb->atr_freq) { + return; + } + + /* + * Calculate an exponential rolling average of the timeout ratio + * + * XXX: Integer arithmetic might be better than floating point + */ + tr = (double)addr->entry->timeouts / addr->entry->completed; + addr->entry->timeouts = addr->entry->completed = 0; + INSIST(addr->entry->atr >= 0.0); + INSIST(addr->entry->atr <= 1.0); + INSIST(adb->atr_discount >= 0.0); + INSIST(adb->atr_discount <= 1.0); + addr->entry->atr *= 1.0 - adb->atr_discount; + addr->entry->atr += tr * adb->atr_discount; + addr->entry->atr = ISC_CLAMP(addr->entry->atr, 0.0, 1.0); + + if (addr->entry->atr < adb->atr_low && addr->entry->mode > 0) { + uint_fast32_t new_quota = + adb->quota * quota_adj[--addr->entry->mode] / 10000; + atomic_store_release(&addr->entry->quota, + ISC_MAX(1, new_quota)); + log_quota(addr->entry, + "atr %0.2f, quota increased to %" PRIuFAST32, + addr->entry->atr, new_quota); + } else if (addr->entry->atr > adb->atr_high && + addr->entry->mode < (QUOTA_ADJ_SIZE - 1)) + { + uint_fast32_t new_quota = + adb->quota * quota_adj[++addr->entry->mode] / 10000; + atomic_store_release(&addr->entry->quota, + ISC_MAX(1, new_quota)); + log_quota(addr->entry, + "atr %0.2f, quota decreased to %" PRIuFAST32, + addr->entry->atr, new_quota); + } +} + +#define EDNSTOS 3U + +void +dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + maybe_adjust_quota(adb, addr, false); + + addr->entry->plain++; + if (addr->entry->plain == 0xff) { + addr->entry->edns >>= 1; + addr->entry->ednsto >>= 1; + addr->entry->plain >>= 1; + addr->entry->plainto >>= 1; + } + UNLOCK(&adb->entrylocks[bucket]); +} + +void +dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + maybe_adjust_quota(adb, addr, true); + + addr->entry->plainto++; + if (addr->entry->plainto == 0xff) { + addr->entry->edns >>= 1; + addr->entry->ednsto >>= 1; + addr->entry->plain >>= 1; + addr->entry->plainto >>= 1; + } + UNLOCK(&adb->entrylocks[bucket]); +} + +void +dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + maybe_adjust_quota(adb, addr, true); + + addr->entry->ednsto++; + if (addr->entry->ednsto == 0xff) { + addr->entry->edns >>= 1; + addr->entry->ednsto >>= 1; + addr->entry->plain >>= 1; + addr->entry->plainto >>= 1; + } + UNLOCK(&adb->entrylocks[bucket]); +} + +void +dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + if (size < 512U) { + size = 512U; + } + if (size > addr->entry->udpsize) { + addr->entry->udpsize = size; + } + + maybe_adjust_quota(adb, addr, false); + + addr->entry->edns++; + if (addr->entry->edns == 0xff) { + addr->entry->edns >>= 1; + addr->entry->ednsto >>= 1; + addr->entry->plain >>= 1; + addr->entry->plainto >>= 1; + } + UNLOCK(&adb->entrylocks[bucket]); +} + +unsigned int +dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + int bucket; + unsigned int size; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + size = addr->entry->udpsize; + UNLOCK(&adb->entrylocks[bucket]); + + return (size); +} + +void +dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + const unsigned char *cookie, size_t len) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + if (addr->entry->cookie != NULL && + (cookie == NULL || len != addr->entry->cookielen)) + { + isc_mem_put(adb->mctx, addr->entry->cookie, + addr->entry->cookielen); + addr->entry->cookie = NULL; + addr->entry->cookielen = 0; + } + + if (addr->entry->cookie == NULL && cookie != NULL && len != 0U) { + addr->entry->cookie = isc_mem_get(adb->mctx, len); + addr->entry->cookielen = (uint16_t)len; + } + + if (addr->entry->cookie != NULL) { + memmove(addr->entry->cookie, cookie, len); + } + UNLOCK(&adb->entrylocks[bucket]); +} + +size_t +dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + unsigned char *cookie, size_t len) { + int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + if (cookie != NULL && addr->entry->cookie != NULL && + len >= addr->entry->cookielen) + { + memmove(cookie, addr->entry->cookie, addr->entry->cookielen); + len = addr->entry->cookielen; + } else { + len = 0; + } + UNLOCK(&adb->entrylocks[bucket]); + + return (len); +} + +isc_result_t +dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa, + dns_adbaddrinfo_t **addrp, isc_stdtime_t now) { + int bucket; + dns_adbentry_t *entry; + dns_adbaddrinfo_t *addr; + isc_result_t result; + in_port_t port; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(addrp != NULL && *addrp == NULL); + + UNUSED(now); + + result = ISC_R_SUCCESS; + bucket = DNS_ADB_INVALIDBUCKET; + entry = find_entry_and_lock(adb, sa, &bucket, now); + INSIST(bucket != DNS_ADB_INVALIDBUCKET); + if (adb->entry_sd[bucket]) { + result = ISC_R_SHUTTINGDOWN; + goto unlock; + } + if (entry == NULL) { + /* + * We don't know anything about this address. + */ + entry = new_adbentry(adb); + entry->sockaddr = *sa; + link_entry(adb, bucket, entry); + DP(ENTER_LEVEL, "findaddrinfo: new entry %p", entry); + } else { + DP(ENTER_LEVEL, "findaddrinfo: found entry %p", entry); + } + + port = isc_sockaddr_getport(sa); + addr = new_adbaddrinfo(adb, entry, port); + inc_entry_refcnt(adb, entry, false); + *addrp = addr; + +unlock: + UNLOCK(&adb->entrylocks[bucket]); + + return (result); +} + +void +dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp) { + dns_adbaddrinfo_t *addr; + dns_adbentry_t *entry; + int bucket; + isc_stdtime_t now; + bool want_check_exit = false; + bool overmem; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(addrp != NULL); + addr = *addrp; + *addrp = NULL; + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + entry = addr->entry; + REQUIRE(DNS_ADBENTRY_VALID(entry)); + + overmem = isc_mem_isovermem(adb->mctx); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + isc_stdtime_get(&now); + if (entry->expires == 0) { + entry->expires = now + ADB_ENTRY_WINDOW; + } + + want_check_exit = dec_entry_refcnt(adb, overmem, entry, false, now); + + UNLOCK(&adb->entrylocks[bucket]); + + addr->entry = NULL; + free_adbaddrinfo(adb, &addr); + + if (want_check_exit) { + LOCK(&adb->lock); + check_exit(adb); + UNLOCK(&adb->lock); + } +} + +void +dns_adb_flush(dns_adb_t *adb) { + unsigned int i; + + INSIST(DNS_ADB_VALID(adb)); + + LOCK(&adb->lock); + + /* + * Call our cleanup routines. + */ + for (i = 0; i < adb->nnames; i++) { + RUNTIME_CHECK(!cleanup_names(adb, i, INT_MAX)); + } + for (i = 0; i < adb->nentries; i++) { + RUNTIME_CHECK(!cleanup_entries(adb, i, INT_MAX)); + } + +#ifdef DUMP_ADB_AFTER_CLEANING + dump_adb(adb, stdout, true, INT_MAX); +#endif /* ifdef DUMP_ADB_AFTER_CLEANING */ + + UNLOCK(&adb->lock); +} + +void +dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name) { + dns_adbname_t *adbname; + dns_adbname_t *nextname; + unsigned int bucket; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(name != NULL); + + LOCK(&adb->lock); + bucket = dns_name_hash(name, false) % adb->nnames; + LOCK(&adb->namelocks[bucket]); + adbname = ISC_LIST_HEAD(adb->names[bucket]); + while (adbname != NULL) { + nextname = ISC_LIST_NEXT(adbname, plink); + if (!NAME_DEAD(adbname) && dns_name_equal(name, &adbname->name)) + { + RUNTIME_CHECK( + !kill_name(&adbname, DNS_EVENT_ADBCANCELED)); + } + adbname = nextname; + } + UNLOCK(&adb->namelocks[bucket]); + UNLOCK(&adb->lock); +} + +void +dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name) { + dns_adbname_t *adbname, *nextname; + unsigned int i; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(name != NULL); + + LOCK(&adb->lock); + for (i = 0; i < adb->nnames; i++) { + LOCK(&adb->namelocks[i]); + adbname = ISC_LIST_HEAD(adb->names[i]); + while (adbname != NULL) { + bool ret; + nextname = ISC_LIST_NEXT(adbname, plink); + if (!NAME_DEAD(adbname) && + dns_name_issubdomain(&adbname->name, name)) + { + ret = kill_name(&adbname, + DNS_EVENT_ADBCANCELED); + RUNTIME_CHECK(!ret); + } + adbname = nextname; + } + UNLOCK(&adb->namelocks[i]); + } + UNLOCK(&adb->lock); +} + +static void +water(void *arg, int mark) { + /* + * We're going to change the way to handle overmem condition: use + * isc_mem_isovermem() instead of storing the state via this callback, + * since the latter way tends to cause race conditions. + * To minimize the change, and in case we re-enable the callback + * approach, however, keep this function at the moment. + */ + + dns_adb_t *adb = arg; + bool overmem = (mark == ISC_MEM_HIWATER); + + REQUIRE(DNS_ADB_VALID(adb)); + + DP(ISC_LOG_DEBUG(1), "adb reached %s water mark", + overmem ? "high" : "low"); +} + +void +dns_adb_setadbsize(dns_adb_t *adb, size_t size) { + size_t hiwater, lowater; + + INSIST(DNS_ADB_VALID(adb)); + + if (size != 0U && size < DNS_ADB_MINADBSIZE) { + size = DNS_ADB_MINADBSIZE; + } + + hiwater = size - (size >> 3); /* Approximately 7/8ths. */ + lowater = size - (size >> 2); /* Approximately 3/4ths. */ + + if (size == 0U || hiwater == 0U || lowater == 0U) { + isc_mem_clearwater(adb->mctx); + } else { + isc_mem_setwater(adb->mctx, water, adb, hiwater, lowater); + } +} + +void +dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low, + double high, double discount) { + REQUIRE(DNS_ADB_VALID(adb)); + + adb->quota = quota; + adb->atr_freq = freq; + adb->atr_low = low; + adb->atr_high = high; + adb->atr_discount = discount; +} + +bool +dns_adbentry_overquota(dns_adbentry_t *entry) { + REQUIRE(DNS_ADBENTRY_VALID(entry)); + + uint_fast32_t quota = atomic_load_relaxed(&entry->quota); + uint_fast32_t active = atomic_load_acquire(&entry->active); + + return (quota != 0 && active >= quota); +} + +void +dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + uint_fast32_t active; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + active = atomic_fetch_add_relaxed(&addr->entry->active, 1); + INSIST(active != UINT32_MAX); +} + +void +dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + uint_fast32_t active; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + active = atomic_fetch_sub_release(&addr->entry->active, 1); + INSIST(active != 0); +} diff --git a/lib/dns/badcache.c b/lib/dns/badcache.c new file mode 100644 index 0000000..dcad721 --- /dev/null +++ b/lib/dns/badcache.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct dns_bcentry dns_bcentry_t; + +struct dns_badcache { + unsigned int magic; + isc_rwlock_t lock; + isc_mem_t *mctx; + + isc_mutex_t *tlocks; + dns_bcentry_t **table; + + atomic_uint_fast32_t count; + atomic_uint_fast32_t sweep; + + unsigned int minsize; + unsigned int size; +}; + +#define BADCACHE_MAGIC ISC_MAGIC('B', 'd', 'C', 'a') +#define VALID_BADCACHE(m) ISC_MAGIC_VALID(m, BADCACHE_MAGIC) + +struct dns_bcentry { + dns_bcentry_t *next; + dns_rdatatype_t type; + isc_time_t expire; + uint32_t flags; + unsigned int hashval; + dns_fixedname_t fname; + dns_name_t *name; +}; + +static void +badcache_resize(dns_badcache_t *bc, isc_time_t *now); + +isc_result_t +dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) { + dns_badcache_t *bc = NULL; + unsigned int i; + + REQUIRE(bcp != NULL && *bcp == NULL); + REQUIRE(mctx != NULL); + + bc = isc_mem_get(mctx, sizeof(dns_badcache_t)); + memset(bc, 0, sizeof(dns_badcache_t)); + + isc_mem_attach(mctx, &bc->mctx); + isc_rwlock_init(&bc->lock, 0, 0); + + bc->table = isc_mem_get(bc->mctx, sizeof(*bc->table) * size); + bc->tlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * size); + for (i = 0; i < size; i++) { + isc_mutex_init(&bc->tlocks[i]); + } + bc->size = bc->minsize = size; + memset(bc->table, 0, bc->size * sizeof(dns_bcentry_t *)); + + atomic_init(&bc->count, 0); + atomic_init(&bc->sweep, 0); + bc->magic = BADCACHE_MAGIC; + + *bcp = bc; + return (ISC_R_SUCCESS); +} + +void +dns_badcache_destroy(dns_badcache_t **bcp) { + dns_badcache_t *bc; + unsigned int i; + + REQUIRE(bcp != NULL && *bcp != NULL); + bc = *bcp; + *bcp = NULL; + + dns_badcache_flush(bc); + + bc->magic = 0; + isc_rwlock_destroy(&bc->lock); + for (i = 0; i < bc->size; i++) { + isc_mutex_destroy(&bc->tlocks[i]); + } + isc_mem_put(bc->mctx, bc->table, sizeof(dns_bcentry_t *) * bc->size); + isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size); + isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t)); +} + +static void +badcache_resize(dns_badcache_t *bc, isc_time_t *now) { + dns_bcentry_t **newtable, *bad, *next; + isc_mutex_t *newlocks; + unsigned int newsize, i; + bool grow; + + RWLOCK(&bc->lock, isc_rwlocktype_write); + + /* + * XXXWPK we will have a thundering herd problem here, + * as all threads will wait on the RWLOCK when there's + * a need to resize badcache. + * However, it happens so rarely it should not be a + * performance issue. This is because we double the + * size every time we grow it, and we don't shrink + * unless the number of entries really shrunk. In a + * high load situation, the number of badcache entries + * will eventually stabilize. + */ + if (atomic_load_relaxed(&bc->count) > bc->size * 8) { + grow = true; + } else if (atomic_load_relaxed(&bc->count) < bc->size * 2 && + bc->size > bc->minsize) + { + grow = false; + } else { + /* Someone resized it already, bail. */ + RWUNLOCK(&bc->lock, isc_rwlocktype_write); + return; + } + + if (grow) { + newsize = bc->size * 2 + 1; + } else { + newsize = (bc->size - 1) / 2; +#ifdef __clang_analyzer__ + /* + * XXXWPK there's a bug in clang static analyzer - + * `value % newsize` is considered undefined even though + * we check if newsize is larger than 0. This helps. + */ + newsize += 1; +#endif + } + RUNTIME_CHECK(newsize > 0); + + newtable = isc_mem_get(bc->mctx, sizeof(dns_bcentry_t *) * newsize); + memset(newtable, 0, sizeof(dns_bcentry_t *) * newsize); + + newlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * newsize); + + /* Copy existing mutexes */ + for (i = 0; i < newsize && i < bc->size; i++) { + newlocks[i] = bc->tlocks[i]; + } + /* Initialize additional mutexes if we're growing */ + for (i = bc->size; i < newsize; i++) { + isc_mutex_init(&newlocks[i]); + } + /* Destroy extra mutexes if we're shrinking */ + for (i = newsize; i < bc->size; i++) { + isc_mutex_destroy(&bc->tlocks[i]); + } + + for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) { + for (bad = bc->table[i]; bad != NULL; bad = next) { + next = bad->next; + if (isc_time_compare(&bad->expire, now) < 0) { + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } else { + bad->next = newtable[bad->hashval % newsize]; + newtable[bad->hashval % newsize] = bad; + } + } + bc->table[i] = NULL; + } + + isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size); + bc->tlocks = newlocks; + + isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size); + bc->size = newsize; + bc->table = newtable; + + RWUNLOCK(&bc->lock, isc_rwlocktype_write); +} + +void +dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name, + dns_rdatatype_t type, bool update, uint32_t flags, + isc_time_t *expire) { + isc_result_t result; + unsigned int hashval, hash; + dns_bcentry_t *bad, *prev, *next; + isc_time_t now; + bool resize = false; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + REQUIRE(expire != NULL); + + RWLOCK(&bc->lock, isc_rwlocktype_read); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) { + isc_time_settoepoch(&now); + } + + hashval = dns_name_hash(name, false); + hash = hashval % bc->size; + LOCK(&bc->tlocks[hash]); + prev = NULL; + for (bad = bc->table[hash]; bad != NULL; bad = next) { + next = bad->next; + if (bad->type == type && dns_name_equal(name, bad->name)) { + if (update) { + bad->expire = *expire; + bad->flags = flags; + } + break; + } + if (isc_time_compare(&bad->expire, &now) < 0) { + if (prev == NULL) { + bc->table[hash] = bad->next; + } else { + prev->next = bad->next; + } + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } else { + prev = bad; + } + } + + if (bad == NULL) { + unsigned count; + isc_buffer_t buffer; + + bad = isc_mem_get(bc->mctx, sizeof(*bad)); + *bad = (dns_bcentry_t){ .type = type, + .hashval = hashval, + .expire = *expire, + .flags = flags, + .next = bc->table[hash] }; + + isc_buffer_init(&buffer, bad + 1, name->length); + bad->name = dns_fixedname_initname(&bad->fname); + dns_name_copy(name, bad->name); + bc->table[hash] = bad; + + count = atomic_fetch_add_relaxed(&bc->count, 1); + if ((count > bc->size * 8) || + (count < bc->size * 2 && bc->size > bc->minsize)) + { + resize = true; + } + } else { + bad->expire = *expire; + } + + UNLOCK(&bc->tlocks[hash]); + RWUNLOCK(&bc->lock, isc_rwlocktype_read); + if (resize) { + badcache_resize(bc, &now); + } +} + +bool +dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name, + dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now) { + dns_bcentry_t *bad, *prev, *next; + bool answer = false; + unsigned int i; + unsigned int hash; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + REQUIRE(now != NULL); + + RWLOCK(&bc->lock, isc_rwlocktype_read); + + /* + * XXXMUKS: dns_name_equal() is expensive as it does a + * octet-by-octet comparison, and it can be made better in two + * ways here. First, lowercase the names (use + * dns_name_downcase() instead of dns_name_copy() in + * dns_badcache_add()) so that dns_name_caseequal() can be used + * which the compiler will emit as SIMD instructions. Second, + * don't put multiple copies of the same name in the chain (or + * multiple names will have to be matched for equality), but use + * name->link to store the type specific part. + */ + + if (atomic_load_relaxed(&bc->count) == 0) { + goto skip; + } + + hash = dns_name_hash(name, false) % bc->size; + prev = NULL; + LOCK(&bc->tlocks[hash]); + for (bad = bc->table[hash]; bad != NULL; bad = next) { + next = bad->next; + /* + * Search the hash list. Clean out expired records as we go. + */ + if (isc_time_compare(&bad->expire, now) < 0) { + if (prev != NULL) { + prev->next = bad->next; + } else { + bc->table[hash] = bad->next; + } + + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub(&bc->count, 1); + continue; + } + if (bad->type == type && dns_name_equal(name, bad->name)) { + if (flagp != NULL) { + *flagp = bad->flags; + } + answer = true; + break; + } + prev = bad; + } + UNLOCK(&bc->tlocks[hash]); +skip: + + /* + * Slow sweep to clean out stale records. + */ + i = atomic_fetch_add(&bc->sweep, 1) % bc->size; + if (isc_mutex_trylock(&bc->tlocks[i]) == ISC_R_SUCCESS) { + bad = bc->table[i]; + if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) { + bc->table[i] = bad->next; + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } + UNLOCK(&bc->tlocks[i]); + } + + RWUNLOCK(&bc->lock, isc_rwlocktype_read); + return (answer); +} + +void +dns_badcache_flush(dns_badcache_t *bc) { + dns_bcentry_t *entry, *next; + unsigned int i; + + RWLOCK(&bc->lock, isc_rwlocktype_write); + REQUIRE(VALID_BADCACHE(bc)); + + for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) { + for (entry = bc->table[i]; entry != NULL; entry = next) { + next = entry->next; + isc_mem_put(bc->mctx, entry, sizeof(*entry)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } + bc->table[i] = NULL; + } + RWUNLOCK(&bc->lock, isc_rwlocktype_write); +} + +void +dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) { + dns_bcentry_t *bad, *prev, *next; + isc_result_t result; + isc_time_t now; + unsigned int hash; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + + RWLOCK(&bc->lock, isc_rwlocktype_read); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) { + isc_time_settoepoch(&now); + } + hash = dns_name_hash(name, false) % bc->size; + LOCK(&bc->tlocks[hash]); + prev = NULL; + for (bad = bc->table[hash]; bad != NULL; bad = next) { + int n; + next = bad->next; + n = isc_time_compare(&bad->expire, &now); + if (n < 0 || dns_name_equal(name, bad->name)) { + if (prev == NULL) { + bc->table[hash] = bad->next; + } else { + prev->next = bad->next; + } + + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } else { + prev = bad; + } + } + UNLOCK(&bc->tlocks[hash]); + + RWUNLOCK(&bc->lock, isc_rwlocktype_read); +} + +void +dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) { + dns_bcentry_t *bad, *prev, *next; + unsigned int i; + int n; + isc_time_t now; + isc_result_t result; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + + /* + * We write lock the tree to avoid relocking every node + * individually. + */ + RWLOCK(&bc->lock, isc_rwlocktype_write); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) { + isc_time_settoepoch(&now); + } + + for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) { + prev = NULL; + for (bad = bc->table[i]; bad != NULL; bad = next) { + next = bad->next; + n = isc_time_compare(&bad->expire, &now); + if (n < 0 || dns_name_issubdomain(bad->name, name)) { + if (prev == NULL) { + bc->table[i] = bad->next; + } else { + prev->next = bad->next; + } + + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + } else { + prev = bad; + } + } + } + + RWUNLOCK(&bc->lock, isc_rwlocktype_write); +} + +void +dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + dns_bcentry_t *bad, *next, *prev; + isc_time_t now; + unsigned int i; + uint64_t t; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(cachename != NULL); + REQUIRE(fp != NULL); + + /* + * We write lock the tree to avoid relocking every node + * individually. + */ + RWLOCK(&bc->lock, isc_rwlocktype_write); + fprintf(fp, ";\n; %s\n;\n", cachename); + + TIME_NOW(&now); + for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) { + prev = NULL; + for (bad = bc->table[i]; bad != NULL; bad = next) { + next = bad->next; + if (isc_time_compare(&bad->expire, &now) < 0) { + if (prev != NULL) { + prev->next = bad->next; + } else { + bc->table[i] = bad->next; + } + + isc_mem_put(bc->mctx, bad, sizeof(*bad)); + atomic_fetch_sub_relaxed(&bc->count, 1); + continue; + } + prev = bad; + dns_name_format(bad->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(bad->type, typebuf, + sizeof(typebuf)); + t = isc_time_microdiff(&bad->expire, &now); + t /= 1000; + fprintf(fp, + "; %s/%s [ttl " + "%" PRIu64 "]\n", + namebuf, typebuf, t); + } + } + RWUNLOCK(&bc->lock, isc_rwlocktype_write); +} diff --git a/lib/dns/byaddr.c b/lib/dns/byaddr.c new file mode 100644 index 0000000..8fa668a --- /dev/null +++ b/lib/dns/byaddr.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * XXXRTH We could use a static event... + */ + +static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +isc_result_t +dns_byaddr_createptrname(const isc_netaddr_t *address, unsigned int options, + dns_name_t *name) { + char textname[128]; + const unsigned char *bytes; + int i; + char *cp; + isc_buffer_t buffer; + unsigned int len; + + REQUIRE(address != NULL); + UNUSED(options); + + /* + * We create the text representation and then convert to a + * dns_name_t. This is not maximally efficient, but it keeps all + * of the knowledge of wire format in the dns_name_ routines. + */ + + bytes = (const unsigned char *)(&address->type); + if (address->family == AF_INET) { + (void)snprintf(textname, sizeof(textname), + "%u.%u.%u.%u.in-addr.arpa.", + ((unsigned int)bytes[3] & 0xffU), + ((unsigned int)bytes[2] & 0xffU), + ((unsigned int)bytes[1] & 0xffU), + ((unsigned int)bytes[0] & 0xffU)); + } else if (address->family == AF_INET6) { + size_t remaining; + + cp = textname; + for (i = 15; i >= 0; i--) { + *cp++ = hex_digits[bytes[i] & 0x0f]; + *cp++ = '.'; + *cp++ = hex_digits[(bytes[i] >> 4) & 0x0f]; + *cp++ = '.'; + } + remaining = sizeof(textname) - (cp - textname); + strlcpy(cp, "ip6.arpa.", remaining); + } else { + return (ISC_R_NOTIMPLEMENTED); + } + + len = (unsigned int)strlen(textname); + isc_buffer_init(&buffer, textname, len); + isc_buffer_add(&buffer, len); + return (dns_name_fromtext(name, &buffer, dns_rootname, 0, NULL)); +} + +struct dns_byaddr { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + dns_fixedname_t name; + /* Locked by lock. */ + unsigned int options; + dns_lookup_t *lookup; + isc_task_t *task; + dns_byaddrevent_t *event; + bool canceled; +}; + +#define BYADDR_MAGIC ISC_MAGIC('B', 'y', 'A', 'd') +#define VALID_BYADDR(b) ISC_MAGIC_VALID(b, BYADDR_MAGIC) + +#define MAX_RESTARTS 16 + +static isc_result_t +copy_ptr_targets(dns_byaddr_t *byaddr, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_name_t *name; + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * The caller must be holding the byaddr's lock. + */ + + result = dns_rdataset_first(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdata_ptr_t ptr; + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + name = isc_mem_get(byaddr->mctx, sizeof(*name)); + dns_name_init(name, NULL); + dns_name_dup(&ptr.ptr, byaddr->mctx, name); + dns_rdata_freestruct(&ptr); + ISC_LIST_APPEND(byaddr->event->names, name, link); + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +static void +lookup_done(isc_task_t *task, isc_event_t *event) { + dns_byaddr_t *byaddr = event->ev_arg; + dns_lookupevent_t *levent; + isc_result_t result; + + REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE); + REQUIRE(VALID_BYADDR(byaddr)); + REQUIRE(byaddr->task == task); + + UNUSED(task); + + levent = (dns_lookupevent_t *)event; + + if (levent->result == ISC_R_SUCCESS) { + result = copy_ptr_targets(byaddr, levent->rdataset); + byaddr->event->result = result; + } else { + byaddr->event->result = levent->result; + } + isc_event_free(&event); + isc_task_sendanddetach(&byaddr->task, (isc_event_t **)&byaddr->event); +} + +static void +bevent_destroy(isc_event_t *event) { + dns_byaddrevent_t *bevent; + dns_name_t *name, *next_name; + isc_mem_t *mctx; + + REQUIRE(event->ev_type == DNS_EVENT_BYADDRDONE); + mctx = event->ev_destroy_arg; + bevent = (dns_byaddrevent_t *)event; + + for (name = ISC_LIST_HEAD(bevent->names); name != NULL; + name = next_name) + { + next_name = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(bevent->names, name, link); + dns_name_free(name, mctx); + isc_mem_put(mctx, name, sizeof(*name)); + } + isc_mem_put(mctx, event, event->ev_size); +} + +isc_result_t +dns_byaddr_create(isc_mem_t *mctx, const isc_netaddr_t *address, + dns_view_t *view, unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_byaddr_t **byaddrp) { + isc_result_t result; + dns_byaddr_t *byaddr; + isc_event_t *ievent; + + byaddr = isc_mem_get(mctx, sizeof(*byaddr)); + byaddr->mctx = NULL; + isc_mem_attach(mctx, &byaddr->mctx); + byaddr->options = options; + + byaddr->event = isc_mem_get(mctx, sizeof(*byaddr->event)); + ISC_EVENT_INIT(byaddr->event, sizeof(*byaddr->event), 0, NULL, + DNS_EVENT_BYADDRDONE, action, arg, byaddr, + bevent_destroy, mctx); + byaddr->event->result = ISC_R_FAILURE; + ISC_LIST_INIT(byaddr->event->names); + + byaddr->task = NULL; + isc_task_attach(task, &byaddr->task); + + isc_mutex_init(&byaddr->lock); + + dns_fixedname_init(&byaddr->name); + + result = dns_byaddr_createptrname(address, options, + dns_fixedname_name(&byaddr->name)); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + byaddr->lookup = NULL; + result = dns_lookup_create(mctx, dns_fixedname_name(&byaddr->name), + dns_rdatatype_ptr, view, 0, task, + lookup_done, byaddr, &byaddr->lookup); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + byaddr->canceled = false; + byaddr->magic = BYADDR_MAGIC; + + *byaddrp = byaddr; + + return (ISC_R_SUCCESS); + +cleanup_lock: + isc_mutex_destroy(&byaddr->lock); + + ievent = (isc_event_t *)byaddr->event; + isc_event_free(&ievent); + byaddr->event = NULL; + + isc_task_detach(&byaddr->task); + + isc_mem_putanddetach(&mctx, byaddr, sizeof(*byaddr)); + + return (result); +} + +void +dns_byaddr_cancel(dns_byaddr_t *byaddr) { + REQUIRE(VALID_BYADDR(byaddr)); + + LOCK(&byaddr->lock); + + if (!byaddr->canceled) { + byaddr->canceled = true; + if (byaddr->lookup != NULL) { + dns_lookup_cancel(byaddr->lookup); + } + } + + UNLOCK(&byaddr->lock); +} + +void +dns_byaddr_destroy(dns_byaddr_t **byaddrp) { + dns_byaddr_t *byaddr; + + REQUIRE(byaddrp != NULL); + byaddr = *byaddrp; + *byaddrp = NULL; + REQUIRE(VALID_BYADDR(byaddr)); + REQUIRE(byaddr->event == NULL); + REQUIRE(byaddr->task == NULL); + dns_lookup_destroy(&byaddr->lookup); + + isc_mutex_destroy(&byaddr->lock); + byaddr->magic = 0; + isc_mem_putanddetach(&byaddr->mctx, byaddr, sizeof(*byaddr)); +} diff --git a/lib/dns/cache.c b/lib/dns/cache.c new file mode 100644 index 0000000..7ffb6f8 --- /dev/null +++ b/lib/dns/cache.c @@ -0,0 +1,1444 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_JSON_C +#include +#endif /* HAVE_JSON_C */ + +#ifdef HAVE_LIBXML2 +#include +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +#include "rbtdb.h" + +#define CACHE_MAGIC ISC_MAGIC('$', '$', '$', '$') +#define VALID_CACHE(cache) ISC_MAGIC_VALID(cache, CACHE_MAGIC) + +/*! + * Control incremental cleaning. + * DNS_CACHE_MINSIZE is how many bytes is the floor for + * dns_cache_setcachesize(). See also DNS_CACHE_CLEANERINCREMENT + */ +#define DNS_CACHE_MINSIZE 2097152U /*%< Bytes. 2097152 = 2 MB */ +/*! + * Control incremental cleaning. + * CLEANERINCREMENT is how many nodes are examined in one pass. + * See also DNS_CACHE_MINSIZE + */ +#define DNS_CACHE_CLEANERINCREMENT 1000U /*%< Number of nodes. */ + +/*** + *** Types + ***/ + +/* + * A cache_cleaner_t encapsulates the state of the periodic + * cache cleaning. + */ + +typedef struct cache_cleaner cache_cleaner_t; + +typedef enum { + cleaner_s_idle, /*%< Waiting for cleaning interval to expire. */ + cleaner_s_busy, /*%< Currently cleaning. */ + cleaner_s_done /*%< Freed enough memory after being overmem. */ +} cleaner_state_t; + +/* + * Convenience macros for comprehensive assertion checking. + */ +#define CLEANER_IDLE(c) \ + ((c)->state == cleaner_s_idle && (c)->resched_event != NULL) +#define CLEANER_BUSY(c) \ + ((c)->state == cleaner_s_busy && (c)->iterator != NULL && \ + (c)->resched_event == NULL) + +/*% + * Accesses to a cache cleaner object are synchronized through + * task/event serialization, or locked from the cache object. + */ +struct cache_cleaner { + isc_mutex_t lock; + /*%< + * Locks overmem_event, overmem. Note: never allocate memory + * while holding this lock - that could lead to deadlock since + * the lock is take by water() which is called from the memory + * allocator. + */ + + dns_cache_t *cache; + isc_task_t *task; + isc_event_t *resched_event; /*% Sent by cleaner task to + * itself to reschedule */ + isc_event_t *overmem_event; + + dns_dbiterator_t *iterator; + unsigned int increment; /*% Number of names to + * clean in one increment */ + cleaner_state_t state; /*% Idle/Busy. */ + bool overmem; /*% The cache is in an overmem state. + * */ + bool replaceiterator; +}; + +/*% + * The actual cache object. + */ + +struct dns_cache { + /* Unlocked. */ + unsigned int magic; + isc_mutex_t lock; + isc_mem_t *mctx; /* Main cache memory */ + isc_mem_t *hmctx; /* Heap memory */ + char *name; + isc_refcount_t references; + isc_refcount_t live_tasks; + + /* Locked by 'lock'. */ + dns_rdataclass_t rdclass; + dns_db_t *db; + cache_cleaner_t cleaner; + char *db_type; + int db_argc; + char **db_argv; + size_t size; + dns_ttl_t serve_stale_ttl; + dns_ttl_t serve_stale_refresh; + isc_stats_t *stats; +}; + +/*** + *** Functions + ***/ + +static isc_result_t +cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, cache_cleaner_t *cleaner); + +static void +incremental_cleaning_action(isc_task_t *task, isc_event_t *event); + +static void +cleaner_shutdown_action(isc_task_t *task, isc_event_t *event); + +static void +overmem_cleaning_action(isc_task_t *task, isc_event_t *event); + +static void +water(void *arg, int mark); + +static isc_result_t +cache_create_db(dns_cache_t *cache, dns_db_t **db) { + isc_result_t result; + result = dns_db_create(cache->mctx, cache->db_type, dns_rootname, + dns_dbtype_cache, cache->rdclass, cache->db_argc, + cache->db_argv, db); + if (result == ISC_R_SUCCESS) { + dns_db_setservestalettl(*db, cache->serve_stale_ttl); + dns_db_setservestalerefresh(*db, cache->serve_stale_refresh); + } + return (result); +} + +isc_result_t +dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *cachename, const char *db_type, + unsigned int db_argc, char **db_argv, dns_cache_t **cachep) { + isc_result_t result; + dns_cache_t *cache; + int i, extra = 0; + isc_task_t *dbtask; + + REQUIRE(cachep != NULL); + REQUIRE(*cachep == NULL); + REQUIRE(cmctx != NULL); + REQUIRE(hmctx != NULL); + REQUIRE(cachename != NULL); + + cache = isc_mem_get(cmctx, sizeof(*cache)); + + cache->mctx = cache->hmctx = NULL; + isc_mem_attach(cmctx, &cache->mctx); + isc_mem_attach(hmctx, &cache->hmctx); + + cache->name = NULL; + if (cachename != NULL) { + cache->name = isc_mem_strdup(cmctx, cachename); + } + + isc_mutex_init(&cache->lock); + + isc_refcount_init(&cache->references, 1); + isc_refcount_init(&cache->live_tasks, 1); + cache->rdclass = rdclass; + cache->serve_stale_ttl = 0; + + cache->stats = NULL; + result = isc_stats_create(cmctx, &cache->stats, + dns_cachestatscounter_max); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + cache->db_type = isc_mem_strdup(cmctx, db_type); + + /* + * For databases of type "rbt" we pass hmctx to dns_db_create() + * via cache->db_argv, followed by the rest of the arguments in + * db_argv (of which there really shouldn't be any). + */ + if (strcmp(cache->db_type, "rbt") == 0) { + extra = 1; + } + + cache->db_argc = db_argc + extra; + cache->db_argv = NULL; + + if (cache->db_argc != 0) { + cache->db_argv = isc_mem_get(cmctx, + cache->db_argc * sizeof(char *)); + + for (i = 0; i < cache->db_argc; i++) { + cache->db_argv[i] = NULL; + } + + cache->db_argv[0] = (char *)hmctx; + for (i = extra; i < cache->db_argc; i++) { + cache->db_argv[i] = isc_mem_strdup(cmctx, + db_argv[i - extra]); + } + } + + /* + * Create the database + */ + cache->db = NULL; + result = cache_create_db(cache, &cache->db); + if (result != ISC_R_SUCCESS) { + goto cleanup_dbargv; + } + if (taskmgr != NULL) { + dbtask = NULL; + result = isc_task_create(taskmgr, 1, &dbtask); + if (result != ISC_R_SUCCESS) { + goto cleanup_db; + } + + isc_task_setname(dbtask, "cache_dbtask", NULL); + dns_db_settask(cache->db, dbtask); + isc_task_detach(&dbtask); + } + + cache->magic = CACHE_MAGIC; + + /* + * RBT-type cache DB has its own mechanism of cache cleaning and doesn't + * need the control of the generic cleaner. + */ + if (strcmp(db_type, "rbt") == 0) { + result = cache_cleaner_init(cache, NULL, NULL, &cache->cleaner); + } else { + result = cache_cleaner_init(cache, taskmgr, timermgr, + &cache->cleaner); + } + if (result != ISC_R_SUCCESS) { + goto cleanup_db; + } + + result = dns_db_setcachestats(cache->db, cache->stats); + if (result != ISC_R_SUCCESS) { + goto cleanup_db; + } + + *cachep = cache; + return (ISC_R_SUCCESS); + +cleanup_db: + dns_db_detach(&cache->db); +cleanup_dbargv: + for (i = extra; i < cache->db_argc; i++) { + if (cache->db_argv[i] != NULL) { + isc_mem_free(cmctx, cache->db_argv[i]); + } + } + if (cache->db_argv != NULL) { + isc_mem_put(cmctx, cache->db_argv, + cache->db_argc * sizeof(char *)); + } + isc_mem_free(cmctx, cache->db_type); + isc_stats_detach(&cache->stats); +cleanup_lock: + isc_mutex_destroy(&cache->lock); + if (cache->name != NULL) { + isc_mem_free(cmctx, cache->name); + } + isc_mem_detach(&cache->hmctx); + isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache)); + return (result); +} + +static void +cache_free(dns_cache_t *cache) { + REQUIRE(VALID_CACHE(cache)); + + isc_refcount_destroy(&cache->references); + isc_refcount_destroy(&cache->live_tasks); + + isc_mem_clearwater(cache->mctx); + + if (cache->cleaner.task != NULL) { + isc_task_detach(&cache->cleaner.task); + } + + if (cache->cleaner.overmem_event != NULL) { + isc_event_free(&cache->cleaner.overmem_event); + } + + if (cache->cleaner.resched_event != NULL) { + isc_event_free(&cache->cleaner.resched_event); + } + + if (cache->cleaner.iterator != NULL) { + dns_dbiterator_destroy(&cache->cleaner.iterator); + } + + isc_mutex_destroy(&cache->cleaner.lock); + + if (cache->db != NULL) { + dns_db_detach(&cache->db); + } + + if (cache->db_argv != NULL) { + /* + * We don't free db_argv[0] in "rbt" cache databases + * as it's a pointer to hmctx + */ + int extra = 0; + if (strcmp(cache->db_type, "rbt") == 0) { + extra = 1; + } + for (int i = extra; i < cache->db_argc; i++) { + if (cache->db_argv[i] != NULL) { + isc_mem_free(cache->mctx, cache->db_argv[i]); + } + } + isc_mem_put(cache->mctx, cache->db_argv, + cache->db_argc * sizeof(char *)); + } + + if (cache->db_type != NULL) { + isc_mem_free(cache->mctx, cache->db_type); + } + + if (cache->name != NULL) { + isc_mem_free(cache->mctx, cache->name); + } + + if (cache->stats != NULL) { + isc_stats_detach(&cache->stats); + } + + isc_mutex_destroy(&cache->lock); + + cache->magic = 0; + isc_mem_detach(&cache->hmctx); + isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache)); +} + +void +dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp) { + REQUIRE(VALID_CACHE(cache)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&cache->references); + + *targetp = cache; +} + +void +dns_cache_detach(dns_cache_t **cachep) { + dns_cache_t *cache; + + REQUIRE(cachep != NULL); + cache = *cachep; + *cachep = NULL; + REQUIRE(VALID_CACHE(cache)); + + if (isc_refcount_decrement(&cache->references) == 1) { + cache->cleaner.overmem = false; + + /* + * If the cleaner task exists, let it free the cache. + */ + if (isc_refcount_decrement(&cache->live_tasks) > 1) { + isc_task_shutdown(cache->cleaner.task); + } else { + cache_free(cache); + } + } +} + +void +dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp) { + REQUIRE(VALID_CACHE(cache)); + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(cache->db != NULL); + + LOCK(&cache->lock); + dns_db_attach(cache->db, dbp); + UNLOCK(&cache->lock); +} + +const char * +dns_cache_getname(dns_cache_t *cache) { + REQUIRE(VALID_CACHE(cache)); + + return (cache->name); +} + +/* + * Initialize the cache cleaner object at *cleaner. + * Space for the object must be allocated by the caller. + */ + +static isc_result_t +cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, cache_cleaner_t *cleaner) { + isc_result_t result; + + isc_mutex_init(&cleaner->lock); + + cleaner->increment = DNS_CACHE_CLEANERINCREMENT; + cleaner->state = cleaner_s_idle; + cleaner->cache = cache; + cleaner->iterator = NULL; + cleaner->overmem = false; + cleaner->replaceiterator = false; + + cleaner->task = NULL; + cleaner->resched_event = NULL; + cleaner->overmem_event = NULL; + + result = dns_db_createiterator(cleaner->cache->db, false, + &cleaner->iterator); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (taskmgr != NULL && timermgr != NULL) { + result = isc_task_create(taskmgr, 1, &cleaner->task); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_task_create() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + isc_refcount_increment(&cleaner->cache->live_tasks); + isc_task_setname(cleaner->task, "cachecleaner", cleaner); + + result = isc_task_onshutdown(cleaner->task, + cleaner_shutdown_action, cache); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement0(&cleaner->cache->live_tasks); + UNEXPECTED_ERROR("cache cleaner: " + "isc_task_onshutdown() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + cleaner->resched_event = isc_event_allocate( + cache->mctx, cleaner, DNS_EVENT_CACHECLEAN, + incremental_cleaning_action, cleaner, + sizeof(isc_event_t)); + + cleaner->overmem_event = isc_event_allocate( + cache->mctx, cleaner, DNS_EVENT_CACHEOVERMEM, + overmem_cleaning_action, cleaner, sizeof(isc_event_t)); + } + + return (ISC_R_SUCCESS); + +cleanup: + if (cleaner->overmem_event != NULL) { + isc_event_free(&cleaner->overmem_event); + } + if (cleaner->resched_event != NULL) { + isc_event_free(&cleaner->resched_event); + } + if (cleaner->task != NULL) { + isc_task_detach(&cleaner->task); + } + if (cleaner->iterator != NULL) { + dns_dbiterator_destroy(&cleaner->iterator); + } + isc_mutex_destroy(&cleaner->lock); + + return (result); +} + +static void +begin_cleaning(cache_cleaner_t *cleaner) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(CLEANER_IDLE(cleaner)); + + /* + * Create an iterator, if it does not already exist, and + * position it at the beginning of the cache. + */ + if (cleaner->iterator == NULL) { + result = dns_db_createiterator(cleaner->cache->db, false, + &cleaner->iterator); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "cache cleaner could not create " + "iterator: %s", + isc_result_totext(result)); + } else { + dns_dbiterator_setcleanmode(cleaner->iterator, true); + result = dns_dbiterator_first(cleaner->iterator); + } + if (result != ISC_R_SUCCESS) { + /* + * If the result is ISC_R_NOMORE, the database is empty, + * so there is nothing to be cleaned. + */ + if (result != ISC_R_NOMORE && cleaner->iterator != NULL) { + UNEXPECTED_ERROR("cache cleaner: " + "dns_dbiterator_first() failed: %s", + isc_result_totext(result)); + dns_dbiterator_destroy(&cleaner->iterator); + } else if (cleaner->iterator != NULL) { + result = dns_dbiterator_pause(cleaner->iterator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + } else { + /* + * Pause the iterator to free its lock. + */ + result = dns_dbiterator_pause(cleaner->iterator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), "begin cache cleaning, mem inuse %lu", + (unsigned long)isc_mem_inuse(cleaner->cache->mctx)); + cleaner->state = cleaner_s_busy; + isc_task_send(cleaner->task, &cleaner->resched_event); + } + + return; +} + +static void +end_cleaning(cache_cleaner_t *cleaner, isc_event_t *event) { + isc_result_t result; + + REQUIRE(CLEANER_BUSY(cleaner)); + REQUIRE(event != NULL); + + result = dns_dbiterator_pause(cleaner->iterator); + if (result != ISC_R_SUCCESS) { + dns_dbiterator_destroy(&cleaner->iterator); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), "end cache cleaning, mem inuse %lu", + (unsigned long)isc_mem_inuse(cleaner->cache->mctx)); + + cleaner->state = cleaner_s_idle; + cleaner->resched_event = event; +} + +/* + * This is called when the cache either surpasses its upper limit + * or shrinks beyond its lower limit. + */ +static void +overmem_cleaning_action(isc_task_t *task, isc_event_t *event) { + cache_cleaner_t *cleaner = event->ev_arg; + bool want_cleaning = false; + + UNUSED(task); + + INSIST(task == cleaner->task); + INSIST(event->ev_type == DNS_EVENT_CACHEOVERMEM); + INSIST(cleaner->overmem_event == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), + "overmem_cleaning_action called, " + "overmem = %d, state = %d", + cleaner->overmem, cleaner->state); + + LOCK(&cleaner->lock); + + if (cleaner->overmem) { + if (cleaner->state == cleaner_s_idle) { + want_cleaning = true; + } + } else { + if (cleaner->state == cleaner_s_busy) { + /* + * end_cleaning() can't be called here because + * then both cleaner->overmem_event and + * cleaner->resched_event will point to this + * event. Set the state to done, and then + * when the incremental_cleaning_action() event + * is posted, it will handle the end_cleaning. + */ + cleaner->state = cleaner_s_done; + } + } + + cleaner->overmem_event = event; + + UNLOCK(&cleaner->lock); + + if (want_cleaning) { + begin_cleaning(cleaner); + } +} + +/* + * Do incremental cleaning. + */ +static void +incremental_cleaning_action(isc_task_t *task, isc_event_t *event) { + cache_cleaner_t *cleaner = event->ev_arg; + isc_result_t result; + unsigned int n_names; + isc_time_t start; + + UNUSED(task); + + INSIST(task == cleaner->task); + INSIST(event->ev_type == DNS_EVENT_CACHECLEAN); + + if (cleaner->state == cleaner_s_done) { + cleaner->state = cleaner_s_busy; + end_cleaning(cleaner, event); + LOCK(&cleaner->cache->lock); + LOCK(&cleaner->lock); + if (cleaner->replaceiterator) { + dns_dbiterator_destroy(&cleaner->iterator); + (void)dns_db_createiterator(cleaner->cache->db, false, + &cleaner->iterator); + cleaner->replaceiterator = false; + } + UNLOCK(&cleaner->lock); + UNLOCK(&cleaner->cache->lock); + return; + } + + INSIST(CLEANER_BUSY(cleaner)); + + n_names = cleaner->increment; + + REQUIRE(DNS_DBITERATOR_VALID(cleaner->iterator)); + + isc_time_now(&start); + while (n_names-- > 0) { + dns_dbnode_t *node = NULL; + + result = dns_dbiterator_current(cleaner->iterator, &node, NULL); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("cache cleaner: " + "dns_dbiterator_current() failed: %s", + isc_result_totext(result)); + + end_cleaning(cleaner, event); + return; + } + + /* + * The node was not needed, but was required by + * dns_dbiterator_current(). Give up its reference. + */ + dns_db_detachnode(cleaner->cache->db, &node); + + /* + * Step to the next node. + */ + result = dns_dbiterator_next(cleaner->iterator); + + if (result != ISC_R_SUCCESS) { + /* + * Either the end was reached (ISC_R_NOMORE) or + * some error was signaled. If the cache is still + * overmem and no error was encountered, + * keep trying to clean it, otherwise stop cleaning. + */ + if (result != ISC_R_NOMORE) { + UNEXPECTED_ERROR("cache cleaner: " + "dns_dbiterator_next() " + "failed: %s", + isc_result_totext(result)); + } else if (cleaner->overmem) { + result = + dns_dbiterator_first(cleaner->iterator); + if (result == ISC_R_SUCCESS) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), + "cache cleaner: " + "still overmem, " + "reset and try again"); + continue; + } + } + + end_cleaning(cleaner, event); + return; + } + } + + /* + * We have successfully performed a cleaning increment but have + * not gone through the entire cache. Free the iterator locks + * and reschedule another batch. If it fails, just try to continue + * anyway. + */ + result = dns_dbiterator_pause(cleaner->iterator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), + "cache cleaner: checked %u nodes, " + "mem inuse %lu, sleeping", + cleaner->increment, + (unsigned long)isc_mem_inuse(cleaner->cache->mctx)); + + isc_task_send(task, &event); + INSIST(CLEANER_BUSY(cleaner)); + return; +} + +/* + * Do immediate cleaning. + */ +isc_result_t +dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now) { + isc_result_t result; + dns_dbiterator_t *iterator = NULL; + + REQUIRE(VALID_CACHE(cache)); + + result = dns_db_createiterator(cache->db, 0, &iterator); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_dbiterator_first(iterator); + + while (result == ISC_R_SUCCESS) { + dns_dbnode_t *node = NULL; + result = dns_dbiterator_current(iterator, &node, + (dns_name_t *)NULL); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Check TTLs, mark expired rdatasets stale. + */ + result = dns_db_expirenode(cache->db, node, now); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("cache cleaner: dns_db_expirenode() " + "failed: %s", + isc_result_totext(result)); + /* + * Continue anyway. + */ + } + + /* + * This is where the actual freeing takes place. + */ + dns_db_detachnode(cache->db, &node); + + result = dns_dbiterator_next(iterator); + } + + dns_dbiterator_destroy(&iterator); + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +static void +water(void *arg, int mark) { + dns_cache_t *cache = arg; + bool overmem = (mark == ISC_MEM_HIWATER); + + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->cleaner.lock); + + if (overmem != cache->cleaner.overmem) { + dns_db_overmem(cache->db, overmem); + cache->cleaner.overmem = overmem; + isc_mem_waterack(cache->mctx, mark); + } + + if (cache->cleaner.overmem_event != NULL) { + isc_task_send(cache->cleaner.task, + &cache->cleaner.overmem_event); + } + + UNLOCK(&cache->cleaner.lock); +} + +void +dns_cache_setcachesize(dns_cache_t *cache, size_t size) { + size_t hiwater, lowater; + + REQUIRE(VALID_CACHE(cache)); + + /* + * Impose a minimum cache size; pathological things happen if there + * is too little room. + */ + if (size != 0U && size < DNS_CACHE_MINSIZE) { + size = DNS_CACHE_MINSIZE; + } + + LOCK(&cache->lock); + cache->size = size; + UNLOCK(&cache->lock); + + hiwater = size - (size >> 3); /* Approximately 7/8ths. */ + lowater = size - (size >> 2); /* Approximately 3/4ths. */ + + /* + * If the cache was overmem and cleaning, but now with the new limits + * it is no longer in an overmem condition, then the next + * isc_mem_put for cache memory will do the right thing and trigger + * water(). + */ + + if (size == 0U || hiwater == 0U || lowater == 0U) { + /* + * Disable cache memory limiting. + */ + isc_mem_clearwater(cache->mctx); + } else { + /* + * Establish new cache memory limits (either for the first + * time, or replacing other limits). + */ + isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater); + } +} + +size_t +dns_cache_getcachesize(dns_cache_t *cache) { + size_t size; + + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + size = cache->size; + UNLOCK(&cache->lock); + + return (size); +} + +void +dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) { + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + cache->serve_stale_ttl = ttl; + UNLOCK(&cache->lock); + + (void)dns_db_setservestalettl(cache->db, ttl); +} + +dns_ttl_t +dns_cache_getservestalettl(dns_cache_t *cache) { + dns_ttl_t ttl; + isc_result_t result; + + REQUIRE(VALID_CACHE(cache)); + + /* + * Could get it straight from the dns_cache_t, but use db + * to confirm the value that the db is really using. + */ + result = dns_db_getservestalettl(cache->db, &ttl); + return (result == ISC_R_SUCCESS ? ttl : 0); +} + +void +dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval) { + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + cache->serve_stale_refresh = interval; + UNLOCK(&cache->lock); + + (void)dns_db_setservestalerefresh(cache->db, interval); +} + +dns_ttl_t +dns_cache_getservestalerefresh(dns_cache_t *cache) { + isc_result_t result; + dns_ttl_t interval; + + REQUIRE(VALID_CACHE(cache)); + + result = dns_db_getservestalerefresh(cache->db, &interval); + return (result == ISC_R_SUCCESS ? interval : 0); +} + +/* + * The cleaner task is shutting down; do the necessary cleanup. + */ +static void +cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) { + dns_cache_t *cache = event->ev_arg; + + UNUSED(task); + + INSIST(task == cache->cleaner.task); + INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN); + + if (CLEANER_BUSY(&cache->cleaner)) { + end_cleaning(&cache->cleaner, event); + } else { + isc_event_free(&event); + } + + /* Make sure we don't reschedule anymore. */ + (void)isc_task_purge(task, NULL, DNS_EVENT_CACHECLEAN, NULL); + + isc_refcount_decrementz(&cache->live_tasks); + + cache_free(cache); +} + +isc_result_t +dns_cache_flush(dns_cache_t *cache) { + dns_db_t *db = NULL, *olddb; + dns_dbiterator_t *dbiterator = NULL, *olddbiterator = NULL; + isc_result_t result; + + result = cache_create_db(cache, &db); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_createiterator(db, false, &dbiterator); + if (result != ISC_R_SUCCESS) { + dns_db_detach(&db); + return (result); + } + + LOCK(&cache->lock); + LOCK(&cache->cleaner.lock); + if (cache->cleaner.state == cleaner_s_idle) { + olddbiterator = cache->cleaner.iterator; + cache->cleaner.iterator = dbiterator; + dbiterator = NULL; + } else { + if (cache->cleaner.state == cleaner_s_busy) { + cache->cleaner.state = cleaner_s_done; + } + cache->cleaner.replaceiterator = true; + } + olddb = cache->db; + cache->db = db; + dns_db_setcachestats(cache->db, cache->stats); + UNLOCK(&cache->cleaner.lock); + UNLOCK(&cache->lock); + + if (dbiterator != NULL) { + dns_dbiterator_destroy(&dbiterator); + } + if (olddbiterator != NULL) { + dns_dbiterator_destroy(&olddbiterator); + } + dns_db_detach(&olddb); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +clearnode(dns_db_t *db, dns_dbnode_t *node) { + isc_result_t result; + dns_rdatasetiter_t *iter = NULL; + + result = dns_db_allrdatasets(db, node, NULL, DNS_DB_STALEOK, + (isc_stdtime_t)0, &iter); + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iter)) + { + dns_rdataset_t rdataset; + dns_rdataset_init(&rdataset); + + dns_rdatasetiter_current(iter, &rdataset); + result = dns_db_deleterdataset(db, node, NULL, rdataset.type, + rdataset.covers); + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + break; + } + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + dns_rdatasetiter_destroy(&iter); + return (result); +} + +static isc_result_t +cleartree(dns_db_t *db, const dns_name_t *name) { + isc_result_t result, answer = ISC_R_SUCCESS; + dns_dbiterator_t *iter = NULL; + dns_dbnode_t *node = NULL, *top = NULL; + dns_fixedname_t fnodename; + dns_name_t *nodename; + + /* + * Create the node if it doesn't exist so dns_dbiterator_seek() + * can find it. We will continue even if this fails. + */ + (void)dns_db_findnode(db, name, true, &top); + + nodename = dns_fixedname_initname(&fnodename); + + result = dns_db_createiterator(db, 0, &iter); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_dbiterator_seek(iter, name); + if (result == DNS_R_PARTIALMATCH) { + result = dns_dbiterator_next(iter); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(iter, &node, nodename); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + /* + * Are we done? + */ + if (!dns_name_issubdomain(nodename, name)) { + goto cleanup; + } + + /* + * If clearnode fails record and move onto the next node. + */ + result = clearnode(db, node); + if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) { + answer = result; + } + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(iter); + } + +cleanup: + if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) { + answer = result; + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (iter != NULL) { + dns_dbiterator_destroy(&iter); + } + if (top != NULL) { + dns_db_detachnode(db, &top); + } + + return (answer); +} + +isc_result_t +dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name) { + return (dns_cache_flushnode(cache, name, false)); +} + +isc_result_t +dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_db_t *db = NULL; + + if (tree && dns_name_equal(name, dns_rootname)) { + return (dns_cache_flush(cache)); + } + + LOCK(&cache->lock); + if (cache->db != NULL) { + dns_db_attach(cache->db, &db); + } + UNLOCK(&cache->lock); + if (db == NULL) { + return (ISC_R_SUCCESS); + } + + if (tree) { + result = cleartree(cache->db, name); + } else { + result = dns_db_findnode(cache->db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + goto cleanup_db; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_db; + } + result = clearnode(cache->db, node); + dns_db_detachnode(cache->db, &node); + } + +cleanup_db: + dns_db_detach(&db); + return (result); +} + +isc_stats_t * +dns_cache_getstats(dns_cache_t *cache) { + REQUIRE(VALID_CACHE(cache)); + return (cache->stats); +} + +void +dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) { + REQUIRE(VALID_CACHE(cache)); + if (cache->stats == NULL) { + return; + } + + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_CNAME: + case DNS_R_DNAME: + case DNS_R_GLUE: + case DNS_R_ZONECUT: + case DNS_R_COVERINGNSEC: + isc_stats_increment(cache->stats, + dns_cachestatscounter_queryhits); + break; + default: + isc_stats_increment(cache->stats, + dns_cachestatscounter_querymisses); + } +} + +/* + * XXX: Much of the following code has been copied in from statschannel.c. + * We should refactor this into a generic function in stats.c that can be + * called from both places. + */ +typedef struct cache_dumparg { + isc_statsformat_t type; + void *arg; /* type dependent argument */ + int ncounters; /* for general statistics */ + int *counterindices; /* for general statistics */ + uint64_t *countervalues; /* for general statistics */ + isc_result_t result; +} cache_dumparg_t; + +static void +getcounter(isc_statscounter_t counter, uint64_t val, void *arg) { + cache_dumparg_t *dumparg = arg; + + REQUIRE(counter < dumparg->ncounters); + dumparg->countervalues[counter] = val; +} + +static void +getcounters(isc_stats_t *stats, isc_statsformat_t type, int ncounters, + int *indices, uint64_t *values) { + cache_dumparg_t dumparg; + + memset(values, 0, sizeof(values[0]) * ncounters); + + dumparg.type = type; + dumparg.ncounters = ncounters; + dumparg.counterindices = indices; + dumparg.countervalues = values; + + isc_stats_dump(stats, getcounter, &dumparg, ISC_STATSDUMP_VERBOSE); +} + +void +dns_cache_dumpstats(dns_cache_t *cache, FILE *fp) { + int indices[dns_cachestatscounter_max]; + uint64_t values[dns_cachestatscounter_max]; + + REQUIRE(VALID_CACHE(cache)); + + getcounters(cache->stats, isc_statsformat_file, + dns_cachestatscounter_max, indices, values); + + fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_hits], + "cache hits"); + fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_misses], + "cache misses"); + fprintf(fp, "%20" PRIu64 " %s\n", + values[dns_cachestatscounter_queryhits], + "cache hits (from query)"); + fprintf(fp, "%20" PRIu64 " %s\n", + values[dns_cachestatscounter_querymisses], + "cache misses (from query)"); + fprintf(fp, "%20" PRIu64 " %s\n", + values[dns_cachestatscounter_deletelru], + "cache records deleted due to memory exhaustion"); + fprintf(fp, "%20" PRIu64 " %s\n", + values[dns_cachestatscounter_deletettl], + "cache records deleted due to TTL expiration"); + fprintf(fp, "%20" PRIu64 " %s\n", + values[dns_cachestatscounter_coveringnsec], + "covering nsec returned"); + fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_main), + "cache database nodes"); + fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_nsec), + "cache NSEC auxiliary database nodes"); + fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)dns_db_hashsize(cache->db), + "cache database hash buckets"); + + fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->mctx), + "cache tree memory total"); + fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->mctx), + "cache tree memory in use"); + fprintf(fp, "%20" PRIu64 " %s\n", + (uint64_t)isc_mem_maxinuse(cache->mctx), + "cache tree highest memory in use"); + + fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->hmctx), + "cache heap memory total"); + fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->hmctx), + "cache heap memory in use"); + fprintf(fp, "%20" PRIu64 " %s\n", + (uint64_t)isc_mem_maxinuse(cache->hmctx), + "cache heap highest memory in use"); +} + +#ifdef HAVE_LIBXML2 +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto error; \ + } while (0) +static int +renderstat(const char *name, uint64_t value, xmlTextWriterPtr writer) { + int xmlrc; + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR name)); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", value)); + TRY0(xmlTextWriterEndElement(writer)); /* counter */ + +error: + return (xmlrc); +} + +int +dns_cache_renderxml(dns_cache_t *cache, void *writer0) { + int indices[dns_cachestatscounter_max]; + uint64_t values[dns_cachestatscounter_max]; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + REQUIRE(VALID_CACHE(cache)); + + getcounters(cache->stats, isc_statsformat_file, + dns_cachestatscounter_max, indices, values); + TRY0(renderstat("CacheHits", values[dns_cachestatscounter_hits], + writer)); + TRY0(renderstat("CacheMisses", values[dns_cachestatscounter_misses], + writer)); + TRY0(renderstat("QueryHits", values[dns_cachestatscounter_queryhits], + writer)); + TRY0(renderstat("QueryMisses", + values[dns_cachestatscounter_querymisses], writer)); + TRY0(renderstat("DeleteLRU", values[dns_cachestatscounter_deletelru], + writer)); + TRY0(renderstat("DeleteTTL", values[dns_cachestatscounter_deletettl], + writer)); + TRY0(renderstat("CoveringNSEC", + values[dns_cachestatscounter_coveringnsec], writer)); + + TRY0(renderstat("CacheNodes", + dns_db_nodecount(cache->db, dns_dbtree_main), writer)); + TRY0(renderstat("CacheNSECNodes", + dns_db_nodecount(cache->db, dns_dbtree_nsec), writer)); + TRY0(renderstat("CacheBuckets", dns_db_hashsize(cache->db), writer)); + + TRY0(renderstat("TreeMemTotal", isc_mem_total(cache->mctx), writer)); + TRY0(renderstat("TreeMemInUse", isc_mem_inuse(cache->mctx), writer)); + TRY0(renderstat("TreeMemMax", isc_mem_maxinuse(cache->mctx), writer)); + + TRY0(renderstat("HeapMemTotal", isc_mem_total(cache->hmctx), writer)); + TRY0(renderstat("HeapMemInUse", isc_mem_inuse(cache->hmctx), writer)); + TRY0(renderstat("HeapMemMax", isc_mem_maxinuse(cache->hmctx), writer)); +error: + return (xmlrc); +} +#endif /* ifdef HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#define CHECKMEM(m) \ + do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto error; \ + } \ + } while (0) + +isc_result_t +dns_cache_renderjson(dns_cache_t *cache, void *cstats0) { + isc_result_t result = ISC_R_SUCCESS; + int indices[dns_cachestatscounter_max]; + uint64_t values[dns_cachestatscounter_max]; + json_object *obj; + json_object *cstats = (json_object *)cstats0; + + REQUIRE(VALID_CACHE(cache)); + + getcounters(cache->stats, isc_statsformat_file, + dns_cachestatscounter_max, indices, values); + + obj = json_object_new_int64(values[dns_cachestatscounter_hits]); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheHits", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_misses]); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheMisses", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_queryhits]); + CHECKMEM(obj); + json_object_object_add(cstats, "QueryHits", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_querymisses]); + CHECKMEM(obj); + json_object_object_add(cstats, "QueryMisses", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_deletelru]); + CHECKMEM(obj); + json_object_object_add(cstats, "DeleteLRU", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_deletettl]); + CHECKMEM(obj); + json_object_object_add(cstats, "DeleteTTL", obj); + + obj = json_object_new_int64(values[dns_cachestatscounter_coveringnsec]); + CHECKMEM(obj); + json_object_object_add(cstats, "CoveringNSEC", obj); + + obj = json_object_new_int64( + dns_db_nodecount(cache->db, dns_dbtree_main)); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheNodes", obj); + + obj = json_object_new_int64( + dns_db_nodecount(cache->db, dns_dbtree_nsec)); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheNSECNodes", obj); + + obj = json_object_new_int64(dns_db_hashsize(cache->db)); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheBuckets", obj); + + obj = json_object_new_int64(isc_mem_total(cache->mctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "TreeMemTotal", obj); + + obj = json_object_new_int64(isc_mem_inuse(cache->mctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "TreeMemInUse", obj); + + obj = json_object_new_int64(isc_mem_maxinuse(cache->mctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "TreeMemMax", obj); + + obj = json_object_new_int64(isc_mem_total(cache->hmctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "HeapMemTotal", obj); + + obj = json_object_new_int64(isc_mem_inuse(cache->hmctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "HeapMemInUse", obj); + + obj = json_object_new_int64(isc_mem_maxinuse(cache->hmctx)); + CHECKMEM(obj); + json_object_object_add(cstats, "HeapMemMax", obj); + + result = ISC_R_SUCCESS; +error: + return (result); +} +#endif /* ifdef HAVE_JSON_C */ diff --git a/lib/dns/callbacks.c b/lib/dns/callbacks.c new file mode 100644 index 0000000..5200b72 --- /dev/null +++ b/lib/dns/callbacks.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include + +static void +stdio_error_warn_callback(dns_rdatacallbacks_t *, const char *, ...) + ISC_FORMAT_PRINTF(2, 3); + +static void +isclog_error_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) + ISC_FORMAT_PRINTF(2, 3); + +static void +isclog_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) + ISC_FORMAT_PRINTF(2, 3); + +/* + * Private + */ + +static void +stdio_error_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, + ...) { + va_list ap; + + UNUSED(callbacks); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void +isclog_error_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) { + va_list ap; + + UNUSED(callbacks); + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, /* XXX */ + ISC_LOG_ERROR, fmt, ap); + va_end(ap); +} + +static void +isclog_warn_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, ...) { + va_list ap; + + UNUSED(callbacks); + + va_start(ap, fmt); + + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, /* XXX */ + ISC_LOG_WARNING, fmt, ap); + va_end(ap); +} + +static void +dns_rdatacallbacks_initcommon(dns_rdatacallbacks_t *callbacks) { + REQUIRE(callbacks != NULL); + + callbacks->magic = DNS_CALLBACK_MAGIC; + callbacks->add = NULL; + callbacks->rawdata = NULL; + callbacks->zone = NULL; + callbacks->add_private = NULL; + callbacks->error_private = NULL; + callbacks->warn_private = NULL; +} + +/* + * Public. + */ + +void +dns_rdatacallbacks_init(dns_rdatacallbacks_t *callbacks) { + dns_rdatacallbacks_initcommon(callbacks); + callbacks->error = isclog_error_callback; + callbacks->warn = isclog_warn_callback; +} + +void +dns_rdatacallbacks_init_stdio(dns_rdatacallbacks_t *callbacks) { + dns_rdatacallbacks_initcommon(callbacks); + callbacks->error = stdio_error_warn_callback; + callbacks->warn = stdio_error_warn_callback; +} diff --git a/lib/dns/catz.c b/lib/dns/catz.c new file mode 100644 index 0000000..b18459e --- /dev/null +++ b/lib/dns/catz.c @@ -0,0 +1,2698 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DNS_CATZ_ZONE_MAGIC ISC_MAGIC('c', 'a', 't', 'z') +#define DNS_CATZ_ZONES_MAGIC ISC_MAGIC('c', 'a', 't', 's') +#define DNS_CATZ_ENTRY_MAGIC ISC_MAGIC('c', 'a', 't', 'e') +#define DNS_CATZ_COO_MAGIC ISC_MAGIC('c', 'a', 't', 'c') + +#define DNS_CATZ_ZONE_VALID(catz) ISC_MAGIC_VALID(catz, DNS_CATZ_ZONE_MAGIC) +#define DNS_CATZ_ZONES_VALID(catzs) ISC_MAGIC_VALID(catzs, DNS_CATZ_ZONES_MAGIC) +#define DNS_CATZ_ENTRY_VALID(entry) ISC_MAGIC_VALID(entry, DNS_CATZ_ENTRY_MAGIC) +#define DNS_CATZ_COO_VALID(coo) ISC_MAGIC_VALID(coo, DNS_CATZ_COO_MAGIC) + +#define DNS_CATZ_VERSION_UNDEFINED ((uint32_t)(-1)) + +/*% + * Change of ownership permissions + */ +struct dns_catz_coo { + unsigned int magic; + dns_name_t name; + isc_refcount_t references; +}; + +/*% + * Single member zone in a catalog + */ +struct dns_catz_entry { + unsigned int magic; + dns_name_t name; + dns_catz_options_t opts; + isc_refcount_t references; +}; + +/*% + * Catalog zone + */ +struct dns_catz_zone { + unsigned int magic; + dns_name_t name; + dns_catz_zones_t *catzs; + dns_rdata_t soa; + uint32_t version; + /* key in entries is 'mhash', not domain name! */ + isc_ht_t *entries; + /* key in coos is domain name */ + isc_ht_t *coos; + + /* + * defoptions are taken from named.conf + * zoneoptions are global options from zone + */ + dns_catz_options_t defoptions; + dns_catz_options_t zoneoptions; + isc_time_t lastupdated; + + bool updatepending; /* there is an update pending */ + bool updaterunning; /* there is an update running */ + isc_result_t updateresult; /* result from the offloaded work */ + dns_db_t *db; /* zones database */ + dns_dbversion_t *dbversion; /* version we will be updating to */ + dns_db_t *updb; /* zones database we're working on */ + dns_dbversion_t *updbversion; /* version we're working on */ + + isc_timer_t *updatetimer; + isc_event_t updateevent; + + bool active; + bool db_registered; + bool broken; + + isc_refcount_t references; + isc_mutex_t lock; +}; + +static void +dns__catz_timer_cb(isc_task_t *task, isc_event_t *event); + +static void +dns__catz_update_cb(void *data); +static void +dns__catz_done_cb(void *data, isc_result_t result); + +static isc_result_t +catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value, + dns_label_t *mhash); +static isc_result_t +catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value, + dns_label_t *mhash, dns_name_t *name); +static void +catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key, + size_t keysize, dns_catz_entry_t *nentry, + dns_catz_entry_t *oentry, const char *msg, + const char *zname, const char *czname); + +/*% + * Collection of catalog zones for a view + */ +struct dns_catz_zones { + unsigned int magic; + isc_ht_t *zones; + isc_mem_t *mctx; + isc_refcount_t references; + isc_mutex_t lock; + dns_catz_zonemodmethods_t *zmm; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + dns_view_t *view; + isc_task_t *updater; + atomic_bool shuttingdown; +}; + +void +dns_catz_options_init(dns_catz_options_t *options) { + REQUIRE(options != NULL); + + dns_ipkeylist_init(&options->masters); + + options->allow_query = NULL; + options->allow_transfer = NULL; + + options->allow_query = NULL; + options->allow_transfer = NULL; + + options->in_memory = false; + options->min_update_interval = 5; + options->zonedir = NULL; +} + +void +dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx) { + REQUIRE(options != NULL); + REQUIRE(mctx != NULL); + + if (options->masters.count != 0) { + dns_ipkeylist_clear(mctx, &options->masters); + } + if (options->zonedir != NULL) { + isc_mem_free(mctx, options->zonedir); + options->zonedir = NULL; + } + if (options->allow_query != NULL) { + isc_buffer_free(&options->allow_query); + } + if (options->allow_transfer != NULL) { + isc_buffer_free(&options->allow_transfer); + } +} + +void +dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *src, + dns_catz_options_t *dst) { + REQUIRE(mctx != NULL); + REQUIRE(src != NULL); + REQUIRE(dst != NULL); + REQUIRE(dst->masters.count == 0); + REQUIRE(dst->allow_query == NULL); + REQUIRE(dst->allow_transfer == NULL); + + if (src->masters.count != 0) { + dns_ipkeylist_copy(mctx, &src->masters, &dst->masters); + } + + if (dst->zonedir != NULL) { + isc_mem_free(mctx, dst->zonedir); + dst->zonedir = NULL; + } + + if (src->zonedir != NULL) { + dst->zonedir = isc_mem_strdup(mctx, src->zonedir); + } + + if (src->allow_query != NULL) { + isc_buffer_dup(mctx, &dst->allow_query, src->allow_query); + } + + if (src->allow_transfer != NULL) { + isc_buffer_dup(mctx, &dst->allow_transfer, src->allow_transfer); + } +} + +void +dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults, + dns_catz_options_t *opts) { + REQUIRE(mctx != NULL); + REQUIRE(defaults != NULL); + REQUIRE(opts != NULL); + + if (opts->masters.count == 0 && defaults->masters.count != 0) { + dns_ipkeylist_copy(mctx, &defaults->masters, &opts->masters); + } + + if (defaults->zonedir != NULL) { + opts->zonedir = isc_mem_strdup(mctx, defaults->zonedir); + } + + if (opts->allow_query == NULL && defaults->allow_query != NULL) { + isc_buffer_dup(mctx, &opts->allow_query, defaults->allow_query); + } + if (opts->allow_transfer == NULL && defaults->allow_transfer != NULL) { + isc_buffer_dup(mctx, &opts->allow_transfer, + defaults->allow_transfer); + } + + /* This option is always taken from config, so it's always 'default' */ + opts->in_memory = defaults->in_memory; +} + +static void +catz_coo_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_coo_t **ncoop) { + dns_catz_coo_t *ncoo; + + REQUIRE(mctx != NULL); + REQUIRE(domain != NULL); + REQUIRE(ncoop != NULL && *ncoop == NULL); + + ncoo = isc_mem_get(mctx, sizeof(*ncoo)); + dns_name_init(&ncoo->name, NULL); + dns_name_dup(domain, mctx, &ncoo->name); + isc_refcount_init(&ncoo->references, 1); + ncoo->magic = DNS_CATZ_COO_MAGIC; + *ncoop = ncoo; +} + +static void +catz_coo_detach(dns_catz_zone_t *catz, dns_catz_coo_t **coop) { + dns_catz_coo_t *coo; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(coop != NULL && DNS_CATZ_COO_VALID(*coop)); + coo = *coop; + *coop = NULL; + + if (isc_refcount_decrement(&coo->references) == 1) { + isc_mem_t *mctx = catz->catzs->mctx; + coo->magic = 0; + isc_refcount_destroy(&coo->references); + if (dns_name_dynamic(&coo->name)) { + dns_name_free(&coo->name, mctx); + } + isc_mem_put(mctx, coo, sizeof(*coo)); + } +} + +void +dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_entry_t **nentryp) { + dns_catz_entry_t *nentry; + + REQUIRE(mctx != NULL); + REQUIRE(nentryp != NULL && *nentryp == NULL); + + nentry = isc_mem_get(mctx, sizeof(*nentry)); + + dns_name_init(&nentry->name, NULL); + if (domain != NULL) { + dns_name_dup(domain, mctx, &nentry->name); + } + + dns_catz_options_init(&nentry->opts); + isc_refcount_init(&nentry->references, 1); + nentry->magic = DNS_CATZ_ENTRY_MAGIC; + *nentryp = nentry; +} + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *entry) { + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + return (&entry->name); +} + +void +dns_catz_entry_copy(dns_catz_zone_t *catz, const dns_catz_entry_t *entry, + dns_catz_entry_t **nentryp) { + dns_catz_entry_t *nentry = NULL; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(nentryp != NULL && *nentryp == NULL); + + dns_catz_entry_new(catz->catzs->mctx, &entry->name, &nentry); + + dns_catz_options_copy(catz->catzs->mctx, &entry->opts, &nentry->opts); + *nentryp = nentry; +} + +void +dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp) { + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(entryp != NULL && *entryp == NULL); + + isc_refcount_increment(&entry->references); + *entryp = entry; +} + +void +dns_catz_entry_detach(dns_catz_zone_t *catz, dns_catz_entry_t **entryp) { + dns_catz_entry_t *entry; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(entryp != NULL && DNS_CATZ_ENTRY_VALID(*entryp)); + entry = *entryp; + *entryp = NULL; + + if (isc_refcount_decrement(&entry->references) == 1) { + isc_mem_t *mctx = catz->catzs->mctx; + entry->magic = 0; + isc_refcount_destroy(&entry->references); + dns_catz_options_free(&entry->opts, mctx); + if (dns_name_dynamic(&entry->name)) { + dns_name_free(&entry->name, mctx); + } + isc_mem_put(mctx, entry, sizeof(*entry)); + } +} + +bool +dns_catz_entry_validate(const dns_catz_entry_t *entry) { + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + UNUSED(entry); + + return (true); +} + +bool +dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb) { + isc_region_t ra, rb; + + REQUIRE(DNS_CATZ_ENTRY_VALID(ea)); + REQUIRE(DNS_CATZ_ENTRY_VALID(eb)); + + if (ea == eb) { + return (true); + } + + if (ea->opts.masters.count != eb->opts.masters.count) { + return (false); + } + + if (memcmp(ea->opts.masters.addrs, eb->opts.masters.addrs, + ea->opts.masters.count * sizeof(isc_sockaddr_t))) + { + return (false); + } + + for (size_t i = 0; i < eb->opts.masters.count; i++) { + if ((ea->opts.masters.keys[i] == NULL) != + (eb->opts.masters.keys[i] == NULL)) + { + return (false); + } + if (ea->opts.masters.keys[i] == NULL) { + continue; + } + if (!dns_name_equal(ea->opts.masters.keys[i], + eb->opts.masters.keys[i])) + { + return (false); + } + } + + for (size_t i = 0; i < eb->opts.masters.count; i++) { + if ((ea->opts.masters.tlss[i] == NULL) != + (eb->opts.masters.tlss[i] == NULL)) + { + return (false); + } + if (ea->opts.masters.tlss[i] == NULL) { + continue; + } + if (!dns_name_equal(ea->opts.masters.tlss[i], + eb->opts.masters.tlss[i])) + { + return (false); + } + } + + /* If one is NULL and the other isn't, the entries don't match */ + if ((ea->opts.allow_query == NULL) != (eb->opts.allow_query == NULL)) { + return (false); + } + + /* If one is non-NULL, then they both are */ + if (ea->opts.allow_query != NULL) { + isc_buffer_usedregion(ea->opts.allow_query, &ra); + isc_buffer_usedregion(eb->opts.allow_query, &rb); + if (isc_region_compare(&ra, &rb)) { + return (false); + } + } + + /* Repeat the above checks with allow_transfer */ + if ((ea->opts.allow_transfer == NULL) != + (eb->opts.allow_transfer == NULL)) + { + return (false); + } + + if (ea->opts.allow_transfer != NULL) { + isc_buffer_usedregion(ea->opts.allow_transfer, &ra); + isc_buffer_usedregion(eb->opts.allow_transfer, &rb); + if (isc_region_compare(&ra, &rb)) { + return (false); + } + } + + return (true); +} + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *catz) { + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + return (&catz->name); +} + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *catz) { + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + return (&catz->defoptions); +} + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *catz) { + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + dns_catz_options_free(&catz->defoptions, catz->catzs->mctx); + dns_catz_options_init(&catz->defoptions); +} + +/*%< + * Merge 'newcatz' into 'catz', calling addzone/delzone/modzone + * (from catz->catzs->zmm) for appropriate member zones. + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + * \li 'newcatz' is a valid dns_catz_zone_t. + * + */ +static isc_result_t +dns__catz_zones_merge(dns_catz_zone_t *catz, dns_catz_zone_t *newcatz) { + isc_result_t result; + isc_ht_iter_t *iter1 = NULL, *iter2 = NULL; + isc_ht_iter_t *iteradd = NULL, *itermod = NULL; + isc_ht_t *toadd = NULL, *tomod = NULL; + bool delcur = false; + char czname[DNS_NAME_FORMATSIZE]; + char zname[DNS_NAME_FORMATSIZE]; + dns_catz_zoneop_fn_t addzone, modzone, delzone; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_CATZ_ZONE_VALID(newcatz)); + + LOCK(&catz->lock); + + /* TODO verify the new zone first! */ + + addzone = catz->catzs->zmm->addzone; + modzone = catz->catzs->zmm->modzone; + delzone = catz->catzs->zmm->delzone; + + /* Copy zoneoptions from newcatz into catz. */ + + dns_catz_options_free(&catz->zoneoptions, catz->catzs->mctx); + dns_catz_options_copy(catz->catzs->mctx, &newcatz->zoneoptions, + &catz->zoneoptions); + dns_catz_options_setdefault(catz->catzs->mctx, &catz->defoptions, + &catz->zoneoptions); + + dns_name_format(&catz->name, czname, DNS_NAME_FORMATSIZE); + + isc_ht_init(&toadd, catz->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&tomod, catz->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE); + isc_ht_iter_create(newcatz->entries, &iter1); + isc_ht_iter_create(catz->entries, &iter2); + + /* + * We can create those iterators now, even though toadd and tomod are + * empty + */ + isc_ht_iter_create(toadd, &iteradd); + isc_ht_iter_create(tomod, &itermod); + + /* + * First - walk the new zone and find all nodes that are not in the + * old zone, or are in both zones and are modified. + */ + for (result = isc_ht_iter_first(iter1); result == ISC_R_SUCCESS; + result = delcur ? isc_ht_iter_delcurrent_next(iter1) + : isc_ht_iter_next(iter1)) + { + isc_result_t zt_find_result; + dns_catz_zone_t *parentcatz = NULL; + dns_catz_entry_t *nentry = NULL; + dns_catz_entry_t *oentry = NULL; + dns_zone_t *zone = NULL; + unsigned char *key = NULL; + size_t keysize; + delcur = false; + + isc_ht_iter_current(iter1, (void **)&nentry); + isc_ht_iter_currentkey(iter1, &key, &keysize); + + /* + * Spurious record that came from suboption without main + * record, removed. + * xxxwpk: make it a separate verification phase? + */ + if (dns_name_countlabels(&nentry->name) == 0) { + dns_catz_entry_detach(newcatz, &nentry); + delcur = true; + continue; + } + + dns_name_format(&nentry->name, zname, DNS_NAME_FORMATSIZE); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: iterating over '%s' from catalog '%s'", + zname, czname); + dns_catz_options_setdefault(catz->catzs->mctx, + &catz->zoneoptions, &nentry->opts); + + /* Try to find the zone in the view */ + zt_find_result = dns_zt_find(catz->catzs->view->zonetable, + dns_catz_entry_getname(nentry), 0, + NULL, &zone); + if (zt_find_result == ISC_R_SUCCESS) { + dns_catz_coo_t *coo = NULL; + char pczname[DNS_NAME_FORMATSIZE]; + bool parentcatz_locked = false; + + /* + * Change of ownership (coo) processing, if required + */ + parentcatz = dns_zone_get_parentcatz(zone); + if (parentcatz != NULL && parentcatz != catz) { + UNLOCK(&catz->lock); + LOCK(&parentcatz->lock); + parentcatz_locked = true; + } + if (parentcatz_locked && + isc_ht_find(parentcatz->coos, nentry->name.ndata, + nentry->name.length, + (void **)&coo) == ISC_R_SUCCESS && + dns_name_equal(&coo->name, &catz->name)) + { + dns_name_format(&parentcatz->name, pczname, + DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: zone '%s' " + "change of ownership from " + "'%s' to '%s'", + zname, pczname, czname); + result = delzone(nentry, parentcatz, + parentcatz->catzs->view, + parentcatz->catzs->taskmgr, + parentcatz->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, + "catz: deleting zone '%s' " + "from catalog '%s' - %s", + zname, pczname, + isc_result_totext(result)); + } + if (parentcatz_locked) { + UNLOCK(&parentcatz->lock); + LOCK(&catz->lock); + } + } + if (zt_find_result == ISC_R_SUCCESS || + zt_find_result == DNS_R_PARTIALMATCH) + { + dns_zone_detach(&zone); + } + + /* Try to find the zone in the old catalog zone */ + result = isc_ht_find(catz->entries, key, (uint32_t)keysize, + (void **)&oentry); + if (result != ISC_R_SUCCESS) { + if (zt_find_result == ISC_R_SUCCESS && + parentcatz == catz) + { + /* + * This means that the zone's unique label + * has been changed, in that case we must + * reset the zone's internal state by removing + * and re-adding it. + * + * Scheduling the addition now, the removal will + * be scheduled below, when walking the old + * zone for remaining entries, and then we will + * perform deletions earlier than additions and + * modifications. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, + "catz: zone '%s' unique label " + "has changed, reset state", + zname); + } + + catz_entry_add_or_mod(catz, toadd, key, keysize, nentry, + NULL, "adding", zname, czname); + continue; + } + + if (zt_find_result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: zone '%s' was expected to exist " + "but can not be found, will be restored", + zname); + catz_entry_add_or_mod(catz, toadd, key, keysize, nentry, + oentry, "adding", zname, czname); + continue; + } + + if (dns_catz_entry_cmp(oentry, nentry) != true) { + catz_entry_add_or_mod(catz, tomod, key, keysize, nentry, + oentry, "modifying", zname, + czname); + continue; + } + + /* + * Delete the old entry so that it won't accidentally be + * removed as a non-existing entry below. + */ + dns_catz_entry_detach(catz, &oentry); + result = isc_ht_delete(catz->entries, key, (uint32_t)keysize); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + RUNTIME_CHECK(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter1); + + /* + * Then - walk the old zone; only deleted entries should remain. + */ + for (result = isc_ht_iter_first(iter2); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter2)) + { + dns_catz_entry_t *entry = NULL; + isc_ht_iter_current(iter2, (void **)&entry); + + dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE); + result = delzone(entry, catz, catz->catzs->view, + catz->catzs->taskmgr, catz->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: deleting zone '%s' from catalog '%s' - %s", + zname, czname, isc_result_totext(result)); + dns_catz_entry_detach(catz, &entry); + } + RUNTIME_CHECK(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter2); + /* At this moment catz->entries has to be be empty. */ + INSIST(isc_ht_count(catz->entries) == 0); + isc_ht_destroy(&catz->entries); + + for (result = isc_ht_iter_first(iteradd); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iteradd)) + { + dns_catz_entry_t *entry = NULL; + isc_ht_iter_current(iteradd, (void **)&entry); + + dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE); + result = addzone(entry, catz, catz->catzs->view, + catz->catzs->taskmgr, catz->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: adding zone '%s' from catalog " + "'%s' - %s", + zname, czname, isc_result_totext(result)); + } + + for (result = isc_ht_iter_first(itermod); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(itermod)) + { + dns_catz_entry_t *entry = NULL; + isc_ht_iter_current(itermod, (void **)&entry); + + dns_name_format(&entry->name, zname, DNS_NAME_FORMATSIZE); + result = modzone(entry, catz, catz->catzs->view, + catz->catzs->taskmgr, catz->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: modifying zone '%s' from catalog " + "'%s' - %s", + zname, czname, isc_result_totext(result)); + } + + catz->entries = newcatz->entries; + newcatz->entries = NULL; + + /* + * We do not need to merge old coo (change of ownership) permission + * records with the new ones, just replace them. + */ + if (catz->coos != NULL && newcatz->coos != NULL) { + isc_ht_iter_t *iter = NULL; + + isc_ht_iter_create(catz->coos, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_coo_t *coo = NULL; + + isc_ht_iter_current(iter, (void **)&coo); + catz_coo_detach(catz, &coo); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(catz->coos) == 0); + isc_ht_destroy(&catz->coos); + + catz->coos = newcatz->coos; + newcatz->coos = NULL; + } + + result = ISC_R_SUCCESS; + + isc_ht_iter_destroy(&iteradd); + isc_ht_iter_destroy(&itermod); + isc_ht_destroy(&toadd); + isc_ht_destroy(&tomod); + + UNLOCK(&catz->lock); + + return (result); +} + +isc_result_t +dns_catz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_catz_zones_t **catzsp, + dns_catz_zonemodmethods_t *zmm) { + isc_result_t result; + dns_catz_zones_t *catzs = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(timermgr != NULL); + REQUIRE(catzsp != NULL && *catzsp == NULL); + REQUIRE(zmm != NULL); + + catzs = isc_mem_get(mctx, sizeof(*catzs)); + *catzs = (dns_catz_zones_t){ .taskmgr = taskmgr, + .timermgr = timermgr, + .zmm = zmm, + .magic = DNS_CATZ_ZONES_MAGIC }; + + result = isc_taskmgr_excltask(taskmgr, &catzs->updater); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + + isc_mutex_init(&catzs->lock); + isc_refcount_init(&catzs->references, 1); + isc_ht_init(&catzs->zones, mctx, 4, ISC_HT_CASE_SENSITIVE); + isc_mem_attach(mctx, &catzs->mctx); + + *catzsp = catzs; + + return (ISC_R_SUCCESS); + +cleanup_task: + isc_mem_put(mctx, catzs, sizeof(*catzs)); + + return (result); +} + +void +dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) { + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(DNS_VIEW_VALID(view)); + /* Either it's a new one or it's being reconfigured. */ + REQUIRE(catzs->view == NULL || !strcmp(catzs->view->name, view->name)); + + catzs->view = view; +} + +isc_result_t +dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **catzp, + const dns_name_t *name) { + isc_result_t result; + dns_catz_zone_t *catz = NULL; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(catzp != NULL && *catzp == NULL); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + catz = isc_mem_get(catzs->mctx, sizeof(*catz)); + *catz = (dns_catz_zone_t){ .active = true, + .version = DNS_CATZ_VERSION_UNDEFINED, + .magic = DNS_CATZ_ZONE_MAGIC }; + + result = isc_timer_create(catzs->timermgr, isc_timertype_inactive, NULL, + NULL, catzs->updater, dns__catz_timer_cb, + catz, &catz->updatetimer); + if (result != ISC_R_SUCCESS) { + goto cleanup_timer; + } + + dns_catz_zones_attach(catzs, &catz->catzs); + isc_mutex_init(&catz->lock); + isc_refcount_init(&catz->references, 1); + isc_ht_init(&catz->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&catz->coos, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE); + isc_time_settoepoch(&catz->lastupdated); + dns_catz_options_init(&catz->defoptions); + dns_catz_options_init(&catz->zoneoptions); + dns_name_init(&catz->name, NULL); + dns_name_dup(name, catzs->mctx, &catz->name); + + *catzp = catz; + + return (ISC_R_SUCCESS); + +cleanup_timer: + isc_mem_put(catzs->mctx, catz, sizeof(*catz)); + + return (result); +} + +isc_result_t +dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name, + dns_catz_zone_t **catzp) { + dns_catz_zone_t *catz = NULL; + isc_result_t result, tresult; + char zname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + REQUIRE(catzp != NULL && *catzp == NULL); + + dns_name_format(name, zname, DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), "catz: dns_catz_add_zone %s", zname); + + LOCK(&catzs->lock); + + result = dns_catz_new_zone(catzs, &catz, name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = isc_ht_add(catzs->zones, catz->name.ndata, catz->name.length, + catz); + if (result != ISC_R_SUCCESS) { + dns_catz_detach_catz(&catz); + if (result != ISC_R_EXISTS) { + goto cleanup; + } + } + + if (result == ISC_R_EXISTS) { + tresult = isc_ht_find(catzs->zones, name->ndata, name->length, + (void **)&catz); + INSIST(tresult == ISC_R_SUCCESS && !catz->active); + catz->active = true; + } + + *catzp = catz; + +cleanup: + UNLOCK(&catzs->lock); + + return (result); +} + +dns_catz_zone_t * +dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name) { + isc_result_t result; + dns_catz_zone_t *found = NULL; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + LOCK(&catzs->lock); + result = isc_ht_find(catzs->zones, name->ndata, name->length, + (void **)&found); + UNLOCK(&catzs->lock); + if (result != ISC_R_SUCCESS) { + return (NULL); + } + + return (found); +} + +static void +dns__catz_shutdown(dns_catz_zone_t *catz) { + /* lock must be locked */ + if (catz->updatetimer != NULL) { + isc_result_t result; + + /* Don't wait for timer to trigger for shutdown */ + result = isc_timer_reset(catz->updatetimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + dns_catz_detach_catz(&catz); +} + +static void +dns__catz_zone_destroy(dns_catz_zone_t *catz) { + isc_mem_t *mctx = catz->catzs->mctx; + + if (catz->entries != NULL) { + isc_ht_iter_t *iter = NULL; + isc_result_t result; + isc_ht_iter_create(catz->entries, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_entry_t *entry = NULL; + + isc_ht_iter_current(iter, (void **)&entry); + dns_catz_entry_detach(catz, &entry); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(catz->entries) == 0); + isc_ht_destroy(&catz->entries); + } + if (catz->coos != NULL) { + isc_ht_iter_t *iter = NULL; + isc_result_t result; + isc_ht_iter_create(catz->coos, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_coo_t *coo = NULL; + + isc_ht_iter_current(iter, (void **)&coo); + catz_coo_detach(catz, &coo); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(catz->coos) == 0); + isc_ht_destroy(&catz->coos); + } + catz->magic = 0; + + isc_mutex_destroy(&catz->lock); + isc_timer_destroy(&catz->updatetimer); + if (catz->db_registered) { + dns_db_updatenotify_unregister( + catz->db, dns_catz_dbupdate_callback, catz->catzs); + } + if (catz->dbversion != NULL) { + dns_db_closeversion(catz->db, &catz->dbversion, false); + } + if (catz->db != NULL) { + dns_db_detach(&catz->db); + } + + INSIST(!catz->updaterunning); + + dns_name_free(&catz->name, mctx); + dns_catz_options_free(&catz->defoptions, mctx); + dns_catz_options_free(&catz->zoneoptions, mctx); + + dns_catz_zones_detach(&catz->catzs); + isc_refcount_destroy(&catz->references); + + isc_mem_put(mctx, catz, sizeof(*catz)); +} + +static void +dns__catz_zones_destroy(dns_catz_zones_t *catzs) { + REQUIRE(atomic_load(&catzs->shuttingdown)); + REQUIRE(catzs->zones == NULL); + + catzs->magic = 0; + isc_task_detach(&catzs->updater); + isc_mutex_destroy(&catzs->lock); + isc_refcount_destroy(&catzs->references); + + isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs)); +} + +void +dns_catz_shutdown_catzs(dns_catz_zones_t *catzs) { + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + if (!atomic_compare_exchange_strong(&catzs->shuttingdown, + &(bool){ false }, true)) + { + return; + } + + LOCK(&catzs->lock); + if (catzs->zones != NULL) { + isc_ht_iter_t *iter = NULL; + isc_result_t result; + isc_ht_iter_create(catzs->zones, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;) + { + dns_catz_zone_t *catz = NULL; + isc_ht_iter_current(iter, (void **)&catz); + result = isc_ht_iter_delcurrent_next(iter); + dns__catz_shutdown(catz); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + INSIST(isc_ht_count(catzs->zones) == 0); + isc_ht_destroy(&catzs->zones); + } + UNLOCK(&catzs->lock); +} + +#ifdef DNS_CATZ_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_catz_zone, dns__catz_zone_destroy); +ISC_REFCOUNT_TRACE_IMPL(dns_catz_zones, dns__catz_zones_destroy); +#else +ISC_REFCOUNT_IMPL(dns_catz_zone, dns__catz_zone_destroy); +ISC_REFCOUNT_IMPL(dns_catz_zones, dns__catz_zones_destroy); +#endif + +typedef enum { + CATZ_OPT_NONE, + CATZ_OPT_ZONES, + CATZ_OPT_COO, + CATZ_OPT_VERSION, + CATZ_OPT_CUSTOM_START, /* CATZ custom properties must go below this */ + CATZ_OPT_EXT, + CATZ_OPT_PRIMARIES, + CATZ_OPT_ALLOW_QUERY, + CATZ_OPT_ALLOW_TRANSFER, +} catz_opt_t; + +static bool +catz_opt_cmp(const dns_label_t *option, const char *opt) { + size_t len = strlen(opt); + + if (option->length - 1 == len && + memcmp(opt, option->base + 1, len) == 0) + { + return (true); + } else { + return (false); + } +} + +static catz_opt_t +catz_get_option(const dns_label_t *option) { + if (catz_opt_cmp(option, "ext")) { + return (CATZ_OPT_EXT); + } else if (catz_opt_cmp(option, "zones")) { + return (CATZ_OPT_ZONES); + } else if (catz_opt_cmp(option, "masters") || + catz_opt_cmp(option, "primaries")) + { + return (CATZ_OPT_PRIMARIES); + } else if (catz_opt_cmp(option, "allow-query")) { + return (CATZ_OPT_ALLOW_QUERY); + } else if (catz_opt_cmp(option, "allow-transfer")) { + return (CATZ_OPT_ALLOW_TRANSFER); + } else if (catz_opt_cmp(option, "coo")) { + return (CATZ_OPT_COO); + } else if (catz_opt_cmp(option, "version")) { + return (CATZ_OPT_VERSION); + } else { + return (CATZ_OPT_NONE); + } +} + +static isc_result_t +catz_process_zones(dns_catz_zone_t *catz, dns_rdataset_t *value, + dns_name_t *name) { + dns_label_t mhash; + dns_name_t opt; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + if (name->labels == 0) { + return (ISC_R_FAILURE); + } + + dns_name_getlabel(name, name->labels - 1, &mhash); + + if (name->labels == 1) { + return (catz_process_zones_entry(catz, value, &mhash)); + } else { + dns_name_init(&opt, NULL); + dns_name_split(name, 1, &opt, NULL); + return (catz_process_zones_suboption(catz, value, &mhash, + &opt)); + } +} + +static isc_result_t +catz_process_coo(dns_catz_zone_t *catz, dns_label_t *mhash, + dns_rdataset_t *value) { + isc_result_t result; + dns_rdata_t rdata; + dns_rdata_ptr_t ptr; + dns_catz_entry_t *entry = NULL; + dns_catz_coo_t *ncoo = NULL; + dns_catz_coo_t *ocoo = NULL; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(mhash != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + /* Change of Ownership was introduced in version "2" of the schema. */ + if (catz->version < 2) { + return (ISC_R_FAILURE); + } + + if (value->type != dns_rdatatype_ptr) { + return (ISC_R_FAILURE); + } + + if (dns_rdataset_count(value) != 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: 'coo' property PTR RRset contains " + "more than one record, which is invalid"); + catz->broken = true; + return (ISC_R_FAILURE); + } + + result = dns_rdataset_first(value); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (dns_name_countlabels(&ptr.ptr) == 0) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = isc_ht_find(catz->entries, mhash->base, mhash->length, + (void **)&entry); + if (result != ISC_R_SUCCESS) { + /* The entry was not found .*/ + goto cleanup; + } + + if (dns_name_countlabels(&entry->name) == 0) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = isc_ht_find(catz->coos, entry->name.ndata, entry->name.length, + (void **)&ocoo); + if (result == ISC_R_SUCCESS) { + /* The change of ownership permission was already registered. */ + goto cleanup; + } + + catz_coo_new(catz->catzs->mctx, &ptr.ptr, &ncoo); + result = isc_ht_add(catz->coos, entry->name.ndata, entry->name.length, + ncoo); + if (result != ISC_R_SUCCESS) { + catz_coo_detach(catz, &ncoo); + } + +cleanup: + dns_rdata_freestruct(&ptr); + + return (result); +} + +static isc_result_t +catz_process_zones_entry(dns_catz_zone_t *catz, dns_rdataset_t *value, + dns_label_t *mhash) { + isc_result_t result; + dns_rdata_t rdata; + dns_rdata_ptr_t ptr; + dns_catz_entry_t *entry = NULL; + + if (value->type != dns_rdatatype_ptr) { + return (ISC_R_FAILURE); + } + + if (dns_rdataset_count(value) != 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: member zone PTR RRset contains " + "more than one record, which is invalid"); + catz->broken = true; + return (ISC_R_FAILURE); + } + + result = dns_rdataset_first(value); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_ht_find(catz->entries, mhash->base, mhash->length, + (void **)&entry); + if (result == ISC_R_SUCCESS) { + if (dns_name_countlabels(&entry->name) != 0) { + /* We have a duplicate. */ + dns_rdata_freestruct(&ptr); + return (ISC_R_FAILURE); + } else { + dns_name_dup(&ptr.ptr, catz->catzs->mctx, &entry->name); + } + } else { + dns_catz_entry_new(catz->catzs->mctx, &ptr.ptr, &entry); + + result = isc_ht_add(catz->entries, mhash->base, mhash->length, + entry); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + dns_catz_entry_detach(catz, &entry); + return (result); + } + } + + dns_rdata_freestruct(&ptr); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_version(dns_catz_zone_t *catz, dns_rdataset_t *value) { + isc_result_t result; + dns_rdata_t rdata; + dns_rdata_txt_t rdatatxt; + dns_rdata_txt_string_t rdatastr; + uint32_t tversion; + char t[16]; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_RDATASET_VALID(value)); + + if (value->type != dns_rdatatype_txt) { + return (ISC_R_FAILURE); + } + + if (dns_rdataset_count(value) != 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: 'version' property TXT RRset contains " + "more than one record, which is invalid"); + catz->broken = true; + return (ISC_R_FAILURE); + } + + result = dns_rdataset_first(value); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + result = dns_rdata_tostruct(&rdata, &rdatatxt, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_rdata_txt_first(&rdatatxt); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdata_txt_current(&rdatatxt, &rdatastr); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdata_txt_next(&rdatatxt); + if (result != ISC_R_NOMORE) { + result = ISC_R_FAILURE; + goto cleanup; + } + if (rdatastr.length > 15) { + result = ISC_R_BADNUMBER; + goto cleanup; + } + memmove(t, rdatastr.data, rdatastr.length); + t[rdatastr.length] = 0; + result = isc_parse_uint32(&tversion, t, 10); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + catz->version = tversion; + result = ISC_R_SUCCESS; + +cleanup: + dns_rdata_freestruct(&rdatatxt); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: invalid record for the catalog " + "zone version property"); + catz->broken = true; + } + return (result); +} + +static isc_result_t +catz_process_primaries(dns_catz_zone_t *catz, dns_ipkeylist_t *ipkl, + dns_rdataset_t *value, dns_name_t *name) { + isc_result_t result; + dns_rdata_t rdata; + dns_rdata_in_a_t rdata_a; + dns_rdata_in_aaaa_t rdata_aaaa; + dns_rdata_txt_t rdata_txt; + dns_rdata_txt_string_t rdatastr; + dns_name_t *keyname = NULL; + isc_mem_t *mctx; + char keycbuf[DNS_NAME_FORMATSIZE]; + isc_buffer_t keybuf; + unsigned int rcount; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(ipkl != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(dns_rdataset_isassociated(value)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + mctx = catz->catzs->mctx; + memset(&rdata_a, 0, sizeof(rdata_a)); + memset(&rdata_aaaa, 0, sizeof(rdata_aaaa)); + memset(&rdata_txt, 0, sizeof(rdata_txt)); + isc_buffer_init(&keybuf, keycbuf, sizeof(keycbuf)); + + /* + * We have three possibilities here: + * - either empty name and IN A/IN AAAA record + * - label and IN A/IN AAAA + * - label and IN TXT - TSIG key name + */ + if (name->labels > 0) { + isc_sockaddr_t sockaddr; + size_t i; + + /* + * We're pre-preparing the data once, we'll put it into + * the right spot in the primaries array once we find it. + */ + result = dns_rdataset_first(value); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + switch (value->type) { + case dns_rdatatype_a: + result = dns_rdata_tostruct(&rdata, &rdata_a, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin(&sockaddr, &rdata_a.in_addr, 0); + dns_rdata_freestruct(&rdata_a); + break; + case dns_rdatatype_aaaa: + result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin6(&sockaddr, &rdata_aaaa.in6_addr, + 0); + dns_rdata_freestruct(&rdata_aaaa); + break; + case dns_rdatatype_txt: + result = dns_rdata_tostruct(&rdata, &rdata_txt, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = dns_rdata_txt_first(&rdata_txt); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&rdata_txt); + return (result); + } + + result = dns_rdata_txt_current(&rdata_txt, &rdatastr); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&rdata_txt); + return (result); + } + + result = dns_rdata_txt_next(&rdata_txt); + if (result != ISC_R_NOMORE) { + dns_rdata_freestruct(&rdata_txt); + return (ISC_R_FAILURE); + } + + /* rdatastr.length < DNS_NAME_MAXTEXT */ + keyname = isc_mem_get(mctx, sizeof(*keyname)); + dns_name_init(keyname, 0); + memmove(keycbuf, rdatastr.data, rdatastr.length); + keycbuf[rdatastr.length] = 0; + dns_rdata_freestruct(&rdata_txt); + result = dns_name_fromstring(keyname, keycbuf, 0, mctx); + if (result != ISC_R_SUCCESS) { + dns_name_free(keyname, mctx); + isc_mem_put(mctx, keyname, sizeof(*keyname)); + return (result); + } + break; + default: + return (ISC_R_FAILURE); + } + + /* + * We have to find the appropriate labeled record in + * primaries if it exists. In the common case we'll + * have no more than 3-4 records here, so no optimization. + */ + for (i = 0; i < ipkl->count; i++) { + if (ipkl->labels[i] != NULL && + !dns_name_compare(name, ipkl->labels[i])) + { + break; + } + } + + if (i < ipkl->count) { /* we have this record already */ + if (value->type == dns_rdatatype_txt) { + ipkl->keys[i] = keyname; + } else { /* A/AAAA */ + memmove(&ipkl->addrs[i], &sockaddr, + sizeof(sockaddr)); + } + } else { + result = dns_ipkeylist_resize(mctx, ipkl, i + 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + + ipkl->labels[i] = isc_mem_get(mctx, + sizeof(*ipkl->labels[0])); + dns_name_init(ipkl->labels[i], NULL); + dns_name_dup(name, mctx, ipkl->labels[i]); + + if (value->type == dns_rdatatype_txt) { + ipkl->keys[i] = keyname; + } else { /* A/AAAA */ + memmove(&ipkl->addrs[i], &sockaddr, + sizeof(sockaddr)); + } + ipkl->count++; + } + return (ISC_R_SUCCESS); + } + /* else - 'simple' case - without labels */ + + if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa) + { + return (ISC_R_FAILURE); + } + + rcount = dns_rdataset_count(value) + ipkl->count; + + result = dns_ipkeylist_resize(mctx, ipkl, rcount); + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (result = dns_rdataset_first(value); result == ISC_R_SUCCESS; + result = dns_rdataset_next(value)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + /* + * port 0 == take the default + */ + if (value->type == dns_rdatatype_a) { + result = dns_rdata_tostruct(&rdata, &rdata_a, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin(&ipkl->addrs[ipkl->count], + &rdata_a.in_addr, 0); + dns_rdata_freestruct(&rdata_a); + } else { + result = dns_rdata_tostruct(&rdata, &rdata_aaaa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin6(&ipkl->addrs[ipkl->count], + &rdata_aaaa.in6_addr, 0); + dns_rdata_freestruct(&rdata_aaaa); + } + ipkl->keys[ipkl->count] = NULL; + ipkl->labels[ipkl->count] = NULL; + ipkl->count++; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_apl(dns_catz_zone_t *catz, isc_buffer_t **aclbp, + dns_rdataset_t *value) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata; + dns_rdata_in_apl_t rdata_apl; + dns_rdata_apl_ent_t apl_ent; + isc_netaddr_t addr; + isc_buffer_t *aclb = NULL; + unsigned char buf[256]; /* larger than INET6_ADDRSTRLEN */ + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(aclbp != NULL); + REQUIRE(*aclbp == NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(dns_rdataset_isassociated(value)); + + if (value->type != dns_rdatatype_apl) { + return (ISC_R_FAILURE); + } + + if (dns_rdataset_count(value) > 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: more than one APL entry for member zone, " + "result is undefined"); + } + result = dns_rdataset_first(value); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + result = dns_rdata_tostruct(&rdata, &rdata_apl, catz->catzs->mctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_allocate(catz->catzs->mctx, &aclb, 16); + isc_buffer_setautorealloc(aclb, true); + for (result = dns_rdata_apl_first(&rdata_apl); result == ISC_R_SUCCESS; + result = dns_rdata_apl_next(&rdata_apl)) + { + result = dns_rdata_apl_current(&rdata_apl, &apl_ent); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + memset(buf, 0, sizeof(buf)); + if (apl_ent.data != NULL && apl_ent.length > 0) { + memmove(buf, apl_ent.data, apl_ent.length); + } + if (apl_ent.family == 1) { + isc_netaddr_fromin(&addr, (struct in_addr *)buf); + } else if (apl_ent.family == 2) { + isc_netaddr_fromin6(&addr, (struct in6_addr *)buf); + } else { + continue; /* xxxwpk log it or simply ignore? */ + } + if (apl_ent.negative) { + isc_buffer_putuint8(aclb, '!'); + } + isc_buffer_reserve(&aclb, INET6_ADDRSTRLEN); + result = isc_netaddr_totext(&addr, aclb); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if ((apl_ent.family == 1 && apl_ent.prefix < 32) || + (apl_ent.family == 2 && apl_ent.prefix < 128)) + { + isc_buffer_putuint8(aclb, '/'); + isc_buffer_putdecint(aclb, apl_ent.prefix); + } + isc_buffer_putstr(aclb, "; "); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } else { + goto cleanup; + } + *aclbp = aclb; + aclb = NULL; +cleanup: + if (aclb != NULL) { + isc_buffer_free(&aclb); + } + dns_rdata_freestruct(&rdata_apl); + return (result); +} + +static isc_result_t +catz_process_zones_suboption(dns_catz_zone_t *catz, dns_rdataset_t *value, + dns_label_t *mhash, dns_name_t *name) { + isc_result_t result; + dns_catz_entry_t *entry = NULL; + dns_label_t option; + dns_name_t prefix; + catz_opt_t opt; + unsigned int suffix_labels = 1; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(mhash != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + if (name->labels < 1) { + return (ISC_R_FAILURE); + } + dns_name_getlabel(name, name->labels - 1, &option); + opt = catz_get_option(&option); + + /* + * The custom properties in version 2 schema must be placed under the + * "ext" label. + */ + if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) { + if (opt != CATZ_OPT_EXT || name->labels < 2) { + return (ISC_R_FAILURE); + } + suffix_labels++; + dns_name_getlabel(name, name->labels - 2, &option); + opt = catz_get_option(&option); + } + + /* + * We're adding this entry now, in case the option is invalid we'll get + * rid of it in verification phase. + */ + result = isc_ht_find(catz->entries, mhash->base, mhash->length, + (void **)&entry); + if (result != ISC_R_SUCCESS) { + dns_catz_entry_new(catz->catzs->mctx, NULL, &entry); + result = isc_ht_add(catz->entries, mhash->base, mhash->length, + entry); + if (result != ISC_R_SUCCESS) { + dns_catz_entry_detach(catz, &entry); + return (result); + } + } + + dns_name_init(&prefix, NULL); + dns_name_split(name, suffix_labels, &prefix, NULL); + switch (opt) { + case CATZ_OPT_COO: + return (catz_process_coo(catz, mhash, value)); + case CATZ_OPT_PRIMARIES: + return (catz_process_primaries(catz, &entry->opts.masters, + value, &prefix)); + case CATZ_OPT_ALLOW_QUERY: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(catz, &entry->opts.allow_query, + value)); + case CATZ_OPT_ALLOW_TRANSFER: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(catz, &entry->opts.allow_transfer, + value)); + default: + return (ISC_R_FAILURE); + } + + return (ISC_R_FAILURE); +} + +static void +catz_entry_add_or_mod(dns_catz_zone_t *catz, isc_ht_t *ht, unsigned char *key, + size_t keysize, dns_catz_entry_t *nentry, + dns_catz_entry_t *oentry, const char *msg, + const char *zname, const char *czname) { + isc_result_t result = isc_ht_add(ht, key, (uint32_t)keysize, nentry); + + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: error %s zone '%s' from catalog '%s' - %s", + msg, zname, czname, isc_result_totext(result)); + } + if (oentry != NULL) { + dns_catz_entry_detach(catz, &oentry); + result = isc_ht_delete(catz->entries, key, (uint32_t)keysize); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +} + +static isc_result_t +catz_process_value(dns_catz_zone_t *catz, dns_name_t *name, + dns_rdataset_t *rdataset) { + dns_label_t option; + dns_name_t prefix; + catz_opt_t opt; + unsigned int suffix_labels = 1; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + if (name->labels < 1) { + return (ISC_R_FAILURE); + } + dns_name_getlabel(name, name->labels - 1, &option); + opt = catz_get_option(&option); + + /* + * The custom properties in version 2 schema must be placed under the + * "ext" label. + */ + if (catz->version >= 2 && opt >= CATZ_OPT_CUSTOM_START) { + if (opt != CATZ_OPT_EXT || name->labels < 2) { + return (ISC_R_FAILURE); + } + suffix_labels++; + dns_name_getlabel(name, name->labels - 2, &option); + opt = catz_get_option(&option); + } + + dns_name_init(&prefix, NULL); + dns_name_split(name, suffix_labels, &prefix, NULL); + + switch (opt) { + case CATZ_OPT_ZONES: + return (catz_process_zones(catz, rdataset, &prefix)); + case CATZ_OPT_PRIMARIES: + return (catz_process_primaries(catz, &catz->zoneoptions.masters, + rdataset, &prefix)); + case CATZ_OPT_ALLOW_QUERY: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(catz, &catz->zoneoptions.allow_query, + rdataset)); + case CATZ_OPT_ALLOW_TRANSFER: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl( + catz, &catz->zoneoptions.allow_transfer, rdataset)); + case CATZ_OPT_VERSION: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_version(catz, rdataset)); + default: + return (ISC_R_FAILURE); + } +} + +/*%< + * Process a single rdataset from a catalog zone 'catz' update, src_name is the + * record name. + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + * \li 'src_name' is a valid dns_name_t. + * \li 'rdataset' is valid rdataset. + */ +static isc_result_t +dns__catz_update_process(dns_catz_zone_t *catz, const dns_name_t *src_name, + dns_rdataset_t *rdataset) { + isc_result_t result; + int order; + unsigned int nlabels; + dns_namereln_t nrres; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + dns_name_t prefix; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC)); + + if (rdataset->rdclass != dns_rdataclass_in) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: RR found which has a non-IN class"); + catz->broken = true; + return (ISC_R_FAILURE); + } + + nrres = dns_name_fullcompare(src_name, &catz->name, &order, &nlabels); + if (nrres == dns_namereln_equal) { + if (rdataset->type == dns_rdatatype_soa) { + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * xxxwpk TODO do we want to save something from SOA? + */ + dns_rdata_freestruct(&soa); + return (result); + } else if (rdataset->type == dns_rdatatype_ns) { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_UNEXPECTED); + } + } else if (nrres != dns_namereln_subdomain) { + return (ISC_R_UNEXPECTED); + } + + dns_name_init(&prefix, NULL); + dns_name_split(src_name, catz->name.labels, &prefix, NULL); + result = catz_process_value(catz, &prefix, rdataset); + + return (result); +} + +static isc_result_t +digest2hex(unsigned char *digest, unsigned int digestlen, char *hash, + size_t hashlen) { + unsigned int i; + for (i = 0; i < digestlen; i++) { + size_t left = hashlen - i * 2; + int ret = snprintf(hash + i * 2, left, "%02x", digest[i]); + if (ret < 0 || (size_t)ret >= left) { + return (ISC_R_NOSPACE); + } + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_catz_generate_masterfilename(dns_catz_zone_t *catz, dns_catz_entry_t *entry, + isc_buffer_t **buffer) { + isc_buffer_t *tbuf = NULL; + isc_region_t r; + isc_result_t result; + size_t rlen; + bool special = false; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(buffer != NULL && *buffer != NULL); + + isc_buffer_allocate(catz->catzs->mctx, &tbuf, + strlen(catz->catzs->view->name) + + 2 * DNS_NAME_FORMATSIZE + 2); + + isc_buffer_putstr(tbuf, catz->catzs->view->name); + isc_buffer_putstr(tbuf, "_"); + result = dns_name_totext(&catz->name, true, tbuf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_buffer_putstr(tbuf, "_"); + result = dns_name_totext(&entry->name, true, tbuf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Search for slash and other special characters in the view and + * zone names. Add a null terminator so we can use strpbrk(), then + * remove it. + */ + isc_buffer_putuint8(tbuf, 0); + if (strpbrk(isc_buffer_base(tbuf), "\\/:") != NULL) { + special = true; + } + isc_buffer_subtract(tbuf, 1); + + /* __catz__.db */ + rlen = (isc_md_type_get_size(ISC_MD_SHA256) * 2 + 1) + 12; + + /* optionally prepend with / */ + if (entry->opts.zonedir != NULL) { + rlen += strlen(entry->opts.zonedir) + 1; + } + + result = isc_buffer_reserve(buffer, (unsigned int)rlen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (entry->opts.zonedir != NULL) { + isc_buffer_putstr(*buffer, entry->opts.zonedir); + isc_buffer_putstr(*buffer, "/"); + } + + isc_buffer_usedregion(tbuf, &r); + isc_buffer_putstr(*buffer, "__catz__"); + if (special || tbuf->used > ISC_SHA256_DIGESTLENGTH * 2 + 1) { + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + + /* we can do that because digest string < 2 * DNS_NAME */ + result = isc_md(ISC_MD_SHA256, r.base, r.length, digest, + &digestlen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = digest2hex(digest, digestlen, (char *)r.base, + ISC_SHA256_DIGESTLENGTH * 2 + 1); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_putstr(*buffer, (char *)r.base); + } else { + isc_buffer_copyregion(*buffer, &r); + } + + isc_buffer_putstr(*buffer, ".db"); + result = ISC_R_SUCCESS; + +cleanup: + isc_buffer_free(&tbuf); + return (result); +} + +/* + * We have to generate a text buffer with regular zone config: + * zone "foo.bar" { + * type secondary; + * primaries { ip1 port port1; ip2 port port2; }; + * } + */ +isc_result_t +dns_catz_generate_zonecfg(dns_catz_zone_t *catz, dns_catz_entry_t *entry, + isc_buffer_t **buf) { + isc_buffer_t *buffer = NULL; + isc_region_t region; + isc_result_t result; + uint32_t i; + isc_netaddr_t netaddr; + char pbuf[sizeof("65535")]; /* used for port number */ + char zname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(buf != NULL && *buf == NULL); + + /* + * The buffer will be reallocated if something won't fit, + * ISC_BUFFER_INCR seems like a good start. + */ + isc_buffer_allocate(catz->catzs->mctx, &buffer, ISC_BUFFER_INCR); + + isc_buffer_setautorealloc(buffer, true); + isc_buffer_putstr(buffer, "zone \""); + dns_name_totext(&entry->name, true, buffer); + isc_buffer_putstr(buffer, "\" { type secondary; primaries"); + + isc_buffer_putstr(buffer, " { "); + for (i = 0; i < entry->opts.masters.count; i++) { + /* + * Every primary must have an IP address assigned. + */ + switch (entry->opts.masters.addrs[i].type.sa.sa_family) { + case AF_INET: + case AF_INET6: + break; + default: + dns_name_format(&entry->name, zname, + DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' uses an invalid primary " + "(no IP address assigned)", + zname); + result = ISC_R_FAILURE; + goto cleanup; + } + isc_netaddr_fromsockaddr(&netaddr, + &entry->opts.masters.addrs[i]); + isc_buffer_reserve(&buffer, INET6_ADDRSTRLEN); + result = isc_netaddr_totext(&netaddr, buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_buffer_putstr(buffer, " port "); + snprintf(pbuf, sizeof(pbuf), "%u", + isc_sockaddr_getport(&entry->opts.masters.addrs[i])); + isc_buffer_putstr(buffer, pbuf); + + if (entry->opts.masters.keys[i] != NULL) { + isc_buffer_putstr(buffer, " key "); + result = dns_name_totext(entry->opts.masters.keys[i], + true, buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + if (entry->opts.masters.tlss[i] != NULL) { + isc_buffer_putstr(buffer, " tls "); + result = dns_name_totext(entry->opts.masters.tlss[i], + true, buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + isc_buffer_putstr(buffer, "; "); + } + isc_buffer_putstr(buffer, "}; "); + if (!entry->opts.in_memory) { + isc_buffer_putstr(buffer, "file \""); + result = dns_catz_generate_masterfilename(catz, entry, &buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_putstr(buffer, "\"; "); + } + if (entry->opts.allow_query != NULL) { + isc_buffer_putstr(buffer, "allow-query { "); + isc_buffer_usedregion(entry->opts.allow_query, ®ion); + isc_buffer_copyregion(buffer, ®ion); + isc_buffer_putstr(buffer, "}; "); + } + if (entry->opts.allow_transfer != NULL) { + isc_buffer_putstr(buffer, "allow-transfer { "); + isc_buffer_usedregion(entry->opts.allow_transfer, ®ion); + isc_buffer_copyregion(buffer, ®ion); + isc_buffer_putstr(buffer, "}; "); + } + + isc_buffer_putstr(buffer, "};"); + *buf = buffer; + + return (ISC_R_SUCCESS); + +cleanup: + isc_buffer_free(&buffer); + return (result); +} + +static void +dns__catz_timer_cb(isc_task_t *task, isc_event_t *event) { + char domain[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_catz_zone_t *catz = NULL; + + UNUSED(task); + REQUIRE(event != NULL); + REQUIRE(event->ev_arg != NULL); + + catz = (dns_catz_zone_t *)event->ev_arg; + isc_event_free(&event); + + REQUIRE(isc_nm_tid() >= 0); + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + if (atomic_load(&catz->catzs->shuttingdown)) { + return; + } + + LOCK(&catz->catzs->lock); + + INSIST(DNS_DB_VALID(catz->db)); + INSIST(catz->dbversion != NULL); + INSIST(catz->updb == NULL); + INSIST(catz->updbversion == NULL); + + catz->updatepending = false; + catz->updaterunning = true; + catz->updateresult = ISC_R_UNSET; + + dns_name_format(&catz->name, domain, DNS_NAME_FORMATSIZE); + + if (!catz->active) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: %s: no longer active, reload is canceled", + domain); + catz->updaterunning = false; + catz->updateresult = ISC_R_CANCELED; + goto exit; + } + + dns_db_attach(catz->db, &catz->updb); + catz->updbversion = catz->dbversion; + catz->dbversion = NULL; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, "catz: %s: reload start", domain); + + dns_catz_ref_catz(catz); + isc_nm_work_offload(isc_task_getnetmgr(catz->catzs->updater), + dns__catz_update_cb, dns__catz_done_cb, catz); + +exit: + result = isc_time_now(&catz->lastupdated); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + UNLOCK(&catz->catzs->lock); +} + +isc_result_t +dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) { + dns_catz_zones_t *catzs = (dns_catz_zones_t *)fn_arg; + dns_catz_zone_t *catz = NULL; + isc_time_t now; + isc_result_t result = ISC_R_SUCCESS; + isc_region_t r; + char dname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + if (atomic_load(&catzs->shuttingdown)) { + return (ISC_R_SHUTTINGDOWN); + } + + dns_name_toregion(&db->origin, &r); + + LOCK(&catzs->lock); + if (catzs->zones == NULL) { + result = ISC_R_SHUTTINGDOWN; + goto cleanup; + } + result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&catz); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* New zone came as AXFR */ + if (catz->db != NULL && catz->db != db) { + /* Old db cleanup. */ + if (catz->dbversion != NULL) { + dns_db_closeversion(catz->db, &catz->dbversion, false); + } + dns_db_updatenotify_unregister( + catz->db, dns_catz_dbupdate_callback, catz->catzs); + dns_db_detach(&catz->db); + catz->db_registered = false; + } + if (catz->db == NULL) { + /* New db registration. */ + dns_db_attach(db, &catz->db); + result = dns_db_updatenotify_register( + db, dns_catz_dbupdate_callback, catz->catzs); + if (result == ISC_R_SUCCESS) { + catz->db_registered = true; + } + } + + dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE); + + if (!catz->updatepending && !catz->updaterunning) { + uint64_t tdiff; + + catz->updatepending = true; + + isc_time_now(&now); + tdiff = isc_time_microdiff(&now, &catz->lastupdated) / 1000000; + if (tdiff < catz->defoptions.min_update_interval) { + uint64_t defer = catz->defoptions.min_update_interval - + tdiff; + isc_interval_t interval; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: %s: new zone version came " + "too soon, deferring update for " + "%" PRIu64 " seconds", + dname, defer); + isc_interval_set(&interval, (unsigned int)defer, 0); + dns_db_currentversion(db, &catz->dbversion); + result = isc_timer_reset(catz->updatetimer, + isc_timertype_once, NULL, + &interval, true); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + isc_event_t *event; + + dns_db_currentversion(db, &catz->dbversion); + ISC_EVENT_INIT( + &catz->updateevent, sizeof(catz->updateevent), + 0, NULL, DNS_EVENT_CATZUPDATED, + dns__catz_timer_cb, catz, catz, NULL, NULL); + event = &catz->updateevent; + isc_task_send(catzs->updater, &event); + } + } else { + catz->updatepending = true; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: %s: update already queued or running", + dname); + if (catz->dbversion != NULL) { + dns_db_closeversion(catz->db, &catz->dbversion, false); + } + dns_db_currentversion(catz->db, &catz->dbversion); + } + +cleanup: + UNLOCK(&catzs->lock); + + return (result); +} + +void +dns_catz_dbupdate_unregister(dns_db_t *db, dns_catz_zones_t *catzs) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + dns_db_updatenotify_unregister(db, dns_catz_dbupdate_callback, catzs); +} + +void +dns_catz_dbupdate_register(dns_db_t *db, dns_catz_zones_t *catzs) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + dns_db_updatenotify_register(db, dns_catz_dbupdate_callback, catzs); +} + +static bool +catz_rdatatype_is_processable(const dns_rdatatype_t type) { + return (!dns_rdatatype_isdnssec(type) && type != dns_rdatatype_cds && + type != dns_rdatatype_cdnskey && type != dns_rdatatype_zonemd); +} + +/* + * Process an updated database for a catalog zone. + * It creates a new catz, iterates over database to fill it with content, and + * then merges new catz into old catz. + */ +static void +dns__catz_update_cb(void *data) { + dns_catz_zone_t *catz = (dns_catz_zone_t *)data; + dns_db_t *updb = NULL; + dns_catz_zones_t *catzs = NULL; + dns_catz_zone_t *oldcatz = NULL, *newcatz = NULL; + isc_result_t result; + isc_region_t r; + dns_dbnode_t *node = NULL; + const dns_dbnode_t *vers_node = NULL; + dns_dbiterator_t *updbit = NULL; + dns_fixedname_t fixname; + dns_name_t *name; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + char bname[DNS_NAME_FORMATSIZE]; + char cname[DNS_NAME_FORMATSIZE]; + bool is_vers_processed = false; + bool is_active; + uint32_t vers; + uint32_t catz_vers; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + REQUIRE(DNS_DB_VALID(catz->updb)); + REQUIRE(DNS_CATZ_ZONES_VALID(catz->catzs)); + + updb = catz->updb; + catzs = catz->catzs; + + if (atomic_load(&catzs->shuttingdown)) { + result = ISC_R_SHUTTINGDOWN; + goto exit; + } + + dns_name_format(&updb->origin, bname, DNS_NAME_FORMATSIZE); + + /* + * Create a new catz in the same context as current catz. + */ + dns_name_toregion(&updb->origin, &r); + LOCK(&catzs->lock); + result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&oldcatz); + is_active = (result == ISC_R_SUCCESS && oldcatz->active); + UNLOCK(&catzs->lock); + if (result != ISC_R_SUCCESS) { + /* This can happen if we remove the zone in the meantime. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' not in config", bname); + goto exit; + } + + INSIST(catz == oldcatz); + + if (!is_active) { + /* This can happen during a reconfiguration. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: zone '%s' is no longer active", bname); + result = ISC_R_CANCELED; + goto exit; + } + + result = dns_db_getsoaserial(updb, oldcatz->updbversion, &vers); + if (result != ISC_R_SUCCESS) { + /* A zone without SOA record?!? */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' has no SOA record (%s)", bname, + isc_result_totext(result)); + goto exit; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, + "catz: updating catalog zone '%s' with serial %" PRIu32, + bname, vers); + + result = dns_catz_new_zone(catzs, &newcatz, &updb->origin); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to create new zone - %s", + isc_result_totext(result)); + goto exit; + } + + result = dns_db_createiterator(updb, DNS_DB_NONSEC3, &updbit); + if (result != ISC_R_SUCCESS) { + dns_catz_detach_catz(&newcatz); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to create DB iterator - %s", + isc_result_totext(result)); + goto exit; + } + + name = dns_fixedname_initname(&fixname); + + /* + * Take the version record to process first, because the other + * records might be processed differently depending on the version of + * the catalog zone's schema. + */ + result = dns_name_fromstring2(name, "version", &updb->origin, 0, NULL); + if (result != ISC_R_SUCCESS) { + dns_dbiterator_destroy(&updbit); + dns_catz_detach_catz(&newcatz); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to create name from string - %s", + isc_result_totext(result)); + goto exit; + } + result = dns_dbiterator_seek(updbit, name); + if (result != ISC_R_SUCCESS) { + dns_dbiterator_destroy(&updbit); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: zone '%s' has no 'version' record (%s)", + bname, isc_result_totext(result)); + newcatz->broken = true; + goto final; + } + + name = dns_fixedname_initname(&fixname); + + /* + * Iterate over database to fill the new zone. + */ + while (result == ISC_R_SUCCESS) { + if (atomic_load(&catzs->shuttingdown)) { + result = ISC_R_SHUTTINGDOWN; + break; + } + + result = dns_dbiterator_current(updbit, &node, name); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to get db iterator - %s", + isc_result_totext(result)); + break; + } + + result = dns_dbiterator_pause(updbit); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (!is_vers_processed) { + /* Keep the version node to skip it later in the loop */ + vers_node = node; + } else if (node == vers_node) { + /* Skip the already processed version node */ + dns_db_detachnode(updb, &node); + result = dns_dbiterator_next(updbit); + continue; + } + + result = dns_db_allrdatasets(updb, node, oldcatz->updbversion, + 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed to fetch rrdatasets - %s", + isc_result_totext(result)); + dns_db_detachnode(updb, &node); + break; + } + + dns_rdataset_init(&rdataset); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + + /* + * Skip processing DNSSEC-related and ZONEMD types, + * because we are not interested in them in the context + * of a catalog zone, and processing them will fail + * and produce an unnecessary warning message. + */ + if (!catz_rdatatype_is_processable(rdataset.type)) { + goto next; + } + + /* + * Although newcatz->coos is accessed in + * catz_process_coo() in the call-chain below, we don't + * need to hold the newcatz->lock, because the newcatz + * is still local to this thread and function and + * newcatz->coos can't be accessed from the outside + * until dns__catz_zones_merge() has been called. + */ + result = dns__catz_update_process(newcatz, name, + &rdataset); + if (result != ISC_R_SUCCESS) { + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + + dns_name_format(name, cname, + DNS_NAME_FORMATSIZE); + dns_rdataclass_format(rdataset.rdclass, + classbuf, + sizeof(classbuf)); + dns_rdatatype_format(rdataset.type, typebuf, + sizeof(typebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_WARNING, + "catz: invalid record in catalog " + "zone - %s %s %s (%s) - ignoring", + cname, classbuf, typebuf, + isc_result_totext(result)); + } + next: + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + + dns_rdatasetiter_destroy(&rdsiter); + + dns_db_detachnode(updb, &node); + + if (!is_vers_processed) { + is_vers_processed = true; + result = dns_dbiterator_first(updbit); + } else { + result = dns_dbiterator_next(updbit); + } + } + + dns_dbiterator_destroy(&updbit); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: update_from_db: iteration finished: %s", + isc_result_totext(result)); + + /* + * Check catalog zone version compatibilites. + */ + catz_vers = (newcatz->version == DNS_CATZ_VERSION_UNDEFINED) + ? oldcatz->version + : newcatz->version; + if (catz_vers == DNS_CATZ_VERSION_UNDEFINED) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: zone '%s' version is not set", bname); + newcatz->broken = true; + } else if (catz_vers != 1 && catz_vers != 2) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: zone '%s' unsupported version " + "'%" PRIu32 "'", + bname, catz_vers); + newcatz->broken = true; + } else { + oldcatz->version = catz_vers; + } + +final: + if (newcatz->broken) { + dns_name_format(name, cname, DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: new catalog zone '%s' is broken and " + "will not be processed", + bname); + dns_catz_detach_catz(&newcatz); + result = ISC_R_FAILURE; + goto exit; + } + + /* + * Finally merge new zone into old zone. + */ + result = dns__catz_zones_merge(oldcatz, newcatz); + dns_catz_detach_catz(&newcatz); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "catz: failed merging zones: %s", + isc_result_totext(result)); + + goto exit; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: update_from_db: new zone merged"); + +exit: + catz->updateresult = result; +} + +static void +dns__catz_done_cb(void *data, isc_result_t result) { + dns_catz_zone_t *catz = (dns_catz_zone_t *)data; + char dname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + if (result == ISC_R_SUCCESS && catz->updateresult != ISC_R_SUCCESS) { + result = catz->updateresult; + } + + LOCK(&catz->catzs->lock); + catz->updaterunning = false; + + dns_name_format(&catz->name, dname, DNS_NAME_FORMATSIZE); + + /* + * When we're doing reconfig and setting a new catalog zone + * from an existing zone we won't have a chance to set up + * update callback in zone_startload or axfr_makedb, but we will + * call onupdate() artificially so we can register the callback + * here. + */ + if (result == ISC_R_SUCCESS && !catz->db_registered) { + result = dns_db_updatenotify_register( + catz->db, dns_catz_dbupdate_callback, catz->catzs); + if (result == ISC_R_SUCCESS) { + catz->db_registered = true; + } + } + + /* If there's no update pending, or if shutting down, finish. */ + if (!catz->updatepending || atomic_load(&catz->catzs->shuttingdown)) { + goto done; + } + + /* If there's an update pending, schedule it */ + if (catz->defoptions.min_update_interval > 0) { + uint64_t defer = catz->defoptions.min_update_interval; + isc_interval_t interval; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: %s: new zone version came " + "too soon, deferring update for " + "%" PRIu64 " seconds", + dname, defer); + isc_interval_set(&interval, (unsigned int)defer, 0); + (void)isc_timer_reset(catz->updatetimer, isc_timertype_once, + NULL, &interval, true); + } else { + isc_event_t *event = NULL; + INSIST(!ISC_LINK_LINKED(&catz->updateevent, ev_link)); + ISC_EVENT_INIT(&catz->updateevent, sizeof(catz->updateevent), 0, + NULL, DNS_EVENT_CATZUPDATED, dns__catz_timer_cb, + catz, catz, NULL, NULL); + event = &catz->updateevent; + isc_task_send(catz->catzs->updater, &event); + } + +done: + dns_db_closeversion(catz->updb, &catz->updbversion, false); + dns_db_detach(&catz->updb); + + UNLOCK(&catz->catzs->lock); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, "catz: %s: reload done: %s", dname, + isc_result_totext(result)); + + dns_catz_unref_catz(catz); +} + +void +dns_catz_prereconfig(dns_catz_zones_t *catzs) { + isc_result_t result; + isc_ht_iter_t *iter = NULL; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + LOCK(&catzs->lock); + isc_ht_iter_create(catzs->zones, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(iter)) + { + dns_catz_zone_t *catz = NULL; + isc_ht_iter_current(iter, (void **)&catz); + catz->active = false; + } + UNLOCK(&catzs->lock); + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); +} + +void +dns_catz_postreconfig(dns_catz_zones_t *catzs) { + isc_result_t result; + dns_catz_zone_t *newcatz = NULL; + isc_ht_iter_t *iter = NULL; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + LOCK(&catzs->lock); + isc_ht_iter_create(catzs->zones, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS;) { + dns_catz_zone_t *catz = NULL; + + isc_ht_iter_current(iter, (void **)&catz); + if (!catz->active) { + char cname[DNS_NAME_FORMATSIZE]; + dns_name_format(&catz->name, cname, + DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_WARNING, + "catz: removing catalog zone %s", cname); + + /* + * Merge the old zone with an empty one to remove + * all members. + */ + result = dns_catz_new_zone(catzs, &newcatz, + &catz->name); + INSIST(result == ISC_R_SUCCESS); + dns__catz_zones_merge(catz, newcatz); + dns_catz_detach_catz(&newcatz); + + /* Make sure that we have an empty catalog zone. */ + INSIST(isc_ht_count(catz->entries) == 0); + result = isc_ht_iter_delcurrent_next(iter); + dns_catz_detach_catz(&catz); + } else { + result = isc_ht_iter_next(iter); + } + } + UNLOCK(&catzs->lock); + RUNTIME_CHECK(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); +} + +void +dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) { + REQUIRE(DNS_CATZ_ZONE_VALID(catz)); + + isc_ht_iter_create(catz->entries, itp); +} diff --git a/lib/dns/client.c b/lib/dns/client.c new file mode 100644 index 0000000..9d8eb87 --- /dev/null +++ b/lib/dns/client.c @@ -0,0 +1,1326 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DNS_CLIENT_MAGIC ISC_MAGIC('D', 'N', 'S', 'c') +#define DNS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, DNS_CLIENT_MAGIC) + +#define RCTX_MAGIC ISC_MAGIC('R', 'c', 't', 'x') +#define RCTX_VALID(c) ISC_MAGIC_VALID(c, RCTX_MAGIC) + +#define UCTX_MAGIC ISC_MAGIC('U', 'c', 't', 'x') +#define UCTX_VALID(c) ISC_MAGIC_VALID(c, UCTX_MAGIC) + +#define MAX_RESTARTS 16 + +#ifdef TUNE_LARGE +#define RESOLVER_NTASKS 523 +#else /* ifdef TUNE_LARGE */ +#define RESOLVER_NTASKS 31 +#endif /* TUNE_LARGE */ + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +/*% + * DNS client object + */ +struct dns_client { + /* Unlocked */ + unsigned int magic; + unsigned int attributes; + isc_mutex_t lock; + isc_mem_t *mctx; + isc_appctx_t *actx; + isc_taskmgr_t *taskmgr; + isc_task_t *task; + isc_nm_t *nm; + isc_timermgr_t *timermgr; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatchv4; + dns_dispatch_t *dispatchv6; + + unsigned int find_timeout; + unsigned int find_udpretries; + + isc_refcount_t references; + + /* Locked */ + dns_viewlist_t viewlist; + ISC_LIST(struct resctx) resctxs; +}; + +#define DEF_FIND_TIMEOUT 5 +#define DEF_FIND_UDPRETRIES 3 + +/*% + * Internal state for a single name resolution procedure + */ +typedef struct resctx { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; + dns_client_t *client; + bool want_dnssec; + bool want_validation; + bool want_cdflag; + bool want_tcp; + + /* Locked */ + ISC_LINK(struct resctx) link; + isc_task_t *task; + dns_view_t *view; + unsigned int restarts; + dns_fixedname_t name; + dns_rdatatype_t type; + dns_fetch_t *fetch; + dns_namelist_t namelist; + isc_result_t result; + dns_clientresevent_t *event; + bool canceled; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; +} resctx_t; + +/*% + * Argument of an internal event for synchronous name resolution. + */ +typedef struct resarg { + /* Unlocked */ + isc_appctx_t *actx; + dns_client_t *client; + isc_mutex_t lock; + + /* Locked */ + isc_result_t result; + isc_result_t vresult; + dns_namelist_t *namelist; + dns_clientrestrans_t *trans; + bool canceled; +} resarg_t; + +static void +client_resfind(resctx_t *rctx, dns_fetchevent_t *event); +static void +cancelresolve(dns_clientrestrans_t *trans); +static void +destroyrestrans(dns_clientrestrans_t **transp); + +/* + * Try honoring the operating system's preferred ephemeral port range. + */ +static isc_result_t +setsourceports(isc_mem_t *mctx, dns_dispatchmgr_t *manager) { + isc_portset_t *v4portset = NULL, *v6portset = NULL; + in_port_t udpport_low, udpport_high; + isc_result_t result; + + result = isc_portset_create(mctx, &v4portset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_portset_addrange(v4portset, udpport_low, udpport_high); + + result = isc_portset_create(mctx, &v6portset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_portset_addrange(v6portset, udpport_low, udpport_high); + + result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset); + +cleanup: + if (v4portset != NULL) { + isc_portset_destroy(mctx, &v4portset); + } + if (v6portset != NULL) { + isc_portset_destroy(mctx, &v6portset); + } + + return (result); +} + +static isc_result_t +getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t **dispp, const isc_sockaddr_t *localaddr) { + dns_dispatch_t *disp = NULL; + isc_result_t result; + isc_sockaddr_t anyaddr; + + if (localaddr == NULL) { + isc_sockaddr_anyofpf(&anyaddr, family); + localaddr = &anyaddr; + } + + result = dns_dispatch_createudp(dispatchmgr, localaddr, &disp); + if (result == ISC_R_SUCCESS) { + *dispp = disp; + } + + return (result); +} + +static isc_result_t +createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, isc_taskmgr_t *taskmgr, + unsigned int ntasks, isc_nm_t *nm, isc_timermgr_t *timermgr, + dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4, + dns_dispatch_t *dispatchv6, dns_view_t **viewp) { + isc_result_t result; + dns_view_t *view = NULL; + + result = dns_view_create(mctx, rdclass, DNS_CLIENTVIEW_NAME, &view); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* Initialize view security roots */ + result = dns_view_initsecroots(view, mctx); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + result = dns_view_createresolver(view, taskmgr, ntasks, 1, nm, timermgr, + 0, dispatchmgr, dispatchv4, + dispatchv6); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache, + rdclass, 0, NULL, &view->cachedb); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + *viewp = view; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, + isc_nm_t *nm, isc_timermgr_t *timermgr, unsigned int options, + dns_client_t **clientp, const isc_sockaddr_t *localaddr4, + const isc_sockaddr_t *localaddr6) { + isc_result_t result; + dns_client_t *client = NULL; + dns_dispatch_t *dispatchv4 = NULL; + dns_dispatch_t *dispatchv6 = NULL; + dns_view_t *view = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(timermgr != NULL); + REQUIRE(nm != NULL); + REQUIRE(clientp != NULL && *clientp == NULL); + + UNUSED(options); + + client = isc_mem_get(mctx, sizeof(*client)); + *client = (dns_client_t){ + .actx = actx, .taskmgr = taskmgr, .timermgr = timermgr, .nm = nm + }; + + isc_mutex_init(&client->lock); + + result = isc_task_create(client->taskmgr, 0, &client->task); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + result = dns_dispatchmgr_create(mctx, nm, &client->dispatchmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + (void)setsourceports(mctx, client->dispatchmgr); + + /* + * If only one address family is specified, use it. + * If neither family is specified, or if both are, use both. + */ + client->dispatchv4 = NULL; + if (localaddr4 != NULL || localaddr6 == NULL) { + result = getudpdispatch(AF_INET, client->dispatchmgr, + &dispatchv4, localaddr4); + if (result == ISC_R_SUCCESS) { + client->dispatchv4 = dispatchv4; + } + } + + client->dispatchv6 = NULL; + if (localaddr6 != NULL || localaddr4 == NULL) { + result = getudpdispatch(AF_INET6, client->dispatchmgr, + &dispatchv6, localaddr6); + if (result == ISC_R_SUCCESS) { + client->dispatchv6 = dispatchv6; + } + } + + /* We need at least one of the dispatchers */ + if (dispatchv4 == NULL && dispatchv6 == NULL) { + INSIST(result != ISC_R_SUCCESS); + goto cleanup_dispatchmgr; + } + + isc_refcount_init(&client->references, 1); + + /* Create the default view for class IN */ + result = createview(mctx, dns_rdataclass_in, taskmgr, RESOLVER_NTASKS, + nm, timermgr, client->dispatchmgr, dispatchv4, + dispatchv6, &view); + if (result != ISC_R_SUCCESS) { + goto cleanup_references; + } + + ISC_LIST_INIT(client->viewlist); + ISC_LIST_APPEND(client->viewlist, view, link); + + dns_view_freeze(view); /* too early? */ + + ISC_LIST_INIT(client->resctxs); + + isc_mem_attach(mctx, &client->mctx); + + client->find_timeout = DEF_FIND_TIMEOUT; + client->find_udpretries = DEF_FIND_UDPRETRIES; + + client->magic = DNS_CLIENT_MAGIC; + + *clientp = client; + + return (ISC_R_SUCCESS); + +cleanup_references: + isc_refcount_decrementz(&client->references); + isc_refcount_destroy(&client->references); +cleanup_dispatchmgr: + if (dispatchv4 != NULL) { + dns_dispatch_detach(&dispatchv4); + } + if (dispatchv6 != NULL) { + dns_dispatch_detach(&dispatchv6); + } + dns_dispatchmgr_detach(&client->dispatchmgr); +cleanup_task: + isc_task_detach(&client->task); +cleanup_lock: + isc_mutex_destroy(&client->lock); + isc_mem_put(mctx, client, sizeof(*client)); + + return (result); +} + +static void +destroyclient(dns_client_t *client) { + dns_view_t *view = NULL; + + isc_refcount_destroy(&client->references); + + while ((view = ISC_LIST_HEAD(client->viewlist)) != NULL) { + ISC_LIST_UNLINK(client->viewlist, view, link); + dns_view_detach(&view); + } + + if (client->dispatchv4 != NULL) { + dns_dispatch_detach(&client->dispatchv4); + } + if (client->dispatchv6 != NULL) { + dns_dispatch_detach(&client->dispatchv6); + } + + dns_dispatchmgr_detach(&client->dispatchmgr); + + isc_task_detach(&client->task); + + isc_mutex_destroy(&client->lock); + client->magic = 0; + + isc_mem_putanddetach(&client->mctx, client, sizeof(*client)); +} + +void +dns_client_detach(dns_client_t **clientp) { + dns_client_t *client = NULL; + + REQUIRE(clientp != NULL); + REQUIRE(DNS_CLIENT_VALID(*clientp)); + + client = *clientp; + *clientp = NULL; + + if (isc_refcount_decrement(&client->references) == 1) { + destroyclient(client); + } +} + +isc_result_t +dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space, isc_sockaddrlist_t *addrs) { + isc_result_t result; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(addrs != NULL); + + if (name_space == NULL) { + name_space = dns_rootname; + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + if (result != ISC_R_SUCCESS) { + UNLOCK(&client->lock); + return (result); + } + UNLOCK(&client->lock); + + result = dns_fwdtable_add(view->fwdtable, name_space, addrs, + dns_fwdpolicy_only); + + dns_view_detach(&view); + + return (result); +} + +isc_result_t +dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space) { + isc_result_t result; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + + if (name_space == NULL) { + name_space = dns_rootname; + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + if (result != ISC_R_SUCCESS) { + UNLOCK(&client->lock); + return (result); + } + UNLOCK(&client->lock); + + result = dns_fwdtable_delete(view->fwdtable, name_space); + + dns_view_detach(&view); + + return (result); +} + +static isc_result_t +getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { + dns_rdataset_t *rdataset; + + REQUIRE(mctx != NULL); + REQUIRE(rdatasetp != NULL && *rdatasetp == NULL); + + rdataset = isc_mem_get(mctx, sizeof(*rdataset)); + + dns_rdataset_init(rdataset); + + *rdatasetp = rdataset; + + return (ISC_R_SUCCESS); +} + +static void +putrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { + dns_rdataset_t *rdataset; + + REQUIRE(rdatasetp != NULL); + rdataset = *rdatasetp; + *rdatasetp = NULL; + REQUIRE(rdataset != NULL); + + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + + isc_mem_put(mctx, rdataset, sizeof(*rdataset)); +} + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + resctx_t *rctx = event->ev_arg; + dns_fetchevent_t *fevent; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + REQUIRE(RCTX_VALID(rctx)); + REQUIRE(rctx->task == task); + fevent = (dns_fetchevent_t *)event; + + client_resfind(rctx, fevent); +} + +static isc_result_t +start_fetch(resctx_t *rctx) { + isc_result_t result; + int fopts = 0; + + /* + * The caller must be holding the rctx's lock. + */ + + REQUIRE(rctx->fetch == NULL); + + if (!rctx->want_cdflag) { + fopts |= DNS_FETCHOPT_NOCDFLAG; + } + if (!rctx->want_validation) { + fopts |= DNS_FETCHOPT_NOVALIDATE; + } + if (rctx->want_tcp) { + fopts |= DNS_FETCHOPT_TCP; + } + + result = dns_resolver_createfetch( + rctx->view->resolver, dns_fixedname_name(&rctx->name), + rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL, + rctx->task, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset, + &rctx->fetch); + + return (result); +} + +static isc_result_t +view_find(resctx_t *rctx, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_name_t *foundname) { + isc_result_t result; + dns_name_t *name = dns_fixedname_name(&rctx->name); + dns_rdatatype_t type; + + if (rctx->type == dns_rdatatype_rrsig) { + type = dns_rdatatype_any; + } else { + type = rctx->type; + } + + result = dns_view_find(rctx->view, name, type, 0, 0, false, false, dbp, + nodep, foundname, rctx->rdataset, + rctx->sigrdataset); + + return (result); +} + +static void +client_resfind(resctx_t *rctx, dns_fetchevent_t *event) { + isc_mem_t *mctx; + isc_result_t tresult, result = ISC_R_SUCCESS; + isc_result_t vresult = ISC_R_SUCCESS; + bool want_restart; + bool send_event = false; + dns_name_t *name = NULL, *prefix = NULL; + dns_fixedname_t foundname, fixed; + dns_rdataset_t *trdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + int order; + dns_namereln_t namereln; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + + REQUIRE(RCTX_VALID(rctx)); + + LOCK(&rctx->lock); + + mctx = rctx->view->mctx; + + name = dns_fixedname_name(&rctx->name); + + do { + dns_name_t *fname = NULL; + dns_name_t *ansname = NULL; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + + rctx->restarts++; + want_restart = false; + + if (event == NULL && !rctx->canceled) { + fname = dns_fixedname_initname(&foundname); + INSIST(!dns_rdataset_isassociated(rctx->rdataset)); + INSIST(rctx->sigrdataset == NULL || + !dns_rdataset_isassociated(rctx->sigrdataset)); + result = view_find(rctx, &db, &node, fname); + if (result == ISC_R_NOTFOUND) { + /* + * We don't know anything about the name. + * Launch a fetch. + */ + if (node != NULL) { + INSIST(db != NULL); + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + result = start_fetch(rctx); + if (result != ISC_R_SUCCESS) { + putrdataset(mctx, &rctx->rdataset); + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, + &rctx->sigrdataset); + } + send_event = true; + } + goto done; + } + } else { + INSIST(event != NULL); + INSIST(event->fetch == rctx->fetch); + dns_resolver_destroyfetch(&rctx->fetch); + db = event->db; + node = event->node; + result = event->result; + vresult = event->vresult; + fname = event->foundname; + INSIST(event->rdataset == rctx->rdataset); + INSIST(event->sigrdataset == rctx->sigrdataset); + } + + /* + * If we've been canceled, forget about the result. + */ + if (rctx->canceled) { + result = ISC_R_CANCELED; + } else { + /* + * Otherwise, get some resource for copying the + * result. + */ + dns_name_t *aname = dns_fixedname_name(&rctx->name); + + ansname = isc_mem_get(mctx, sizeof(*ansname)); + dns_name_init(ansname, NULL); + + dns_name_dup(aname, mctx, ansname); + } + + switch (result) { + case ISC_R_SUCCESS: + send_event = true; + /* + * This case is handled in the main line below. + */ + break; + case DNS_R_CNAME: + /* + * Add the CNAME to the answer list. + */ + trdataset = rctx->rdataset; + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + + /* + * Copy the CNAME's target into the lookup's + * query name and start over. + */ + tresult = dns_rdataset_first(trdataset); + if (tresult != ISC_R_SUCCESS) { + goto done; + } + dns_rdataset_current(trdataset, &rdata); + tresult = dns_rdata_tostruct(&rdata, &cname, NULL); + dns_rdata_reset(&rdata); + if (tresult != ISC_R_SUCCESS) { + goto done; + } + dns_name_copy(&cname.cname, name); + dns_rdata_freestruct(&cname); + want_restart = true; + goto done; + case DNS_R_DNAME: + /* + * Add the DNAME to the answer list. + */ + trdataset = rctx->rdataset; + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + + namereln = dns_name_fullcompare(name, fname, &order, + &nlabels); + INSIST(namereln == dns_namereln_subdomain); + /* + * Get the target name of the DNAME. + */ + tresult = dns_rdataset_first(trdataset); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + dns_rdataset_current(trdataset, &rdata); + tresult = dns_rdata_tostruct(&rdata, &dname, NULL); + dns_rdata_reset(&rdata); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + /* + * Construct the new query name and start over. + */ + prefix = dns_fixedname_initname(&fixed); + dns_name_split(name, nlabels, prefix, NULL); + tresult = dns_name_concatenate(prefix, &dname.dname, + name, NULL); + dns_rdata_freestruct(&dname); + if (tresult == ISC_R_SUCCESS) { + want_restart = true; + } else { + result = tresult; + } + goto done; + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + rctx->rdataset = NULL; + /* What about sigrdataset? */ + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + send_event = true; + goto done; + default: + if (rctx->rdataset != NULL) { + putrdataset(mctx, &rctx->rdataset); + } + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + send_event = true; + goto done; + } + + if (rctx->type == dns_rdatatype_any) { + int n = 0; + dns_rdatasetiter_t *rdsiter = NULL; + + tresult = dns_db_allrdatasets(db, node, NULL, 0, 0, + &rdsiter); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + + tresult = dns_rdatasetiter_first(rdsiter); + while (tresult == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, + rctx->rdataset); + if (rctx->rdataset->type != 0) { + ISC_LIST_APPEND(ansname->list, + rctx->rdataset, link); + n++; + rctx->rdataset = NULL; + } else { + /* + * We're not interested in this + * rdataset. + */ + dns_rdataset_disassociate( + rctx->rdataset); + } + tresult = dns_rdatasetiter_next(rdsiter); + + if (tresult == ISC_R_SUCCESS && + rctx->rdataset == NULL) + { + tresult = getrdataset(mctx, + &rctx->rdataset); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + POST(result); + break; + } + } + } + if (rctx->rdataset != NULL) { + putrdataset(mctx, &rctx->rdataset); + } + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + if (n == 0) { + /* + * We didn't match any rdatasets (which means + * something went wrong in this + * implementation). + */ + result = DNS_R_SERVFAIL; /* better code? */ + POST(result); + } else { + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + } + dns_rdatasetiter_destroy(&rdsiter); + if (tresult != ISC_R_NOMORE) { + result = DNS_R_SERVFAIL; /* ditto */ + } else { + result = ISC_R_SUCCESS; + } + goto done; + } else { + /* + * This is the "normal" case -- an ordinary question + * to which we've got the answer. + */ + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + } + + done: + /* + * Free temporary resources + */ + if (ansname != NULL) { + dns_rdataset_t *rdataset; + + while ((rdataset = ISC_LIST_HEAD(ansname->list)) != + NULL) + { + ISC_LIST_UNLINK(ansname->list, rdataset, link); + putrdataset(mctx, &rdataset); + } + dns_name_free(ansname, mctx); + isc_mem_put(mctx, ansname, sizeof(*ansname)); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (event != NULL) { + isc_event_free(ISC_EVENT_PTR(&event)); + } + + /* + * Limit the number of restarts. + */ + if (want_restart && rctx->restarts == MAX_RESTARTS) { + want_restart = false; + result = ISC_R_QUOTA; + send_event = true; + } + + /* + * Prepare further find with new resources + */ + if (want_restart) { + INSIST(rctx->rdataset == NULL && + rctx->sigrdataset == NULL); + + result = getrdataset(mctx, &rctx->rdataset); + if (result == ISC_R_SUCCESS && rctx->want_dnssec) { + result = getrdataset(mctx, &rctx->sigrdataset); + if (result != ISC_R_SUCCESS) { + putrdataset(mctx, &rctx->rdataset); + } + } + + if (result != ISC_R_SUCCESS) { + want_restart = false; + send_event = true; + } + } + } while (want_restart); + + if (send_event) { + isc_task_t *task; + + while ((name = ISC_LIST_HEAD(rctx->namelist)) != NULL) { + ISC_LIST_UNLINK(rctx->namelist, name, link); + ISC_LIST_APPEND(rctx->event->answerlist, name, link); + } + + rctx->event->result = result; + rctx->event->vresult = vresult; + task = rctx->event->ev_sender; + rctx->event->ev_sender = rctx; + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&rctx->event)); + } + + UNLOCK(&rctx->lock); +} + +static void +suspend(isc_task_t *task, isc_event_t *event) { + isc_appctx_t *actx = event->ev_arg; + + UNUSED(task); + + isc_app_ctxsuspend(actx); + isc_event_free(&event); +} + +static void +resolve_done(isc_task_t *task, isc_event_t *event) { + resarg_t *resarg = event->ev_arg; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_name_t *name = NULL; + dns_client_t *client = resarg->client; + isc_result_t result; + + UNUSED(task); + + LOCK(&resarg->lock); + + resarg->result = rev->result; + resarg->vresult = rev->vresult; + while ((name = ISC_LIST_HEAD(rev->answerlist)) != NULL) { + ISC_LIST_UNLINK(rev->answerlist, name, link); + ISC_LIST_APPEND(*resarg->namelist, name, link); + } + + destroyrestrans(&resarg->trans); + isc_event_free(&event); + resarg->client = NULL; + + if (!resarg->canceled) { + UNLOCK(&resarg->lock); + + /* + * We may or may not be running. isc__appctx_onrun will + * fail if we are currently running otherwise we post a + * action to call isc_app_ctxsuspend when we do start + * running. + */ + result = isc_app_ctxonrun(resarg->actx, client->mctx, task, + suspend, resarg->actx); + if (result == ISC_R_ALREADYRUNNING) { + isc_app_ctxsuspend(resarg->actx); + } + } else { + /* + * We have already exited from the loop (due to some + * unexpected event). Just clean the arg up. + */ + UNLOCK(&resarg->lock); + isc_mutex_destroy(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + } + + dns_client_detach(&client); +} + +isc_result_t +dns_client_resolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, dns_namelist_t *namelist) { + isc_result_t result; + resarg_t *resarg = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(client->actx != NULL); + REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist)); + + resarg = isc_mem_get(client->mctx, sizeof(*resarg)); + + *resarg = (resarg_t){ + .actx = client->actx, + .client = client, + .result = DNS_R_SERVFAIL, + .namelist = namelist, + }; + + isc_mutex_init(&resarg->lock); + + result = dns_client_startresolve(client, name, rdclass, type, options, + client->task, resolve_done, resarg, + &resarg->trans); + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + return (result); + } + + /* + * Start internal event loop. It blocks until the entire process + * is completed. + */ + result = isc_app_ctxrun(client->actx); + + LOCK(&resarg->lock); + if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) { + result = resarg->result; + } + if (result != ISC_R_SUCCESS && resarg->vresult != ISC_R_SUCCESS) { + /* + * If this lookup failed due to some error in DNSSEC + * validation, return the validation error code. + * XXX: or should we pass the validation result separately? + */ + result = resarg->vresult; + } + if (resarg->trans != NULL) { + /* + * Unusual termination (perhaps due to signal). We need some + * tricky cleanup process. + */ + resarg->canceled = true; + cancelresolve(resarg->trans); + + UNLOCK(&resarg->lock); + + /* resarg will be freed in the event handler. */ + } else { + UNLOCK(&resarg->lock); + + isc_mutex_destroy(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + } + + return (result); +} + +isc_result_t +dns_client_startresolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_clientrestrans_t **transp) { + dns_view_t *view = NULL; + dns_clientresevent_t *event = NULL; + resctx_t *rctx = NULL; + isc_task_t *tclone = NULL; + isc_mem_t *mctx; + isc_result_t result; + dns_rdataset_t *rdataset, *sigrdataset; + bool want_dnssec, want_validation, want_cdflag, want_tcp; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(transp != NULL && *transp == NULL); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + mctx = client->mctx; + rdataset = NULL; + sigrdataset = NULL; + want_dnssec = ((options & DNS_CLIENTRESOPT_NODNSSEC) == 0); + want_validation = ((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0); + want_cdflag = ((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0); + want_tcp = ((options & DNS_CLIENTRESOPT_TCP) != 0); + + /* + * Prepare some intermediate resources + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_clientresevent_t *)isc_event_allocate( + mctx, tclone, DNS_EVENT_CLIENTRESDONE, action, arg, + sizeof(*event)); + event->result = DNS_R_SERVFAIL; + ISC_LIST_INIT(event->answerlist); + + rctx = isc_mem_get(mctx, sizeof(*rctx)); + isc_mutex_init(&rctx->lock); + + result = getrdataset(mctx, &rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + rctx->rdataset = rdataset; + + if (want_dnssec) { + result = getrdataset(mctx, &sigrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + rctx->sigrdataset = sigrdataset; + + dns_fixedname_init(&rctx->name); + dns_name_copy(name, dns_fixedname_name(&rctx->name)); + + rctx->client = client; + ISC_LINK_INIT(rctx, link); + rctx->canceled = false; + rctx->task = client->task; + rctx->type = type; + rctx->view = view; + rctx->restarts = 0; + rctx->fetch = NULL; + rctx->want_dnssec = want_dnssec; + rctx->want_validation = want_validation; + rctx->want_cdflag = want_cdflag; + rctx->want_tcp = want_tcp; + ISC_LIST_INIT(rctx->namelist); + rctx->event = event; + + rctx->magic = RCTX_MAGIC; + isc_refcount_increment(&client->references); + + LOCK(&client->lock); + ISC_LIST_APPEND(client->resctxs, rctx, link); + UNLOCK(&client->lock); + + *transp = (dns_clientrestrans_t *)rctx; + client_resfind(rctx, NULL); + + return (ISC_R_SUCCESS); + +cleanup: + if (rdataset != NULL) { + putrdataset(client->mctx, &rdataset); + } + if (sigrdataset != NULL) { + putrdataset(client->mctx, &sigrdataset); + } + isc_mutex_destroy(&rctx->lock); + isc_mem_put(mctx, rctx, sizeof(*rctx)); + isc_event_free(ISC_EVENT_PTR(&event)); + isc_task_detach(&tclone); + dns_view_detach(&view); + + return (result); +} + +/*%< + * Cancel an ongoing resolution procedure started via + * dns_client_startresolve(). + * + * If the resolution procedure has not completed, post its CLIENTRESDONE + * event with a result code of #ISC_R_CANCELED. + */ +static void +cancelresolve(dns_clientrestrans_t *trans) { + resctx_t *rctx = NULL; + + REQUIRE(trans != NULL); + rctx = (resctx_t *)trans; + REQUIRE(RCTX_VALID(rctx)); + + LOCK(&rctx->lock); + + if (!rctx->canceled) { + rctx->canceled = true; + if (rctx->fetch != NULL) { + dns_resolver_cancelfetch(rctx->fetch); + } + } + + UNLOCK(&rctx->lock); +} + +void +dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist) { + dns_name_t *name; + dns_rdataset_t *rdataset; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(namelist != NULL); + + while ((name = ISC_LIST_HEAD(*namelist)) != NULL) { + ISC_LIST_UNLINK(*namelist, name, link); + while ((rdataset = ISC_LIST_HEAD(name->list)) != NULL) { + ISC_LIST_UNLINK(name->list, rdataset, link); + putrdataset(client->mctx, &rdataset); + } + dns_name_free(name, client->mctx); + isc_mem_put(client->mctx, name, sizeof(*name)); + } +} + +/*% + * Destroy name resolution transaction state identified by '*transp'. + * + * The caller must have received the CLIENTRESDONE event (either because the + * resolution completed or because cancelresolve() was called). + */ +static void +destroyrestrans(dns_clientrestrans_t **transp) { + resctx_t *rctx = NULL; + isc_mem_t *mctx = NULL; + dns_client_t *client = NULL; + + REQUIRE(transp != NULL); + + rctx = (resctx_t *)*transp; + *transp = NULL; + + REQUIRE(RCTX_VALID(rctx)); + REQUIRE(rctx->fetch == NULL); + REQUIRE(rctx->event == NULL); + + client = rctx->client; + + REQUIRE(DNS_CLIENT_VALID(client)); + + mctx = client->mctx; + dns_view_detach(&rctx->view); + + /* + * Wait for the lock in client_resfind to be released before + * destroying the lock. + */ + LOCK(&rctx->lock); + UNLOCK(&rctx->lock); + + LOCK(&client->lock); + + INSIST(ISC_LINK_LINKED(rctx, link)); + ISC_LIST_UNLINK(client->resctxs, rctx, link); + + UNLOCK(&client->lock); + + INSIST(ISC_LIST_EMPTY(rctx->namelist)); + + isc_mutex_destroy(&rctx->lock); + rctx->magic = 0; + + isc_mem_put(mctx, rctx, sizeof(*rctx)); +} + +isc_result_t +dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, const dns_name_t *keyname, + isc_buffer_t *databuf) { + isc_result_t result; + dns_view_t *view = NULL; + dns_keytable_t *secroots = NULL; + dns_name_t *name = NULL; + char rdatabuf[DST_KEY_MAXSIZE]; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_decompress_t dctx; + dns_rdata_t rdata; + isc_buffer_t b; + + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + CHECK(result); + + CHECK(dns_view_getsecroots(view, &secroots)); + + DE_CONST(keyname, name); + + if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + + isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf)); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); + dns_rdata_init(&rdata); + isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf)); + CHECK(dns_rdata_fromwire(&rdata, rdclass, rdtype, databuf, &dctx, 0, + &b)); + dns_decompress_invalidate(&dctx); + + if (rdtype == dns_rdatatype_ds) { + CHECK(dns_rdata_tostruct(&rdata, &ds, NULL)); + } else { + CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, + digest, &ds)); + } + + CHECK(dns_keytable_add(secroots, false, false, name, &ds, NULL, NULL)); + +cleanup: + if (view != NULL) { + dns_view_detach(&view); + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + return (result); +} diff --git a/lib/dns/clientinfo.c b/lib/dns/clientinfo.c new file mode 100644 index 0000000..1ea5e7d --- /dev/null +++ b/lib/dns/clientinfo.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +void +dns_clientinfomethods_init(dns_clientinfomethods_t *methods, + dns_clientinfo_sourceip_t sourceip) { + methods->version = DNS_CLIENTINFOMETHODS_VERSION; + methods->age = DNS_CLIENTINFOMETHODS_AGE; + methods->sourceip = sourceip; +} + +void +dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp) { + ci->version = DNS_CLIENTINFO_VERSION; + ci->data = data; + ci->dbversion = versionp; + dns_ecs_init(&ci->ecs); +} + +void +dns_clientinfo_setecs(dns_clientinfo_t *ci, dns_ecs_t *ecs) { + if (ecs != NULL) { + ci->ecs = *ecs; + } else { + dns_ecs_init(&ci->ecs); + } +} diff --git a/lib/dns/compress.c b/lib/dns/compress.c new file mode 100644 index 0000000..1460e0c --- /dev/null +++ b/lib/dns/compress.c @@ -0,0 +1,580 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#define DNS_NAME_USEINLINE 1 + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define CCTX_MAGIC ISC_MAGIC('C', 'C', 'T', 'X') +#define VALID_CCTX(x) ISC_MAGIC_VALID(x, CCTX_MAGIC) + +#define DCTX_MAGIC ISC_MAGIC('D', 'C', 'T', 'X') +#define VALID_DCTX(x) ISC_MAGIC_VALID(x, DCTX_MAGIC) + +static unsigned char maptolower[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff +}; + +/* + * The tableindex array below is of size 256, one entry for each + * unsigned char value. The tableindex array elements are dependent on + * DNS_COMPRESS_TABLESIZE. The table was created using the following + * function. + * + * static void + * gentable(unsigned char *table) { + * unsigned int i; + * const unsigned int left = DNS_COMPRESS_TABLESIZE - 38; + * long r; + * + * for (i = 0; i < 26; i++) { + * table['A' + i] = i; + * table['a' + i] = i; + * } + * + * for (i = 0; i <= 9; i++) + * table['0' + i] = i + 26; + * + * table['-'] = 36; + * table['_'] = 37; + * + * for (i = 0; i < 256; i++) { + * if ((i >= 'a' && i <= 'z') || + * (i >= 'A' && i <= 'Z') || + * (i >= '0' && i <= '9') || + * (i == '-') || + * (i == '_')) + * continue; + * r = random() % left; + * table[i] = 38 + r; + * } + * } + */ +static unsigned char tableindex[256] = { + 0x3e, 0x3e, 0x33, 0x2d, 0x30, 0x38, 0x31, 0x3c, 0x2b, 0x33, 0x30, 0x3f, + 0x2d, 0x3c, 0x36, 0x3a, 0x28, 0x2c, 0x2a, 0x37, 0x3d, 0x34, 0x35, 0x2d, + 0x39, 0x2b, 0x2f, 0x2c, 0x3b, 0x32, 0x2b, 0x39, 0x30, 0x38, 0x28, 0x3c, + 0x32, 0x33, 0x39, 0x38, 0x27, 0x2b, 0x39, 0x30, 0x27, 0x24, 0x2f, 0x2b, + 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x3a, 0x29, 0x36, + 0x31, 0x3c, 0x35, 0x26, 0x31, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x3e, 0x3b, 0x39, 0x2f, 0x25, + 0x27, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x36, 0x3b, 0x2f, 0x2f, 0x2e, 0x29, 0x33, 0x2a, 0x36, + 0x28, 0x3f, 0x2e, 0x29, 0x2c, 0x29, 0x36, 0x2d, 0x32, 0x3d, 0x33, 0x2a, + 0x2e, 0x2f, 0x3b, 0x30, 0x3d, 0x39, 0x2b, 0x36, 0x2a, 0x2f, 0x2c, 0x26, + 0x3a, 0x37, 0x30, 0x3d, 0x2a, 0x36, 0x33, 0x2c, 0x38, 0x3d, 0x32, 0x3e, + 0x26, 0x2a, 0x2c, 0x35, 0x27, 0x39, 0x3b, 0x31, 0x2a, 0x37, 0x3c, 0x27, + 0x32, 0x29, 0x39, 0x37, 0x34, 0x3f, 0x39, 0x2e, 0x38, 0x2b, 0x2c, 0x3e, + 0x3b, 0x3b, 0x2d, 0x33, 0x3b, 0x3b, 0x32, 0x3d, 0x3f, 0x3a, 0x34, 0x26, + 0x35, 0x30, 0x31, 0x39, 0x27, 0x2f, 0x3d, 0x35, 0x35, 0x36, 0x2e, 0x29, + 0x38, 0x27, 0x34, 0x32, 0x2c, 0x3c, 0x31, 0x28, 0x37, 0x38, 0x37, 0x34, + 0x33, 0x29, 0x32, 0x34, 0x3f, 0x26, 0x34, 0x34, 0x32, 0x27, 0x30, 0x33, + 0x33, 0x2d, 0x2b, 0x28, 0x3f, 0x33, 0x2b, 0x39, 0x37, 0x39, 0x2c, 0x3d, + 0x35, 0x39, 0x27, 0x2f +}; + +/*** + *** Compression + ***/ + +isc_result_t +dns_compress_init(dns_compress_t *cctx, int edns, isc_mem_t *mctx) { + REQUIRE(cctx != NULL); + REQUIRE(mctx != NULL); /* See: rdataset.c:towiresorted(). */ + + cctx->edns = edns; + cctx->mctx = mctx; + cctx->count = 0; + cctx->allowed = DNS_COMPRESS_ENABLED; + cctx->arena_off = 0; + + memset(&cctx->table[0], 0, sizeof(cctx->table)); + + cctx->magic = CCTX_MAGIC; + + return (ISC_R_SUCCESS); +} + +void +dns_compress_invalidate(dns_compress_t *cctx) { + dns_compressnode_t *node; + unsigned int i; + + REQUIRE(VALID_CCTX(cctx)); + + for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) { + while (cctx->table[i] != NULL) { + node = cctx->table[i]; + cctx->table[i] = cctx->table[i]->next; + if ((node->offset & 0x8000) != 0) { + isc_mem_put(cctx->mctx, node->r.base, + node->r.length); + } + if (node->count < DNS_COMPRESS_INITIALNODES) { + continue; + } + isc_mem_put(cctx->mctx, node, sizeof(*node)); + } + } + + cctx->magic = 0; + cctx->allowed = 0; + cctx->edns = -1; +} + +void +dns_compress_setmethods(dns_compress_t *cctx, unsigned int allowed) { + REQUIRE(VALID_CCTX(cctx)); + + cctx->allowed &= ~DNS_COMPRESS_ALL; + cctx->allowed |= (allowed & DNS_COMPRESS_ALL); +} + +unsigned int +dns_compress_getmethods(dns_compress_t *cctx) { + REQUIRE(VALID_CCTX(cctx)); + return (cctx->allowed & DNS_COMPRESS_ALL); +} + +void +dns_compress_disable(dns_compress_t *cctx) { + REQUIRE(VALID_CCTX(cctx)); + cctx->allowed &= ~DNS_COMPRESS_ENABLED; +} + +void +dns_compress_setsensitive(dns_compress_t *cctx, bool sensitive) { + REQUIRE(VALID_CCTX(cctx)); + + if (sensitive) { + cctx->allowed |= DNS_COMPRESS_CASESENSITIVE; + } else { + cctx->allowed &= ~DNS_COMPRESS_CASESENSITIVE; + } +} + +bool +dns_compress_getsensitive(dns_compress_t *cctx) { + REQUIRE(VALID_CCTX(cctx)); + + return (cctx->allowed & DNS_COMPRESS_CASESENSITIVE); +} + +int +dns_compress_getedns(dns_compress_t *cctx) { + REQUIRE(VALID_CCTX(cctx)); + return (cctx->edns); +} + +/* + * Find the longest match of name in the table. + * If match is found return true. prefix, suffix and offset are updated. + * If no match is found return false. + */ +bool +dns_compress_findglobal(dns_compress_t *cctx, const dns_name_t *name, + dns_name_t *prefix, uint16_t *offset) { + dns_name_t tname; + dns_compressnode_t *node = NULL; + unsigned int labels, i, n; + unsigned int numlabels; + unsigned char *p; + + REQUIRE(VALID_CCTX(cctx)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(offset != NULL); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) { + return (false); + } + + if (cctx->count == 0) { + return (false); + } + + labels = dns_name_countlabels(name); + INSIST(labels > 0); + + dns_name_init(&tname, NULL); + + numlabels = labels > 3U ? 3U : labels; + p = name->ndata; + + for (n = 0; n < numlabels - 1; n++) { + unsigned char ch, llen; + unsigned int firstoffset, length; + + firstoffset = (unsigned int)(p - name->ndata); + length = name->length - firstoffset; + + /* + * We calculate the table index using the first + * character in the first label of the suffix name. + */ + ch = p[1]; + i = tableindex[ch]; + if ((cctx->allowed & DNS_COMPRESS_CASESENSITIVE) != 0) { + for (node = cctx->table[i]; node != NULL; + node = node->next) + { + if (node->name.length != length) { + continue; + } + + if (memcmp(node->name.ndata, p, length) == 0) { + goto found; + } + } + } else { + for (node = cctx->table[i]; node != NULL; + node = node->next) + { + unsigned int l, count; + unsigned char c; + unsigned char *label1, *label2; + + if (node->name.length != length) { + continue; + } + + l = labels - n; + if (node->name.labels != l) { + continue; + } + + label1 = node->name.ndata; + label2 = p; + while (l-- > 0) { + count = *label1++; + if (count != *label2++) { + goto cont1; + } + + /* no bitstring support */ + INSIST(count <= 63); + + /* Loop unrolled for performance */ + while (count > 3) { + c = maptolower[label1[0]]; + if (c != maptolower[label2[0]]) + { + goto cont1; + } + c = maptolower[label1[1]]; + if (c != maptolower[label2[1]]) + { + goto cont1; + } + c = maptolower[label1[2]]; + if (c != maptolower[label2[2]]) + { + goto cont1; + } + c = maptolower[label1[3]]; + if (c != maptolower[label2[3]]) + { + goto cont1; + } + count -= 4; + label1 += 4; + label2 += 4; + } + while (count-- > 0) { + c = maptolower[*label1++]; + if (c != maptolower[*label2++]) + { + goto cont1; + } + } + } + break; + cont1: + continue; + } + } + + if (node != NULL) { + break; + } + + llen = *p; + p += llen + 1; + } + +found: + /* + * If node == NULL, we found no match at all. + */ + if (node == NULL) { + return (false); + } + + if (n == 0) { + dns_name_reset(prefix); + } else { + dns_name_getlabelsequence(name, 0, n, prefix); + } + + *offset = (node->offset & 0x7fff); + return (true); +} + +static unsigned int +name_length(const dns_name_t *name) { + isc_region_t r; + dns_name_toregion(name, &r); + return (r.length); +} + +void +dns_compress_add(dns_compress_t *cctx, const dns_name_t *name, + const dns_name_t *prefix, uint16_t offset) { + dns_name_t tname, xname; + unsigned int start; + unsigned int n; + unsigned int count; + unsigned int i; + dns_compressnode_t *node; + unsigned int length; + unsigned int tlength; + uint16_t toffset; + unsigned char *tmp; + isc_region_t r; + bool allocated = false; + + REQUIRE(VALID_CCTX(cctx)); + REQUIRE(dns_name_isabsolute(name)); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) { + return; + } + + if (offset >= 0x4000) { + return; + } + dns_name_init(&tname, NULL); + dns_name_init(&xname, NULL); + + n = dns_name_countlabels(name); + count = dns_name_countlabels(prefix); + if (dns_name_isabsolute(prefix)) { + count--; + } + if (count == 0) { + return; + } + start = 0; + dns_name_toregion(name, &r); + length = r.length; + if (cctx->arena_off + length < DNS_COMPRESS_ARENA_SIZE) { + tmp = &cctx->arena[cctx->arena_off]; + cctx->arena_off += length; + } else { + allocated = true; + tmp = isc_mem_get(cctx->mctx, length); + } + /* + * Copy name data to 'tmp' and make 'r' use 'tmp'. + */ + memmove(tmp, r.base, r.length); + r.base = tmp; + dns_name_fromregion(&xname, &r); + + if (count > 2U) { + count = 2U; + } + + while (count > 0) { + unsigned char ch; + + dns_name_getlabelsequence(&xname, start, n, &tname); + /* + * We calculate the table index using the first + * character in the first label of tname. + */ + ch = tname.ndata[1]; + i = tableindex[ch]; + tlength = name_length(&tname); + toffset = (uint16_t)(offset + (length - tlength)); + if (toffset >= 0x4000) { + break; + } + /* + * Create a new node and add it. + */ + if (cctx->count < DNS_COMPRESS_INITIALNODES) { + node = &cctx->initialnodes[cctx->count]; + } else { + node = isc_mem_get(cctx->mctx, + sizeof(dns_compressnode_t)); + } + node->count = cctx->count++; + /* + * 'node->r.base' becomes 'tmp' when start == 0. + * Record this by setting 0x8000 so it can be freed later. + */ + if (start == 0 && allocated) { + toffset |= 0x8000; + } + node->offset = toffset; + dns_name_toregion(&tname, &node->r); + dns_name_init(&node->name, NULL); + node->name.length = node->r.length; + node->name.ndata = node->r.base; + node->name.labels = tname.labels; + node->name.attributes = DNS_NAMEATTR_ABSOLUTE; + node->next = cctx->table[i]; + cctx->table[i] = node; + start++; + n--; + count--; + } + + if (start == 0) { + if (!allocated) { + cctx->arena_off -= length; + } else { + isc_mem_put(cctx->mctx, tmp, length); + } + } +} + +void +dns_compress_rollback(dns_compress_t *cctx, uint16_t offset) { + unsigned int i; + dns_compressnode_t *node; + + REQUIRE(VALID_CCTX(cctx)); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) { + return; + } + + for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) { + node = cctx->table[i]; + /* + * This relies on nodes with greater offsets being + * closer to the beginning of the list, and the + * items with the greatest offsets being at the end + * of the initialnodes[] array. + */ + while (node != NULL && (node->offset & 0x7fff) >= offset) { + cctx->table[i] = node->next; + if ((node->offset & 0x8000) != 0) { + isc_mem_put(cctx->mctx, node->r.base, + node->r.length); + } + if (node->count >= DNS_COMPRESS_INITIALNODES) { + isc_mem_put(cctx->mctx, node, sizeof(*node)); + } + cctx->count--; + node = cctx->table[i]; + } + } +} + +/*** + *** Decompression + ***/ + +void +dns_decompress_init(dns_decompress_t *dctx, int edns, + dns_decompresstype_t type) { + REQUIRE(dctx != NULL); + REQUIRE(edns >= -1 && edns <= 255); + + dctx->allowed = DNS_COMPRESS_NONE; + dctx->edns = edns; + dctx->type = type; + dctx->magic = DCTX_MAGIC; +} + +void +dns_decompress_invalidate(dns_decompress_t *dctx) { + REQUIRE(VALID_DCTX(dctx)); + + dctx->magic = 0; +} + +void +dns_decompress_setmethods(dns_decompress_t *dctx, unsigned int allowed) { + REQUIRE(VALID_DCTX(dctx)); + + switch (dctx->type) { + case DNS_DECOMPRESS_ANY: + dctx->allowed = DNS_COMPRESS_ALL; + break; + case DNS_DECOMPRESS_NONE: + dctx->allowed = DNS_COMPRESS_NONE; + break; + case DNS_DECOMPRESS_STRICT: + dctx->allowed = allowed; + break; + } +} + +unsigned int +dns_decompress_getmethods(dns_decompress_t *dctx) { + REQUIRE(VALID_DCTX(dctx)); + + return (dctx->allowed); +} + +int +dns_decompress_edns(dns_decompress_t *dctx) { + REQUIRE(VALID_DCTX(dctx)); + + return (dctx->edns); +} + +dns_decompresstype_t +dns_decompress_type(dns_decompress_t *dctx) { + REQUIRE(VALID_DCTX(dctx)); + + return (dctx->type); +} diff --git a/lib/dns/db.c b/lib/dns/db.c new file mode 100644 index 0000000..c95d19a --- /dev/null +++ b/lib/dns/db.c @@ -0,0 +1,1121 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*** + *** Private Types + ***/ + +struct dns_dbimplementation { + const char *name; + dns_dbcreatefunc_t create; + isc_mem_t *mctx; + void *driverarg; + ISC_LINK(dns_dbimplementation_t) link; +}; + +/*** + *** Supported DB Implementations Registry + ***/ + +/* + * Built in database implementations are registered here. + */ + +#include "rbtdb.h" + +unsigned int dns_pps = 0U; + +static ISC_LIST(dns_dbimplementation_t) implementations; +static isc_rwlock_t implock; +static isc_once_t once = ISC_ONCE_INIT; + +static dns_dbimplementation_t rbtimp; + +static void +initialize(void) { + isc_rwlock_init(&implock, 0, 0); + + rbtimp.name = "rbt"; + rbtimp.create = dns_rbtdb_create; + rbtimp.mctx = NULL; + rbtimp.driverarg = NULL; + ISC_LINK_INIT(&rbtimp, link); + + ISC_LIST_INIT(implementations); + ISC_LIST_APPEND(implementations, &rbtimp, link); +} + +static dns_dbimplementation_t * +impfind(const char *name) { + dns_dbimplementation_t *imp; + + for (imp = ISC_LIST_HEAD(implementations); imp != NULL; + imp = ISC_LIST_NEXT(imp, link)) + { + if (strcasecmp(name, imp->name) == 0) { + return (imp); + } + } + return (NULL); +} + +/*** + *** Basic DB Methods + ***/ + +isc_result_t +dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin, + dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc, + char *argv[], dns_db_t **dbp) { + dns_dbimplementation_t *impinfo; + + RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS); + + /* + * Create a new database using implementation 'db_type'. + */ + + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(dns_name_isabsolute(origin)); + + RWLOCK(&implock, isc_rwlocktype_read); + impinfo = impfind(db_type); + if (impinfo != NULL) { + isc_result_t result; + result = ((impinfo->create)(mctx, origin, type, rdclass, argc, + argv, impinfo->driverarg, dbp)); + RWUNLOCK(&implock, isc_rwlocktype_read); + return (result); + } + + RWUNLOCK(&implock, isc_rwlocktype_read); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DB, + ISC_LOG_ERROR, "unsupported database type '%s'", db_type); + + return (ISC_R_NOTFOUND); +} + +void +dns_db_attach(dns_db_t *source, dns_db_t **targetp) { + /* + * Attach *targetp to source. + */ + + REQUIRE(DNS_DB_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + (source->methods->attach)(source, targetp); + + ENSURE(*targetp == source); +} + +void +dns_db_detach(dns_db_t **dbp) { + /* + * Detach *dbp from its database. + */ + + REQUIRE(dbp != NULL); + REQUIRE(DNS_DB_VALID(*dbp)); + + ((*dbp)->methods->detach)(dbp); + + ENSURE(*dbp == NULL); +} + +bool +dns_db_iscache(dns_db_t *db) { + /* + * Does 'db' have cache semantics? + */ + + REQUIRE(DNS_DB_VALID(db)); + + if ((db->attributes & DNS_DBATTR_CACHE) != 0) { + return (true); + } + + return (false); +} + +bool +dns_db_iszone(dns_db_t *db) { + /* + * Does 'db' have zone semantics? + */ + + REQUIRE(DNS_DB_VALID(db)); + + if ((db->attributes & (DNS_DBATTR_CACHE | DNS_DBATTR_STUB)) == 0) { + return (true); + } + + return (false); +} + +bool +dns_db_isstub(dns_db_t *db) { + /* + * Does 'db' have stub semantics? + */ + + REQUIRE(DNS_DB_VALID(db)); + + if ((db->attributes & DNS_DBATTR_STUB) != 0) { + return (true); + } + + return (false); +} + +bool +dns_db_isdnssec(dns_db_t *db) { + /* + * Is 'db' secure or partially secure? + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + + if (db->methods->isdnssec != NULL) { + return ((db->methods->isdnssec)(db)); + } + return ((db->methods->issecure)(db)); +} + +bool +dns_db_issecure(dns_db_t *db) { + /* + * Is 'db' secure? + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + + return ((db->methods->issecure)(db)); +} + +bool +dns_db_ispersistent(dns_db_t *db) { + /* + * Is 'db' persistent? + */ + + REQUIRE(DNS_DB_VALID(db)); + + return ((db->methods->ispersistent)(db)); +} + +dns_name_t * +dns_db_origin(dns_db_t *db) { + /* + * The origin of the database. + */ + + REQUIRE(DNS_DB_VALID(db)); + + return (&db->origin); +} + +dns_rdataclass_t +dns_db_class(dns_db_t *db) { + /* + * The class of the database. + */ + + REQUIRE(DNS_DB_VALID(db)); + + return (db->rdclass); +} + +isc_result_t +dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + /* + * Begin loading 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + + return ((db->methods->beginload)(db, callbacks)); +} + +isc_result_t +dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + dns_dbonupdatelistener_t *listener; + + /* + * Finish loading 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + REQUIRE(callbacks->add_private != NULL); + + for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + listener->onupdate(db, listener->onupdate_arg); + } + + return ((db->methods->endload)(db, callbacks)); +} + +isc_result_t +dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, + unsigned int options) { + isc_result_t result, eresult; + dns_rdatacallbacks_t callbacks; + + /* + * Load master file 'filename' into 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + + if ((db->attributes & DNS_DBATTR_CACHE) != 0) { + options |= DNS_MASTER_AGETTL; + } + + dns_rdatacallbacks_init(&callbacks); + result = dns_db_beginload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_master_loadfile(filename, &db->origin, &db->origin, + db->rdclass, options, 0, &callbacks, NULL, + NULL, db->mctx, format, 0); + eresult = dns_db_endload(db, &callbacks); + /* + * We always call dns_db_endload(), but we only want to return its + * result if dns_master_loadfile() succeeded. If dns_master_loadfile() + * failed, we want to return the result code it gave us. + */ + if (eresult != ISC_R_SUCCESS && + (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE)) + { + result = eresult; + } + + return (result); +} + +isc_result_t +dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename) { + return ((db->methods->dump)(db, version, filename, + dns_masterformat_text)); +} + +/*** + *** Version Methods + ***/ + +void +dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp) { + /* + * Open the current version for reading. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + REQUIRE(versionp != NULL && *versionp == NULL); + + (db->methods->currentversion)(db, versionp); +} + +isc_result_t +dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp) { + /* + * Open a new version for reading and writing. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + REQUIRE(versionp != NULL && *versionp == NULL); + + return ((db->methods->newversion)(db, versionp)); +} + +void +dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp) { + /* + * Attach '*targetp' to 'source'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + REQUIRE(source != NULL); + REQUIRE(targetp != NULL && *targetp == NULL); + + (db->methods->attachversion)(db, source, targetp); + + ENSURE(*targetp != NULL); +} + +void +dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) { + dns_dbonupdatelistener_t *listener; + + /* + * Close version '*versionp'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0); + REQUIRE(versionp != NULL && *versionp != NULL); + + (db->methods->closeversion)(db, versionp, commit); + + if (commit) { + for (listener = ISC_LIST_HEAD(db->update_listeners); + listener != NULL; listener = ISC_LIST_NEXT(listener, link)) + { + listener->onupdate(db, listener->onupdate_arg); + } + } + + ENSURE(*versionp == NULL); +} + +/*** + *** Node Methods + ***/ + +isc_result_t +dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + /* + * Find the node with name 'name'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (db->methods->findnode != NULL) { + return ((db->methods->findnode)(db, name, create, nodep)); + } else { + return ((db->methods->findnodeext)(db, name, create, NULL, NULL, + nodep)); + } +} + +isc_result_t +dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep) { + /* + * Find the node with name 'name', passing 'arg' to the database + * implementation. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (db->methods->findnodeext != NULL) { + return ((db->methods->findnodeext)(db, name, create, methods, + clientinfo, nodep)); + } else { + return ((db->methods->findnode)(db, name, create, nodep)); + } +} + +isc_result_t +dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + /* + * Find the node with name 'name'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(nodep != NULL && *nodep == NULL); + + return ((db->methods->findnsec3node)(db, name, create, nodep)); +} + +isc_result_t +dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + /* + * Find the best match for 'name' and 'type' in version 'version' + * of 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(type != dns_rdatatype_rrsig); + REQUIRE(nodep == NULL || *nodep == NULL); + REQUIRE(dns_name_hasbuffer(foundname)); + REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) && + !dns_rdataset_isassociated(rdataset))); + REQUIRE(sigrdataset == NULL || + (DNS_RDATASET_VALID(sigrdataset) && + !dns_rdataset_isassociated(sigrdataset))); + + if (db->methods->find != NULL) { + return ((db->methods->find)(db, name, version, type, options, + now, nodep, foundname, rdataset, + sigrdataset)); + } else { + return ((db->methods->findext)(db, name, version, type, options, + now, nodep, foundname, NULL, + NULL, rdataset, sigrdataset)); + } +} + +isc_result_t +dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + /* + * Find the best match for 'name' and 'type' in version 'version' + * of 'db', passing in 'arg'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(type != dns_rdatatype_rrsig); + REQUIRE(nodep == NULL || *nodep == NULL); + REQUIRE(dns_name_hasbuffer(foundname)); + REQUIRE(rdataset == NULL || (DNS_RDATASET_VALID(rdataset) && + !dns_rdataset_isassociated(rdataset))); + REQUIRE(sigrdataset == NULL || + (DNS_RDATASET_VALID(sigrdataset) && + !dns_rdataset_isassociated(sigrdataset))); + + if (db->methods->findext != NULL) { + return ((db->methods->findext)( + db, name, version, type, options, now, nodep, foundname, + methods, clientinfo, rdataset, sigrdataset)); + } else { + return ((db->methods->find)(db, name, version, type, options, + now, nodep, foundname, rdataset, + sigrdataset)); + } +} + +isc_result_t +dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_name_t *dcname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + /* + * Find the deepest known zonecut which encloses 'name' in 'db'. + * foundname is the zonecut, dcname is the deepest name we have + * in database that is part of queried name. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + REQUIRE(nodep == NULL || *nodep == NULL); + REQUIRE(dns_name_hasbuffer(foundname)); + REQUIRE(sigrdataset == NULL || + (DNS_RDATASET_VALID(sigrdataset) && + !dns_rdataset_isassociated(sigrdataset))); + + return ((db->methods->findzonecut)(db, name, options, now, nodep, + foundname, dcname, rdataset, + sigrdataset)); +} + +void +dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + /* + * Attach *targetp to source. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(source != NULL); + REQUIRE(targetp != NULL && *targetp == NULL); + + (db->methods->attachnode)(db, source, targetp); +} + +void +dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep) { + /* + * Detach *nodep from its node. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(nodep != NULL && *nodep != NULL); + + (db->methods->detachnode)(db, nodep); + + ENSURE(*nodep == NULL); +} + +void +dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep, + dns_dbnode_t **targetp) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(targetp != NULL && *targetp == NULL); + /* + * This doesn't check the implementation magic. If we find that + * we need such checks in future then this will be done in the + * method. + */ + REQUIRE(sourcep != NULL && *sourcep != NULL); + + UNUSED(db); + + if (db->methods->transfernode == NULL) { + *targetp = *sourcep; + *sourcep = NULL; + } else { + (db->methods->transfernode)(db, sourcep, targetp); + } + + ENSURE(*sourcep == NULL); +} + +isc_result_t +dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + /* + * Mark as stale all records at 'node' which expire at or before 'now'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + REQUIRE(node != NULL); + + return ((db->methods->expirenode)(db, node, now)); +} + +void +dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) { + /* + * Print a textual representation of the contents of the node to + * 'out'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(node != NULL); + + (db->methods->printnode)(db, node, out); +} + +/*** + *** DB Iterator Creation + ***/ + +isc_result_t +dns_db_createiterator(dns_db_t *db, unsigned int flags, + dns_dbiterator_t **iteratorp) { + /* + * Create an iterator for version 'version' of 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(iteratorp != NULL && *iteratorp == NULL); + + return (db->methods->createiterator(db, flags, iteratorp)); +} + +/*** + *** Rdataset Methods + ***/ + +isc_result_t +dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, + isc_stdtime_t now, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(node != NULL); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + REQUIRE(covers == 0 || type == dns_rdatatype_rrsig); + REQUIRE(type != dns_rdatatype_any); + REQUIRE(sigrdataset == NULL || + (DNS_RDATASET_VALID(sigrdataset) && + !dns_rdataset_isassociated(sigrdataset))); + + return ((db->methods->findrdataset)(db, node, version, type, covers, + now, rdataset, sigrdataset)); +} + +isc_result_t +dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp) { + /* + * Make '*iteratorp' an rdataset iteratator for all rdatasets at + * 'node' in version 'version' of 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(iteratorp != NULL && *iteratorp == NULL); + + return ((db->methods->allrdatasets)(db, node, version, options, now, + iteratorp)); +} + +isc_result_t +dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *addedrdataset) { + /* + * Add 'rdataset' to 'node' in version 'version' of 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(node != NULL); + REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) || + ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL && + (options & DNS_DBADD_MERGE) == 0)); + REQUIRE((options & DNS_DBADD_EXACT) == 0 || + (options & DNS_DBADD_MERGE) != 0); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(dns_rdataset_isassociated(rdataset)); + REQUIRE(rdataset->rdclass == db->rdclass); + REQUIRE(addedrdataset == NULL || + (DNS_RDATASET_VALID(addedrdataset) && + !dns_rdataset_isassociated(addedrdataset))); + + return ((db->methods->addrdataset)(db, node, version, now, rdataset, + options, addedrdataset)); +} + +isc_result_t +dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *newrdataset) { + /* + * Remove any rdata in 'rdataset' from 'node' in version 'version' of + * 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(node != NULL); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(dns_rdataset_isassociated(rdataset)); + REQUIRE(rdataset->rdclass == db->rdclass); + REQUIRE(newrdataset == NULL || + (DNS_RDATASET_VALID(newrdataset) && + !dns_rdataset_isassociated(newrdataset))); + + return ((db->methods->subtractrdataset)(db, node, version, rdataset, + options, newrdataset)); +} + +isc_result_t +dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdatatype_t type, + dns_rdatatype_t covers) { + /* + * Make it so that no rdataset of type 'type' exists at 'node' in + * version version 'version' of 'db'. + */ + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(node != NULL); + REQUIRE(((db->attributes & DNS_DBATTR_CACHE) == 0 && version != NULL) || + ((db->attributes & DNS_DBATTR_CACHE) != 0 && version == NULL)); + + return ((db->methods->deleterdataset)(db, node, version, type, covers)); +} + +void +dns_db_overmem(dns_db_t *db, bool overmem) { + REQUIRE(DNS_DB_VALID(db)); + + (db->methods->overmem)(db, overmem); +} + +isc_result_t +dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t buffer; + + REQUIRE(dns_db_iszone(db) || dns_db_isstub(db)); + + result = dns_db_findnode(db, dns_db_origin(db), false, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto freenode; + } + + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) { + goto freerdataset; + } + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdataset_next(&rdataset); + INSIST(result == ISC_R_NOMORE); + + INSIST(rdata.length > 20); + isc_buffer_init(&buffer, rdata.data, rdata.length); + isc_buffer_add(&buffer, rdata.length); + isc_buffer_forward(&buffer, rdata.length - 20); + *serialp = isc_buffer_getuint32(&buffer); + + result = ISC_R_SUCCESS; + +freerdataset: + dns_rdataset_disassociate(&rdataset); + +freenode: + dns_db_detachnode(db, &node); + return (result); +} + +unsigned int +dns_db_nodecount(dns_db_t *db, dns_dbtree_t tree) { + REQUIRE(DNS_DB_VALID(db)); + + return ((db->methods->nodecount)(db, tree)); +} + +size_t +dns_db_hashsize(dns_db_t *db) { + REQUIRE(DNS_DB_VALID(db)); + + if (db->methods->hashsize == NULL) { + return (0); + } + + return ((db->methods->hashsize)(db)); +} + +void +dns_db_settask(dns_db_t *db, isc_task_t *task) { + REQUIRE(DNS_DB_VALID(db)); + + (db->methods->settask)(db, task); +} + +isc_result_t +dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg, + isc_mem_t *mctx, dns_dbimplementation_t **dbimp) { + dns_dbimplementation_t *imp; + + REQUIRE(name != NULL); + REQUIRE(dbimp != NULL && *dbimp == NULL); + + RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS); + + RWLOCK(&implock, isc_rwlocktype_write); + imp = impfind(name); + if (imp != NULL) { + RWUNLOCK(&implock, isc_rwlocktype_write); + return (ISC_R_EXISTS); + } + + imp = isc_mem_get(mctx, sizeof(dns_dbimplementation_t)); + imp->name = name; + imp->create = create; + imp->mctx = NULL; + imp->driverarg = driverarg; + isc_mem_attach(mctx, &imp->mctx); + ISC_LINK_INIT(imp, link); + ISC_LIST_APPEND(implementations, imp, link); + RWUNLOCK(&implock, isc_rwlocktype_write); + + *dbimp = imp; + + return (ISC_R_SUCCESS); +} + +void +dns_db_unregister(dns_dbimplementation_t **dbimp) { + dns_dbimplementation_t *imp; + + REQUIRE(dbimp != NULL && *dbimp != NULL); + + RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS); + + imp = *dbimp; + *dbimp = NULL; + RWLOCK(&implock, isc_rwlocktype_write); + ISC_LIST_UNLINK(implementations, imp, link); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_dbimplementation_t)); + RWUNLOCK(&implock, isc_rwlocktype_write); + ENSURE(*dbimp == NULL); +} + +isc_result_t +dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (db->methods->getoriginnode != NULL) { + return ((db->methods->getoriginnode)(db, nodep)); + } + + return (ISC_R_NOTFOUND); +} + +dns_stats_t * +dns_db_getrrsetstats(dns_db_t *db) { + REQUIRE(DNS_DB_VALID(db)); + + if (db->methods->getrrsetstats != NULL) { + return ((db->methods->getrrsetstats)(db)); + } + + return (NULL); +} + +isc_result_t +dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats) { + REQUIRE(DNS_DB_VALID(db)); + + if (db->methods->setcachestats != NULL) { + return ((db->methods->setcachestats)(db, stats)); + } + + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, + dns_hash_t *hash, uint8_t *flags, + uint16_t *iterations, unsigned char *salt, + size_t *salt_length) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + + if (db->methods->getnsec3parameters != NULL) { + return ((db->methods->getnsec3parameters)(db, version, hash, + flags, iterations, + salt, salt_length)); + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, + uint64_t *bytes) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(dns_db_iszone(db)); + + if (db->methods->getsize != NULL) { + return ((db->methods->getsize)(db, version, records, bytes)); + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, + isc_stdtime_t resign) { + if (db->methods->setsigningtime != NULL) { + return ((db->methods->setsigningtime)(db, rdataset, resign)); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, + dns_name_t *name) { + if (db->methods->getsigningtime != NULL) { + return ((db->methods->getsigningtime)(db, rdataset, name)); + } + return (ISC_R_NOTFOUND); +} + +void +dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset, + dns_dbversion_t *version) { + if (db->methods->resigned != NULL) { + (db->methods->resigned)(db, rdataset, version); + } +} + +/* + * Attach a database to policy zone databases. + * This should only happen when the caller has already ensured that + * it is dealing with a database that understands response policy zones. + */ +void +dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) { + REQUIRE(db->methods->rpz_attach != NULL); + (db->methods->rpz_attach)(db, rpzs, rpz_num); +} + +/* + * Finish loading a response policy zone. + */ +isc_result_t +dns_db_rpz_ready(dns_db_t *db) { + if (db->methods->rpz_ready == NULL) { + return (ISC_R_SUCCESS); + } + return ((db->methods->rpz_ready)(db)); +} + +/* + * Attach a notify-on-update function the database + */ +isc_result_t +dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn, + void *fn_arg) { + dns_dbonupdatelistener_t *listener; + + REQUIRE(db != NULL); + REQUIRE(fn != NULL); + + for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + if ((listener->onupdate == fn) && + (listener->onupdate_arg == fn_arg)) + { + return (ISC_R_SUCCESS); + } + } + + listener = isc_mem_get(db->mctx, sizeof(dns_dbonupdatelistener_t)); + + listener->onupdate = fn; + listener->onupdate_arg = fn_arg; + + ISC_LINK_INIT(listener, link); + ISC_LIST_APPEND(db->update_listeners, listener, link); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn, + void *fn_arg) { + dns_dbonupdatelistener_t *listener; + + REQUIRE(db != NULL); + + for (listener = ISC_LIST_HEAD(db->update_listeners); listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + { + if ((listener->onupdate == fn) && + (listener->onupdate_arg == fn_arg)) + { + ISC_LIST_UNLINK(db->update_listeners, listener, link); + isc_mem_put(db->mctx, listener, + sizeof(dns_dbonupdatelistener_t)); + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) { + REQUIRE(db != NULL); + REQUIRE(node != NULL); + REQUIRE(name != NULL); + + if (db->methods->nodefullname == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((db->methods->nodefullname)(db, node, name)); +} + +isc_result_t +dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->setservestalettl != NULL) { + return ((db->methods->setservestalettl)(db, ttl)); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->getservestalettl != NULL) { + return ((db->methods->getservestalettl)(db, ttl)); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->setservestalerefresh != NULL) { + return ((db->methods->setservestalerefresh)(db, interval)); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval) { + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->getservestalerefresh != NULL) { + return ((db->methods->getservestalerefresh)(db, interval)); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats) { + REQUIRE(dns_db_iszone(db)); + REQUIRE(stats != NULL); + + if (db->methods->setgluecachestats != NULL) { + return ((db->methods->setgluecachestats)(db, stats)); + } + + return (ISC_R_NOTIMPLEMENTED); +} diff --git a/lib/dns/dbiterator.c b/lib/dns/dbiterator.c new file mode 100644 index 0000000..39d9471 --- /dev/null +++ b/lib/dns/dbiterator.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include + +#include +#include + +void +dns_dbiterator_destroy(dns_dbiterator_t **iteratorp) { + /* + * Destroy '*iteratorp'. + */ + + REQUIRE(iteratorp != NULL); + REQUIRE(DNS_DBITERATOR_VALID(*iteratorp)); + + (*iteratorp)->methods->destroy(iteratorp); + + ENSURE(*iteratorp == NULL); +} + +isc_result_t +dns_dbiterator_first(dns_dbiterator_t *iterator) { + /* + * Move the node cursor to the first node in the database (if any). + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->first(iterator)); +} + +isc_result_t +dns_dbiterator_last(dns_dbiterator_t *iterator) { + /* + * Move the node cursor to the first node in the database (if any). + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->last(iterator)); +} + +isc_result_t +dns_dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) { + /* + * Move the node cursor to the node with name 'name'. + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->seek(iterator, name)); +} + +isc_result_t +dns_dbiterator_prev(dns_dbiterator_t *iterator) { + /* + * Move the node cursor to the previous node in the database (if any). + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->prev(iterator)); +} + +isc_result_t +dns_dbiterator_next(dns_dbiterator_t *iterator) { + /* + * Move the node cursor to the next node in the database (if any). + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->next(iterator)); +} + +isc_result_t +dns_dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name) { + /* + * Return the current node. + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(name == NULL || dns_name_hasbuffer(name)); + + return (iterator->methods->current(iterator, nodep, name)); +} + +isc_result_t +dns_dbiterator_pause(dns_dbiterator_t *iterator) { + /* + * Pause iteration. + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + return (iterator->methods->pause(iterator)); +} + +isc_result_t +dns_dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) { + /* + * Return the origin to which returned node names are relative. + */ + + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + REQUIRE(iterator->relative_names); + REQUIRE(dns_name_hasbuffer(name)); + + return (iterator->methods->origin(iterator, name)); +} + +void +dns_dbiterator_setcleanmode(dns_dbiterator_t *iterator, bool mode) { + REQUIRE(DNS_DBITERATOR_VALID(iterator)); + + iterator->cleaning = mode; +} diff --git a/lib/dns/diff.c b/lib/dns/diff.c new file mode 100644 index 0000000..52f5aca --- /dev/null +++ b/lib/dns/diff.c @@ -0,0 +1,686 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define DIFF_COMMON_LOGARGS \ + dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF + +static dns_rdatatype_t +rdata_covers(dns_rdata_t *rdata) { + return (rdata->type == dns_rdatatype_rrsig ? dns_rdata_covers(rdata) + : 0); +} + +isc_result_t +dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp) { + dns_difftuple_t *t; + unsigned int size; + unsigned char *datap; + + REQUIRE(tp != NULL && *tp == NULL); + + /* + * Create a new tuple. The variable-size wire-format name data and + * rdata immediately follow the dns_difftuple_t structure + * in memory. + */ + size = sizeof(*t) + name->length + rdata->length; + t = isc_mem_allocate(mctx, size); + t->mctx = NULL; + isc_mem_attach(mctx, &t->mctx); + t->op = op; + + datap = (unsigned char *)(t + 1); + + memmove(datap, name->ndata, name->length); + dns_name_init(&t->name, NULL); + dns_name_clone(name, &t->name); + t->name.ndata = datap; + datap += name->length; + + t->ttl = ttl; + + dns_rdata_init(&t->rdata); + dns_rdata_clone(rdata, &t->rdata); + if (rdata->data != NULL) { + memmove(datap, rdata->data, rdata->length); + t->rdata.data = datap; + datap += rdata->length; + } else { + t->rdata.data = NULL; + INSIST(rdata->length == 0); + } + + ISC_LINK_INIT(&t->rdata, link); + ISC_LINK_INIT(t, link); + t->magic = DNS_DIFFTUPLE_MAGIC; + + INSIST(datap == (unsigned char *)t + size); + + *tp = t; + return (ISC_R_SUCCESS); +} + +void +dns_difftuple_free(dns_difftuple_t **tp) { + dns_difftuple_t *t = *tp; + *tp = NULL; + isc_mem_t *mctx; + + REQUIRE(DNS_DIFFTUPLE_VALID(t)); + + dns_name_invalidate(&t->name); + t->magic = 0; + mctx = t->mctx; + isc_mem_free(mctx, t); + isc_mem_detach(&mctx); +} + +isc_result_t +dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp) { + return (dns_difftuple_create(orig->mctx, orig->op, &orig->name, + orig->ttl, &orig->rdata, copyp)); +} + +void +dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff) { + diff->mctx = mctx; + ISC_LIST_INIT(diff->tuples); + diff->magic = DNS_DIFF_MAGIC; +} + +void +dns_diff_clear(dns_diff_t *diff) { + dns_difftuple_t *t; + REQUIRE(DNS_DIFF_VALID(diff)); + while ((t = ISC_LIST_HEAD(diff->tuples)) != NULL) { + ISC_LIST_UNLINK(diff->tuples, t, link); + dns_difftuple_free(&t); + } + ENSURE(ISC_LIST_EMPTY(diff->tuples)); +} + +void +dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuplep) { + ISC_LIST_APPEND(diff->tuples, *tuplep, link); + *tuplep = NULL; +} + +/* XXX this is O(N) */ + +void +dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep) { + dns_difftuple_t *ot, *next_ot; + + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep)); + + /* + * Look for an existing tuple with the same owner name, + * rdata, and TTL. If we are doing an addition and find a + * deletion or vice versa, remove both the old and the + * new tuple since they cancel each other out (assuming + * that we never delete nonexistent data or add existing + * data). + * + * If we find an old update of the same kind as + * the one we are doing, there must be a programming + * error. We report it but try to continue anyway. + */ + for (ot = ISC_LIST_HEAD(diff->tuples); ot != NULL; ot = next_ot) { + next_ot = ISC_LIST_NEXT(ot, link); + if (dns_name_caseequal(&ot->name, &(*tuplep)->name) && + dns_rdata_compare(&ot->rdata, &(*tuplep)->rdata) == 0 && + ot->ttl == (*tuplep)->ttl) + { + ISC_LIST_UNLINK(diff->tuples, ot, link); + if ((*tuplep)->op == ot->op) { + UNEXPECTED_ERROR("unexpected non-minimal diff"); + } else { + dns_difftuple_free(tuplep); + } + dns_difftuple_free(&ot); + break; + } + } + + if (*tuplep != NULL) { + ISC_LIST_APPEND(diff->tuples, *tuplep, link); + *tuplep = NULL; + } +} + +static isc_stdtime_t +setresign(dns_rdataset_t *modified) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + int64_t when; + isc_result_t result; + + result = dns_rdataset_first(modified); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(modified, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + when = 0; + } else { + when = dns_time64_from32(sig.timeexpire); + } + dns_rdata_reset(&rdata); + + result = dns_rdataset_next(modified); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(modified, &rdata); + (void)dns_rdata_tostruct(&rdata, &sig, NULL); + if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) { + goto next_rr; + } + if (when == 0 || dns_time64_from32(sig.timeexpire) < when) { + when = dns_time64_from32(sig.timeexpire); + } + next_rr: + dns_rdata_reset(&rdata); + result = dns_rdataset_next(modified); + } + INSIST(result == ISC_R_NOMORE); + return ((isc_stdtime_t)when); +} + +static void +getownercase(dns_rdataset_t *rdataset, dns_name_t *name) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_getownercase(rdataset, name); + } +} + +static void +setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_setownercase(rdataset, name); + } +} + +static isc_result_t +diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver, bool warn) { + dns_difftuple_t *t; + dns_dbnode_t *node = NULL; + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(DNS_DB_VALID(db)); + + t = ISC_LIST_HEAD(diff->tuples); + while (t != NULL) { + dns_name_t *name; + + INSIST(node == NULL); + name = &t->name; + /* + * Find the node. + * We create the node if it does not exist. + * This will cause an empty node to be created if the diff + * contains a deletion of an RR at a nonexistent name, + * but such diffs should never be created in the first + * place. + */ + + while (t != NULL && dns_name_equal(&t->name, name)) { + dns_rdatatype_t type, covers; + dns_diffop_t op; + dns_rdatalist_t rdl; + dns_rdataset_t rds; + dns_rdataset_t ardataset; + unsigned int options; + + op = t->op; + type = t->rdata.type; + covers = rdata_covers(&t->rdata); + + /* + * Collect a contiguous set of updates with + * the same operation (add/delete) and RR type + * into a single rdatalist so that the + * database rrset merging/subtraction code + * can work more efficiently than if each + * RR were merged into / subtracted from + * the database separately. + * + * This is done by linking rdata structures from the + * diff into "rdatalist". This uses the rdata link + * field, not the diff link field, so the structure + * of the diff itself is not affected. + */ + + dns_rdatalist_init(&rdl); + rdl.type = type; + rdl.covers = covers; + rdl.rdclass = t->rdata.rdclass; + rdl.ttl = t->ttl; + + node = NULL; + if (type != dns_rdatatype_nsec3 && + covers != dns_rdatatype_nsec3) + { + CHECK(dns_db_findnode(db, name, true, &node)); + } else { + CHECK(dns_db_findnsec3node(db, name, true, + &node)); + } + + while (t != NULL && dns_name_equal(&t->name, name) && + t->op == op && t->rdata.type == type && + rdata_covers(&t->rdata) == covers) + { + /* + * Remember the add name for + * dns_rdataset_setownercase. + */ + name = &t->name; + if (t->ttl != rdl.ttl && warn) { + dns_name_format(name, namebuf, + sizeof(namebuf)); + dns_rdatatype_format(t->rdata.type, + typebuf, + sizeof(typebuf)); + dns_rdataclass_format(t->rdata.rdclass, + classbuf, + sizeof(classbuf)); + isc_log_write(DIFF_COMMON_LOGARGS, + ISC_LOG_WARNING, + "'%s/%s/%s': TTL differs " + "in " + "rdataset, adjusting " + "%lu -> %lu", + namebuf, typebuf, + classbuf, + (unsigned long)t->ttl, + (unsigned long)rdl.ttl); + } + ISC_LIST_APPEND(rdl.rdata, &t->rdata, link); + t = ISC_LIST_NEXT(t, link); + } + + /* + * Convert the rdatalist into a rdataset. + */ + dns_rdataset_init(&rds); + dns_rdataset_init(&ardataset); + CHECK(dns_rdatalist_tordataset(&rdl, &rds)); + rds.trust = dns_trust_ultimate; + + /* + * Merge the rdataset into the database. + */ + switch (op) { + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + options = DNS_DBADD_MERGE | DNS_DBADD_EXACT | + DNS_DBADD_EXACTTTL; + result = dns_db_addrdataset(db, node, ver, 0, + &rds, options, + &ardataset); + break; + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD; + result = dns_db_subtractrdataset(db, node, ver, + &rds, options, + &ardataset); + break; + default: + UNREACHABLE(); + } + + if (result == ISC_R_SUCCESS) { + if (rds.type == dns_rdatatype_rrsig && + (op == DNS_DIFFOP_DELRESIGN || + op == DNS_DIFFOP_ADDRESIGN)) + { + isc_stdtime_t resign; + resign = setresign(&ardataset); + dns_db_setsigningtime(db, &ardataset, + resign); + } + if (op == DNS_DIFFOP_ADD || + op == DNS_DIFFOP_ADDRESIGN) + { + setownercase(&ardataset, name); + } + if (op == DNS_DIFFOP_DEL || + op == DNS_DIFFOP_DELRESIGN) + { + getownercase(&ardataset, name); + } + } else if (result == DNS_R_UNCHANGED) { + /* + * This will not happen when executing a + * dynamic update, because that code will + * generate strictly minimal diffs. + * It may happen when receiving an IXFR + * from a server that is not as careful. + * Issue a warning and continue. + */ + if (warn) { + dns_name_format(dns_db_origin(db), + namebuf, + sizeof(namebuf)); + dns_rdataclass_format(dns_db_class(db), + classbuf, + sizeof(classbuf)); + isc_log_write(DIFF_COMMON_LOGARGS, + ISC_LOG_WARNING, + "%s/%s: dns_diff_apply: " + "update with no effect", + namebuf, classbuf); + } + if (op == DNS_DIFFOP_ADD || + op == DNS_DIFFOP_ADDRESIGN) + { + setownercase(&ardataset, name); + } + if (op == DNS_DIFFOP_DEL || + op == DNS_DIFFOP_DELRESIGN) + { + getownercase(&ardataset, name); + } + } else if (result == DNS_R_NXRRSET) { + /* + * OK. + */ + if (op == DNS_DIFFOP_DEL || + op == DNS_DIFFOP_DELRESIGN) + { + getownercase(&ardataset, name); + } + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + } else { + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + CHECK(result); + } + dns_db_detachnode(db, &node); + if (dns_rdataset_isassociated(&ardataset)) { + dns_rdataset_disassociate(&ardataset); + } + } + } + return (ISC_R_SUCCESS); + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +isc_result_t +dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { + return (diff_apply(diff, db, ver, true)); +} + +isc_result_t +dns_diff_applysilently(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) { + return (diff_apply(diff, db, ver, false)); +} + +/* XXX this duplicates lots of code in diff_apply(). */ + +isc_result_t +dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc, + void *add_private) { + dns_difftuple_t *t; + isc_result_t result; + + REQUIRE(DNS_DIFF_VALID(diff)); + + t = ISC_LIST_HEAD(diff->tuples); + while (t != NULL) { + dns_name_t *name; + + name = &t->name; + while (t != NULL && dns_name_caseequal(&t->name, name)) { + dns_rdatatype_t type, covers; + dns_diffop_t op; + dns_rdatalist_t rdl; + dns_rdataset_t rds; + + op = t->op; + type = t->rdata.type; + covers = rdata_covers(&t->rdata); + + dns_rdatalist_init(&rdl); + rdl.type = type; + rdl.covers = covers; + rdl.rdclass = t->rdata.rdclass; + rdl.ttl = t->ttl; + + while (t != NULL && + dns_name_caseequal(&t->name, name) && + t->op == op && t->rdata.type == type && + rdata_covers(&t->rdata) == covers) + { + ISC_LIST_APPEND(rdl.rdata, &t->rdata, link); + t = ISC_LIST_NEXT(t, link); + } + + /* + * Convert the rdatalist into a rdataset. + */ + dns_rdataset_init(&rds); + CHECK(dns_rdatalist_tordataset(&rdl, &rds)); + rds.trust = dns_trust_ultimate; + + INSIST(op == DNS_DIFFOP_ADD); + result = (*addfunc)(add_private, name, &rds); + if (result == DNS_R_UNCHANGED) { + isc_log_write(DIFF_COMMON_LOGARGS, + ISC_LOG_WARNING, + "dns_diff_load: " + "update with no effect"); + } else if (result == ISC_R_SUCCESS || + result == DNS_R_NXRRSET) + { + /* + * OK. + */ + } else { + CHECK(result); + } + } + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/* + * XXX uses qsort(); a merge sort would be more natural for lists, + * and perhaps safer wrt thread stack overflow. + */ +isc_result_t +dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) { + unsigned int length = 0; + unsigned int i; + dns_difftuple_t **v; + dns_difftuple_t *p; + REQUIRE(DNS_DIFF_VALID(diff)); + + for (p = ISC_LIST_HEAD(diff->tuples); p != NULL; + p = ISC_LIST_NEXT(p, link)) + { + length++; + } + if (length == 0) { + return (ISC_R_SUCCESS); + } + v = isc_mem_get(diff->mctx, length * sizeof(dns_difftuple_t *)); + for (i = 0; i < length; i++) { + p = ISC_LIST_HEAD(diff->tuples); + v[i] = p; + ISC_LIST_UNLINK(diff->tuples, p, link); + } + INSIST(ISC_LIST_HEAD(diff->tuples) == NULL); + qsort(v, length, sizeof(v[0]), compare); + for (i = 0; i < length; i++) { + ISC_LIST_APPEND(diff->tuples, v[i], link); + } + isc_mem_put(diff->mctx, v, length * sizeof(dns_difftuple_t *)); + return (ISC_R_SUCCESS); +} + +/* + * Create an rdataset containing the single RR of the given + * tuple. The caller must allocate the rdata, rdataset and + * an rdatalist structure for it to refer to. + */ + +static isc_result_t +diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata, + dns_rdatalist_t *rdl, dns_rdataset_t *rds) { + REQUIRE(DNS_DIFFTUPLE_VALID(t)); + REQUIRE(rdl != NULL); + REQUIRE(rds != NULL); + + dns_rdatalist_init(rdl); + rdl->type = t->rdata.type; + rdl->rdclass = t->rdata.rdclass; + rdl->ttl = t->ttl; + dns_rdataset_init(rds); + ISC_LINK_INIT(rdata, link); + dns_rdata_clone(&t->rdata, rdata); + ISC_LIST_APPEND(rdl->rdata, rdata, link); + return (dns_rdatalist_tordataset(rdl, rds)); +} + +isc_result_t +dns_diff_print(dns_diff_t *diff, FILE *file) { + isc_result_t result; + dns_difftuple_t *t; + char *mem = NULL; + unsigned int size = 2048; + const char *op = NULL; + + REQUIRE(DNS_DIFF_VALID(diff)); + + mem = isc_mem_get(diff->mctx, size); + + for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + isc_buffer_t buf; + isc_region_t r; + + dns_rdatalist_t rdl; + dns_rdataset_t rds; + dns_rdata_t rd = DNS_RDATA_INIT; + + result = diff_tuple_tordataset(t, &rd, &rdl, &rds); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("diff_tuple_tordataset failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + again: + isc_buffer_init(&buf, mem, size); + result = dns_rdataset_totext(&rds, &t->name, false, false, + &buf); + + if (result == ISC_R_NOSPACE) { + isc_mem_put(diff->mctx, mem, size); + size += 1024; + mem = isc_mem_get(diff->mctx, size); + goto again; + } + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + /* + * Get rid of final newline. + */ + INSIST(buf.used >= 1 && + ((char *)buf.base)[buf.used - 1] == '\n'); + buf.used--; + + isc_buffer_usedregion(&buf, &r); + switch (t->op) { + case DNS_DIFFOP_EXISTS: + op = "exists"; + break; + case DNS_DIFFOP_ADD: + op = "add"; + break; + case DNS_DIFFOP_DEL: + op = "del"; + break; + case DNS_DIFFOP_ADDRESIGN: + op = "add re-sign"; + break; + case DNS_DIFFOP_DELRESIGN: + op = "del re-sign"; + break; + } + if (file != NULL) { + fprintf(file, "%s %.*s\n", op, (int)r.length, + (char *)r.base); + } else { + isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_DEBUG(7), + "%s %.*s", op, (int)r.length, + (char *)r.base); + } + } + result = ISC_R_SUCCESS; +cleanup: + if (mem != NULL) { + isc_mem_put(diff->mctx, mem, size); + } + return (result); +} diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c new file mode 100644 index 0000000..d737363 --- /dev/null +++ b/lib/dns/dispatch.c @@ -0,0 +1,2296 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef ISC_LIST(dns_dispentry_t) dns_displist_t; + +typedef struct dns_qid { + unsigned int magic; + isc_mutex_t lock; + unsigned int qid_nbuckets; /*%< hash table size */ + unsigned int qid_increment; /*%< id increment on collision */ + dns_displist_t *qid_table; /*%< the table itself */ +} dns_qid_t; + +struct dns_dispatchmgr { + /* Unlocked. */ + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + dns_acl_t *blackhole; + isc_stats_t *stats; + isc_nm_t *nm; + + /* Locked by "lock". */ + isc_mutex_t lock; + ISC_LIST(dns_dispatch_t) list; + + dns_qid_t *qid; + + in_port_t *v4ports; /*%< available ports for IPv4 */ + unsigned int nv4ports; /*%< # of available ports for IPv4 */ + in_port_t *v6ports; /*%< available ports for IPv4 */ + unsigned int nv6ports; /*%< # of available ports for IPv4 */ +}; + +typedef enum { + DNS_DISPATCHSTATE_NONE = 0UL, + DNS_DISPATCHSTATE_CONNECTING, + DNS_DISPATCHSTATE_CONNECTED, + DNS_DISPATCHSTATE_CANCELED, +} dns_dispatchstate_t; + +struct dns_dispentry { + unsigned int magic; + isc_refcount_t references; + dns_dispatch_t *disp; + isc_nmhandle_t *handle; /*%< netmgr handle for UDP connection */ + dns_dispatchstate_t state; + unsigned int bucket; + unsigned int retries; + unsigned int timeout; + isc_time_t start; + isc_sockaddr_t local; + isc_sockaddr_t peer; + in_port_t port; + dns_messageid_t id; + dispatch_cb_t connected; + dispatch_cb_t sent; + dispatch_cb_t response; + void *arg; + bool reading; + isc_result_t result; + ISC_LINK(dns_dispentry_t) link; + ISC_LINK(dns_dispentry_t) alink; + ISC_LINK(dns_dispentry_t) plink; + ISC_LINK(dns_dispentry_t) rlink; +}; + +struct dns_dispatch { + /* Unlocked. */ + unsigned int magic; /*%< magic */ + int tid; + dns_dispatchmgr_t *mgr; /*%< dispatch manager */ + isc_nmhandle_t *handle; /*%< netmgr handle for TCP connection */ + isc_sockaddr_t local; /*%< local address */ + in_port_t localport; /*%< local UDP port */ + isc_sockaddr_t peer; /*%< peer address (TCP) */ + + /*% Locked by mgr->lock. */ + ISC_LINK(dns_dispatch_t) link; + + /* Locked by "lock". */ + isc_mutex_t lock; /*%< locks all below */ + isc_socktype_t socktype; + dns_dispatchstate_t state; + isc_refcount_t references; + + bool reading; + + dns_displist_t pending; + dns_displist_t active; + + unsigned int requests; /*%< how many requests we have */ + + unsigned int timedout; +}; + +#define QID_MAGIC ISC_MAGIC('Q', 'i', 'd', ' ') +#define VALID_QID(e) ISC_MAGIC_VALID((e), QID_MAGIC) + +#define RESPONSE_MAGIC ISC_MAGIC('D', 'r', 's', 'p') +#define VALID_RESPONSE(e) ISC_MAGIC_VALID((e), RESPONSE_MAGIC) + +#define DISPSOCK_MAGIC ISC_MAGIC('D', 's', 'o', 'c') +#define VALID_DISPSOCK(e) ISC_MAGIC_VALID((e), DISPSOCK_MAGIC) + +#define DISPATCH_MAGIC ISC_MAGIC('D', 'i', 's', 'p') +#define VALID_DISPATCH(e) ISC_MAGIC_VALID((e), DISPATCH_MAGIC) + +#define DNS_DISPATCHMGR_MAGIC ISC_MAGIC('D', 'M', 'g', 'r') +#define VALID_DISPATCHMGR(e) ISC_MAGIC_VALID((e), DNS_DISPATCHMGR_MAGIC) + +/*% + * Number of buckets in the QID hash table, and the value to + * increment the QID by when attempting to avoid collisions. + * The number of buckets should be prime, and the increment + * should be the next higher prime number. + */ +#ifndef DNS_QID_BUCKETS +#define DNS_QID_BUCKETS 16411 +#endif /* ifndef DNS_QID_BUCKETS */ +#ifndef DNS_QID_INCREMENT +#define DNS_QID_INCREMENT 16433 +#endif /* ifndef DNS_QID_INCREMENT */ + +#if DNS_DISPATCH_TRACE +#define dns_dispentry_ref(ptr) \ + dns_dispentry__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispentry_unref(ptr) \ + dns_dispentry__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispentry_attach(ptr, ptrp) \ + dns_dispentry__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define dns_dispentry_detach(ptrp) \ + dns_dispentry__detach(ptrp, __func__, __FILE__, __LINE__) +ISC_REFCOUNT_TRACE_DECL(dns_dispentry); +#else +ISC_REFCOUNT_DECL(dns_dispentry); +#endif + +/* + * Statics. + */ +static void +dispatchmgr_destroy(dns_dispatchmgr_t *mgr); + +static dns_dispentry_t * +entry_search(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t, + unsigned int); +static void +udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg); +static void +tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg); +static void +tcp_recv_done(dns_dispentry_t *resp, isc_result_t eresult, + isc_region_t *region); +static uint32_t +dns_hash(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t); +static void +dispentry_cancel(dns_dispentry_t *resp, isc_result_t result); +static isc_result_t +dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + dns_dispatch_t **dispp); +static void +qid_allocate(dns_dispatchmgr_t *mgr, dns_qid_t **qidp); +static void +qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp); +static void +udp_startrecv(isc_nmhandle_t *handle, dns_dispentry_t *resp); +static void +udp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp); +static void +tcp_startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp, + dns_dispentry_t *resp); +static void +tcp_dispatch_getnext(dns_dispatch_t *disp, dns_dispentry_t *resp, + int32_t timeout); +static void +udp_dispatch_getnext(dns_dispentry_t *resp, int32_t timeout); + +#define LVL(x) ISC_LOG_DEBUG(x) + +static const char * +socktype2str(dns_dispentry_t *resp) { + dns_dispatch_t *disp = resp->disp; + + switch (disp->socktype) { + case isc_socktype_udp: + return ("UDP"); + case isc_socktype_tcp: + return ("TCP"); + default: + return (""); + } +} + +static const char * +state2str(dns_dispatchstate_t state) { + switch (state) { + case DNS_DISPATCHSTATE_NONE: + return ("none"); + case DNS_DISPATCHSTATE_CONNECTING: + return ("connecting"); + case DNS_DISPATCHSTATE_CONNECTED: + return ("connected"); + case DNS_DISPATCHSTATE_CANCELED: + return ("canceled"); + default: + return (""); + } +} + +static void +mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +static void +mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH, + DNS_LOGMODULE_DISPATCH, level, "dispatchmgr %p: %s", mgr, + msgbuf); +} + +static void +inc_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) { + if (mgr->stats != NULL) { + isc_stats_increment(mgr->stats, counter); + } +} + +static void +dec_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) { + if (mgr->stats != NULL) { + isc_stats_decrement(mgr->stats, counter); + } +} + +static void +dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +static void +dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + int r; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + r = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + if (r < 0) { + msgbuf[0] = '\0'; + } else if ((unsigned int)r >= sizeof(msgbuf)) { + /* Truncated */ + msgbuf[sizeof(msgbuf) - 1] = '\0'; + } + va_end(ap); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH, + DNS_LOGMODULE_DISPATCH, level, "dispatch %p: %s", disp, + msgbuf); +} + +static void +dispentry_log(dns_dispentry_t *resp, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +static void +dispentry_log(dns_dispentry_t *resp, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + int r; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + r = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + if (r < 0) { + msgbuf[0] = '\0'; + } else if ((unsigned int)r >= sizeof(msgbuf)) { + /* Truncated */ + msgbuf[sizeof(msgbuf) - 1] = '\0'; + } + va_end(ap); + + dispatch_log(resp->disp, level, "%s response %p: %s", + socktype2str(resp), resp, msgbuf); +} + +/* + * Return a hash of the destination and message id. + */ +static uint32_t +dns_hash(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id, + in_port_t port) { + uint32_t ret; + + ret = isc_sockaddr_hash(dest, true); + ret ^= ((uint32_t)id << 16) | port; + ret %= qid->qid_nbuckets; + + INSIST(ret < qid->qid_nbuckets); + + return (ret); +} + +/*% + * Choose a random port number for a dispatch entry. + * The caller must hold the disp->lock + */ +static isc_result_t +setup_socket(dns_dispatch_t *disp, dns_dispentry_t *resp, + const isc_sockaddr_t *dest, in_port_t *portp) { + dns_dispatchmgr_t *mgr = disp->mgr; + unsigned int nports; + in_port_t *ports = NULL; + in_port_t port = *portp; + + if (resp->retries++ > 5) { + return (ISC_R_FAILURE); + } + + if (isc_sockaddr_pf(&disp->local) == AF_INET) { + nports = mgr->nv4ports; + ports = mgr->v4ports; + } else { + nports = mgr->nv6ports; + ports = mgr->v6ports; + } + if (nports == 0) { + return (ISC_R_ADDRNOTAVAIL); + } + + resp->local = disp->local; + resp->peer = *dest; + + if (port == 0) { + port = ports[isc_random_uniform(nports)]; + isc_sockaddr_setport(&resp->local, port); + *portp = port; + } + resp->port = port; + + return (ISC_R_SUCCESS); +} + +/* + * Find an entry for query ID 'id', socket address 'dest', and port number + * 'port'. + * Return NULL if no such entry exists. + */ +static dns_dispentry_t * +entry_search(dns_qid_t *qid, const isc_sockaddr_t *dest, dns_messageid_t id, + in_port_t port, unsigned int bucket) { + dns_dispentry_t *res = NULL; + + REQUIRE(VALID_QID(qid)); + REQUIRE(bucket < qid->qid_nbuckets); + + res = ISC_LIST_HEAD(qid->qid_table[bucket]); + + while (res != NULL) { + if (res->id == id && isc_sockaddr_equal(dest, &res->peer) && + res->port == port) + { + return (res); + } + res = ISC_LIST_NEXT(res, link); + } + + return (NULL); +} + +static void +dispentry_destroy(dns_dispentry_t *resp) { + dns_dispatch_t *disp = resp->disp; + + /* + * We need to call this from here in case there's an external event that + * shuts down our dispatch (like ISC_R_SHUTTINGDOWN). + */ + dispentry_cancel(resp, ISC_R_CANCELED); + + LOCK(&disp->lock); + INSIST(disp->requests > 0); + disp->requests--; + UNLOCK(&disp->lock); + + isc_refcount_destroy(&resp->references); + + resp->magic = 0; + + INSIST(!ISC_LINK_LINKED(resp, link)); + INSIST(!ISC_LINK_LINKED(resp, plink)); + INSIST(!ISC_LINK_LINKED(resp, alink)); + INSIST(!ISC_LINK_LINKED(resp, rlink)); + + dispentry_log(resp, LVL(90), "destroying"); + + if (resp->handle != NULL) { + dispentry_log(resp, LVL(90), "detaching handle %p from %p", + resp->handle, &resp->handle); + isc_nmhandle_detach(&resp->handle); + } + + isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp)); + + dns_dispatch_detach(&disp); /* DISPATCH001 */ +} + +#if DNS_DISPATCH_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_dispentry, dispentry_destroy); +#else +ISC_REFCOUNT_IMPL(dns_dispentry, dispentry_destroy); +#endif + +/* + * How long in milliseconds has it been since this dispentry + * started reading? + */ +static unsigned int +dispentry_runtime(dns_dispentry_t *resp, const isc_time_t *now) { + if (isc_time_isepoch(&resp->start)) { + return (0); + } + + return (isc_time_microdiff(now, &resp->start) / 1000); +} + +/* + * General flow: + * + * If I/O result == CANCELED or error, free the buffer. + * + * If query, free the buffer, restart. + * + * If response: + * Allocate event, fill in details. + * If cannot allocate, free buffer, restart. + * find target. If not found, free buffer, restart. + * if event queue is not empty, queue. else, send. + * restart. + */ +static void +udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg) { + dns_dispentry_t *resp = (dns_dispentry_t *)arg; + dns_dispatch_t *disp = NULL; + dns_messageid_t id; + isc_result_t dres; + isc_buffer_t source; + unsigned int flags; + isc_sockaddr_t peer; + isc_netaddr_t netaddr; + int match, timeout = 0; + dispatch_cb_t response = NULL; + isc_time_t now; + + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + disp = resp->disp; + + LOCK(&disp->lock); + INSIST(resp->reading); + resp->reading = false; + + response = resp->response; + + if (resp->state == DNS_DISPATCHSTATE_CANCELED) { + /* + * Nobody is interested in the callback if the response + * has been canceled already. Detach from the response + * and the handle. + */ + response = NULL; + eresult = ISC_R_CANCELED; + } + + dispentry_log(resp, LVL(90), "read callback:%s, requests %d", + isc_result_totext(eresult), disp->requests); + + if (eresult != ISC_R_SUCCESS) { + /* + * This is most likely a network error on a connected + * socket, a timeout, or the query has been canceled. + * It makes no sense to check the address or parse the + * packet, but we can return the error to the caller. + */ + goto done; + } + + peer = isc_nmhandle_peeraddr(handle); + isc_netaddr_fromsockaddr(&netaddr, &peer); + + /* + * If this is from a blackholed address, drop it. + */ + if (disp->mgr->blackhole != NULL && + dns_acl_match(&netaddr, NULL, disp->mgr->blackhole, NULL, &match, + NULL) == ISC_R_SUCCESS && + match > 0) + { + if (isc_log_wouldlog(dns_lctx, LVL(10))) { + char netaddrstr[ISC_NETADDR_FORMATSIZE]; + isc_netaddr_format(&netaddr, netaddrstr, + sizeof(netaddrstr)); + dispentry_log(resp, LVL(10), + "blackholed packet from %s", netaddrstr); + } + goto next; + } + + /* + * Peek into the buffer to see what we can see. + */ + id = resp->id; + isc_buffer_init(&source, region->base, region->length); + isc_buffer_add(&source, region->length); + dres = dns_message_peekheader(&source, &id, &flags); + if (dres != ISC_R_SUCCESS) { + char netaddrstr[ISC_NETADDR_FORMATSIZE]; + isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr)); + dispentry_log(resp, LVL(10), "got garbage packet from %s", + netaddrstr); + goto next; + } + + dispentry_log(resp, LVL(92), + "got valid DNS message header, /QR %c, id %u", + (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id); + + /* + * Look at the message flags. If it's a query, ignore it. + */ + if ((flags & DNS_MESSAGEFLAG_QR) == 0) { + goto next; + } + + /* + * The QID and the address must match the expected ones. + */ + if (resp->id != id || !isc_sockaddr_equal(&peer, &resp->peer)) { + dispentry_log(resp, LVL(90), "response doesn't match"); + inc_stats(disp->mgr, dns_resstatscounter_mismatch); + goto next; + } + + /* + * We have the right resp, so call the caller back. + */ + goto done; + +next: + /* + * This is the wrong response. Check whether there is still enough + * time to wait for the correct one to arrive before the timeout fires. + */ + TIME_NOW(&now); + timeout = resp->timeout - dispentry_runtime(resp, &now); + if (timeout <= 0) { + /* + * The time window for receiving the correct response is + * already closed, libuv has just not processed the socket + * timer yet. Invoke the read callback, indicating a timeout. + */ + eresult = ISC_R_TIMEDOUT; + goto done; + } + + /* + * Do not invoke the read callback just yet and instead wait for the + * proper response to arrive until the original timeout fires. + */ + response = NULL; + udp_dispatch_getnext(resp, timeout); + +done: + UNLOCK(&disp->lock); + + if (response != NULL) { + dispentry_log(resp, LVL(90), "UDP read callback on %p: %s", + handle, isc_result_totext(eresult)); + response(eresult, region, resp->arg); + } + + dns_dispentry_detach(&resp); /* DISPENTRY003 */ +} + +static isc_result_t +tcp_recv_oldest(dns_dispatch_t *disp, dns_dispentry_t **respp) { + dns_dispentry_t *resp = NULL; + resp = ISC_LIST_HEAD(disp->active); + if (resp != NULL) { + disp->timedout++; + + *respp = resp; + return (ISC_R_TIMEDOUT); + } + + return (ISC_R_NOTFOUND); +} + +static isc_result_t +tcp_recv_success(dns_dispatch_t *disp, isc_region_t *region, dns_qid_t *qid, + isc_sockaddr_t *peer, dns_dispentry_t **respp) { + isc_buffer_t source; + dns_messageid_t id; + unsigned int flags; + unsigned int bucket; + isc_result_t result = ISC_R_SUCCESS; + dns_dispentry_t *resp = NULL; + + dispatch_log(disp, LVL(90), "TCP read success, length == %d, addr = %p", + region->length, region->base); + + /* + * Peek into the buffer to see what we can see. + */ + isc_buffer_init(&source, region->base, region->length); + isc_buffer_add(&source, region->length); + result = dns_message_peekheader(&source, &id, &flags); + if (result != ISC_R_SUCCESS) { + dispatch_log(disp, LVL(10), "got garbage packet"); + return (ISC_R_UNEXPECTED); + } + + dispatch_log(disp, LVL(92), + "got valid DNS message header, /QR %c, id %u", + (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id); + + /* + * Look at the message flags. If it's a query, ignore it and keep + * reading. + */ + if ((flags & DNS_MESSAGEFLAG_QR) == 0) { + dispatch_log(disp, LVL(10), "got DNS query instead of answer"); + return (ISC_R_UNEXPECTED); + } + + /* + * We have a valid response; find the associated dispentry object + * and call the caller back. + */ + bucket = dns_hash(qid, peer, id, disp->localport); + LOCK(&qid->lock); + resp = entry_search(qid, peer, id, disp->localport, bucket); + if (resp != NULL) { + if (resp->reading) { + *respp = resp; + } else { + /* We already got our DNS message. */ + result = ISC_R_UNEXPECTED; + } + } else { + /* We are not expecting this DNS message */ + result = ISC_R_NOTFOUND; + } + dispatch_log(disp, LVL(90), "search for response in bucket %d: %s", + bucket, isc_result_totext(result)); + UNLOCK(&qid->lock); + + return (result); +} + +static void +tcp_recv_add(dns_displist_t *resps, dns_dispentry_t *resp, + isc_result_t result) { + dns_dispentry_ref(resp); /* DISPENTRY009 */ + ISC_LIST_UNLINK(resp->disp->active, resp, alink); + ISC_LIST_APPEND(*resps, resp, rlink); + INSIST(resp->reading); + resp->reading = false; + resp->result = result; +} + +static void +tcp_recv_shutdown(dns_dispatch_t *disp, dns_displist_t *resps, + isc_result_t result) { + dns_dispentry_t *resp = NULL, *next = NULL; + + /* + * If there are any active responses, shut them all down. + */ + for (resp = ISC_LIST_HEAD(disp->active); resp != NULL; resp = next) { + next = ISC_LIST_NEXT(resp, alink); + tcp_recv_add(resps, resp, result); + } + disp->state = DNS_DISPATCHSTATE_CANCELED; +} + +static void +tcp_recv_done(dns_dispentry_t *resp, isc_result_t eresult, + isc_region_t *region) { + dispentry_log(resp, LVL(90), "read callback: %s", + isc_result_totext(eresult)); + + resp->response(eresult, region, resp->arg); + dns_dispentry_detach(&resp); /* DISPENTRY009 */ +} + +static void +tcp_recv_processall(dns_displist_t *resps, isc_region_t *region) { + dns_dispentry_t *resp = NULL, *next = NULL; + + for (resp = ISC_LIST_HEAD(*resps); resp != NULL; resp = next) { + next = ISC_LIST_NEXT(resp, rlink); + ISC_LIST_UNLINK(*resps, resp, rlink); + tcp_recv_done(resp, resp->result, region); + } +} + +/* + * General flow: + * + * If I/O result == CANCELED, EOF, or error, notify everyone as the + * various queues drain. + * + * If response: + * Allocate event, fill in details. + * If cannot allocate, restart. + * find target. If not found, restart. + * if event queue is not empty, queue. else, send. + * restart. + */ +static void +tcp_recv(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *arg) { + dns_dispatch_t *disp = (dns_dispatch_t *)arg; + dns_dispentry_t *resp = NULL; + dns_qid_t *qid = NULL; + char buf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t peer; + dns_displist_t resps = ISC_LIST_INITIALIZER; + isc_time_t now; + int timeout; + + REQUIRE(VALID_DISPATCH(disp)); + + qid = disp->mgr->qid; + + TIME_NOW(&now); + + LOCK(&disp->lock); + INSIST(disp->reading); + disp->reading = false; + + dispatch_log(disp, LVL(90), "TCP read:%s:requests %u", + isc_result_totext(result), disp->requests); + + peer = isc_nmhandle_peeraddr(handle); + + /* + * Phase 1: Process timeout and success. + */ + switch (result) { + case ISC_R_TIMEDOUT: + /* + * Time out the oldest response in the active queue. + */ + result = tcp_recv_oldest(disp, &resp); + break; + case ISC_R_SUCCESS: + /* We got an answer */ + result = tcp_recv_success(disp, region, qid, &peer, &resp); + break; + + default: + break; + } + + if (resp != NULL) { + tcp_recv_add(&resps, resp, result); + } + + /* + * Phase 2: Look if we timed out before. + */ + + if (result == ISC_R_NOTFOUND) { + if (disp->timedout > 0) { + /* There was active query that timed-out before */ + disp->timedout--; + } else { + result = ISC_R_UNEXPECTED; + } + } + + /* + * Phase 3: Trigger timeouts. It's possible that the responses would + * have been timedout out already, but non-matching TCP reads have + * prevented this. + */ + dns_dispentry_t *next = NULL; + for (resp = ISC_LIST_HEAD(disp->active); resp != NULL; resp = next) { + next = ISC_LIST_NEXT(resp, alink); + + timeout = resp->timeout - dispentry_runtime(resp, &now); + if (timeout <= 0) { + tcp_recv_add(&resps, resp, ISC_R_TIMEDOUT); + } + } + + /* + * Phase 4: log if we errored out. + */ + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_TIMEDOUT: + case ISC_R_NOTFOUND: + break; + + case ISC_R_SHUTTINGDOWN: + case ISC_R_CANCELED: + case ISC_R_EOF: + case ISC_R_CONNECTIONRESET: + isc_sockaddr_format(&peer, buf, sizeof(buf)); + dispatch_log(disp, LVL(90), "shutting down TCP: %s: %s", buf, + isc_result_totext(result)); + tcp_recv_shutdown(disp, &resps, result); + break; + default: + isc_sockaddr_format(&peer, buf, sizeof(buf)); + dispatch_log(disp, ISC_LOG_ERROR, + "shutting down due to TCP " + "receive error: %s: %s", + buf, isc_result_totext(result)); + tcp_recv_shutdown(disp, &resps, result); + break; + } + + /* + * Phase 5: Resume reading if there are still active responses + */ + resp = ISC_LIST_HEAD(disp->active); + if (resp != NULL) { + timeout = resp->timeout - dispentry_runtime(resp, &now); + INSIST(timeout > 0); + tcp_startrecv(NULL, disp, resp); + isc_nmhandle_settimeout(handle, timeout); + } + + UNLOCK(&disp->lock); + + /* + * Phase 6: Process all scheduled callbacks. + */ + tcp_recv_processall(&resps, region); + + dns_dispatch_detach(&disp); /* DISPATCH002 */ +} + +/*% + * Create a temporary port list to set the initial default set of dispatch + * ephemeral ports. This is almost meaningless as the application will + * normally set the ports explicitly, but is provided to fill some minor corner + * cases. + */ +static void +create_default_portset(isc_mem_t *mctx, int family, isc_portset_t **portsetp) { + in_port_t low, high; + + isc_net_getudpportrange(family, &low, &high); + + isc_portset_create(mctx, portsetp); + isc_portset_addrange(*portsetp, low, high); +} + +static isc_result_t +setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, + isc_portset_t *v6portset) { + in_port_t *v4ports, *v6ports, p = 0; + unsigned int nv4ports, nv6ports, i4 = 0, i6 = 0; + + nv4ports = isc_portset_nports(v4portset); + nv6ports = isc_portset_nports(v6portset); + + v4ports = NULL; + if (nv4ports != 0) { + v4ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv4ports); + } + v6ports = NULL; + if (nv6ports != 0) { + v6ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv6ports); + } + + do { + if (isc_portset_isset(v4portset, p)) { + INSIST(i4 < nv4ports); + v4ports[i4++] = p; + } + if (isc_portset_isset(v6portset, p)) { + INSIST(i6 < nv6ports); + v6ports[i6++] = p; + } + } while (p++ < 65535); + INSIST(i4 == nv4ports && i6 == nv6ports); + + if (mgr->v4ports != NULL) { + isc_mem_put(mgr->mctx, mgr->v4ports, + mgr->nv4ports * sizeof(in_port_t)); + } + mgr->v4ports = v4ports; + mgr->nv4ports = nv4ports; + + if (mgr->v6ports != NULL) { + isc_mem_put(mgr->mctx, mgr->v6ports, + mgr->nv6ports * sizeof(in_port_t)); + } + mgr->v6ports = v6ports; + mgr->nv6ports = nv6ports; + + return (ISC_R_SUCCESS); +} + +/* + * Publics. + */ + +isc_result_t +dns_dispatchmgr_create(isc_mem_t *mctx, isc_nm_t *nm, + dns_dispatchmgr_t **mgrp) { + dns_dispatchmgr_t *mgr = NULL; + isc_portset_t *v4portset = NULL; + isc_portset_t *v6portset = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(mgrp != NULL && *mgrp == NULL); + + mgr = isc_mem_get(mctx, sizeof(dns_dispatchmgr_t)); + *mgr = (dns_dispatchmgr_t){ .magic = 0 }; + +#if DNS_DISPATCH_TRACE + fprintf(stderr, "dns_dispatchmgr__init:%s:%s:%d:%p->references = 1\n", + __func__, __FILE__, __LINE__, mgr); +#endif + isc_refcount_init(&mgr->references, 1); + + isc_mem_attach(mctx, &mgr->mctx); + isc_nm_attach(nm, &mgr->nm); + + isc_mutex_init(&mgr->lock); + + ISC_LIST_INIT(mgr->list); + + create_default_portset(mctx, AF_INET, &v4portset); + create_default_portset(mctx, AF_INET6, &v6portset); + + setavailports(mgr, v4portset, v6portset); + + isc_portset_destroy(mctx, &v4portset); + isc_portset_destroy(mctx, &v6portset); + + qid_allocate(mgr, &mgr->qid); + mgr->magic = DNS_DISPATCHMGR_MAGIC; + + *mgrp = mgr; + return (ISC_R_SUCCESS); +} + +#if DNS_DISPATCH_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_dispatchmgr, dispatchmgr_destroy); +#else +ISC_REFCOUNT_IMPL(dns_dispatchmgr, dispatchmgr_destroy); +#endif + +void +dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + if (mgr->blackhole != NULL) { + dns_acl_detach(&mgr->blackhole); + } + dns_acl_attach(blackhole, &mgr->blackhole); +} + +dns_acl_t * +dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + return (mgr->blackhole); +} + +isc_result_t +dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, + isc_portset_t *v6portset) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + return (setavailports(mgr, v4portset, v6portset)); +} + +static void +dispatchmgr_destroy(dns_dispatchmgr_t *mgr) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + + isc_refcount_destroy(&mgr->references); + + mgr->magic = 0; + isc_mutex_destroy(&mgr->lock); + + qid_destroy(mgr->mctx, &mgr->qid); + + if (mgr->blackhole != NULL) { + dns_acl_detach(&mgr->blackhole); + } + + if (mgr->stats != NULL) { + isc_stats_detach(&mgr->stats); + } + + if (mgr->v4ports != NULL) { + isc_mem_put(mgr->mctx, mgr->v4ports, + mgr->nv4ports * sizeof(in_port_t)); + } + if (mgr->v6ports != NULL) { + isc_mem_put(mgr->mctx, mgr->v6ports, + mgr->nv6ports * sizeof(in_port_t)); + } + + isc_nm_detach(&mgr->nm); + + isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(dns_dispatchmgr_t)); +} + +void +dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(ISC_LIST_EMPTY(mgr->list)); + REQUIRE(mgr->stats == NULL); + + isc_stats_attach(stats, &mgr->stats); +} + +static void +qid_allocate(dns_dispatchmgr_t *mgr, dns_qid_t **qidp) { + dns_qid_t *qid = NULL; + unsigned int i; + + REQUIRE(qidp != NULL && *qidp == NULL); + + qid = isc_mem_get(mgr->mctx, sizeof(*qid)); + *qid = (dns_qid_t){ .qid_nbuckets = DNS_QID_BUCKETS, + .qid_increment = DNS_QID_INCREMENT }; + + qid->qid_table = isc_mem_get(mgr->mctx, + DNS_QID_BUCKETS * sizeof(dns_displist_t)); + for (i = 0; i < qid->qid_nbuckets; i++) { + ISC_LIST_INIT(qid->qid_table[i]); + } + + isc_mutex_init(&qid->lock); + qid->magic = QID_MAGIC; + *qidp = qid; +} + +static void +qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp) { + dns_qid_t *qid = NULL; + + REQUIRE(qidp != NULL); + qid = *qidp; + *qidp = NULL; + + REQUIRE(VALID_QID(qid)); + + qid->magic = 0; + isc_mem_put(mctx, qid->qid_table, + qid->qid_nbuckets * sizeof(dns_displist_t)); + isc_mutex_destroy(&qid->lock); + isc_mem_put(mctx, qid, sizeof(*qid)); +} + +/* + * Allocate and set important limits. + */ +static void +dispatch_allocate(dns_dispatchmgr_t *mgr, isc_socktype_t type, + dns_dispatch_t **dispp) { + dns_dispatch_t *disp = NULL; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(dispp != NULL && *dispp == NULL); + + /* + * Set up the dispatcher, mostly. Don't bother setting some of + * the options that are controlled by tcp vs. udp, etc. + */ + + disp = isc_mem_get(mgr->mctx, sizeof(*disp)); + *disp = (dns_dispatch_t){ + .socktype = type, + .link = ISC_LINK_INITIALIZER, + .active = ISC_LIST_INITIALIZER, + .pending = ISC_LIST_INITIALIZER, + .tid = isc_nm_tid(), + .magic = DISPATCH_MAGIC, + }; + + dns_dispatchmgr_attach(mgr, &disp->mgr); +#if DNS_DISPATCH_TRACE + fprintf(stderr, "dns_dispatch__init:%s:%s:%d:%p->references = 1\n", + __func__, __FILE__, __LINE__, disp); +#endif + isc_refcount_init(&disp->references, 1); /* DISPATCH000 */ + isc_mutex_init(&disp->lock); + + *dispp = disp; +} + +isc_result_t +dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + const isc_sockaddr_t *destaddr, dns_dispatch_t **dispp) { + dns_dispatch_t *disp = NULL; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(destaddr != NULL); + + LOCK(&mgr->lock); + + dispatch_allocate(mgr, isc_socktype_tcp, &disp); + + disp->peer = *destaddr; + + if (localaddr != NULL) { + disp->local = *localaddr; + } else { + int pf; + pf = isc_sockaddr_pf(destaddr); + isc_sockaddr_anyofpf(&disp->local, pf); + isc_sockaddr_setport(&disp->local, 0); + } + + /* + * Append it to the dispatcher list. + */ + + /* FIXME: There should be a lookup hashtable here */ + ISC_LIST_APPEND(mgr->list, disp, link); + UNLOCK(&mgr->lock); + + if (isc_log_wouldlog(dns_lctx, 90)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(&disp->local, addrbuf, + ISC_SOCKADDR_FORMATSIZE); + + mgr_log(mgr, LVL(90), + "dns_dispatch_createtcp: created TCP dispatch %p for " + "%s", + disp, addrbuf); + } + *dispp = disp; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr, + const isc_sockaddr_t *localaddr, dns_dispatch_t **dispp) { + dns_dispatch_t *disp_connected = NULL; + dns_dispatch_t *disp_fallback = NULL; + isc_result_t result = ISC_R_NOTFOUND; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(destaddr != NULL); + REQUIRE(dispp != NULL && *dispp == NULL); + + LOCK(&mgr->lock); + + for (dns_dispatch_t *disp = ISC_LIST_HEAD(mgr->list); disp != NULL; + disp = ISC_LIST_NEXT(disp, link)) + { + isc_sockaddr_t sockname; + isc_sockaddr_t peeraddr; + + LOCK(&disp->lock); + + if (disp->tid != isc_nm_tid()) { + UNLOCK(&disp->lock); + continue; + } + + if (disp->handle != NULL) { + sockname = isc_nmhandle_localaddr(disp->handle); + peeraddr = isc_nmhandle_peeraddr(disp->handle); + } else { + sockname = disp->local; + peeraddr = disp->peer; + } + + /* + * The conditions match: + * 1. socktype is TCP + * 2. destination address is same + * 3. local address is either NULL or same + */ + if (disp->socktype != isc_socktype_tcp || + !isc_sockaddr_equal(destaddr, &peeraddr) || + (localaddr != NULL && + !isc_sockaddr_eqaddr(localaddr, &sockname))) + { + UNLOCK(&disp->lock); + continue; + } + + switch (disp->state) { + case DNS_DISPATCHSTATE_NONE: + /* A dispatch in indeterminate state, skip it */ + break; + case DNS_DISPATCHSTATE_CONNECTED: + if (ISC_LIST_EMPTY(disp->active)) { + /* Ignore dispatch with no responses */ + break; + } + /* We found a connected dispatch */ + dns_dispatch_attach(disp, &disp_connected); + break; + case DNS_DISPATCHSTATE_CONNECTING: + if (ISC_LIST_EMPTY(disp->pending)) { + /* Ignore dispatch with no responses */ + break; + } + /* We found "a" dispatch, store it for later */ + if (disp_fallback == NULL) { + dns_dispatch_attach(disp, &disp_fallback); + } + break; + case DNS_DISPATCHSTATE_CANCELED: + /* A canceled dispatch, skip it. */ + break; + default: + UNREACHABLE(); + } + + UNLOCK(&disp->lock); + + if (disp_connected != NULL) { + break; + } + } + + if (disp_connected != NULL) { + /* We found connected dispatch */ + INSIST(disp_connected->handle != NULL); + + *dispp = disp_connected; + disp_connected = NULL; + + result = ISC_R_SUCCESS; + + if (disp_fallback != NULL) { + dns_dispatch_detach(&disp_fallback); + } + } else if (disp_fallback != NULL) { + *dispp = disp_fallback; + + result = ISC_R_SUCCESS; + } + + UNLOCK(&mgr->lock); + + return (result); +} + +isc_result_t +dns_dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + dns_dispatch_t **dispp) { + isc_result_t result; + dns_dispatch_t *disp = NULL; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(localaddr != NULL); + REQUIRE(dispp != NULL && *dispp == NULL); + + LOCK(&mgr->lock); + result = dispatch_createudp(mgr, localaddr, &disp); + if (result == ISC_R_SUCCESS) { + *dispp = disp; + } + UNLOCK(&mgr->lock); + + return (result); +} + +static isc_result_t +dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + dns_dispatch_t **dispp) { + isc_result_t result = ISC_R_SUCCESS; + dns_dispatch_t *disp = NULL; + isc_sockaddr_t sa_any; + + /* + * Check whether this address/port is available locally. + */ + isc_sockaddr_anyofpf(&sa_any, isc_sockaddr_pf(localaddr)); + if (!isc_sockaddr_eqaddr(&sa_any, localaddr)) { + result = isc_nm_checkaddr(localaddr, isc_socktype_udp); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + dispatch_allocate(mgr, isc_socktype_udp, &disp); + + if (isc_log_wouldlog(dns_lctx, 90)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(localaddr, addrbuf, + ISC_SOCKADDR_FORMATSIZE); + mgr_log(mgr, LVL(90), + "dispatch_createudp: created UDP dispatch %p for %s", + disp, addrbuf); + } + + disp->local = *localaddr; + + /* + * Don't append it to the dispatcher list, we don't care about UDP, only + * TCP should be searched + * + * ISC_LIST_APPEND(mgr->list, disp, link); + */ + + *dispp = disp; + + return (result); +} + +static void +dispatch_destroy(dns_dispatch_t *disp) { + dns_dispatchmgr_t *mgr = disp->mgr; + + isc_refcount_destroy(&disp->references); + disp->magic = 0; + + LOCK(&mgr->lock); + if (ISC_LINK_LINKED(disp, link)) { + ISC_LIST_UNLINK(disp->mgr->list, disp, link); + } + UNLOCK(&mgr->lock); + + INSIST(disp->requests == 0); + INSIST(ISC_LIST_EMPTY(disp->pending)); + INSIST(ISC_LIST_EMPTY(disp->active)); + + INSIST(!ISC_LINK_LINKED(disp, link)); + + dispatch_log(disp, LVL(90), "destroying dispatch %p", disp); + + if (disp->handle) { + dispatch_log(disp, LVL(90), "detaching TCP handle %p from %p", + disp->handle, &disp->handle); + isc_nmhandle_detach(&disp->handle); + } + + isc_mutex_destroy(&disp->lock); + + isc_mem_put(mgr->mctx, disp, sizeof(*disp)); + + /* + * Because dispatch uses mgr->mctx, we must detach after freeing + * dispatch, not before. + */ + dns_dispatchmgr_detach(&mgr); +} + +#if DNS_DISPATCH_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_dispatch, dispatch_destroy); +#else +ISC_REFCOUNT_IMPL(dns_dispatch, dispatch_destroy); +#endif + +isc_result_t +dns_dispatch_add(dns_dispatch_t *disp, unsigned int options, + unsigned int timeout, const isc_sockaddr_t *dest, + dispatch_cb_t connected, dispatch_cb_t sent, + dispatch_cb_t response, void *arg, dns_messageid_t *idp, + dns_dispentry_t **respp) { + dns_dispentry_t *resp = NULL; + dns_qid_t *qid = NULL; + in_port_t localport; + dns_messageid_t id; + unsigned int bucket; + bool ok = false; + int i = 0; + + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(dest != NULL); + REQUIRE(respp != NULL && *respp == NULL); + REQUIRE(idp != NULL); + REQUIRE(disp->socktype == isc_socktype_tcp || + disp->socktype == isc_socktype_udp); + REQUIRE(connected != NULL); + REQUIRE(response != NULL); + REQUIRE(sent != NULL); + + LOCK(&disp->lock); + + if (disp->state == DNS_DISPATCHSTATE_CANCELED) { + UNLOCK(&disp->lock); + return (ISC_R_CANCELED); + } + + qid = disp->mgr->qid; + + localport = isc_sockaddr_getport(&disp->local); + + resp = isc_mem_get(disp->mgr->mctx, sizeof(*resp)); + *resp = (dns_dispentry_t){ + .port = localport, + .timeout = timeout, + .peer = *dest, + .connected = connected, + .sent = sent, + .response = response, + .arg = arg, + .link = ISC_LINK_INITIALIZER, + .alink = ISC_LINK_INITIALIZER, + .plink = ISC_LINK_INITIALIZER, + .rlink = ISC_LINK_INITIALIZER, + .magic = RESPONSE_MAGIC, + }; + +#if DNS_DISPATCH_TRACE + fprintf(stderr, "dns_dispentry__init:%s:%s:%d:%p->references = 1\n", + __func__, __FILE__, __LINE__, resp); +#endif + isc_refcount_init(&resp->references, 1); /* DISPENTRY000 */ + + if (disp->socktype == isc_socktype_udp) { + isc_result_t result = setup_socket(disp, resp, dest, + &localport); + if (result != ISC_R_SUCCESS) { + isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp)); + UNLOCK(&disp->lock); + inc_stats(disp->mgr, dns_resstatscounter_dispsockfail); + return (result); + } + } + + /* + * Try somewhat hard to find a unique ID. Start with + * a random number unless DNS_DISPATCHOPT_FIXEDID is set, + * in which case we start with the ID passed in via *idp. + */ + if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) { + id = *idp; + } else { + id = (dns_messageid_t)isc_random16(); + } + + LOCK(&qid->lock); + do { + dns_dispentry_t *entry = NULL; + bucket = dns_hash(qid, dest, id, localport); + entry = entry_search(qid, dest, id, localport, bucket); + if (entry == NULL) { + ok = true; + break; + } + if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) { + /* When using fixed ID, we either must use it or fail */ + break; + } + id += qid->qid_increment; + id &= 0x0000ffff; + } while (i++ < 64); + + if (ok) { + resp->id = id; + resp->bucket = bucket; + ISC_LIST_APPEND(qid->qid_table[bucket], resp, link); + } + UNLOCK(&qid->lock); + + if (!ok) { + isc_mem_put(disp->mgr->mctx, resp, sizeof(*resp)); + UNLOCK(&disp->lock); + return (ISC_R_NOMORE); + } + + dns_dispatch_attach(disp, &resp->disp); /* DISPATCH001 */ + + disp->requests++; + + inc_stats(disp->mgr, (disp->socktype == isc_socktype_udp) + ? dns_resstatscounter_disprequdp + : dns_resstatscounter_dispreqtcp); + + UNLOCK(&disp->lock); + + *idp = id; + *respp = resp; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dispatch_getnext(dns_dispentry_t *resp) { + isc_time_t now; + + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + dns_dispatch_t *disp = resp->disp; + isc_result_t result = ISC_R_SUCCESS; + int32_t timeout = -1; + + dispentry_log(resp, LVL(90), "getnext for QID %d", resp->id); + + TIME_NOW(&now); + timeout = resp->timeout - dispentry_runtime(resp, &now); + if (timeout <= 0) { + return (ISC_R_TIMEDOUT); + } + + LOCK(&disp->lock); + switch (disp->socktype) { + case isc_socktype_udp: + udp_dispatch_getnext(resp, timeout); + break; + case isc_socktype_tcp: + tcp_dispatch_getnext(disp, resp, timeout); + break; + default: + UNREACHABLE(); + } + UNLOCK(&disp->lock); + + return (result); +} + +static void +udp_dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + REQUIRE(VALID_DISPATCHMGR(resp->disp->mgr)); + + dns_dispatch_t *disp = resp->disp; + dns_dispatchmgr_t *mgr = disp->mgr; + dns_qid_t *qid = mgr->qid; + dispatch_cb_t response = NULL; + + LOCK(&disp->lock); + dispentry_log(resp, LVL(90), + "canceling response: %s, %s/%s (%s/%s), " + "requests %u", + isc_result_totext(result), state2str(resp->state), + resp->reading ? "reading" : "not reading", + state2str(disp->state), + disp->reading ? "reading" : "not reading", + disp->requests); + + if (ISC_LINK_LINKED(resp, alink)) { + ISC_LIST_UNLINK(disp->active, resp, alink); + } + + switch (resp->state) { + case DNS_DISPATCHSTATE_NONE: + break; + + case DNS_DISPATCHSTATE_CONNECTING: + break; + + case DNS_DISPATCHSTATE_CONNECTED: + if (resp->reading) { + dns_dispentry_ref(resp); /* DISPENTRY003 */ + response = resp->response; + + dispentry_log(resp, LVL(90), "canceling read on %p", + resp->handle); + isc_nm_cancelread(resp->handle); + } + break; + + case DNS_DISPATCHSTATE_CANCELED: + goto unlock; + + default: + UNREACHABLE(); + } + + dec_stats(disp->mgr, dns_resstatscounter_disprequdp); + + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->qid_table[resp->bucket], resp, link); + UNLOCK(&qid->lock); + resp->state = DNS_DISPATCHSTATE_CANCELED; + +unlock: + UNLOCK(&disp->lock); + + if (response) { + dispentry_log(resp, LVL(90), "read callback: %s", + isc_result_totext(result)); + response(result, NULL, resp->arg); + dns_dispentry_detach(&resp); /* DISPENTRY003 */ + } +} + +static void +tcp_dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + REQUIRE(VALID_DISPATCHMGR(resp->disp->mgr)); + + dns_dispatch_t *disp = resp->disp; + dns_dispatchmgr_t *mgr = disp->mgr; + dns_qid_t *qid = mgr->qid; + dns_displist_t resps = ISC_LIST_INITIALIZER; + + LOCK(&disp->lock); + dispentry_log(resp, LVL(90), + "canceling response: %s, %s/%s (%s/%s), " + "requests %u", + isc_result_totext(result), state2str(resp->state), + resp->reading ? "reading" : "not reading", + state2str(disp->state), + disp->reading ? "reading" : "not reading", + disp->requests); + + switch (resp->state) { + case DNS_DISPATCHSTATE_NONE: + break; + + case DNS_DISPATCHSTATE_CONNECTING: + break; + + case DNS_DISPATCHSTATE_CONNECTED: + if (resp->reading) { + tcp_recv_add(&resps, resp, ISC_R_CANCELED); + } + + INSIST(!ISC_LINK_LINKED(resp, alink)); + + if (ISC_LIST_EMPTY(disp->active)) { + INSIST(disp->handle != NULL); + +#if DISPATCH_TCP_KEEPALIVE + /* + * This is an experimental code that keeps the TCP + * connection open for 1 second before it is finally + * closed. By keeping the TCP connection open, it can + * be reused by dns_request that uses + * dns_dispatch_gettcp() to join existing TCP + * connections. + * + * It is disabled for now, because it changes the + * behaviour, but I am keeping the code here for future + * reference when we improve the dns_dispatch to reuse + * the TCP connections also in the resolver. + * + * The TCP connection reuse should be seamless and not + * require any extra handling on the client side though. + */ + isc_nmhandle_cleartimeout(disp->handle); + isc_nmhandle_settimeout(disp->handle, 1000); + + if (!disp->reading) { + dispentry_log(resp, LVL(90), + "final 1 second timeout on %p", + disp->handle); + tcp_startrecv(NULL, disp, NULL); + } +#else + if (disp->reading) { + dispentry_log(resp, LVL(90), + "canceling read on %p", + disp->handle); + isc_nm_cancelread(disp->handle); + } +#endif + } + break; + + case DNS_DISPATCHSTATE_CANCELED: + goto unlock; + + default: + UNREACHABLE(); + } + + dec_stats(disp->mgr, dns_resstatscounter_dispreqtcp); + + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->qid_table[resp->bucket], resp, link); + UNLOCK(&qid->lock); + resp->state = DNS_DISPATCHSTATE_CANCELED; + +unlock: + UNLOCK(&disp->lock); + + /* + * NOTE: Calling the response callback directly from here should be done + * asynchronously, as the dns_dispatch_done() is usually called directly + * from the response callback, so there's a slight chance that the call + * stack will get higher here, but it's mitigated by the ".reading" + * flag, so we don't ever go into a loop. + */ + + tcp_recv_processall(&resps, NULL); +} + +static void +dispentry_cancel(dns_dispentry_t *resp, isc_result_t result) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + dns_dispatch_t *disp = resp->disp; + + switch (disp->socktype) { + case isc_socktype_udp: + udp_dispentry_cancel(resp, result); + break; + case isc_socktype_tcp: + tcp_dispentry_cancel(resp, result); + break; + default: + UNREACHABLE(); + } +} + +void +dns_dispatch_done(dns_dispentry_t **respp) { + REQUIRE(VALID_RESPONSE(*respp)); + + dns_dispentry_t *resp = *respp; + *respp = NULL; + + dispentry_cancel(resp, ISC_R_CANCELED); + dns_dispentry_detach(&resp); /* DISPENTRY000 */ +} + +static void +udp_startrecv(isc_nmhandle_t *handle, dns_dispentry_t *resp) { + REQUIRE(VALID_RESPONSE(resp)); + + dispentry_log(resp, LVL(90), "attaching handle %p to %p", handle, + &resp->handle); + isc_nmhandle_attach(handle, &resp->handle); + dns_dispentry_ref(resp); /* DISPENTRY003 */ + dispentry_log(resp, LVL(90), "reading"); + isc_nm_read(resp->handle, udp_recv, resp); + resp->reading = true; +} + +static void +tcp_startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp, + dns_dispentry_t *resp) { + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(disp->socktype == isc_socktype_tcp); + + if (handle != NULL) { + isc_nmhandle_attach(handle, &disp->handle); + } + dns_dispatch_ref(disp); /* DISPATCH002 */ + if (resp != NULL) { + dispentry_log(resp, LVL(90), "reading from %p", disp->handle); + INSIST(!isc_time_isepoch(&resp->start)); + } else { + dispatch_log(disp, LVL(90), + "TCP reading without response from %p", + disp->handle); + } + isc_nm_read(disp->handle, tcp_recv, disp); + disp->reading = true; +} + +static void +tcp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + dns_dispatch_t *disp = (dns_dispatch_t *)arg; + dns_dispentry_t *resp = NULL; + dns_dispentry_t *next = NULL; + dns_displist_t resps = ISC_LIST_INITIALIZER; + + if (isc_log_wouldlog(dns_lctx, 90)) { + char localbuf[ISC_SOCKADDR_FORMATSIZE]; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + if (handle != NULL) { + isc_sockaddr_t local = isc_nmhandle_localaddr(handle); + isc_sockaddr_t peer = isc_nmhandle_peeraddr(handle); + + isc_sockaddr_format(&local, localbuf, + ISC_SOCKADDR_FORMATSIZE); + isc_sockaddr_format(&peer, peerbuf, + ISC_SOCKADDR_FORMATSIZE); + } else { + isc_sockaddr_format(&disp->local, localbuf, + ISC_SOCKADDR_FORMATSIZE); + isc_sockaddr_format(&disp->peer, peerbuf, + ISC_SOCKADDR_FORMATSIZE); + } + + dispatch_log(disp, LVL(90), "connected from %s to %s: %s", + localbuf, peerbuf, isc_result_totext(eresult)); + } + + LOCK(&disp->lock); + INSIST(disp->state == DNS_DISPATCHSTATE_CONNECTING); + + /* + * If there are pending responses, call the connect + * callbacks for all of them. + */ + for (resp = ISC_LIST_HEAD(disp->pending); resp != NULL; resp = next) { + next = ISC_LIST_NEXT(resp, plink); + ISC_LIST_UNLINK(disp->pending, resp, plink); + ISC_LIST_APPEND(resps, resp, rlink); + resp->result = eresult; + + if (resp->state == DNS_DISPATCHSTATE_CANCELED) { + resp->result = ISC_R_CANCELED; + } else if (eresult == ISC_R_SUCCESS) { + resp->state = DNS_DISPATCHSTATE_CONNECTED; + ISC_LIST_APPEND(disp->active, resp, alink); + resp->reading = true; + dispentry_log(resp, LVL(90), "start reading"); + } else { + resp->state = DNS_DISPATCHSTATE_NONE; + } + } + + if (ISC_LIST_EMPTY(disp->active)) { + /* All responses have been canceled */ + disp->state = DNS_DISPATCHSTATE_CANCELED; + } else if (eresult == ISC_R_SUCCESS) { + disp->state = DNS_DISPATCHSTATE_CONNECTED; + tcp_startrecv(handle, disp, resp); + } else { + disp->state = DNS_DISPATCHSTATE_NONE; + } + + UNLOCK(&disp->lock); + + for (resp = ISC_LIST_HEAD(resps); resp != NULL; resp = next) { + next = ISC_LIST_NEXT(resp, rlink); + ISC_LIST_UNLINK(resps, resp, rlink); + + dispentry_log(resp, LVL(90), "connect callback: %s", + isc_result_totext(resp->result)); + resp->connected(resp->result, NULL, resp->arg); + dns_dispentry_detach(&resp); /* DISPENTRY005 */ + } + + dns_dispatch_detach(&disp); /* DISPATCH003 */ +} + +static void +udp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + dns_dispentry_t *resp = (dns_dispentry_t *)arg; + dns_dispatch_t *disp = resp->disp; + + dispentry_log(resp, LVL(90), "connected: %s", + isc_result_totext(eresult)); + + LOCK(&disp->lock); + + switch (resp->state) { + case DNS_DISPATCHSTATE_CANCELED: + eresult = ISC_R_CANCELED; + ISC_LIST_UNLINK(disp->pending, resp, plink); + goto unlock; + case DNS_DISPATCHSTATE_CONNECTING: + ISC_LIST_UNLINK(disp->pending, resp, plink); + break; + default: + UNREACHABLE(); + } + + switch (eresult) { + case ISC_R_CANCELED: + break; + case ISC_R_SUCCESS: + resp->state = DNS_DISPATCHSTATE_CONNECTED; + udp_startrecv(handle, resp); + break; + case ISC_R_NOPERM: + case ISC_R_ADDRINUSE: { + in_port_t localport = isc_sockaddr_getport(&disp->local); + isc_result_t result; + + /* probably a port collision; try a different one */ + result = setup_socket(disp, resp, &resp->peer, &localport); + if (result == ISC_R_SUCCESS) { + UNLOCK(&disp->lock); + udp_dispatch_connect(disp, resp); + goto detach; + } + resp->state = DNS_DISPATCHSTATE_NONE; + break; + } + default: + resp->state = DNS_DISPATCHSTATE_NONE; + break; + } +unlock: + UNLOCK(&disp->lock); + + dispentry_log(resp, LVL(90), "connect callback: %s", + isc_result_totext(eresult)); + resp->connected(eresult, NULL, resp->arg); + +detach: + dns_dispentry_detach(&resp); /* DISPENTRY004 */ +} + +static void +udp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp) { + LOCK(&disp->lock); + resp->state = DNS_DISPATCHSTATE_CONNECTING; + TIME_NOW(&resp->start); + dns_dispentry_ref(resp); /* DISPENTRY004 */ + ISC_LIST_APPEND(disp->pending, resp, plink); + UNLOCK(&disp->lock); + + isc_nm_udpconnect(disp->mgr->nm, &resp->local, &resp->peer, + udp_connected, resp, resp->timeout, 0); +} + +static isc_result_t +tcp_dispatch_connect(dns_dispatch_t *disp, dns_dispentry_t *resp) { + /* Check whether the dispatch is already connecting or connected. */ + LOCK(&disp->lock); + switch (disp->state) { + case DNS_DISPATCHSTATE_NONE: + /* First connection, continue with connecting */ + disp->state = DNS_DISPATCHSTATE_CONNECTING; + resp->state = DNS_DISPATCHSTATE_CONNECTING; + TIME_NOW(&resp->start); + dns_dispentry_ref(resp); /* DISPENTRY005 */ + ISC_LIST_APPEND(disp->pending, resp, plink); + UNLOCK(&disp->lock); + + char localbuf[ISC_SOCKADDR_FORMATSIZE]; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(&disp->local, localbuf, + ISC_SOCKADDR_FORMATSIZE); + isc_sockaddr_format(&disp->peer, peerbuf, + ISC_SOCKADDR_FORMATSIZE); + + dns_dispatch_ref(disp); /* DISPATCH003 */ + dispentry_log(resp, LVL(90), + "connecting from %s to %s, timeout %u", localbuf, + peerbuf, resp->timeout); + + isc_nm_tcpdnsconnect(disp->mgr->nm, &disp->local, &disp->peer, + tcp_connected, disp, resp->timeout, 0); + break; + + case DNS_DISPATCHSTATE_CONNECTING: + /* Connection pending; add resp to the list */ + resp->state = DNS_DISPATCHSTATE_CONNECTING; + TIME_NOW(&resp->start); + dns_dispentry_ref(resp); /* DISPENTRY005 */ + ISC_LIST_APPEND(disp->pending, resp, plink); + UNLOCK(&disp->lock); + break; + + case DNS_DISPATCHSTATE_CONNECTED: + resp->state = DNS_DISPATCHSTATE_CONNECTED; + TIME_NOW(&resp->start); + + /* Add the resp to the reading list */ + ISC_LIST_APPEND(disp->active, resp, alink); + dispentry_log(resp, LVL(90), "already connected; attaching"); + resp->reading = true; + + if (!disp->reading) { + /* Restart the reading */ + tcp_startrecv(NULL, disp, resp); + } + + UNLOCK(&disp->lock); + /* We are already connected; call the connected cb */ + dispentry_log(resp, LVL(90), "connect callback: %s", + isc_result_totext(ISC_R_SUCCESS)); + resp->connected(ISC_R_SUCCESS, NULL, resp->arg); + break; + + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dispatch_connect(dns_dispentry_t *resp) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + dns_dispatch_t *disp = resp->disp; + + switch (disp->socktype) { + case isc_socktype_tcp: + return (tcp_dispatch_connect(disp, resp)); + + case isc_socktype_udp: + udp_dispatch_connect(disp, resp); + return (ISC_R_SUCCESS); + + default: + UNREACHABLE(); + } +} + +static void +send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + dns_dispentry_t *resp = (dns_dispentry_t *)cbarg; + + REQUIRE(VALID_RESPONSE(resp)); + + dns_dispatch_t *disp = resp->disp; + + REQUIRE(VALID_DISPATCH(disp)); + + dispentry_log(resp, LVL(90), "sent: %s", isc_result_totext(result)); + + resp->sent(result, NULL, resp->arg); + + if (result != ISC_R_SUCCESS) { + dispentry_cancel(resp, result); + } + + dns_dispentry_detach(&resp); /* DISPENTRY007 */ + isc_nmhandle_detach(&handle); +} + +static void +tcp_dispatch_getnext(dns_dispatch_t *disp, dns_dispentry_t *resp, + int32_t timeout) { + REQUIRE(timeout <= INT16_MAX); + + if (disp->reading) { + return; + } + + if (timeout > 0) { + isc_nmhandle_settimeout(disp->handle, timeout); + } + + dispentry_log(resp, LVL(90), "continue reading"); + + dns_dispatch_ref(disp); /* DISPATCH002 */ + isc_nm_read(disp->handle, tcp_recv, disp); + disp->reading = true; + + ISC_LIST_APPEND(disp->active, resp, alink); + resp->reading = true; +} + +static void +udp_dispatch_getnext(dns_dispentry_t *resp, int32_t timeout) { + REQUIRE(timeout <= INT16_MAX); + + if (resp->reading) { + return; + } + + if (timeout > 0) { + isc_nmhandle_settimeout(resp->handle, timeout); + } + + dispentry_log(resp, LVL(90), "continue reading"); + + dns_dispentry_ref(resp); /* DISPENTRY003 */ + isc_nm_read(resp->handle, udp_recv, resp); + resp->reading = true; +} + +void +dns_dispatch_resume(dns_dispentry_t *resp, uint16_t timeout) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + dns_dispatch_t *disp = resp->disp; + + LOCK(&disp->lock); + switch (disp->socktype) { + case isc_socktype_udp: { + udp_dispatch_getnext(resp, timeout); + break; + } + case isc_socktype_tcp: + INSIST(disp->timedout > 0); + disp->timedout--; + tcp_dispatch_getnext(disp, resp, timeout); + break; + default: + UNREACHABLE(); + } + + UNLOCK(&disp->lock); +} + +void +dns_dispatch_send(dns_dispentry_t *resp, isc_region_t *r) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + + dns_dispatch_t *disp = resp->disp; + isc_nmhandle_t *sendhandle = NULL; + + dispentry_log(resp, LVL(90), "sending"); + switch (disp->socktype) { + case isc_socktype_udp: + isc_nmhandle_attach(resp->handle, &sendhandle); + break; + case isc_socktype_tcp: + isc_nmhandle_attach(disp->handle, &sendhandle); + break; + default: + UNREACHABLE(); + } + dns_dispentry_ref(resp); /* DISPENTRY007 */ + isc_nm_send(sendhandle, r, send_done, resp); +} + +isc_result_t +dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp) { + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(addrp != NULL); + + if (disp->socktype == isc_socktype_udp) { + *addrp = disp->local; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_dispentry_getlocaladdress(dns_dispentry_t *resp, isc_sockaddr_t *addrp) { + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(VALID_DISPATCH(resp->disp)); + REQUIRE(addrp != NULL); + + dns_dispatch_t *disp = resp->disp; + + switch (disp->socktype) { + case isc_socktype_tcp: + *addrp = disp->local; + return (ISC_R_SUCCESS); + case isc_socktype_udp: + *addrp = isc_nmhandle_localaddr(resp->handle); + return (ISC_R_SUCCESS); + default: + UNREACHABLE(); + } +} + +dns_dispatch_t * +dns_dispatchset_get(dns_dispatchset_t *dset) { + dns_dispatch_t *disp = NULL; + + /* check that dispatch set is configured */ + if (dset == NULL || dset->ndisp == 0) { + return (NULL); + } + + LOCK(&dset->lock); + disp = dset->dispatches[dset->cur]; + dset->cur++; + if (dset->cur == dset->ndisp) { + dset->cur = 0; + } + UNLOCK(&dset->lock); + + return (disp); +} + +isc_result_t +dns_dispatchset_create(isc_mem_t *mctx, dns_dispatch_t *source, + dns_dispatchset_t **dsetp, int n) { + isc_result_t result; + dns_dispatchset_t *dset = NULL; + dns_dispatchmgr_t *mgr = NULL; + int i, j; + + REQUIRE(VALID_DISPATCH(source)); + REQUIRE(source->socktype == isc_socktype_udp); + REQUIRE(dsetp != NULL && *dsetp == NULL); + + mgr = source->mgr; + + dset = isc_mem_get(mctx, sizeof(dns_dispatchset_t)); + *dset = (dns_dispatchset_t){ .ndisp = n }; + + isc_mutex_init(&dset->lock); + + dset->dispatches = isc_mem_get(mctx, sizeof(dns_dispatch_t *) * n); + + isc_mem_attach(mctx, &dset->mctx); + + dset->dispatches[0] = NULL; + dns_dispatch_attach(source, &dset->dispatches[0]); /* DISPATCH004 */ + + LOCK(&mgr->lock); + for (i = 1; i < n; i++) { + dset->dispatches[i] = NULL; + result = dispatch_createudp(mgr, &source->local, + &dset->dispatches[i]); + if (result != ISC_R_SUCCESS) { + goto fail; + } + } + + UNLOCK(&mgr->lock); + *dsetp = dset; + + return (ISC_R_SUCCESS); + +fail: + UNLOCK(&mgr->lock); + + for (j = 0; j < i; j++) { + dns_dispatch_detach(&(dset->dispatches[j])); /* DISPATCH004 */ + } + isc_mem_put(mctx, dset->dispatches, sizeof(dns_dispatch_t *) * n); + if (dset->mctx == mctx) { + isc_mem_detach(&dset->mctx); + } + + isc_mutex_destroy(&dset->lock); + isc_mem_put(mctx, dset, sizeof(dns_dispatchset_t)); + return (result); +} + +void +dns_dispatchset_destroy(dns_dispatchset_t **dsetp) { + dns_dispatchset_t *dset = NULL; + int i; + + REQUIRE(dsetp != NULL && *dsetp != NULL); + + dset = *dsetp; + *dsetp = NULL; + for (i = 0; i < dset->ndisp; i++) { + dns_dispatch_detach(&(dset->dispatches[i])); /* DISPATCH004 */ + } + isc_mem_put(dset->mctx, dset->dispatches, + sizeof(dns_dispatch_t *) * dset->ndisp); + isc_mutex_destroy(&dset->lock); + isc_mem_putanddetach(&dset->mctx, dset, sizeof(dns_dispatchset_t)); +} diff --git a/lib/dns/dlz.c b/lib/dns/dlz.c new file mode 100644 index 0000000..4462a7b --- /dev/null +++ b/lib/dns/dlz.c @@ -0,0 +1,537 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/*** + *** Supported DLZ DB Implementations Registry + ***/ + +static ISC_LIST(dns_dlzimplementation_t) dlz_implementations; +static isc_rwlock_t dlz_implock; +static isc_once_t once = ISC_ONCE_INIT; + +static void +dlz_initialize(void) { + isc_rwlock_init(&dlz_implock, 0, 0); + ISC_LIST_INIT(dlz_implementations); +} + +/*% + * Searches the dlz_implementations list for a driver matching name. + */ +static dns_dlzimplementation_t * +dlz_impfind(const char *name) { + dns_dlzimplementation_t *imp; + + for (imp = ISC_LIST_HEAD(dlz_implementations); imp != NULL; + imp = ISC_LIST_NEXT(imp, link)) + { + if (strcasecmp(name, imp->name) == 0) { + return (imp); + } + } + return (NULL); +} + +/*** + *** Basic DLZ Methods + ***/ + +isc_result_t +dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name, + const isc_sockaddr_t *clientaddr, dns_db_t **dbp) { + isc_result_t result = ISC_R_NOTFOUND; + dns_dlzallowzonexfr_t allowzonexfr; + dns_dlzdb_t *dlzdb; + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(name != NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + + /* + * Find a driver in which the zone exists and transfer is supported + */ + for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL; + dlzdb = ISC_LIST_NEXT(dlzdb, link)) + { + REQUIRE(DNS_DLZ_VALID(dlzdb)); + + allowzonexfr = dlzdb->implementation->methods->allowzonexfr; + result = (*allowzonexfr)(dlzdb->implementation->driverarg, + dlzdb->dbdata, dlzdb->mctx, + view->rdclass, name, clientaddr, dbp); + + /* + * In these cases, we found the right database. Non-success + * result codes indicate the zone might not transfer. + */ + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_NOPERM: + case ISC_R_DEFAULT: + return (result); + default: + break; + } + } + + if (result == ISC_R_NOTIMPLEMENTED) { + result = ISC_R_NOTFOUND; + } + + return (result); +} + +isc_result_t +dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername, + unsigned int argc, char *argv[], dns_dlzdb_t **dbp) { + dns_dlzimplementation_t *impinfo; + isc_result_t result; + dns_dlzdb_t *db = NULL; + + /* + * initialize the dlz_implementations list, this is guaranteed + * to only really happen once. + */ + RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS); + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(dlzname != NULL); + REQUIRE(drivername != NULL); + REQUIRE(mctx != NULL); + + /* write log message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_INFO, "Loading '%s' using driver %s", dlzname, + drivername); + + /* lock the dlz_implementations list so we can search it. */ + RWLOCK(&dlz_implock, isc_rwlocktype_read); + + /* search for the driver implementation */ + impinfo = dlz_impfind(drivername); + if (impinfo == NULL) { + RWUNLOCK(&dlz_implock, isc_rwlocktype_read); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "unsupported DLZ database driver '%s'." + " %s not loaded.", + drivername, dlzname); + + return (ISC_R_NOTFOUND); + } + + /* Allocate memory to hold the DLZ database driver */ + db = isc_mem_get(mctx, sizeof(dns_dlzdb_t)); + + /* Make sure memory region is set to all 0's */ + memset(db, 0, sizeof(dns_dlzdb_t)); + + ISC_LINK_INIT(db, link); + db->implementation = impinfo; + if (dlzname != NULL) { + db->dlzname = isc_mem_strdup(mctx, dlzname); + } + + /* Create a new database using implementation 'drivername'. */ + result = ((impinfo->methods->create)(mctx, dlzname, argc, argv, + impinfo->driverarg, &db->dbdata)); + + /* mark the DLZ driver as valid */ + if (result == ISC_R_SUCCESS) { + RWUNLOCK(&dlz_implock, isc_rwlocktype_read); + db->magic = DNS_DLZ_MAGIC; + isc_mem_attach(mctx, &db->mctx); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "DLZ driver loaded successfully."); + *dbp = db; + return (ISC_R_SUCCESS); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "DLZ driver failed to load."); + } + + /* impinfo->methods->create failed. */ + RWUNLOCK(&dlz_implock, isc_rwlocktype_read); + isc_mem_free(mctx, db->dlzname); + isc_mem_put(mctx, db, sizeof(dns_dlzdb_t)); + return (result); +} + +void +dns_dlzdestroy(dns_dlzdb_t **dbp) { + dns_dlzdestroy_t destroy; + dns_dlzdb_t *db; + + /* Write debugging message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unloading DLZ driver."); + + /* + * Perform checks to make sure data is as we expect it to be. + */ + REQUIRE(dbp != NULL && DNS_DLZ_VALID(*dbp)); + + db = *dbp; + *dbp = NULL; + + if (db->ssutable != NULL) { + dns_ssutable_detach(&db->ssutable); + } + + /* call the drivers destroy method */ + if (db->dlzname != NULL) { + isc_mem_free(db->mctx, db->dlzname); + } + destroy = db->implementation->methods->destroy; + (*destroy)(db->implementation->driverarg, db->dbdata); + /* return memory and detach */ + isc_mem_putanddetach(&db->mctx, db, sizeof(dns_dlzdb_t)); +} + +/*% + * Registers a DLZ driver. This basically just adds the dlz + * driver to the list of available drivers in the dlz_implementations list. + */ +isc_result_t +dns_dlzregister(const char *drivername, const dns_dlzmethods_t *methods, + void *driverarg, isc_mem_t *mctx, + dns_dlzimplementation_t **dlzimp) { + dns_dlzimplementation_t *dlz_imp; + + /* Write debugging message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ driver '%s'", + drivername); + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(drivername != NULL); + REQUIRE(methods != NULL); + REQUIRE(methods->create != NULL); + REQUIRE(methods->destroy != NULL); + REQUIRE(methods->findzone != NULL); + REQUIRE(mctx != NULL); + REQUIRE(dlzimp != NULL && *dlzimp == NULL); + + /* + * initialize the dlz_implementations list, this is guaranteed + * to only really happen once. + */ + RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS); + + /* lock the dlz_implementations list so we can modify it. */ + RWLOCK(&dlz_implock, isc_rwlocktype_write); + + /* + * check that another already registered driver isn't using + * the same name + */ + dlz_imp = dlz_impfind(drivername); + if (dlz_imp != NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "DLZ Driver '%s' already registered", drivername); + RWUNLOCK(&dlz_implock, isc_rwlocktype_write); + return (ISC_R_EXISTS); + } + + /* + * Allocate memory for a dlz_implementation object. Error if + * we cannot. + */ + dlz_imp = isc_mem_get(mctx, sizeof(dns_dlzimplementation_t)); + + /* Make sure memory region is set to all 0's */ + memset(dlz_imp, 0, sizeof(dns_dlzimplementation_t)); + + /* Store the data passed into this method */ + dlz_imp->name = drivername; + dlz_imp->methods = methods; + dlz_imp->mctx = NULL; + dlz_imp->driverarg = driverarg; + + /* attach the new dlz_implementation object to a memory context */ + isc_mem_attach(mctx, &dlz_imp->mctx); + + /* + * prepare the dlz_implementation object to be put in a list, + * and append it to the list + */ + ISC_LINK_INIT(dlz_imp, link); + ISC_LIST_APPEND(dlz_implementations, dlz_imp, link); + + /* Unlock the dlz_implementations list. */ + RWUNLOCK(&dlz_implock, isc_rwlocktype_write); + + /* Pass back the dlz_implementation that we created. */ + *dlzimp = dlz_imp; + + return (ISC_R_SUCCESS); +} + +/*% + * Tokenize the string "s" into whitespace-separated words, + * return the number of words in '*argcp' and an array + * of pointers to the words in '*argvp'. The caller + * must free the array using isc_mem_put(). The string + * is modified in-place. + */ +isc_result_t +dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) { + return (isc_commandline_strtoargv(mctx, s, argcp, argvp, 0)); +} + +/*% + * Unregisters a DLZ driver. This basically just removes the dlz + * driver from the list of available drivers in the dlz_implementations list. + */ +void +dns_dlzunregister(dns_dlzimplementation_t **dlzimp) { + dns_dlzimplementation_t *dlz_imp; + + /* Write debugging message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ driver."); + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(dlzimp != NULL && *dlzimp != NULL); + + /* + * initialize the dlz_implementations list, this is guaranteed + * to only really happen once. + */ + RUNTIME_CHECK(isc_once_do(&once, dlz_initialize) == ISC_R_SUCCESS); + + dlz_imp = *dlzimp; + + /* lock the dlz_implementations list so we can modify it. */ + RWLOCK(&dlz_implock, isc_rwlocktype_write); + + /* remove the dlz_implementation object from the list */ + ISC_LIST_UNLINK(dlz_implementations, dlz_imp, link); + + /* + * Return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_putanddetach(&dlz_imp->mctx, dlz_imp, sizeof(*dlz_imp)); + + /* Unlock the dlz_implementations list. */ + RWUNLOCK(&dlz_implock, isc_rwlocktype_write); +} + +/* + * Create a writeable DLZ zone. This can be called by DLZ drivers + * during configure() to create a zone that can be updated. The zone + * type is set to dns_zone_dlz, which is equivalent to a primary zone + * + * This function uses a callback setup in dns_dlzconfigure() to call + * into the server zone code to setup the remaining pieces of server + * specific functionality on the zone + */ +isc_result_t +dns_dlz_writeablezone(dns_view_t *view, dns_dlzdb_t *dlzdb, + const char *zone_name) { + dns_zone_t *zone = NULL; + dns_zone_t *dupzone = NULL; + isc_result_t result; + isc_buffer_t buffer; + dns_fixedname_t fixorigin; + dns_name_t *origin; + + REQUIRE(DNS_DLZ_VALID(dlzdb)); + + REQUIRE(dlzdb->configure_callback != NULL); + + isc_buffer_constinit(&buffer, zone_name, strlen(zone_name)); + isc_buffer_add(&buffer, strlen(zone_name)); + dns_fixedname_init(&fixorigin); + result = dns_name_fromtext(dns_fixedname_name(&fixorigin), &buffer, + dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + origin = dns_fixedname_name(&fixorigin); + + if (!dlzdb->search) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_WARNING, + "DLZ %s has 'search no;', but attempted to " + "register writeable zone %s.", + dlzdb->dlzname, zone_name); + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* See if the zone already exists */ + result = dns_view_findzone(view, origin, &dupzone); + if (result == ISC_R_SUCCESS) { + dns_zone_detach(&dupzone); + result = ISC_R_EXISTS; + goto cleanup; + } + INSIST(dupzone == NULL); + + /* Create it */ + result = dns_zone_create(&zone, view->mctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_zone_setorigin(zone, origin); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + dns_zone_setview(zone, view); + + dns_zone_setadded(zone, true); + + if (dlzdb->ssutable == NULL) { + dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable, dlzdb); + } + dns_zone_setssutable(zone, dlzdb->ssutable); + + result = dlzdb->configure_callback(view, dlzdb, zone); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_view_addzone(view, zone); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +/*% + * Configure a DLZ driver. This is optional, and if supplied gives + * the backend an opportunity to configure parameters related to DLZ. + */ +isc_result_t +dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb, + dlzconfigure_callback_t callback) { + dns_dlzimplementation_t *impl; + isc_result_t result; + + REQUIRE(DNS_DLZ_VALID(dlzdb)); + REQUIRE(dlzdb->implementation != NULL); + + impl = dlzdb->implementation; + + if (impl->methods->configure == NULL) { + return (ISC_R_SUCCESS); + } + + dlzdb->configure_callback = callback; + + result = impl->methods->configure(impl->driverarg, dlzdb->dbdata, view, + dlzdb); + return (result); +} + +bool +dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *tcpaddr, + dns_rdatatype_t type, const dst_key_t *key) { + dns_dlzimplementation_t *impl; + bool r; + + REQUIRE(dlzdatabase != NULL); + REQUIRE(dlzdatabase->implementation != NULL); + REQUIRE(dlzdatabase->implementation->methods != NULL); + impl = dlzdatabase->implementation; + + if (impl->methods->ssumatch == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "No ssumatch method for DLZ database"); + return (false); + } + + r = impl->methods->ssumatch(signer, name, tcpaddr, type, key, + impl->driverarg, dlzdatabase->dbdata); + return (r); +} diff --git a/lib/dns/dns64.c b/lib/dns/dns64.c new file mode 100644 index 0000000..b575726 --- /dev/null +++ b/lib/dns/dns64.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct dns_dns64 { + unsigned char bits[16]; /* + * Prefix + suffix bits. + */ + dns_acl_t *clients; /* + * Which clients get mapped + * addresses. + */ + dns_acl_t *mapped; /* + * IPv4 addresses to be mapped. + */ + dns_acl_t *excluded; /* + * IPv6 addresses that are + * treated as not existing. + */ + unsigned int prefixlen; /* + * Start of mapped address. + */ + unsigned int flags; + isc_mem_t *mctx; + ISC_LINK(dns_dns64_t) link; +}; + +isc_result_t +dns_dns64_create(isc_mem_t *mctx, const isc_netaddr_t *prefix, + unsigned int prefixlen, const isc_netaddr_t *suffix, + dns_acl_t *clients, dns_acl_t *mapped, dns_acl_t *excluded, + unsigned int flags, dns_dns64_t **dns64p) { + dns_dns64_t *dns64; + unsigned int nbytes = 16; + + REQUIRE(prefix != NULL && prefix->family == AF_INET6); + /* Legal prefix lengths from rfc6052.txt. */ + REQUIRE(prefixlen == 32 || prefixlen == 40 || prefixlen == 48 || + prefixlen == 56 || prefixlen == 64 || prefixlen == 96); + REQUIRE(isc_netaddr_prefixok(prefix, prefixlen) == ISC_R_SUCCESS); + REQUIRE(dns64p != NULL && *dns64p == NULL); + + if (suffix != NULL) { + static const unsigned char zeros[16]; + REQUIRE(prefix->family == AF_INET6); + nbytes = prefixlen / 8 + 4; + /* Bits 64-71 are zeros. rfc6052.txt */ + if (prefixlen >= 32 && prefixlen <= 64) { + nbytes++; + } + REQUIRE(memcmp(suffix->type.in6.s6_addr, zeros, nbytes) == 0); + } + + dns64 = isc_mem_get(mctx, sizeof(dns_dns64_t)); + memset(dns64->bits, 0, sizeof(dns64->bits)); + memmove(dns64->bits, prefix->type.in6.s6_addr, prefixlen / 8); + if (suffix != NULL) { + memmove(dns64->bits + nbytes, suffix->type.in6.s6_addr + nbytes, + 16 - nbytes); + } + dns64->clients = NULL; + if (clients != NULL) { + dns_acl_attach(clients, &dns64->clients); + } + dns64->mapped = NULL; + if (mapped != NULL) { + dns_acl_attach(mapped, &dns64->mapped); + } + dns64->excluded = NULL; + if (excluded != NULL) { + dns_acl_attach(excluded, &dns64->excluded); + } + dns64->prefixlen = prefixlen; + dns64->flags = flags; + ISC_LINK_INIT(dns64, link); + dns64->mctx = NULL; + isc_mem_attach(mctx, &dns64->mctx); + *dns64p = dns64; + return (ISC_R_SUCCESS); +} + +void +dns_dns64_destroy(dns_dns64_t **dns64p) { + dns_dns64_t *dns64; + + REQUIRE(dns64p != NULL && *dns64p != NULL); + + dns64 = *dns64p; + *dns64p = NULL; + + REQUIRE(!ISC_LINK_LINKED(dns64, link)); + + if (dns64->clients != NULL) { + dns_acl_detach(&dns64->clients); + } + if (dns64->mapped != NULL) { + dns_acl_detach(&dns64->mapped); + } + if (dns64->excluded != NULL) { + dns_acl_detach(&dns64->excluded); + } + isc_mem_putanddetach(&dns64->mctx, dns64, sizeof(*dns64)); +} + +isc_result_t +dns_dns64_aaaafroma(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, dns_aclenv_t *env, + unsigned int flags, unsigned char *a, unsigned char *aaaa) { + unsigned int nbytes, i; + isc_result_t result; + int match; + + if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 && + (flags & DNS_DNS64_RECURSIVE) == 0) + { + return (DNS_R_DISALLOWED); + } + + if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 && + (flags & DNS_DNS64_DNSSEC) != 0) + { + return (DNS_R_DISALLOWED); + } + + if (dns64->clients != NULL) { + result = dns_acl_match(reqaddr, reqsigner, dns64->clients, env, + &match, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (match <= 0) { + return (DNS_R_DISALLOWED); + } + } + + if (dns64->mapped != NULL) { + struct in_addr ina; + isc_netaddr_t netaddr; + + memmove(&ina.s_addr, a, 4); + isc_netaddr_fromin(&netaddr, &ina); + result = dns_acl_match(&netaddr, NULL, dns64->mapped, env, + &match, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (match <= 0) { + return (DNS_R_DISALLOWED); + } + } + + nbytes = dns64->prefixlen / 8; + INSIST(nbytes <= 12); + /* Copy prefix. */ + memmove(aaaa, dns64->bits, nbytes); + /* Bits 64-71 are zeros. rfc6052.txt */ + if (nbytes == 8) { + aaaa[nbytes++] = 0; + } + /* Copy mapped address. */ + for (i = 0; i < 4U; i++) { + aaaa[nbytes++] = a[i]; + /* Bits 64-71 are zeros. rfc6052.txt */ + if (nbytes == 8) { + aaaa[nbytes++] = 0; + } + } + /* Copy suffix. */ + memmove(aaaa + nbytes, dns64->bits + nbytes, 16 - nbytes); + return (ISC_R_SUCCESS); +} + +dns_dns64_t * +dns_dns64_next(dns_dns64_t *dns64) { + dns64 = ISC_LIST_NEXT(dns64, link); + return (dns64); +} + +void +dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64) { + ISC_LIST_APPEND(*list, dns64, link); +} + +void +dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64) { + ISC_LIST_UNLINK(*list, dns64, link); +} + +bool +dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, dns_aclenv_t *env, + unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok, + size_t aaaaoklen) { + struct in6_addr in6; + isc_netaddr_t netaddr; + isc_result_t result; + int match; + bool answer = false; + bool found = false; + unsigned int i, ok; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == dns_rdatatype_aaaa); + REQUIRE(rdataset->rdclass == dns_rdataclass_in); + if (aaaaok != NULL) { + REQUIRE(aaaaoklen == dns_rdataset_count(rdataset)); + } + + for (; dns64 != NULL; dns64 = ISC_LIST_NEXT(dns64, link)) { + if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 && + (flags & DNS_DNS64_RECURSIVE) == 0) + { + continue; + } + + if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 && + (flags & DNS_DNS64_DNSSEC) != 0) + { + continue; + } + /* + * Work out if this dns64 structure applies to this client. + */ + if (dns64->clients != NULL) { + result = dns_acl_match(reqaddr, reqsigner, + dns64->clients, env, &match, + NULL); + if (result != ISC_R_SUCCESS) { + continue; + } + if (match <= 0) { + continue; + } + } + + if (!found && aaaaok != NULL) { + for (i = 0; i < aaaaoklen; i++) { + aaaaok[i] = false; + } + } + found = true; + + /* + * If we are not excluding any addresses then any AAAA + * will do. + */ + if (dns64->excluded == NULL) { + answer = true; + if (aaaaok == NULL) { + goto done; + } + for (i = 0; i < aaaaoklen; i++) { + aaaaok[i] = true; + } + goto done; + } + + i = 0; + ok = 0; + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + if (aaaaok == NULL || !aaaaok[i]) { + dns_rdataset_current(rdataset, &rdata); + memmove(&in6.s6_addr, rdata.data, 16); + isc_netaddr_fromin6(&netaddr, &in6); + + result = dns_acl_match(&netaddr, NULL, + dns64->excluded, env, + &match, NULL); + if (result == ISC_R_SUCCESS && match <= 0) { + answer = true; + if (aaaaok == NULL) { + goto done; + } + aaaaok[i] = true; + ok++; + } + } else { + ok++; + } + i++; + } + /* + * Are all addresses ok? + */ + if (aaaaok != NULL && ok == aaaaoklen) { + goto done; + } + } + +done: + if (!found && aaaaok != NULL) { + for (i = 0; i < aaaaoklen; i++) { + aaaaok[i] = true; + } + } + return (found ? answer : true); +} + +/* + * Posible mapping of IPV4ONLY.ARPA A records into AAAA records + * for valid RFC6052 prefixes. + */ +static struct { + const unsigned char aa[16]; /* mapped version of 192.0.0.170 */ + const unsigned char ab[16]; /* mapped version of 192.0.0.171 */ + const unsigned char mask[16]; + const unsigned int plen; +} const prefixes[6] = { + { { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0 }, + 32 }, + { { 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0 }, + 40 }, + { { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0 }, + 48 }, + { { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0 }, + 56 }, + { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0 }, + 64 }, + { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255 }, + 96 } +}; + +static unsigned int +search(const dns_rdata_t *rd1, const dns_rdata_t *rd2, unsigned int plen) { + unsigned int i = 0, j; + const unsigned char *c, *m; + + /* + * Resume looking for another aa match? + */ + if (plen != 0U && rd2 == NULL) { + while (i < 6U) { + /* Post increment as we resume on next entry. */ + if (prefixes[i++].plen == plen) { + break; + } + } + } + + for (; i < 6U; i++) { + j = 0; + if (rd2 != NULL) { + /* Find the right entry. */ + if (prefixes[i].plen != plen) { + continue; + } + /* Does the prefix match? */ + while ((j * 8U) < plen) { + if (rd1->data[j] != rd2->data[j]) { + return (0); + } + j++; + } + } + + /* Match well known mapped addresses. */ + c = (rd2 == NULL) ? prefixes[i].aa : prefixes[i].ab; + m = prefixes[i].mask; + for (; j < 16U; j++) { + if ((rd1->data[j] & m[j]) != (c[j] & m[j])) { + break; + } + } + if (j == 16U) { + return (prefixes[i].plen); + } + if (rd2 != NULL) { + return (0); + } + } + return (0); +} + +isc_result_t +dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix, + size_t *len) { + dns_rdataset_t outer, inner; + unsigned int oplen, iplen; + size_t count = 0; + struct in6_addr ina6; + isc_result_t result; + + REQUIRE(prefix != NULL && len != NULL && *len != 0U); + REQUIRE(rdataset != NULL && rdataset->type == dns_rdatatype_aaaa); + + dns_rdataset_init(&outer); + dns_rdataset_init(&inner); + dns_rdataset_clone(rdataset, &outer); + dns_rdataset_clone(rdataset, &inner); + + for (result = dns_rdataset_first(&outer); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&outer)) + { + dns_rdata_t rd1 = DNS_RDATA_INIT; + dns_rdataset_current(&outer, &rd1); + oplen = 0; + resume: + /* Look for a 192.0.0.170 match. */ + oplen = search(&rd1, NULL, oplen); + if (oplen == 0) { + continue; + } + + /* Look for the 192.0.0.171 match. */ + for (result = dns_rdataset_first(&inner); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&inner)) + { + dns_rdata_t rd2 = DNS_RDATA_INIT; + + dns_rdataset_current(&inner, &rd2); + iplen = search(&rd2, &rd1, oplen); + if (iplen == 0) { + continue; + } + INSIST(iplen == oplen); + if (count >= *len) { + count++; + break; + } + + /* We have a prefix. */ + memset(ina6.s6_addr, 0, sizeof(ina6.s6_addr)); + memmove(ina6.s6_addr, rd1.data, oplen / 8); + isc_netaddr_fromin6(&prefix[count].addr, &ina6); + prefix[count].prefixlen = oplen; + count++; + break; + } + /* Didn't find a match look for a different prefix length. */ + if (result == ISC_R_NOMORE) { + goto resume; + } + } + if (count == 0U) { + return (ISC_R_NOTFOUND); + } + if (count > *len) { + *len = count; + return (ISC_R_NOSPACE); + } + *len = count; + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c new file mode 100644 index 0000000..d4a1c65 --- /dev/null +++ b/lib/dns/dnsrps.c @@ -0,0 +1,1004 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#ifdef USE_DNSRPS + +#include + +#include +#include +#include + +#include +#define LIBRPZ_LIB_OPEN DNSRPS_LIB_OPEN +#include + +#include +#include +#include +#include + +librpz_t *librpz; +librpz_emsg_t librpz_lib_open_emsg; +static void *librpz_handle; + +#define RPSDB_MAGIC ISC_MAGIC('R', 'P', 'Z', 'F') +#define VALID_RPSDB(rpsdb) ((rpsdb)->common.impmagic == RPSDB_MAGIC) + +#define RD_DB(r) ((r)->private1) +#define RD_CUR_RR(r) ((r)->private2) +#define RD_NEXT_RR(r) ((r)->resign) +#define RD_COUNT(r) ((r)->privateuint4) + +typedef struct { + dns_rdatasetiter_t common; + dns_rdatatype_t type; + dns_rdataclass_t class; + uint32_t ttl; + uint count; + librpz_idx_t next_rr; +} rpsdb_rdatasetiter_t; + +static dns_dbmethods_t rpsdb_db_methods; +static dns_rdatasetmethods_t rpsdb_rdataset_methods; +static dns_rdatasetitermethods_t rpsdb_rdatasetiter_methods; + +static librpz_clist_t *clist; + +static isc_mutex_t dnsrps_mutex; + +static void +dnsrps_lock(void *mutex0) { + isc_mutex_t *mutex = mutex0; + + LOCK(mutex); +} + +static void +dnsrps_unlock(void *mutex0) { + isc_mutex_t *mutex = mutex0; + + UNLOCK(mutex); +} + +static void +dnsrps_mutex_destroy(void *mutex0) { + isc_mutex_t *mutex = mutex0; + + isc_mutex_destroy(mutex); +} + +static void +dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) { + int isc_level; + + UNUSED(ctxt); + + /* Setting librpz_log_level in the configuration overrides the + * BIND9 logging levels. */ + if (level > LIBRPZ_LOG_TRACE1 && + level <= librpz->log_level_val(LIBRPZ_LOG_INVALID)) + { + level = LIBRPZ_LOG_TRACE1; + } + + switch (level) { + case LIBRPZ_LOG_FATAL: + case LIBRPZ_LOG_ERROR: /* errors */ + default: + isc_level = DNS_RPZ_ERROR_LEVEL; + break; + + case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */ + isc_level = DNS_RPZ_INFO_LEVEL; + break; + + case LIBRPZ_LOG_TRACE2: /* smaller dnsrpzd zone transfers */ + isc_level = DNS_RPZ_DEBUG_LEVEL1; + break; + + case LIBRPZ_LOG_TRACE3: /* librpz hits */ + isc_level = DNS_RPZ_DEBUG_LEVEL2; + break; + + case LIBRPZ_LOG_TRACE4: /* librpz lookups */ + isc_level = DNS_RPZ_DEBUG_LEVEL3; + break; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, + isc_level, "dnsrps: %s", buf); +} + +/* + * Start dnsrps for the entire server. + * This is not thread safe, but it is called by a single thread. + */ +isc_result_t +dns_dnsrps_server_create(void) { + librpz_emsg_t emsg; + + INSIST(clist == NULL); + INSIST(librpz == NULL); + INSIST(librpz_handle == NULL); + + /* + * Notice if librpz is available. + */ + librpz = librpz_lib_open(&librpz_lib_open_emsg, &librpz_handle, + DNSRPS_LIBRPZ_PATH); + /* + * Stop now without complaining if librpz is not available. + * Complain later if and when librpz is needed for a view with + * "dnsrps-enable yes" (including the default view). + */ + if (librpz == NULL) { + return (ISC_R_SUCCESS); + } + + isc_mutex_init(&dnsrps_mutex); + + librpz->set_log(dnsrps_log_fnc, NULL); + + clist = librpz->clist_create(&emsg, dnsrps_lock, dnsrps_unlock, + dnsrps_mutex_destroy, &dnsrps_mutex, + dns_lctx); + if (clist == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dnsrps: %s", emsg.c); + return (ISC_R_NOMEMORY); + } + return (ISC_R_SUCCESS); +} + +/* + * Stop dnsrps for the entire server. + * This is not thread safe. + */ +void +dns_dnsrps_server_destroy(void) { + if (clist != NULL) { + librpz->clist_detach(&clist); + } + +#ifdef LIBRPZ_USE_DLOPEN + if (librpz != NULL) { + INSIST(librpz_handle != NULL); + if (dlclose(librpz_handle) != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dnsrps: dlclose(): %s", dlerror()); + } + librpz_handle = NULL; + } +#endif /* ifdef LIBRPZ_USE_DLOPEN */ +} + +/* + * Ready dnsrps for a view. + */ +isc_result_t +dns_dnsrps_view_init(dns_rpz_zones_t *new, char *rps_cstr) { + librpz_emsg_t emsg; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, + DNS_RPZ_DEBUG_LEVEL3, "dnsrps configuration \"%s\"", + rps_cstr); + + new->rps_client = librpz->client_create(&emsg, clist, rps_cstr, false); + if (new->rps_client == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "librpz->client_create(): %s", emsg.c); + new->p.dnsrps_enabled = false; + return (ISC_R_FAILURE); + } + + new->p.dnsrps_enabled = true; + return (ISC_R_SUCCESS); +} + +/* + * Connect to and start the dnsrps daemon, dnsrpzd. + */ +isc_result_t +dns_dnsrps_connect(dns_rpz_zones_t *rpzs) { + librpz_emsg_t emsg; + + if (rpzs == NULL || !rpzs->p.dnsrps_enabled) { + return (ISC_R_SUCCESS); + } + + /* + * Fail only if we failed to link to librpz. + */ + if (librpz == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "librpz->connect(): %s", librpz_lib_open_emsg.c); + return (ISC_R_FAILURE); + } + + if (!librpz->connect(&emsg, rpzs->rps_client, true)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "librpz->connect(): %s", emsg.c); + return (ISC_R_SUCCESS); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, + DNS_RPZ_INFO_LEVEL, "dnsrps: librpz version %s", + librpz->version); + + return (ISC_R_SUCCESS); +} + +/* + * Get ready to try RPZ rewriting. + */ +isc_result_t +dns_dnsrps_rewrite_init(librpz_emsg_t *emsg, dns_rpz_st_t *st, + dns_rpz_zones_t *rpzs, const dns_name_t *qname, + isc_mem_t *mctx, bool have_rd) { + rpsdb_t *rpsdb; + + rpsdb = isc_mem_get(mctx, sizeof(*rpsdb)); + memset(rpsdb, 0, sizeof(*rpsdb)); + + if (!librpz->rsp_create(emsg, &rpsdb->rsp, NULL, rpzs->rps_client, + have_rd, false)) + { + isc_mem_put(mctx, rpsdb, sizeof(*rpsdb)); + return (DNS_R_SERVFAIL); + } + if (rpsdb->rsp == NULL) { + isc_mem_put(mctx, rpsdb, sizeof(*rpsdb)); + return (DNS_R_DISALLOWED); + } + + rpsdb->common.magic = DNS_DB_MAGIC; + rpsdb->common.impmagic = RPSDB_MAGIC; + rpsdb->common.methods = &rpsdb_db_methods; + rpsdb->common.rdclass = dns_rdataclass_in; + dns_name_init(&rpsdb->common.origin, NULL); + isc_mem_attach(mctx, &rpsdb->common.mctx); + + rpsdb->ref_cnt = 1; + rpsdb->qname = qname; + + st->rpsdb = &rpsdb->common; + return (ISC_R_SUCCESS); +} + +/* + * Convert a dnsrps policy to a classic BIND9 RPZ policy. + */ +dns_rpz_policy_t +dns_dnsrps_2policy(librpz_policy_t rps_policy) { + switch (rps_policy) { + case LIBRPZ_POLICY_UNDEFINED: + return (DNS_RPZ_POLICY_MISS); + case LIBRPZ_POLICY_PASSTHRU: + return (DNS_RPZ_POLICY_PASSTHRU); + case LIBRPZ_POLICY_DROP: + return (DNS_RPZ_POLICY_DROP); + case LIBRPZ_POLICY_TCP_ONLY: + return (DNS_RPZ_POLICY_TCP_ONLY); + case LIBRPZ_POLICY_NXDOMAIN: + return (DNS_RPZ_POLICY_NXDOMAIN); + case LIBRPZ_POLICY_NODATA: + return (DNS_RPZ_POLICY_NODATA); + case LIBRPZ_POLICY_RECORD: + case LIBRPZ_POLICY_CNAME: + return (DNS_RPZ_POLICY_RECORD); + + case LIBRPZ_POLICY_DELETED: + case LIBRPZ_POLICY_GIVEN: + case LIBRPZ_POLICY_DISABLED: + default: + UNREACHABLE(); + } +} + +/* + * Convert a dnsrps trigger to a classic BIND9 RPZ rewrite or trigger type. + */ +dns_rpz_type_t +dns_dnsrps_trig2type(librpz_trig_t trig) { + switch (trig) { + case LIBRPZ_TRIG_BAD: + default: + return (DNS_RPZ_TYPE_BAD); + case LIBRPZ_TRIG_CLIENT_IP: + return (DNS_RPZ_TYPE_CLIENT_IP); + case LIBRPZ_TRIG_QNAME: + return (DNS_RPZ_TYPE_QNAME); + case LIBRPZ_TRIG_IP: + return (DNS_RPZ_TYPE_IP); + case LIBRPZ_TRIG_NSDNAME: + return (DNS_RPZ_TYPE_NSDNAME); + case LIBRPZ_TRIG_NSIP: + return (DNS_RPZ_TYPE_NSIP); + } +} + +/* + * Convert a classic BIND9 RPZ rewrite or trigger type to a librpz trigger type. + */ +librpz_trig_t +dns_dnsrps_type2trig(dns_rpz_type_t type) { + switch (type) { + case DNS_RPZ_TYPE_BAD: + default: + return (LIBRPZ_TRIG_BAD); + case DNS_RPZ_TYPE_CLIENT_IP: + return (LIBRPZ_TRIG_CLIENT_IP); + case DNS_RPZ_TYPE_QNAME: + return (LIBRPZ_TRIG_QNAME); + case DNS_RPZ_TYPE_IP: + return (LIBRPZ_TRIG_IP); + case DNS_RPZ_TYPE_NSDNAME: + return (LIBRPZ_TRIG_NSDNAME); + case DNS_RPZ_TYPE_NSIP: + return (LIBRPZ_TRIG_NSIP); + } +} + +static void +rpsdb_attach(dns_db_t *source, dns_db_t **targetp) { + rpsdb_t *rpsdb = (rpsdb_t *)source; + + REQUIRE(VALID_RPSDB(rpsdb)); + + /* + * Use a simple count because only one thread uses any single rpsdb_t + */ + ++rpsdb->ref_cnt; + *targetp = source; +} + +static void +rpsdb_detach(dns_db_t **dbp) { + rpsdb_t *rpsdb = (rpsdb_t *)*dbp; + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(rpsdb->ref_cnt > 0); + + *dbp = NULL; + + /* + * Simple count because only one thread uses a rpsdb_t. + */ + if (--rpsdb->ref_cnt != 0) { + return; + } + + librpz->rsp_detach(&rpsdb->rsp); + rpsdb->common.impmagic = 0; + isc_mem_putanddetach(&rpsdb->common.mctx, rpsdb, sizeof(*rpsdb)); +} + +static void +rpsdb_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(targetp != NULL && *targetp == NULL); + REQUIRE(source == &rpsdb->origin_node || source == &rpsdb->data_node); + + /* + * Simple count because only one thread uses a rpsdb_t. + */ + ++rpsdb->ref_cnt; + *targetp = source; +} + +static void +rpsdb_detachnode(dns_db_t *db, dns_dbnode_t **targetp) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(*targetp == &rpsdb->origin_node || + *targetp == &rpsdb->data_node); + + *targetp = NULL; + rpsdb_detach(&db); +} + +static isc_result_t +rpsdb_findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + dns_db_t *dbp; + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(!create); + + /* + * A fake/shim rpsdb has two nodes. + * One is the origin to support query_addsoa() in bin/named/query.c. + * The other contains rewritten RRs. + */ + if (dns_name_equal(name, &db->origin)) { + *nodep = &rpsdb->origin_node; + } else { + *nodep = &rpsdb->data_node; + } + dbp = NULL; + rpsdb_attach(db, &dbp); + + return (ISC_R_SUCCESS); +} + +static void +rpsdb_bind_rdataset(dns_rdataset_t *rdataset, uint count, librpz_idx_t next_rr, + dns_rdatatype_t type, uint16_t class, uint32_t ttl, + rpsdb_t *rpsdb) { + dns_db_t *dbp; + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ + REQUIRE(type != dns_rdatatype_none); + + rdataset->methods = &rpsdb_rdataset_methods; + rdataset->rdclass = class; + rdataset->type = type; + rdataset->ttl = ttl; + dbp = NULL; + dns_db_attach(&rpsdb->common, &dbp); + RD_DB(rdataset) = dbp; + RD_COUNT(rdataset) = count; + RD_NEXT_RR(rdataset) = next_rr; + RD_CUR_RR(rdataset) = NULL; +} + +static isc_result_t +rpsdb_bind_soa(dns_rdataset_t *rdataset, rpsdb_t *rpsdb) { + uint32_t ttl; + librpz_emsg_t emsg; + + if (!librpz->rsp_soa(&emsg, &ttl, NULL, NULL, &rpsdb->result, + rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + rpsdb_bind_rdataset(rdataset, 1, LIBRPZ_IDX_BAD, dns_rdatatype_soa, + dns_rdataclass_in, ttl, rpsdb); + return (ISC_R_SUCCESS); +} + +/* + * Forge an rdataset of the desired type from a librpz result. + * This is written for simplicity instead of speed, because RPZ rewriting + * should be rare compared to normal BIND operations. + */ +static isc_result_t +rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, + isc_stdtime_t now, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + dns_rdatatype_t foundtype; + dns_rdataclass_t class; + uint32_t ttl; + uint count; + librpz_emsg_t emsg; + + UNUSED(version); + UNUSED(covers); + UNUSED(now); + UNUSED(sigrdataset); + + REQUIRE(VALID_RPSDB(rpsdb)); + + if (node == &rpsdb->origin_node) { + if (type == dns_rdatatype_any) { + return (ISC_R_SUCCESS); + } + if (type == dns_rdatatype_soa) { + return (rpsdb_bind_soa(rdataset, rpsdb)); + } + return (DNS_R_NXRRSET); + } + + REQUIRE(node == &rpsdb->data_node); + + switch (rpsdb->result.policy) { + case LIBRPZ_POLICY_UNDEFINED: + case LIBRPZ_POLICY_DELETED: + case LIBRPZ_POLICY_PASSTHRU: + case LIBRPZ_POLICY_DROP: + case LIBRPZ_POLICY_TCP_ONLY: + case LIBRPZ_POLICY_GIVEN: + case LIBRPZ_POLICY_DISABLED: + default: + librpz->log(LIBRPZ_LOG_ERROR, NULL, + "impossible dnsrps policy %d at %s:%d", + rpsdb->result.policy, __FILE__, __LINE__); + return (DNS_R_SERVFAIL); + + case LIBRPZ_POLICY_NXDOMAIN: + return (DNS_R_NXDOMAIN); + + case LIBRPZ_POLICY_NODATA: + return (DNS_R_NXRRSET); + + case LIBRPZ_POLICY_RECORD: + case LIBRPZ_POLICY_CNAME: + break; + } + + if (type == dns_rdatatype_soa) { + return (rpsdb_bind_soa(rdataset, rpsdb)); + } + + /* + * There is little to do for an ANY query. + */ + if (type == dns_rdatatype_any) { + return (ISC_R_SUCCESS); + } + + /* + * Reset to the start of the RRs. + * This function is only used after a policy has been chosen, + * and so without caring whether it is after recursion. + */ + if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + if (!librpz->rsp_rr(&emsg, &foundtype, &class, &ttl, NULL, + &rpsdb->result, rpsdb->qname->ndata, + rpsdb->qname->length, rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + REQUIRE(foundtype != dns_rdatatype_none); + + /* + * Ho many of the target RR type are available? + */ + count = 0; + do { + if (type == foundtype || type == dns_rdatatype_any) { + ++count; + } + + if (!librpz->rsp_rr(&emsg, &foundtype, NULL, NULL, NULL, + &rpsdb->result, rpsdb->qname->ndata, + rpsdb->qname->length, rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + } while (foundtype != dns_rdatatype_none); + if (count == 0) { + return (DNS_R_NXRRSET); + } + rpsdb_bind_rdataset(rdataset, count, rpsdb->result.next_rr, type, class, + ttl, rpsdb); + return (ISC_R_SUCCESS); +} + +static isc_result_t +rpsdb_finddb(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_dbnode_t *node; + + UNUSED(version); + UNUSED(options); + UNUSED(now); + UNUSED(sigrdataset); + + if (nodep == NULL) { + node = NULL; + nodep = &node; + } + rpsdb_findnode(db, name, false, nodep); + dns_name_copy(name, foundname); + return (rpsdb_findrdataset(db, *nodep, NULL, type, 0, 0, rdataset, + sigrdataset)); +} + +static isc_result_t +rpsdb_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + rpsdb_rdatasetiter_t *rpsdb_iter; + + UNUSED(version); + UNUSED(now); + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(node == &rpsdb->origin_node || node == &rpsdb->data_node); + + rpsdb_iter = isc_mem_get(rpsdb->common.mctx, sizeof(*rpsdb_iter)); + + memset(rpsdb_iter, 0, sizeof(*rpsdb_iter)); + rpsdb_iter->common.magic = DNS_RDATASETITER_MAGIC; + rpsdb_iter->common.methods = &rpsdb_rdatasetiter_methods; + rpsdb_iter->common.db = db; + rpsdb_iter->common.options = options; + rpsdb_attachnode(db, node, &rpsdb_iter->common.node); + + *iteratorp = &rpsdb_iter->common; + + return (ISC_R_SUCCESS); +} + +static bool +rpsdb_issecure(dns_db_t *db) { + UNUSED(db); + + return (false); +} + +static isc_result_t +rpsdb_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { + rpsdb_t *rpsdb = (rpsdb_t *)db; + + REQUIRE(VALID_RPSDB(rpsdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + rpsdb_attachnode(db, &rpsdb->origin_node, nodep); + return (ISC_R_SUCCESS); +} + +static void +rpsdb_rdataset_disassociate(dns_rdataset_t *rdataset) { + dns_db_t *db; + + /* + * Detach the last RR delivered. + */ + if (RD_CUR_RR(rdataset) != NULL) { + free(RD_CUR_RR(rdataset)); + RD_CUR_RR(rdataset) = NULL; + } + + db = RD_DB(rdataset); + RD_DB(rdataset) = NULL; + dns_db_detach(&db); +} + +static isc_result_t +rpsdb_rdataset_next(dns_rdataset_t *rdataset) { + rpsdb_t *rpsdb; + uint16_t type; + dns_rdataclass_t class; + librpz_rr_t *rr; + librpz_emsg_t emsg; + + rpsdb = RD_DB(rdataset); + + /* + * Detach the previous RR. + */ + if (RD_CUR_RR(rdataset) != NULL) { + free(RD_CUR_RR(rdataset)); + RD_CUR_RR(rdataset) = NULL; + } + + /* + * Get the next RR of the specified type. + * SOAs differ. + */ + if (rdataset->type == dns_rdatatype_soa) { + if (RD_NEXT_RR(rdataset) == LIBRPZ_IDX_NULL) { + return (ISC_R_NOMORE); + } + RD_NEXT_RR(rdataset) = LIBRPZ_IDX_NULL; + if (!librpz->rsp_soa(&emsg, NULL, &rr, NULL, &rpsdb->result, + rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + RD_CUR_RR(rdataset) = rr; + return (ISC_R_SUCCESS); + } + + rpsdb->result.next_rr = RD_NEXT_RR(rdataset); + for (;;) { + if (!librpz->rsp_rr(&emsg, &type, &class, NULL, &rr, + &rpsdb->result, rpsdb->qname->ndata, + rpsdb->qname->length, rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + if (rdataset->type == type && rdataset->rdclass == class) { + RD_CUR_RR(rdataset) = rr; + RD_NEXT_RR(rdataset) = rpsdb->result.next_rr; + return (ISC_R_SUCCESS); + } + if (type == dns_rdatatype_none) { + return (ISC_R_NOMORE); + } + free(rr); + } +} + +static isc_result_t +rpsdb_rdataset_first(dns_rdataset_t *rdataset) { + rpsdb_t *rpsdb; + librpz_emsg_t emsg; + + rpsdb = RD_DB(rdataset); + REQUIRE(VALID_RPSDB(rpsdb)); + + if (RD_CUR_RR(rdataset) != NULL) { + free(RD_CUR_RR(rdataset)); + RD_CUR_RR(rdataset) = NULL; + } + + if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + if (rdataset->type == dns_rdatatype_soa) { + RD_NEXT_RR(rdataset) = LIBRPZ_IDX_BAD; + } else { + RD_NEXT_RR(rdataset) = rpsdb->result.next_rr; + } + + return (rpsdb_rdataset_next(rdataset)); +} + +static void +rpsdb_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + rpsdb_t *rpsdb; + librpz_rr_t *rr; + isc_region_t r; + + rpsdb = RD_DB(rdataset); + REQUIRE(VALID_RPSDB(rpsdb)); + rr = RD_CUR_RR(rdataset); + REQUIRE(rr != NULL); + + r.length = ntohs(rr->rdlength); + r.base = rr->rdata; + dns_rdata_fromregion(rdata, ntohs(rr->class), ntohs(rr->type), &r); +} + +static void +rpsdb_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + rpsdb_t *rpsdb; + dns_db_t *dbp; + + INSIST(!ISC_LINK_LINKED(target, link)); + *target = *source; + ISC_LINK_INIT(target, link); + rpsdb = RD_DB(source); + REQUIRE(VALID_RPSDB(rpsdb)); + dbp = NULL; + dns_db_attach(&rpsdb->common, &dbp); + RD_DB(target) = dbp; + RD_CUR_RR(target) = NULL; + RD_NEXT_RR(target) = LIBRPZ_IDX_NULL; +} + +static unsigned int +rpsdb_rdataset_count(dns_rdataset_t *rdataset) { + rpsdb_t *rpsdb; + + rpsdb = RD_DB(rdataset); + REQUIRE(VALID_RPSDB(rpsdb)); + + return (RD_COUNT(rdataset)); +} + +static void +rpsdb_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + rpsdb_t *rpsdb; + dns_rdatasetiter_t *iterator; + isc_mem_t *mctx; + + iterator = *iteratorp; + *iteratorp = NULL; + rpsdb = (rpsdb_t *)iterator->db; + REQUIRE(VALID_RPSDB(rpsdb)); + + mctx = iterator->db->mctx; + dns_db_detachnode(iterator->db, &iterator->node); + isc_mem_put(mctx, iterator, sizeof(rpsdb_rdatasetiter_t)); +} + +static isc_result_t +rpsdb_rdatasetiter_next(dns_rdatasetiter_t *iter) { + rpsdb_t *rpsdb; + rpsdb_rdatasetiter_t *rpsdb_iter; + dns_rdatatype_t next_type, type; + dns_rdataclass_t next_class, class; + uint32_t ttl; + librpz_emsg_t emsg; + + rpsdb = (rpsdb_t *)iter->db; + REQUIRE(VALID_RPSDB(rpsdb)); + rpsdb_iter = (rpsdb_rdatasetiter_t *)iter; + + /* + * This function is only used after a policy has been chosen, + * and so without caring whether it is after recursion. + */ + if (!librpz->rsp_result(&emsg, &rpsdb->result, true, rpsdb->rsp)) { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + /* + * Find the next class and type after the current class and type + * among the RRs in current result. + * As a side effect, count the number of those RRs. + */ + rpsdb_iter->count = 0; + next_class = dns_rdataclass_reserved0; + next_type = dns_rdatatype_none; + for (;;) { + if (!librpz->rsp_rr(&emsg, &type, &class, &ttl, NULL, + &rpsdb->result, rpsdb->qname->ndata, + rpsdb->qname->length, rpsdb->rsp)) + { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "%s", emsg.c); + return (DNS_R_SERVFAIL); + } + if (type == dns_rdatatype_none) { + if (next_type == dns_rdatatype_none) { + return (ISC_R_NOMORE); + } + rpsdb_iter->type = next_type; + rpsdb_iter->class = next_class; + return (ISC_R_SUCCESS); + } + /* + * Skip RRs with the current class and type or before. + */ + if (rpsdb_iter->class > class || + (rpsdb_iter->class = class && rpsdb_iter->type >= type)) + { + continue; + } + if (next_type == dns_rdatatype_none || next_class > class || + (next_class == class && next_type > type)) + { + /* + * This is the first of a subsequent class and type. + */ + next_type = type; + next_class = class; + rpsdb_iter->ttl = ttl; + rpsdb_iter->count = 1; + rpsdb_iter->next_rr = rpsdb->result.next_rr; + } else if (next_type == type && next_class == class) { + ++rpsdb_iter->count; + } + } +} + +static isc_result_t +rpsdb_rdatasetiter_first(dns_rdatasetiter_t *iterator) { + rpsdb_t *rpsdb; + rpsdb_rdatasetiter_t *rpsdb_iter; + + rpsdb = (rpsdb_t *)iterator->db; + REQUIRE(VALID_RPSDB(rpsdb)); + rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator; + + rpsdb_iter->type = dns_rdatatype_none; + rpsdb_iter->class = dns_rdataclass_reserved0; + return (rpsdb_rdatasetiter_next(iterator)); +} + +static void +rpsdb_rdatasetiter_current(dns_rdatasetiter_t *iterator, + dns_rdataset_t *rdataset) { + rpsdb_t *rpsdb; + rpsdb_rdatasetiter_t *rpsdb_iter; + + rpsdb = (rpsdb_t *)iterator->db; + REQUIRE(VALID_RPSDB(rpsdb)); + rpsdb_iter = (rpsdb_rdatasetiter_t *)iterator; + REQUIRE(rpsdb_iter->type != dns_rdatatype_none); + + rpsdb_bind_rdataset(rdataset, rpsdb_iter->count, rpsdb_iter->next_rr, + rpsdb_iter->type, rpsdb_iter->class, + rpsdb_iter->ttl, rpsdb); +} + +static dns_dbmethods_t rpsdb_db_methods = { + rpsdb_attach, + rpsdb_detach, + NULL, /* beginload */ + NULL, /* endload */ + NULL, /* dump */ + NULL, /* currentversion */ + NULL, /* newversion */ + NULL, /* attachversion */ + NULL, /* closeversion */ + rpsdb_findnode, + rpsdb_finddb, + NULL, /* findzonecut*/ + rpsdb_attachnode, + rpsdb_detachnode, + NULL, /* expirenode */ + NULL, /* printnode */ + NULL, /* createiterator */ + rpsdb_findrdataset, + rpsdb_allrdatasets, + NULL, /* addrdataset */ + NULL, /* subtractrdataset */ + NULL, /* deleterdataset */ + rpsdb_issecure, + NULL, /* nodecount */ + NULL, /* ispersistent */ + NULL, /* overmem */ + NULL, /* settask */ + rpsdb_getoriginnode, + NULL, /* transfernode */ + NULL, /* getnsec3parameters */ + NULL, /* findnsec3node */ + NULL, /* setsigningtime */ + NULL, /* getsigningtime */ + NULL, /* resigned */ + NULL, /* isdnssec */ + NULL, /* getrrsetstats */ + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ + NULL, /* findnodeext */ + NULL, /* findext */ + NULL, /* setcachestats */ + NULL, /* hashsize */ + NULL, /* nodefullname */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL, /* getservestalettl */ + NULL, /* setservestalerefresh */ + NULL, /* getservestalerefresh */ + NULL, /* setgluecachestats */ +}; + +static dns_rdatasetmethods_t rpsdb_rdataset_methods = { + rpsdb_rdataset_disassociate, + rpsdb_rdataset_first, + rpsdb_rdataset_next, + rpsdb_rdataset_current, + rpsdb_rdataset_clone, + rpsdb_rdataset_count, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static dns_rdatasetitermethods_t rpsdb_rdatasetiter_methods = { + rpsdb_rdatasetiter_destroy, rpsdb_rdatasetiter_first, + rpsdb_rdatasetiter_next, rpsdb_rdatasetiter_current +}; + +#endif /* USE_DNSRPS */ diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c new file mode 100644 index 0000000..5678c05 --- /dev/null +++ b/lib/dns/dnssec.c @@ -0,0 +1,2533 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for DNS_TSIG_FUDGE */ + +isc_stats_t *dns_dnssec_stats; + +#define is_response(msg) ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define TYPE_SIGN 0 +#define TYPE_VERIFY 1 + +static isc_result_t +digest_callback(void *arg, isc_region_t *data); + +static int +rdata_compare_wrapper(const void *rdata1, const void *rdata2); + +static isc_result_t +rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx, + dns_rdata_t **rdata, int *nrdata); + +static isc_result_t +digest_callback(void *arg, isc_region_t *data) { + dst_context_t *ctx = arg; + + return (dst_context_adddata(ctx, data)); +} + +static void +inc_stat(isc_statscounter_t counter) { + if (dns_dnssec_stats != NULL) { + isc_stats_increment(dns_dnssec_stats, counter); + } +} + +/* + * Make qsort happy. + */ +static int +rdata_compare_wrapper(const void *rdata1, const void *rdata2) { + return (dns_rdata_compare((const dns_rdata_t *)rdata1, + (const dns_rdata_t *)rdata2)); +} + +/* + * Sort the rdataset into an array. + */ +static isc_result_t +rdataset_to_sortedarray(dns_rdataset_t *set, isc_mem_t *mctx, + dns_rdata_t **rdata, int *nrdata) { + isc_result_t ret; + int i = 0, n; + dns_rdata_t *data; + dns_rdataset_t rdataset; + + n = dns_rdataset_count(set); + + data = isc_mem_get(mctx, n * sizeof(dns_rdata_t)); + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(set, &rdataset); + ret = dns_rdataset_first(&rdataset); + if (ret != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + isc_mem_put(mctx, data, n * sizeof(dns_rdata_t)); + return (ret); + } + + /* + * Put them in the array. + */ + do { + dns_rdata_init(&data[i]); + dns_rdataset_current(&rdataset, &data[i++]); + } while (dns_rdataset_next(&rdataset) == ISC_R_SUCCESS); + + /* + * Sort the array. + */ + qsort(data, n, sizeof(dns_rdata_t), rdata_compare_wrapper); + *rdata = data; + *nrdata = n; + dns_rdataset_disassociate(&rdataset); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata, + isc_mem_t *mctx, dst_key_t **key) { + isc_buffer_t b; + isc_region_t r; + + INSIST(name != NULL); + INSIST(rdata != NULL); + INSIST(mctx != NULL); + INSIST(key != NULL); + INSIST(*key == NULL); + REQUIRE(rdata->type == dns_rdatatype_key || + rdata->type == dns_rdatatype_dnskey); + + dns_rdata_toregion(rdata, &r); + isc_buffer_init(&b, r.base, r.length); + isc_buffer_add(&b, r.length); + return (dst_key_fromdns(name, rdata->rdclass, &b, mctx, key)); +} + +static isc_result_t +digest_sig(dst_context_t *ctx, bool downcase, dns_rdata_t *sigrdata, + dns_rdata_rrsig_t *rrsig) { + isc_region_t r; + isc_result_t ret; + dns_fixedname_t fname; + + dns_rdata_toregion(sigrdata, &r); + INSIST(r.length >= 19); + + r.length = 18; + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + if (downcase) { + dns_fixedname_init(&fname); + + RUNTIME_CHECK(dns_name_downcase(&rrsig->signer, + dns_fixedname_name(&fname), + NULL) == ISC_R_SUCCESS); + dns_name_toregion(dns_fixedname_name(&fname), &r); + } else { + dns_name_toregion(&rrsig->signer, &r); + } + + return (dst_context_adddata(ctx, &r)); +} + +isc_result_t +dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_stdtime_t *inception, isc_stdtime_t *expire, + isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata) { + dns_rdata_rrsig_t sig; + dns_rdata_t tmpsigrdata; + dns_rdata_t *rdatas; + int nrdatas, i; + isc_buffer_t sigbuf, envbuf; + isc_region_t r; + dst_context_t *ctx = NULL; + isc_result_t ret; + isc_buffer_t *databuf = NULL; + char data[256 + 8]; + uint32_t flags; + unsigned int sigsize; + dns_fixedname_t fnewname; + dns_fixedname_t fsigner; + + REQUIRE(name != NULL); + REQUIRE(dns_name_countlabels(name) <= 255); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(inception != NULL); + REQUIRE(expire != NULL); + REQUIRE(mctx != NULL); + REQUIRE(sigrdata != NULL); + + if (*inception >= *expire) { + return (DNS_R_INVALIDTIME); + } + + /* + * Is the key allowed to sign data? + */ + flags = dst_key_flags(key); + if ((flags & DNS_KEYTYPE_NOAUTH) != 0) { + return (DNS_R_KEYUNAUTHORIZED); + } + if ((flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) { + return (DNS_R_KEYUNAUTHORIZED); + } + + sig.mctx = mctx; + sig.common.rdclass = set->rdclass; + sig.common.rdtype = dns_rdatatype_rrsig; + ISC_LINK_INIT(&sig.common, link); + + /* + * Downcase signer. + */ + dns_name_init(&sig.signer, NULL); + dns_fixedname_init(&fsigner); + RUNTIME_CHECK(dns_name_downcase(dst_key_name(key), + dns_fixedname_name(&fsigner), + NULL) == ISC_R_SUCCESS); + dns_name_clone(dns_fixedname_name(&fsigner), &sig.signer); + + sig.covered = set->type; + sig.algorithm = dst_key_alg(key); + sig.labels = dns_name_countlabels(name) - 1; + if (dns_name_iswildcard(name)) { + sig.labels--; + } + sig.originalttl = set->ttl; + sig.timesigned = *inception; + sig.timeexpire = *expire; + sig.keyid = dst_key_id(key); + ret = dst_key_sigsize(key, &sigsize); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + sig.siglen = sigsize; + /* + * The actual contents of sig.signature are not important yet, since + * they're not used in digest_sig(). + */ + sig.signature = isc_mem_get(mctx, sig.siglen); + + isc_buffer_allocate(mctx, &databuf, sigsize + 256 + 18); + + dns_rdata_init(&tmpsigrdata); + ret = dns_rdata_fromstruct(&tmpsigrdata, sig.common.rdclass, + sig.common.rdtype, &sig, databuf); + if (ret != ISC_R_SUCCESS) { + goto cleanup_databuf; + } + + ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0, + &ctx); + if (ret != ISC_R_SUCCESS) { + goto cleanup_databuf; + } + + /* + * Digest the SIG rdata. + */ + ret = digest_sig(ctx, false, &tmpsigrdata, &sig); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + dns_fixedname_init(&fnewname); + RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname), + NULL) == ISC_R_SUCCESS); + dns_name_toregion(dns_fixedname_name(&fnewname), &r); + + /* + * Create an envelope for each rdata: . + */ + isc_buffer_init(&envbuf, data, sizeof(data)); + memmove(data, r.base, r.length); + isc_buffer_add(&envbuf, r.length); + isc_buffer_putuint16(&envbuf, set->type); + isc_buffer_putuint16(&envbuf, set->rdclass); + isc_buffer_putuint32(&envbuf, set->ttl); + + ret = rdataset_to_sortedarray(set, mctx, &rdatas, &nrdatas); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + isc_buffer_usedregion(&envbuf, &r); + + for (i = 0; i < nrdatas; i++) { + uint16_t len; + isc_buffer_t lenbuf; + isc_region_t lenr; + + /* + * Skip duplicates. + */ + if (i > 0 && dns_rdata_compare(&rdatas[i], &rdatas[i - 1]) == 0) + { + continue; + } + + /* + * Digest the envelope. + */ + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + + /* + * Digest the length of the rdata. + */ + isc_buffer_init(&lenbuf, &len, sizeof(len)); + INSIST(rdatas[i].length < 65536); + isc_buffer_putuint16(&lenbuf, (uint16_t)rdatas[i].length); + isc_buffer_usedregion(&lenbuf, &lenr); + ret = dst_context_adddata(ctx, &lenr); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + + /* + * Digest the rdata. + */ + ret = dns_rdata_digest(&rdatas[i], digest_callback, ctx); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + } + + isc_buffer_init(&sigbuf, sig.signature, sig.siglen); + ret = dst_context_sign(ctx, &sigbuf); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + isc_buffer_usedregion(&sigbuf, &r); + if (r.length != sig.siglen) { + ret = ISC_R_NOSPACE; + goto cleanup_array; + } + + ret = dns_rdata_fromstruct(sigrdata, sig.common.rdclass, + sig.common.rdtype, &sig, buffer); + +cleanup_array: + isc_mem_put(mctx, rdatas, nrdatas * sizeof(dns_rdata_t)); +cleanup_context: + dst_context_destroy(&ctx); +cleanup_databuf: + isc_buffer_free(&databuf); + isc_mem_put(mctx, sig.signature, sig.siglen); + + return (ret); +} + +isc_result_t +dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, unsigned int maxbits, isc_mem_t *mctx, + dns_rdata_t *sigrdata, dns_name_t *wild) { + dns_rdata_rrsig_t sig; + dns_fixedname_t fnewname; + isc_region_t r; + isc_buffer_t envbuf; + dns_rdata_t *rdatas; + int nrdatas, i; + isc_stdtime_t now; + isc_result_t ret; + unsigned char data[300]; + dst_context_t *ctx = NULL; + int labels = 0; + uint32_t flags; + bool downcase = false; + + REQUIRE(name != NULL); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(mctx != NULL); + REQUIRE(sigrdata != NULL && sigrdata->type == dns_rdatatype_rrsig); + + ret = dns_rdata_tostruct(sigrdata, &sig, NULL); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + if (set->type != sig.covered) { + return (DNS_R_SIGINVALID); + } + + if (isc_serial_lt(sig.timeexpire, sig.timesigned)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGINVALID); + } + + if (!ignoretime) { + isc_stdtime_get(&now); + + /* + * Is SIG temporally valid? + */ + if (isc_serial_lt((uint32_t)now, sig.timesigned)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGFUTURE); + } else if (isc_serial_lt(sig.timeexpire, (uint32_t)now)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGEXPIRED); + } + } + + /* + * NS, SOA and DNSSKEY records are signed by their owner. + * DS records are signed by the parent. + */ + switch (set->type) { + case dns_rdatatype_ns: + case dns_rdatatype_soa: + case dns_rdatatype_dnskey: + if (!dns_name_equal(name, &sig.signer)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGINVALID); + } + break; + case dns_rdatatype_ds: + if (dns_name_equal(name, &sig.signer)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGINVALID); + } + FALLTHROUGH; + default: + if (!dns_name_issubdomain(name, &sig.signer)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGINVALID); + } + break; + } + + /* + * Is the key allowed to sign data? + */ + flags = dst_key_flags(key); + if ((flags & DNS_KEYTYPE_NOAUTH) != 0) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_KEYUNAUTHORIZED); + } + if ((flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_KEYUNAUTHORIZED); + } + +again: + ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false, + maxbits, &ctx); + if (ret != ISC_R_SUCCESS) { + goto cleanup_struct; + } + + /* + * Digest the SIG rdata (not including the signature). + */ + ret = digest_sig(ctx, downcase, sigrdata, &sig); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * If the name is an expanded wildcard, use the wildcard name. + */ + dns_fixedname_init(&fnewname); + labels = dns_name_countlabels(name) - 1; + RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname), + NULL) == ISC_R_SUCCESS); + if (labels - sig.labels > 0) { + dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1, + NULL, dns_fixedname_name(&fnewname)); + } + + dns_name_toregion(dns_fixedname_name(&fnewname), &r); + + /* + * Create an envelope for each rdata: . + */ + isc_buffer_init(&envbuf, data, sizeof(data)); + if (labels - sig.labels > 0) { + isc_buffer_putuint8(&envbuf, 1); + isc_buffer_putuint8(&envbuf, '*'); + memmove(data + 2, r.base, r.length); + } else { + memmove(data, r.base, r.length); + } + isc_buffer_add(&envbuf, r.length); + isc_buffer_putuint16(&envbuf, set->type); + isc_buffer_putuint16(&envbuf, set->rdclass); + isc_buffer_putuint32(&envbuf, sig.originalttl); + + ret = rdataset_to_sortedarray(set, mctx, &rdatas, &nrdatas); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + isc_buffer_usedregion(&envbuf, &r); + + for (i = 0; i < nrdatas; i++) { + uint16_t len; + isc_buffer_t lenbuf; + isc_region_t lenr; + + /* + * Skip duplicates. + */ + if (i > 0 && dns_rdata_compare(&rdatas[i], &rdatas[i - 1]) == 0) + { + continue; + } + + /* + * Digest the envelope. + */ + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + + /* + * Digest the rdata length. + */ + isc_buffer_init(&lenbuf, &len, sizeof(len)); + INSIST(rdatas[i].length < 65536); + isc_buffer_putuint16(&lenbuf, (uint16_t)rdatas[i].length); + isc_buffer_usedregion(&lenbuf, &lenr); + + /* + * Digest the rdata. + */ + ret = dst_context_adddata(ctx, &lenr); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + ret = dns_rdata_digest(&rdatas[i], digest_callback, ctx); + if (ret != ISC_R_SUCCESS) { + goto cleanup_array; + } + } + + r.base = sig.signature; + r.length = sig.siglen; + ret = dst_context_verify2(ctx, maxbits, &r); + if (ret == ISC_R_SUCCESS && downcase) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&sig.signer, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "successfully validated after lower casing " + "signer '%s'", + namebuf); + inc_stat(dns_dnssecstats_downcase); + } else if (ret == ISC_R_SUCCESS) { + inc_stat(dns_dnssecstats_asis); + } + +cleanup_array: + isc_mem_put(mctx, rdatas, nrdatas * sizeof(dns_rdata_t)); +cleanup_context: + dst_context_destroy(&ctx); + if (ret == DST_R_VERIFYFAILURE && !downcase) { + downcase = true; + goto again; + } +cleanup_struct: + dns_rdata_freestruct(&sig); + + if (ret == DST_R_VERIFYFAILURE) { + ret = DNS_R_SIGINVALID; + } + + if (ret != ISC_R_SUCCESS) { + inc_stat(dns_dnssecstats_fail); + } + + if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) { + if (wild != NULL) { + RUNTIME_CHECK(dns_name_concatenate( + dns_wildcardname, + dns_fixedname_name(&fnewname), + wild, NULL) == ISC_R_SUCCESS); + } + inc_stat(dns_dnssecstats_wildcard); + ret = DNS_R_FROMWILDCARD; + } + return (ret); +} + +bool +dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { + isc_result_t result; + isc_stdtime_t publish, active, revoke, remove; + bool hint_publish, hint_zsign, hint_ksign, hint_revoke, hint_remove; + int major, minor; + bool ksk = false, zsk = false; + isc_result_t ret; + + /* Is this an old-style key? */ + result = dst_key_getprivateformat(key, &major, &minor); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Is this a KSK? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + } + + /* + * Smart signing started with key format 1.3; prior to that, all + * keys are assumed active. + */ + if (major == 1 && minor <= 2) { + return (true); + } + + hint_publish = dst_key_is_published(key, now, &publish); + hint_zsign = dst_key_is_signing(key, DST_BOOL_ZSK, now, &active); + hint_ksign = dst_key_is_signing(key, DST_BOOL_KSK, now, &active); + hint_revoke = dst_key_is_revoked(key, now, &revoke); + hint_remove = dst_key_is_removed(key, now, &remove); + + if (hint_remove) { + return (false); + } + if (hint_publish && hint_revoke) { + return (true); + } + if (hint_zsign && zsk) { + return (true); + } + if (hint_ksign && ksk) { + return (true); + } + return (false); +} + +/*%< + * Indicate whether a key is scheduled to to have CDS/CDNSKEY records + * published now. + * + * Returns true if. + * - kasp says the DS record should be published (e.g. the DS state is in + * RUMOURED or OMNIPRESENT state). + * Or: + * - SyncPublish is set and in the past, AND + * - SyncDelete is unset or in the future + */ +static bool +syncpublish(dst_key_t *key, isc_stdtime_t now) { + isc_result_t result; + isc_stdtime_t when; + dst_key_state_t state; + int major, minor; + bool publish; + + /* + * Is this an old-style key? + */ + result = dst_key_getprivateformat(key, &major, &minor); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * Smart signing started with key format 1.3 + */ + if (major == 1 && minor <= 2) { + return (false); + } + + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_RUMOURED || + state == DST_KEY_STATE_OMNIPRESENT); + } + + /* If no kasp state, check timings. */ + publish = false; + result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when); + if (result == ISC_R_SUCCESS && when <= now) { + publish = true; + } + result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); + if (result == ISC_R_SUCCESS && when < now) { + publish = false; + } + return (publish); +} + +/*%< + * Indicate whether a key is scheduled to to have CDS/CDNSKEY records + * deleted now. + * + * Returns true if: + * - kasp says the DS record should be unpublished (e.g. the DS state is in + * UNRETENTIVE or HIDDEN state). + * Or: + * - SyncDelete is set and in the past. + */ +static bool +syncdelete(dst_key_t *key, isc_stdtime_t now) { + isc_result_t result; + isc_stdtime_t when; + dst_key_state_t state; + int major, minor; + + /* + * Is this an old-style key? + */ + result = dst_key_getprivateformat(key, &major, &minor); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * Smart signing started with key format 1.3. + */ + if (major == 1 && minor <= 2) { + return (false); + } + + /* Check kasp state first. */ + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + return (state == DST_KEY_STATE_UNRETENTIVE || + state == DST_KEY_STATE_HIDDEN); + } + + /* If no kasp state, check timings. */ + result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); + if (result != ISC_R_SUCCESS) { + return (false); + } + if (when <= now) { + return (true); + } + return (false); +} + +#define is_zone_key(key) \ + ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE) + +isc_result_t +dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, + const dns_name_t *name, const char *directory, + isc_stdtime_t now, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys) { + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dst_key_t *pubkey = NULL; + unsigned int count = 0; + + REQUIRE(nkeys != NULL); + REQUIRE(keys != NULL); + + *nkeys = 0; + memset(keys, 0, sizeof(*keys) * maxkeys); + dns_rdataset_init(&rdataset); + RETERR(dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, 0, 0, + &rdataset, NULL)); + RETERR(dns_rdataset_first(&rdataset)); + while (result == ISC_R_SUCCESS && count < maxkeys) { + pubkey = NULL; + dns_rdataset_current(&rdataset, &rdata); + RETERR(dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey)); + dst_key_setttl(pubkey, rdataset.ttl); + + if (!is_zone_key(pubkey) || + (dst_key_flags(pubkey) & DNS_KEYTYPE_NOAUTH) != 0) + { + goto next; + } + /* Corrupted .key file? */ + if (!dns_name_equal(name, dst_key_name(pubkey))) { + goto next; + } + keys[count] = NULL; + result = dst_key_fromfile( + dst_key_name(pubkey), dst_key_id(pubkey), + dst_key_alg(pubkey), + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE, + directory, mctx, &keys[count]); + + /* + * If the key was revoked and the private file + * doesn't exist, maybe it was revoked internally + * by named. Try loading the unrevoked version. + */ + if (result == ISC_R_FILENOTFOUND) { + uint32_t flags; + flags = dst_key_flags(pubkey); + if ((flags & DNS_KEYFLAG_REVOKE) != 0) { + dst_key_setflags(pubkey, + flags & ~DNS_KEYFLAG_REVOKE); + result = dst_key_fromfile( + dst_key_name(pubkey), + dst_key_id(pubkey), dst_key_alg(pubkey), + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE, + directory, mctx, &keys[count]); + if (result == ISC_R_SUCCESS && + dst_key_pubcompare(pubkey, keys[count], + false)) + { + dst_key_setflags(keys[count], flags); + } + dst_key_setflags(pubkey, flags); + } + } + + if (result != ISC_R_SUCCESS) { + char filename[DNS_NAME_FORMATSIZE + + DNS_SECALG_FORMATSIZE + + sizeof("key file for //65535")]; + isc_result_t result2; + isc_buffer_t buf; + + isc_buffer_init(&buf, filename, NAME_MAX); + result2 = dst_key_getfilename( + dst_key_name(pubkey), dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE), + directory, mctx, &buf); + if (result2 != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + char algbuf[DNS_SECALG_FORMATSIZE]; + + dns_name_format(dst_key_name(pubkey), namebuf, + sizeof(namebuf)); + dns_secalg_format(dst_key_alg(pubkey), algbuf, + sizeof(algbuf)); + snprintf(filename, sizeof(filename) - 1, + "key file for %s/%s/%d", namebuf, + algbuf, dst_key_id(pubkey)); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "dns_dnssec_findzonekeys2: error " + "reading %s: %s", + filename, isc_result_totext(result)); + } + + if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { + keys[count] = pubkey; + pubkey = NULL; + count++; + goto next; + } + + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * If a key is marked inactive, skip it + */ + if (!dns_dnssec_keyactive(keys[count], now)) { + dst_key_setinactive(pubkey, true); + dst_key_free(&keys[count]); + keys[count] = pubkey; + pubkey = NULL; + count++; + goto next; + } + + /* + * Whatever the key's default TTL may have + * been, the rdataset TTL takes priority. + */ + dst_key_setttl(keys[count], rdataset.ttl); + + if ((dst_key_flags(keys[count]) & DNS_KEYTYPE_NOAUTH) != 0) { + /* We should never get here. */ + dst_key_free(&keys[count]); + goto next; + } + count++; + next: + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + if (count == 0) { + result = ISC_R_NOTFOUND; + } else { + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (result != ISC_R_SUCCESS) { + while (count > 0) { + dst_key_free(&keys[--count]); + } + } + *nkeys = count; + return (result); +} + +isc_result_t +dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key) { + dns_rdata_sig_t sig; /* SIG(0) */ + unsigned char data[512]; + unsigned char header[DNS_MESSAGE_HEADERLEN]; + isc_buffer_t headerbuf, databuf, sigbuf; + unsigned int sigsize; + isc_buffer_t *dynbuf = NULL; + dns_rdata_t *rdata; + dns_rdatalist_t *datalist; + dns_rdataset_t *dataset; + isc_region_t r; + isc_stdtime_t now; + dst_context_t *ctx = NULL; + isc_mem_t *mctx; + isc_result_t result; + + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + + if (is_response(msg)) { + REQUIRE(msg->query.base != NULL); + } + + mctx = msg->mctx; + + memset(&sig, 0, sizeof(sig)); + + sig.mctx = mctx; + sig.common.rdclass = dns_rdataclass_any; + sig.common.rdtype = dns_rdatatype_sig; /* SIG(0) */ + ISC_LINK_INIT(&sig.common, link); + + sig.covered = 0; + sig.algorithm = dst_key_alg(key); + sig.labels = 0; /* the root name */ + sig.originalttl = 0; + + if (msg->fuzzing) { + now = msg->fuzztime; + } else { + isc_stdtime_get(&now); + } + sig.timesigned = now - DNS_TSIG_FUDGE; + sig.timeexpire = now + DNS_TSIG_FUDGE; + + sig.keyid = dst_key_id(key); + + dns_name_init(&sig.signer, NULL); + dns_name_clone(dst_key_name(key), &sig.signer); + + sig.siglen = 0; + sig.signature = NULL; + + isc_buffer_init(&databuf, data, sizeof(data)); + + RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, true, 0, + &ctx)); + + /* + * Digest the fields of the SIG - we can cheat and use + * dns_rdata_fromstruct. Since siglen is 0, the digested data + * is identical to dns format. + */ + RETERR(dns_rdata_fromstruct(NULL, dns_rdataclass_any, + dns_rdatatype_sig /* SIG(0) */, &sig, + &databuf)); + isc_buffer_usedregion(&databuf, &r); + RETERR(dst_context_adddata(ctx, &r)); + + /* + * If this is a response, digest the query. + */ + if (is_response(msg)) { + RETERR(dst_context_adddata(ctx, &msg->query)); + } + + /* + * Digest the header. + */ + isc_buffer_init(&headerbuf, header, sizeof(header)); + dns_message_renderheader(msg, &headerbuf); + isc_buffer_usedregion(&headerbuf, &r); + RETERR(dst_context_adddata(ctx, &r)); + + /* + * Digest the remainder of the message. + */ + isc_buffer_usedregion(msg->buffer, &r); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + RETERR(dst_context_adddata(ctx, &r)); + + RETERR(dst_key_sigsize(key, &sigsize)); + sig.siglen = sigsize; + sig.signature = isc_mem_get(mctx, sig.siglen); + + isc_buffer_init(&sigbuf, sig.signature, sig.siglen); + RETERR(dst_context_sign(ctx, &sigbuf)); + dst_context_destroy(&ctx); + + rdata = NULL; + RETERR(dns_message_gettemprdata(msg, &rdata)); + isc_buffer_allocate(msg->mctx, &dynbuf, 1024); + RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any, + dns_rdatatype_sig /* SIG(0) */, &sig, + dynbuf)); + + isc_mem_put(mctx, sig.signature, sig.siglen); + + dns_message_takebuffer(msg, &dynbuf); + + datalist = NULL; + RETERR(dns_message_gettemprdatalist(msg, &datalist)); + datalist->rdclass = dns_rdataclass_any; + datalist->type = dns_rdatatype_sig; /* SIG(0) */ + ISC_LIST_APPEND(datalist->rdata, rdata, link); + dataset = NULL; + RETERR(dns_message_gettemprdataset(msg, &dataset)); + RUNTIME_CHECK(dns_rdatalist_tordataset(datalist, dataset) == + ISC_R_SUCCESS); + msg->sig0 = dataset; + + return (ISC_R_SUCCESS); + +failure: + if (dynbuf != NULL) { + isc_buffer_free(&dynbuf); + } + if (sig.signature != NULL) { + isc_mem_put(mctx, sig.signature, sig.siglen); + } + if (ctx != NULL) { + dst_context_destroy(&ctx); + } + + return (result); +} + +isc_result_t +dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg, + dst_key_t *key) { + dns_rdata_sig_t sig; /* SIG(0) */ + unsigned char header[DNS_MESSAGE_HEADERLEN]; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r, source_r, sig_r, header_r; + isc_stdtime_t now; + dst_context_t *ctx = NULL; + isc_mem_t *mctx; + isc_result_t result; + uint16_t addcount, addcount_n; + bool signeedsfree = false; + + REQUIRE(source != NULL); + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + + mctx = msg->mctx; + + msg->verify_attempted = 1; + msg->verified_sig = 0; + msg->sig0status = dns_tsigerror_badsig; + + if (is_response(msg)) { + if (msg->query.base == NULL) { + return (DNS_R_UNEXPECTEDTSIG); + } + } + + isc_buffer_usedregion(source, &source_r); + + RETERR(dns_rdataset_first(msg->sig0)); + dns_rdataset_current(msg->sig0, &rdata); + + RETERR(dns_rdata_tostruct(&rdata, &sig, NULL)); + signeedsfree = true; + + if (sig.labels != 0) { + result = DNS_R_SIGINVALID; + goto failure; + } + + if (isc_serial_lt(sig.timeexpire, sig.timesigned)) { + result = DNS_R_SIGINVALID; + msg->sig0status = dns_tsigerror_badtime; + goto failure; + } + + if (msg->fuzzing) { + now = msg->fuzztime; + } else { + isc_stdtime_get(&now); + } + + if (isc_serial_lt((uint32_t)now, sig.timesigned)) { + result = DNS_R_SIGFUTURE; + msg->sig0status = dns_tsigerror_badtime; + goto failure; + } else if (isc_serial_lt(sig.timeexpire, (uint32_t)now)) { + result = DNS_R_SIGEXPIRED; + msg->sig0status = dns_tsigerror_badtime; + goto failure; + } + + if (!dns_name_equal(dst_key_name(key), &sig.signer)) { + result = DNS_R_SIGINVALID; + msg->sig0status = dns_tsigerror_badkey; + goto failure; + } + + RETERR(dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false, 0, + &ctx)); + + /* + * Digest the SIG(0) record, except for the signature. + */ + dns_rdata_toregion(&rdata, &r); + r.length -= sig.siglen; + RETERR(dst_context_adddata(ctx, &r)); + + /* + * If this is a response, digest the query. + */ + if (is_response(msg)) { + RETERR(dst_context_adddata(ctx, &msg->query)); + } + + /* + * Extract the header. + */ + memmove(header, source_r.base, DNS_MESSAGE_HEADERLEN); + + /* + * Decrement the additional field counter. + */ + memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2); + addcount_n = ntohs(addcount); + addcount = htons((uint16_t)(addcount_n - 1)); + memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2); + + /* + * Digest the modified header. + */ + header_r.base = (unsigned char *)header; + header_r.length = DNS_MESSAGE_HEADERLEN; + RETERR(dst_context_adddata(ctx, &header_r)); + + /* + * Digest all non-SIG(0) records. + */ + r.base = source_r.base + DNS_MESSAGE_HEADERLEN; + r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN; + RETERR(dst_context_adddata(ctx, &r)); + + sig_r.base = sig.signature; + sig_r.length = sig.siglen; + result = dst_context_verify(ctx, &sig_r); + if (result != ISC_R_SUCCESS) { + msg->sig0status = dns_tsigerror_badsig; + goto failure; + } + + msg->verified_sig = 1; + msg->sig0status = dns_rcode_noerror; + + dst_context_destroy(&ctx); + dns_rdata_freestruct(&sig); + + return (ISC_R_SUCCESS); + +failure: + if (signeedsfree) { + dns_rdata_freestruct(&sig); + } + if (ctx != NULL) { + dst_context_destroy(&ctx); + } + + return (result); +} + +/*% + * Does this key ('rdata') self sign the rrset ('rdataset')? + */ +bool +dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + bool ignoretime, isc_mem_t *mctx) { + INSIST(rdataset->type == dns_rdatatype_key || + rdataset->type == dns_rdatatype_dnskey); + if (rdataset->type == dns_rdatatype_key) { + INSIST(sigrdataset->type == dns_rdatatype_sig); + INSIST(sigrdataset->covers == dns_rdatatype_key); + } else { + INSIST(sigrdataset->type == dns_rdatatype_rrsig); + INSIST(sigrdataset->covers == dns_rdatatype_dnskey); + } + + return (dns_dnssec_signs(rdata, name, rdataset, sigrdataset, ignoretime, + mctx)); +} + +bool +dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + bool ignoretime, isc_mem_t *mctx) { + dst_key_t *dstkey = NULL; + dns_keytag_t keytag; + dns_rdata_dnskey_t key; + dns_rdata_rrsig_t sig; + dns_rdata_t sigrdata = DNS_RDATA_INIT; + isc_result_t result; + + INSIST(sigrdataset->type == dns_rdatatype_rrsig); + if (sigrdataset->covers != rdataset->type) { + return (false); + } + + result = dns_dnssec_keyfromrdata(name, rdata, mctx, &dstkey); + if (result != ISC_R_SUCCESS) { + return (false); + } + result = dns_rdata_tostruct(rdata, &key, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + keytag = dst_key_id(dstkey); + for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigrdataset)) + { + dns_rdata_reset(&sigrdata); + dns_rdataset_current(sigrdataset, &sigrdata); + result = dns_rdata_tostruct(&sigrdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (sig.algorithm == key.algorithm && sig.keyid == keytag) { + result = dns_dnssec_verify(name, rdataset, dstkey, + ignoretime, 0, mctx, + &sigrdata, NULL); + if (result == ISC_R_SUCCESS) { + dst_key_free(&dstkey); + return (true); + } + } + } + dst_key_free(&dstkey); + return (false); +} + +isc_result_t +dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, + dns_dnsseckey_t **dkp) { + isc_result_t result; + dns_dnsseckey_t *dk; + int major, minor; + + REQUIRE(dkp != NULL && *dkp == NULL); + dk = isc_mem_get(mctx, sizeof(dns_dnsseckey_t)); + + dk->key = *dstkey; + *dstkey = NULL; + dk->force_publish = false; + dk->force_sign = false; + dk->hint_publish = false; + dk->hint_sign = false; + dk->hint_revoke = false; + dk->hint_remove = false; + dk->first_sign = false; + dk->is_active = false; + dk->purge = false; + dk->prepublish = 0; + dk->source = dns_keysource_unknown; + dk->index = 0; + + /* KSK or ZSK? */ + result = dst_key_getbool(dk->key, DST_BOOL_KSK, &dk->ksk); + if (result != ISC_R_SUCCESS) { + dk->ksk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) != 0); + } + result = dst_key_getbool(dk->key, DST_BOOL_ZSK, &dk->zsk); + if (result != ISC_R_SUCCESS) { + dk->zsk = ((dst_key_flags(dk->key) & DNS_KEYFLAG_KSK) == 0); + } + + /* Is this an old-style key? */ + result = dst_key_getprivateformat(dk->key, &major, &minor); + INSIST(result == ISC_R_SUCCESS); + + /* Smart signing started with key format 1.3 */ + dk->legacy = (major == 1 && minor <= 2); + + ISC_LINK_INIT(dk, link); + *dkp = dk; + return (ISC_R_SUCCESS); +} + +void +dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp) { + dns_dnsseckey_t *dk; + + REQUIRE(dkp != NULL && *dkp != NULL); + dk = *dkp; + *dkp = NULL; + if (dk->key != NULL) { + dst_key_free(&dk->key); + } + isc_mem_put(mctx, dk, sizeof(dns_dnsseckey_t)); +} + +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { + isc_stdtime_t publish = 0, active = 0, revoke = 0, remove = 0; + + REQUIRE(key != NULL && key->key != NULL); + + key->hint_publish = dst_key_is_published(key->key, now, &publish); + key->hint_sign = dst_key_is_signing(key->key, DST_BOOL_ZSK, now, + &active); + key->hint_revoke = dst_key_is_revoked(key->key, now, &revoke); + key->hint_remove = dst_key_is_removed(key->key, now, &remove); + + /* + * Activation date is set (maybe in the future), but publication date + * isn't. Most likely the user wants to publish now and activate later. + * Most likely because this is true for most rollovers, except for: + * 1. The unpopular ZSK Double-RRSIG method. + * 2. When introducing a new algorithm. + * These two cases are rare enough that we will set hint_publish + * anyway when hint_sign is set, because BIND 9 natively does not + * support the ZSK Double-RRSIG method, and when introducing a new + * algorithm, we strive to publish its signatures and DNSKEY records + * at the same time. + */ + if (key->hint_sign && publish == 0) { + key->hint_publish = true; + } + + /* + * If activation date is in the future, make note of how far off. + */ + if (key->hint_publish && active > now) { + key->prepublish = active - now; + } + + /* + * Metadata says revoke. If the key is published, we *have to* sign + * with it per RFC5011 -- even if it was not active before. + * + * If it hasn't already been done, we should also revoke it now. + */ + if (key->hint_publish && key->hint_revoke) { + uint32_t flags; + key->hint_sign = true; + flags = dst_key_flags(key->key); + if ((flags & DNS_KEYFLAG_REVOKE) == 0) { + flags |= DNS_KEYFLAG_REVOKE; + dst_key_setflags(key->key, flags); + } + } + + /* + * Metadata says delete, so don't publish this key or sign with it + * (note that signatures of a removed key may still be reused). + */ + if (key->hint_remove) { + key->hint_publish = false; + key->hint_sign = false; + } +} + +/*% + * Get a list of DNSSEC keys from the key repository. + */ +isc_result_t +dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, + isc_stdtime_t now, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist) { + isc_result_t result = ISC_R_SUCCESS; + bool dir_open = false; + dns_dnsseckeylist_t list; + isc_dir_t dir; + dns_dnsseckey_t *key = NULL; + dst_key_t *dstkey = NULL; + char namebuf[DNS_NAME_FORMATSIZE]; + isc_buffer_t b; + unsigned int len, i, alg; + + REQUIRE(keylist != NULL); + ISC_LIST_INIT(list); + isc_dir_init(&dir); + + isc_buffer_init(&b, namebuf, sizeof(namebuf) - 1); + RETERR(dns_name_tofilenametext(origin, false, &b)); + len = isc_buffer_usedlength(&b); + namebuf[len] = '\0'; + + if (directory == NULL) { + directory = "."; + } + RETERR(isc_dir_open(&dir, directory)); + dir_open = true; + + while (isc_dir_read(&dir) == ISC_R_SUCCESS) { + if (dir.entry.name[0] != 'K' || dir.entry.length < len + 1 || + dir.entry.name[len + 1] != '+' || + strncasecmp(dir.entry.name + 1, namebuf, len) != 0) + { + continue; + } + + alg = 0; + for (i = len + 1 + 1; i < dir.entry.length; i++) { + if (!isdigit((unsigned char)dir.entry.name[i])) { + break; + } + alg *= 10; + alg += dir.entry.name[i] - '0'; + } + + /* + * Did we not read exactly 3 digits? + * Did we overflow? + * Did we correctly terminate? + */ + if (i != len + 1 + 1 + 3 || i >= dir.entry.length || + dir.entry.name[i] != '+') + { + continue; + } + + for (i++; i < dir.entry.length; i++) { + if (!isdigit((unsigned char)dir.entry.name[i])) { + break; + } + } + + /* + * Did we not read exactly 5 more digits? + * Did we overflow? + * Did we correctly terminate? + */ + if (i != len + 1 + 1 + 3 + 1 + 5 || i >= dir.entry.length || + strcmp(dir.entry.name + i, ".private") != 0) + { + continue; + } + + dstkey = NULL; + result = dst_key_fromnamedfile( + dir.entry.name, directory, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE, + mctx, &dstkey); + + switch (alg) { + case DST_ALG_HMACMD5: + case DST_ALG_HMACSHA1: + case DST_ALG_HMACSHA224: + case DST_ALG_HMACSHA256: + case DST_ALG_HMACSHA384: + case DST_ALG_HMACSHA512: + case DST_ALG_DH: + if (result == DST_R_BADKEYTYPE) { + continue; + } + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "dns_dnssec_findmatchingkeys: " + "error reading key file %s: %s", + dir.entry.name, + isc_result_totext(result)); + continue; + } + + RETERR(dns_dnsseckey_create(mctx, &dstkey, &key)); + key->source = dns_keysource_repository; + dns_dnssec_get_hints(key, now); + + if (key->legacy) { + dns_dnsseckey_destroy(mctx, &key); + } else { + ISC_LIST_APPEND(list, key, link); + key = NULL; + } + } + + if (!ISC_LIST_EMPTY(list)) { + result = ISC_R_SUCCESS; + ISC_LIST_APPENDLIST(*keylist, list, link); + } else { + result = ISC_R_NOTFOUND; + } + +failure: + if (dir_open) { + isc_dir_close(&dir); + } + INSIST(key == NULL); + while ((key = ISC_LIST_HEAD(list)) != NULL) { + ISC_LIST_UNLINK(list, key, link); + INSIST(key->key != NULL); + dst_key_free(&key->key); + dns_dnsseckey_destroy(mctx, &key); + } + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} + +/*% + * Add 'newkey' to 'keylist' if it's not already there. + * + * If 'savekeys' is true, then we need to preserve all + * the keys in the keyset, regardless of whether they have + * metadata indicating they should be deactivated or removed. + */ +static isc_result_t +addkey(dns_dnsseckeylist_t *keylist, dst_key_t **newkey, bool savekeys, + isc_mem_t *mctx) { + dns_dnsseckey_t *key; + isc_result_t result; + + /* Skip duplicates */ + for (key = ISC_LIST_HEAD(*keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (dst_key_id(key->key) == dst_key_id(*newkey) && + dst_key_alg(key->key) == dst_key_alg(*newkey) && + dns_name_equal(dst_key_name(key->key), + dst_key_name(*newkey))) + { + break; + } + } + + if (key != NULL) { + /* + * Found a match. If the old key was only public and the + * new key is private, replace the old one; otherwise + * leave it. But either way, mark the key as having + * been found in the zone. + */ + if (dst_key_isprivate(key->key)) { + dst_key_free(newkey); + } else if (dst_key_isprivate(*newkey)) { + dst_key_free(&key->key); + key->key = *newkey; + } + + key->source = dns_keysource_zoneapex; + return (ISC_R_SUCCESS); + } + + result = dns_dnsseckey_create(mctx, newkey, &key); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (key->legacy || savekeys) { + key->force_publish = true; + key->force_sign = dst_key_isprivate(key->key); + } + key->source = dns_keysource_zoneapex; + ISC_LIST_APPEND(*keylist, key, link); + *newkey = NULL; + return (ISC_R_SUCCESS); +} + +/*% + * Mark all keys which signed the DNSKEY/SOA RRsets as "active", + * for future reference. + */ +static isc_result_t +mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t sigs; + dns_dnsseckey_t *key; + + REQUIRE(rrsigs != NULL && dns_rdataset_isassociated(rrsigs)); + + dns_rdataset_init(&sigs); + dns_rdataset_clone(rrsigs, &sigs); + for (key = ISC_LIST_HEAD(*keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + uint16_t keyid, sigid; + dns_secalg_t keyalg, sigalg; + keyid = dst_key_id(key->key); + keyalg = dst_key_alg(key->key); + + for (result = dns_rdataset_first(&sigs); + result == ISC_R_SUCCESS; result = dns_rdataset_next(&sigs)) + { + dns_rdata_rrsig_t sig; + + dns_rdata_reset(&rdata); + dns_rdataset_current(&sigs, &rdata); + result = dns_rdata_tostruct(&rdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + sigalg = sig.algorithm; + sigid = sig.keyid; + if (keyid == sigid && keyalg == sigalg) { + key->is_active = true; + break; + } + } + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + if (dns_rdataset_isassociated(&sigs)) { + dns_rdataset_disassociate(&sigs); + } + return (result); +} + +/*% + * Add the contents of a DNSKEY rdataset 'keyset' to 'keylist'. + */ +isc_result_t +dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, + isc_mem_t *mctx, dns_rdataset_t *keyset, + dns_rdataset_t *keysigs, dns_rdataset_t *soasigs, + bool savekeys, bool publickey, + dns_dnsseckeylist_t *keylist) { + dns_rdataset_t keys; + dns_rdata_t rdata = DNS_RDATA_INIT; + dst_key_t *dnskey = NULL, *pubkey = NULL, *privkey = NULL; + isc_result_t result; + + REQUIRE(keyset != NULL && dns_rdataset_isassociated(keyset)); + + dns_rdataset_init(&keys); + + dns_rdataset_clone(keyset, &keys); + for (result = dns_rdataset_first(&keys); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&keys)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(&keys, &rdata); + + REQUIRE(rdata.type == dns_rdatatype_key || + rdata.type == dns_rdatatype_dnskey); + REQUIRE(rdata.length > 3); + + /* Skip unsupported algorithms */ + if (!dst_algorithm_supported(rdata.data[3])) { + goto skip; + } + + RETERR(dns_dnssec_keyfromrdata(origin, &rdata, mctx, &dnskey)); + dst_key_setttl(dnskey, keys.ttl); + + if (!is_zone_key(dnskey) || + (dst_key_flags(dnskey) & DNS_KEYTYPE_NOAUTH) != 0) + { + goto skip; + } + + /* Corrupted .key file? */ + if (!dns_name_equal(origin, dst_key_name(dnskey))) { + goto skip; + } + + if (publickey) { + RETERR(addkey(keylist, &dnskey, savekeys, mctx)); + goto skip; + } + + /* Try to read the public key. */ + result = dst_key_fromfile( + dst_key_name(dnskey), dst_key_id(dnskey), + dst_key_alg(dnskey), (DST_TYPE_PUBLIC | DST_TYPE_STATE), + directory, mctx, &pubkey); + if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { + result = ISC_R_SUCCESS; + } + RETERR(result); + + /* Now read the private key. */ + result = dst_key_fromfile( + dst_key_name(dnskey), dst_key_id(dnskey), + dst_key_alg(dnskey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE), + directory, mctx, &privkey); + + /* + * If the key was revoked and the private file + * doesn't exist, maybe it was revoked internally + * by named. Try loading the unrevoked version. + */ + if (result == ISC_R_FILENOTFOUND) { + uint32_t flags; + flags = dst_key_flags(dnskey); + if ((flags & DNS_KEYFLAG_REVOKE) != 0) { + dst_key_setflags(dnskey, + flags & ~DNS_KEYFLAG_REVOKE); + result = dst_key_fromfile( + dst_key_name(dnskey), + dst_key_id(dnskey), dst_key_alg(dnskey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE), + directory, mctx, &privkey); + if (result == ISC_R_SUCCESS && + dst_key_pubcompare(dnskey, privkey, false)) + { + dst_key_setflags(privkey, flags); + } + dst_key_setflags(dnskey, flags); + } + } + + if (result != ISC_R_SUCCESS) { + char filename[DNS_NAME_FORMATSIZE + + DNS_SECALG_FORMATSIZE + + sizeof("key file for //65535")]; + isc_result_t result2; + isc_buffer_t buf; + + isc_buffer_init(&buf, filename, NAME_MAX); + result2 = dst_key_getfilename( + dst_key_name(dnskey), dst_key_id(dnskey), + dst_key_alg(dnskey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE), + directory, mctx, &buf); + if (result2 != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + char algbuf[DNS_SECALG_FORMATSIZE]; + + dns_name_format(dst_key_name(dnskey), namebuf, + sizeof(namebuf)); + dns_secalg_format(dst_key_alg(dnskey), algbuf, + sizeof(algbuf)); + snprintf(filename, sizeof(filename) - 1, + "key file for %s/%s/%d", namebuf, + algbuf, dst_key_id(dnskey)); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "dns_dnssec_keylistfromrdataset: error " + "reading %s: %s", + filename, isc_result_totext(result)); + } + + if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { + if (pubkey != NULL) { + RETERR(addkey(keylist, &pubkey, savekeys, + mctx)); + } else { + RETERR(addkey(keylist, &dnskey, savekeys, + mctx)); + } + goto skip; + } + RETERR(result); + + /* This should never happen. */ + if ((dst_key_flags(privkey) & DNS_KEYTYPE_NOAUTH) != 0) { + goto skip; + } + + /* + * Whatever the key's default TTL may have + * been, the rdataset TTL takes priority. + */ + dst_key_setttl(privkey, dst_key_getttl(dnskey)); + + RETERR(addkey(keylist, &privkey, savekeys, mctx)); + skip: + if (dnskey != NULL) { + dst_key_free(&dnskey); + } + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (privkey != NULL) { + dst_key_free(&privkey); + } + } + + if (result != ISC_R_NOMORE) { + RETERR(result); + } + + if (keysigs != NULL && dns_rdataset_isassociated(keysigs)) { + RETERR(mark_active_keys(keylist, keysigs)); + } + + if (soasigs != NULL && dns_rdataset_isassociated(soasigs)) { + RETERR(mark_active_keys(keylist, soasigs)); + } + + result = ISC_R_SUCCESS; + +failure: + if (dns_rdataset_isassociated(&keys)) { + dns_rdataset_disassociate(&keys); + } + if (dnskey != NULL) { + dst_key_free(&dnskey); + } + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (privkey != NULL) { + dst_key_free(&privkey); + } + return (result); +} + +static isc_result_t +make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize, + dns_rdata_t *target) { + isc_result_t result; + isc_buffer_t b; + isc_region_t r; + + isc_buffer_init(&b, buf, bufsize); + result = dst_key_todns(key, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_reset(target); + isc_buffer_usedregion(&b, &r); + dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey, + &r); + return (ISC_R_SUCCESS); +} + +static isc_result_t +addrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + + RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_ADD, origin, ttl, rdata, + &tuple)); + dns_diff_appendminimal(diff, &tuple); + +failure: + return (result); +} + +static isc_result_t +delrdata(dns_rdata_t *rdata, dns_diff_t *diff, const dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + + RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_DEL, origin, ttl, rdata, + &tuple)); + dns_diff_appendminimal(diff, &tuple); + +failure: + return (result); +} + +static isc_result_t +publish_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx, + void (*report)(const char *, ...) ISC_FORMAT_PRINTF(1, 2)) { + isc_result_t result; + unsigned char buf[DST_KEY_MAXSIZE]; + char keystr[DST_KEY_FORMATSIZE]; + dns_rdata_t dnskey = DNS_RDATA_INIT; + + dns_rdata_reset(&dnskey); + RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey)); + dst_key_format(key->key, keystr, sizeof(keystr)); + + report("Fetching %s (%s) from key %s.", keystr, + key->ksk ? (key->zsk ? "CSK" : "KSK") : "ZSK", + key->source == dns_keysource_user ? "file" : "repository"); + + if (key->prepublish && ttl > key->prepublish) { + isc_stdtime_t now; + + report("Key %s: Delaying activation to match the DNSKEY TTL " + "(%u).", + keystr, ttl); + + isc_stdtime_get(&now); + dst_key_settime(key->key, DST_TIME_ACTIVATE, now + ttl); + } + + /* publish key */ + result = addrdata(&dnskey, diff, origin, ttl, mctx); + +failure: + return (result); +} + +static isc_result_t +remove_key(dns_diff_t *diff, dns_dnsseckey_t *key, const dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx, const char *reason, + void (*report)(const char *, ...) ISC_FORMAT_PRINTF(1, 2)) { + isc_result_t result; + unsigned char buf[DST_KEY_MAXSIZE]; + dns_rdata_t dnskey = DNS_RDATA_INIT; + char alg[80]; + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_secalg_format(dst_key_alg(key->key), alg, sizeof(alg)); + dns_name_format(dst_key_name(key->key), namebuf, sizeof(namebuf)); + report("Removing %s key %s/%d/%s from DNSKEY RRset.", reason, namebuf, + dst_key_id(key->key), alg); + + RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey)); + result = delrdata(&dnskey, diff, origin, ttl, mctx); + +failure: + return (result); +} + +static bool +exists(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + isc_result_t result; + dns_rdataset_t trdataset; + + dns_rdataset_init(&trdataset); + dns_rdataset_clone(rdataset, &trdataset); + for (result = dns_rdataset_first(&trdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&trdataset)) + { + dns_rdata_t current = DNS_RDATA_INIT; + + dns_rdataset_current(&trdataset, ¤t); + if (dns_rdata_compare(rdata, ¤t) == 0) { + dns_rdataset_disassociate(&trdataset); + return (true); + } + } + dns_rdataset_disassociate(&trdataset); + return (false); +} + +isc_result_t +dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, + dns_rdataset_t *cds, dns_rdataset_t *cdnskey, + isc_stdtime_t now, dns_ttl_t ttl, dns_diff_t *diff, + isc_mem_t *mctx) { + unsigned char dsbuf1[DNS_DS_BUFFERSIZE]; + unsigned char dsbuf2[DNS_DS_BUFFERSIZE]; + unsigned char keybuf[DST_KEY_MAXSIZE]; + isc_result_t result; + dns_dnsseckey_t *key; + + for (key = ISC_LIST_HEAD(*keys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + dns_rdata_t cds_sha1 = DNS_RDATA_INIT; + dns_rdata_t cds_sha256 = DNS_RDATA_INIT; + dns_rdata_t cdnskeyrdata = DNS_RDATA_INIT; + dns_name_t *origin = dst_key_name(key->key); + + RETERR(make_dnskey(key->key, keybuf, sizeof(keybuf), + &cdnskeyrdata)); + + /* + * We construct the SHA-1 version of the record so we can + * delete any old records generated by previous versions of + * BIND. We only add SHA-256 records. + * + * XXXMPA we need to be able to specify the DS algorithms + * to be used here and below with rmkeys. + */ + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA1, dsbuf1, &cds_sha1)); + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA256, dsbuf2, + &cds_sha256)); + + /* + * Now that the we have created the DS records convert + * the rdata to CDNSKEY and CDS for comparison. + */ + cdnskeyrdata.type = dns_rdatatype_cdnskey; + cds_sha1.type = dns_rdatatype_cds; + cds_sha256.type = dns_rdatatype_cds; + + if (syncpublish(key->key, now)) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + + if (!dns_rdataset_isassociated(cdnskey) || + !exists(cdnskey, &cdnskeyrdata)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "CDS for key %s is now published", + keystr); + RETERR(addrdata(&cdnskeyrdata, diff, origin, + ttl, mctx)); + } + /* Only publish SHA-256 (SHA-1 is deprecated) */ + if (!dns_rdataset_isassociated(cds) || + !exists(cds, &cds_sha256)) + { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDNSKEY for key %s is now published", + keystr); + RETERR(addrdata(&cds_sha256, diff, origin, ttl, + mctx)); + } + } + + if (syncdelete(key->key, now)) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + + if (dns_rdataset_isassociated(cds)) { + /* Delete both SHA-1 and SHA-256 */ + if (exists(cds, &cds_sha1)) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "CDS (SHA-1) for key %s " + "is now deleted", + keystr); + RETERR(delrdata(&cds_sha1, diff, origin, + cds->ttl, mctx)); + } + if (exists(cds, &cds_sha256)) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "CDS (SHA-256) for key " + "%s is now deleted", + keystr); + RETERR(delrdata(&cds_sha256, diff, + origin, cds->ttl, + mctx)); + } + } + + if (dns_rdataset_isassociated(cdnskey)) { + if (exists(cdnskey, &cdnskeyrdata)) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "CDNSKEY for key %s is " + "now deleted", + keystr); + RETERR(delrdata(&cdnskeyrdata, diff, + origin, cdnskey->ttl, + mctx)); + } + } + } + } + + if (!dns_rdataset_isassociated(cds) && + !dns_rdataset_isassociated(cdnskey)) + { + return (ISC_R_SUCCESS); + } + + /* + * Unconditionally remove CDS/DNSKEY records for removed keys. + */ + for (key = ISC_LIST_HEAD(*rmkeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + dns_rdata_t cds_sha1 = DNS_RDATA_INIT; + dns_rdata_t cds_sha256 = DNS_RDATA_INIT; + dns_rdata_t cdnskeyrdata = DNS_RDATA_INIT; + dns_name_t *origin = dst_key_name(key->key); + + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + + RETERR(make_dnskey(key->key, keybuf, sizeof(keybuf), + &cdnskeyrdata)); + + if (dns_rdataset_isassociated(cds)) { + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA1, dsbuf1, + &cds_sha1)); + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA256, dsbuf2, + &cds_sha256)); + if (exists(cds, &cds_sha1)) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDS (SHA-1) for key %s is now deleted", + keystr); + RETERR(delrdata(&cds_sha1, diff, origin, + cds->ttl, mctx)); + } + if (exists(cds, &cds_sha256)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "CDS (SHA-256) for key %s is now " + "deleted", + keystr); + RETERR(delrdata(&cds_sha256, diff, origin, + cds->ttl, mctx)); + } + } + + if (dns_rdataset_isassociated(cdnskey)) { + if (exists(cdnskey, &cdnskeyrdata)) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDNSKEY for key %s is now deleted", + keystr); + RETERR(delrdata(&cdnskeyrdata, diff, origin, + cdnskey->ttl, mctx)); + } + } + } + + result = ISC_R_SUCCESS; + +failure: + return (result); +} + +isc_result_t +dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, + dns_name_t *origin, dns_rdataclass_t zclass, + dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx, + bool expect_cds_delete, bool expect_cdnskey_delete) { + unsigned char dsbuf[5] = { 0, 0, 0, 0, 0 }; /* CDS DELETE rdata */ + unsigned char keybuf[5] = { 0, 0, 3, 0, 0 }; /* CDNSKEY DELETE rdata */ + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rdata_t cds_delete = DNS_RDATA_INIT; + dns_rdata_t cdnskey_delete = DNS_RDATA_INIT; + isc_region_t r; + isc_result_t result; + + r.base = keybuf; + r.length = sizeof(keybuf); + dns_rdata_fromregion(&cdnskey_delete, zclass, dns_rdatatype_cdnskey, + &r); + + r.base = dsbuf; + r.length = sizeof(dsbuf); + dns_rdata_fromregion(&cds_delete, zclass, dns_rdatatype_cds, &r); + + dns_name_format(origin, namebuf, sizeof(namebuf)); + + if (expect_cds_delete) { + if (!dns_rdataset_isassociated(cds) || + !exists(cds, &cds_delete)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDS (DELETE) for zone %s is now " + "published", + namebuf); + RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx)); + } + } else { + if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDS (DELETE) for zone %s is now " + "deleted", + namebuf); + RETERR(delrdata(&cds_delete, diff, origin, cds->ttl, + mctx)); + } + } + + if (expect_cdnskey_delete) { + if (!dns_rdataset_isassociated(cdnskey) || + !exists(cdnskey, &cdnskey_delete)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDNSKEY (DELETE) for zone %s is now " + "published", + namebuf); + RETERR(addrdata(&cdnskey_delete, diff, origin, ttl, + mctx)); + } + } else { + if (dns_rdataset_isassociated(cdnskey) && + exists(cdnskey, &cdnskey_delete)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDNSKEY (DELETE) for zone %s is now " + "deleted", + namebuf); + RETERR(delrdata(&cdnskey_delete, diff, origin, + cdnskey->ttl, mctx)); + } + } + + result = ISC_R_SUCCESS; + +failure: + return (result); +} + +/* + * Update 'keys' with information from 'newkeys'. + * + * If 'removed' is not NULL, any keys that are being removed from + * the zone will be added to the list for post-removal processing. + */ +isc_result_t +dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, + dns_dnsseckeylist_t *removed, const dns_name_t *origin, + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, + void (*report)(const char *, ...) + ISC_FORMAT_PRINTF(1, 2)) { + isc_result_t result; + dns_dnsseckey_t *key, *key1, *key2, *next; + bool found_ttl = false; + dns_ttl_t ttl = hint_ttl; + + /* + * First, look through the existing key list to find keys + * supplied from the command line which are not in the zone. + * Update the zone to include them. + * + * Also, if there are keys published in the zone already, + * use their TTL for all subsequent published keys. + */ + for (key = ISC_LIST_HEAD(*keys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (key->source == dns_keysource_user && + (key->hint_publish || key->force_publish)) + { + RETERR(publish_key(diff, key, origin, ttl, mctx, + report)); + } + if (key->source == dns_keysource_zoneapex) { + ttl = dst_key_getttl(key->key); + found_ttl = true; + } + } + + /* + * If there were no existing keys, use the smallest nonzero + * TTL of the keys found in the repository. + */ + if (!found_ttl && !ISC_LIST_EMPTY(*newkeys)) { + dns_ttl_t shortest = 0; + + for (key = ISC_LIST_HEAD(*newkeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + dns_ttl_t thisttl = dst_key_getttl(key->key); + if (thisttl != 0 && + (shortest == 0 || thisttl < shortest)) + { + shortest = thisttl; + } + } + + if (shortest != 0) { + ttl = shortest; + } + } + + /* + * Second, scan the list of newly found keys looking for matches + * with known keys, and update accordingly. + */ + for (key1 = ISC_LIST_HEAD(*newkeys); key1 != NULL; key1 = next) { + bool key_revoked = false; + char keystr1[DST_KEY_FORMATSIZE]; + char keystr2[DST_KEY_FORMATSIZE]; + + next = ISC_LIST_NEXT(key1, link); + + for (key2 = ISC_LIST_HEAD(*keys); key2 != NULL; + key2 = ISC_LIST_NEXT(key2, link)) + { + int f1 = dst_key_flags(key1->key); + int f2 = dst_key_flags(key2->key); + int nr1 = f1 & ~DNS_KEYFLAG_REVOKE; + int nr2 = f2 & ~DNS_KEYFLAG_REVOKE; + if (nr1 == nr2 && + dst_key_alg(key1->key) == dst_key_alg(key2->key) && + dst_key_pubcompare(key1->key, key2->key, true)) + { + int r1, r2; + r1 = dst_key_flags(key1->key) & + DNS_KEYFLAG_REVOKE; + r2 = dst_key_flags(key2->key) & + DNS_KEYFLAG_REVOKE; + key_revoked = (r1 != r2); + break; + } + } + + /* Printable version of key1 (the newly acquired key) */ + dst_key_format(key1->key, keystr1, sizeof(keystr1)); + + /* No match found in keys; add the new key. */ + if (key2 == NULL) { + ISC_LIST_UNLINK(*newkeys, key1, link); + ISC_LIST_APPEND(*keys, key1, link); + + if (key1->source != dns_keysource_zoneapex && + (key1->hint_publish || key1->force_publish)) + { + RETERR(publish_key(diff, key1, origin, ttl, + mctx, report)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "DNSKEY %s (%s) is now published", + keystr1, + key1->ksk ? (key1->zsk ? "CSK" : "KSK") + : "ZSK"); + if (key1->hint_sign || key1->force_sign) { + key1->first_sign = true; + isc_log_write( + dns_lctx, + DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, + "DNSKEY %s (%s) is now " + "active", + keystr1, + key1->ksk ? (key1->zsk ? "CSK" + : "KSK") + : "ZSK"); + } + } + + continue; + } + + /* Printable version of key2 (the old key, if any) */ + dst_key_format(key2->key, keystr2, sizeof(keystr2)); + + /* Copy key metadata. */ + dst_key_copy_metadata(key2->key, key1->key); + + /* Match found: remove or update it as needed */ + if (key1->hint_remove) { + RETERR(remove_key(diff, key2, origin, ttl, mctx, + "expired", report)); + ISC_LIST_UNLINK(*keys, key2, link); + + if (removed != NULL) { + ISC_LIST_APPEND(*removed, key2, link); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "DNSKEY %s (%s) is now deleted", + keystr2, + key2->ksk ? (key2->zsk ? "CSK" : "KSK") + : "ZSK"); + } else { + dns_dnsseckey_destroy(mctx, &key2); + } + } else if (key_revoked && + (dst_key_flags(key1->key) & DNS_KEYFLAG_REVOKE) != 0) + { + /* + * A previously valid key has been revoked. + * We need to remove the old version and pull + * in the new one. + */ + RETERR(remove_key(diff, key2, origin, ttl, mctx, + "revoked", report)); + ISC_LIST_UNLINK(*keys, key2, link); + if (removed != NULL) { + ISC_LIST_APPEND(*removed, key2, link); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "DNSKEY %s (%s) is now revoked; " + "new ID is %05d", + keystr2, + key2->ksk ? (key2->zsk ? "CSK" : "KSK") + : "ZSK", + dst_key_id(key1->key)); + } else { + dns_dnsseckey_destroy(mctx, &key2); + } + + RETERR(publish_key(diff, key1, origin, ttl, mctx, + report)); + ISC_LIST_UNLINK(*newkeys, key1, link); + ISC_LIST_APPEND(*keys, key1, link); + + /* + * XXX: The revoke flag is only defined for trust + * anchors. Setting the flag on a non-KSK is legal, + * but not defined in any RFC. It seems reasonable + * to treat it the same as a KSK: keep it in the + * zone, sign the DNSKEY set with it, but not + * sign other records with it. + */ + key1->ksk = true; + continue; + } else { + if (!key2->is_active && + (key1->hint_sign || key1->force_sign)) + { + key2->first_sign = true; + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "DNSKEY %s (%s) is now active", keystr1, + key1->ksk ? (key1->zsk ? "CSK" : "KSK") + : "ZSK"); + } else if (key2->is_active && !key1->hint_sign && + !key1->force_sign) + { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "DNSKEY %s (%s) is now inactive", + keystr1, + key1->ksk ? (key1->zsk ? "CSK" : "KSK") + : "ZSK"); + } + + key2->hint_sign = key1->hint_sign; + key2->hint_publish = key1->hint_publish; + } + } + + /* Free any leftover keys in newkeys */ + while (!ISC_LIST_EMPTY(*newkeys)) { + key1 = ISC_LIST_HEAD(*newkeys); + ISC_LIST_UNLINK(*newkeys, key1, link); + dns_dnsseckey_destroy(mctx, &key1); + } + + result = ISC_R_SUCCESS; + +failure: + return (result); +} + +isc_result_t +dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata, + dns_rdataset_t *keyset, dns_rdata_t *keyrdata) { + isc_result_t result; + unsigned char buf[DNS_DS_BUFFERSIZE]; + dns_keytag_t keytag; + dns_rdata_dnskey_t key; + dns_rdata_ds_t ds; + isc_region_t r; + + result = dns_rdata_tostruct(dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + for (result = dns_rdataset_first(keyset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(keyset)) + { + dns_rdata_t newdsrdata = DNS_RDATA_INIT; + + dns_rdata_reset(keyrdata); + dns_rdataset_current(keyset, keyrdata); + + result = dns_rdata_tostruct(keyrdata, &key, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdata_toregion(keyrdata, &r); + keytag = dst_region_computeid(&r); + + if (ds.key_tag != keytag || ds.algorithm != key.algorithm) { + continue; + } + + result = dns_ds_buildrdata(name, keyrdata, ds.digest_type, buf, + &newdsrdata); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (dns_rdata_compare(dsrdata, &newdsrdata) == 0) { + break; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_NOTFOUND; + } + + return (result); +} diff --git a/lib/dns/dnstap.c b/lib/dns/dnstap.c new file mode 100644 index 0000000..28b88b7 --- /dev/null +++ b/lib/dns/dnstap.c @@ -0,0 +1,1386 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (c) 2013-2014, Farsight Security, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*! \file */ + +#ifndef HAVE_DNSTAP +#error DNSTAP not configured. +#endif /* HAVE_DNSTAP */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dnstap.pb-c.h" + +#define DTENV_MAGIC ISC_MAGIC('D', 't', 'n', 'v') +#define VALID_DTENV(env) ISC_MAGIC_VALID(env, DTENV_MAGIC) + +#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap" +#define DNSTAP_INITIAL_BUF_SIZE 256 + +struct dns_dtmsg { + void *buf; + size_t len; + Dnstap__Dnstap d; + Dnstap__Message m; +}; + +struct dns_dthandle { + dns_dtmode_t mode; + struct fstrm_reader *reader; + isc_mem_t *mctx; +}; + +struct dns_dtenv { + unsigned int magic; + isc_refcount_t refcount; + + isc_mem_t *mctx; + + struct fstrm_iothr *iothr; + struct fstrm_iothr_options *fopt; + + isc_task_t *reopen_task; + isc_mutex_t reopen_lock; /* locks 'reopen_queued' + * */ + bool reopen_queued; + + isc_region_t identity; + isc_region_t version; + char *path; + dns_dtmode_t mode; + isc_offset_t max_size; + int rolls; + isc_log_rollsuffix_t suffix; + isc_stats_t *stats; +}; + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +typedef struct ioq { + unsigned int generation; + struct fstrm_iothr_queue *ioq; +} dt__ioq_t; + +static thread_local dt__ioq_t dt_ioq = { 0 }; + +static atomic_uint_fast32_t global_generation; + +isc_result_t +dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path, + struct fstrm_iothr_options **foptp, isc_task_t *reopen_task, + dns_dtenv_t **envp) { + isc_result_t result = ISC_R_SUCCESS; + fstrm_res res; + struct fstrm_unix_writer_options *fuwopt = NULL; + struct fstrm_file_options *ffwopt = NULL; + struct fstrm_writer_options *fwopt = NULL; + struct fstrm_writer *fw = NULL; + dns_dtenv_t *env = NULL; + + REQUIRE(path != NULL); + REQUIRE(envp != NULL && *envp == NULL); + REQUIRE(foptp != NULL && *foptp != NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, + ISC_LOG_INFO, "opening dnstap destination '%s'", path); + + atomic_fetch_add_release(&global_generation, 1); + + env = isc_mem_get(mctx, sizeof(dns_dtenv_t)); + + memset(env, 0, sizeof(dns_dtenv_t)); + isc_mem_attach(mctx, &env->mctx); + env->reopen_task = reopen_task; + isc_mutex_init(&env->reopen_lock); + env->reopen_queued = false; + env->path = isc_mem_strdup(env->mctx, path); + isc_refcount_init(&env->refcount, 1); + CHECK(isc_stats_create(env->mctx, &env->stats, dns_dnstapcounter_max)); + + fwopt = fstrm_writer_options_init(); + if (fwopt == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + res = fstrm_writer_options_add_content_type( + fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1); + if (res != fstrm_res_success) { + CHECK(ISC_R_FAILURE); + } + + if (mode == dns_dtmode_file) { + ffwopt = fstrm_file_options_init(); + if (ffwopt != NULL) { + fstrm_file_options_set_file_path(ffwopt, env->path); + fw = fstrm_file_writer_init(ffwopt, fwopt); + } + } else if (mode == dns_dtmode_unix) { + fuwopt = fstrm_unix_writer_options_init(); + if (fuwopt != NULL) { + fstrm_unix_writer_options_set_socket_path(fuwopt, + env->path); + fw = fstrm_unix_writer_init(fuwopt, fwopt); + } + } else { + CHECK(ISC_R_FAILURE); + } + + if (fw == NULL) { + CHECK(ISC_R_FAILURE); + } + + env->iothr = fstrm_iothr_init(*foptp, &fw); + if (env->iothr == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, + DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING, + "unable to initialize dnstap I/O thread"); + fstrm_writer_destroy(&fw); + CHECK(ISC_R_FAILURE); + } + env->mode = mode; + env->max_size = 0; + env->rolls = ISC_LOG_ROLLINFINITE; + env->fopt = *foptp; + *foptp = NULL; + + env->magic = DTENV_MAGIC; + *envp = env; + +cleanup: + if (ffwopt != NULL) { + fstrm_file_options_destroy(&ffwopt); + } + + if (fuwopt != NULL) { + fstrm_unix_writer_options_destroy(&fuwopt); + } + + if (fwopt != NULL) { + fstrm_writer_options_destroy(&fwopt); + } + + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&env->reopen_lock); + isc_mem_free(env->mctx, env->path); + if (env->stats != NULL) { + isc_stats_detach(&env->stats); + } + isc_mem_putanddetach(&env->mctx, env, sizeof(dns_dtenv_t)); + } + + return (result); +} + +isc_result_t +dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls, + isc_log_rollsuffix_t suffix) { + REQUIRE(VALID_DTENV(env)); + + /* + * If we're using unix domain socket mode, then any + * change from the default values is invalid. + */ + if (env->mode == dns_dtmode_unix) { + if (max_size == 0 && rolls == ISC_LOG_ROLLINFINITE && + suffix == isc_log_rollsuffix_increment) + { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_INVALIDFILE); + } + } + + env->max_size = max_size; + env->rolls = rolls; + env->suffix = suffix; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dt_reopen(dns_dtenv_t *env, int roll) { + isc_result_t result = ISC_R_SUCCESS; + fstrm_res res; + isc_logfile_t file; + struct fstrm_unix_writer_options *fuwopt = NULL; + struct fstrm_file_options *ffwopt = NULL; + struct fstrm_writer_options *fwopt = NULL; + struct fstrm_writer *fw = NULL; + + REQUIRE(VALID_DTENV(env)); + + /* + * Run in task-exclusive mode. + */ + result = isc_task_beginexclusive(env->reopen_task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * Check that we can create a new fw object. + */ + fwopt = fstrm_writer_options_init(); + if (fwopt == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + res = fstrm_writer_options_add_content_type( + fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1); + if (res != fstrm_res_success) { + CHECK(ISC_R_FAILURE); + } + + if (env->mode == dns_dtmode_file) { + ffwopt = fstrm_file_options_init(); + if (ffwopt != NULL) { + fstrm_file_options_set_file_path(ffwopt, env->path); + fw = fstrm_file_writer_init(ffwopt, fwopt); + } + } else if (env->mode == dns_dtmode_unix) { + fuwopt = fstrm_unix_writer_options_init(); + if (fuwopt != NULL) { + fstrm_unix_writer_options_set_socket_path(fuwopt, + env->path); + fw = fstrm_unix_writer_init(fuwopt, fwopt); + } + } else { + CHECK(ISC_R_NOTIMPLEMENTED); + } + + if (fw == NULL) { + CHECK(ISC_R_FAILURE); + } + + /* + * We are committed here. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, + ISC_LOG_INFO, "%s dnstap destination '%s'", + (roll < 0) ? "reopening" : "rolling", env->path); + + atomic_fetch_add_release(&global_generation, 1); + + if (env->iothr != NULL) { + fstrm_iothr_destroy(&env->iothr); + } + + if (roll == 0) { + roll = env->rolls; + } + + if (env->mode == dns_dtmode_file && roll != 0) { + /* + * Create a temporary isc_logfile_t structure so we can + * take advantage of the logfile rolling facility. + */ + char *filename = isc_mem_strdup(env->mctx, env->path); + file.name = filename; + file.stream = NULL; + file.versions = roll; + file.maximum_size = 0; + file.maximum_reached = false; + file.suffix = env->suffix; + result = isc_logfile_roll(&file); + isc_mem_free(env->mctx, filename); + CHECK(result); + } + + env->iothr = fstrm_iothr_init(env->fopt, &fw); + if (env->iothr == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, + DNS_LOGMODULE_DNSTAP, ISC_LOG_WARNING, + "unable to initialize dnstap I/O thread"); + CHECK(ISC_R_FAILURE); + } + +cleanup: + if (fw != NULL) { + fstrm_writer_destroy(&fw); + } + + if (fuwopt != NULL) { + fstrm_unix_writer_options_destroy(&fuwopt); + } + + if (ffwopt != NULL) { + fstrm_file_options_destroy(&ffwopt); + } + + if (fwopt != NULL) { + fstrm_writer_options_destroy(&fwopt); + } + + isc_task_endexclusive(env->reopen_task); + + return (result); +} + +static isc_result_t +toregion(dns_dtenv_t *env, isc_region_t *r, const char *str) { + unsigned char *p = NULL; + + REQUIRE(r != NULL); + + if (str != NULL) { + p = (unsigned char *)isc_mem_strdup(env->mctx, str); + } + + if (r->base != NULL) { + isc_mem_free(env->mctx, r->base); + r->length = 0; + } + + if (p != NULL) { + r->base = p; + r->length = strlen((char *)p); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dt_setidentity(dns_dtenv_t *env, const char *identity) { + REQUIRE(VALID_DTENV(env)); + + return (toregion(env, &env->identity, identity)); +} + +isc_result_t +dns_dt_setversion(dns_dtenv_t *env, const char *version) { + REQUIRE(VALID_DTENV(env)); + + return (toregion(env, &env->version, version)); +} + +static void +set_dt_ioq(unsigned int generation, struct fstrm_iothr_queue *ioq) { + dt_ioq.generation = generation; + dt_ioq.ioq = ioq; +} + +static struct fstrm_iothr_queue * +dt_queue(dns_dtenv_t *env) { + REQUIRE(VALID_DTENV(env)); + + unsigned int generation; + + if (env->iothr == NULL) { + return (NULL); + } + + generation = atomic_load_acquire(&global_generation); + if (dt_ioq.ioq != NULL && dt_ioq.generation != generation) { + set_dt_ioq(0, NULL); + } + if (dt_ioq.ioq == NULL) { + struct fstrm_iothr_queue *ioq = + fstrm_iothr_get_input_queue(env->iothr); + set_dt_ioq(generation, ioq); + } + + return (dt_ioq.ioq); +} + +void +dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp) { + REQUIRE(VALID_DTENV(source)); + REQUIRE(destp != NULL && *destp == NULL); + + isc_refcount_increment(&source->refcount); + *destp = source; +} + +isc_result_t +dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp) { + REQUIRE(VALID_DTENV(env)); + REQUIRE(statsp != NULL && *statsp == NULL); + + if (env->stats == NULL) { + return (ISC_R_NOTFOUND); + } + isc_stats_attach(env->stats, statsp); + return (ISC_R_SUCCESS); +} + +static void +destroy(dns_dtenv_t *env) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, DNS_LOGMODULE_DNSTAP, + ISC_LOG_INFO, "closing dnstap"); + env->magic = 0; + + atomic_fetch_add(&global_generation, 1); + + if (env->iothr != NULL) { + fstrm_iothr_destroy(&env->iothr); + } + if (env->fopt != NULL) { + fstrm_iothr_options_destroy(&env->fopt); + } + + if (env->identity.base != NULL) { + isc_mem_free(env->mctx, env->identity.base); + env->identity.length = 0; + } + if (env->version.base != NULL) { + isc_mem_free(env->mctx, env->version.base); + env->version.length = 0; + } + if (env->path != NULL) { + isc_mem_free(env->mctx, env->path); + } + if (env->stats != NULL) { + isc_stats_detach(&env->stats); + } + + isc_mem_putanddetach(&env->mctx, env, sizeof(*env)); +} + +void +dns_dt_detach(dns_dtenv_t **envp) { + REQUIRE(envp != NULL && VALID_DTENV(*envp)); + dns_dtenv_t *env = *envp; + *envp = NULL; + + if (isc_refcount_decrement(&env->refcount) == 1) { + isc_refcount_destroy(&env->refcount); + destroy(env); + } +} + +static isc_result_t +pack_dt(const Dnstap__Dnstap *d, void **buf, size_t *sz) { + ProtobufCBufferSimple sbuf; + + REQUIRE(d != NULL); + REQUIRE(sz != NULL); + + memset(&sbuf, 0, sizeof(sbuf)); + sbuf.base.append = protobuf_c_buffer_simple_append; + sbuf.len = 0; + sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE; + + /* Need to use malloc() here because protobuf uses free() */ + sbuf.data = malloc(sbuf.alloced); + if (sbuf.data == NULL) { + return (ISC_R_NOMEMORY); + } + sbuf.must_free_data = 1; + + *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *)&sbuf); + if (sbuf.data == NULL) { + return (ISC_R_FAILURE); + } + *buf = sbuf.data; + + return (ISC_R_SUCCESS); +} + +static void +send_dt(dns_dtenv_t *env, void *buf, size_t len) { + struct fstrm_iothr_queue *ioq; + fstrm_res res; + + REQUIRE(env != NULL); + + if (buf == NULL) { + return; + } + + ioq = dt_queue(env); + if (ioq == NULL) { + free(buf); + return; + } + + res = fstrm_iothr_submit(env->iothr, ioq, buf, len, fstrm_free_wrapper, + NULL); + if (res != fstrm_res_success) { + if (env->stats != NULL) { + isc_stats_increment(env->stats, dns_dnstapcounter_drop); + } + free(buf); + } else { + if (env->stats != NULL) { + isc_stats_increment(env->stats, + dns_dnstapcounter_success); + } + } +} + +static void +init_msg(dns_dtenv_t *env, dns_dtmsg_t *dm, Dnstap__Message__Type mtype) { + memset(dm, 0, sizeof(*dm)); + dm->d.base.descriptor = &dnstap__dnstap__descriptor; + dm->m.base.descriptor = &dnstap__message__descriptor; + dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE; + dm->d.message = &dm->m; + dm->m.type = mtype; + + if (env->identity.length != 0) { + dm->d.identity.data = env->identity.base; + dm->d.identity.len = env->identity.length; + dm->d.has_identity = true; + } + + if (env->version.length != 0) { + dm->d.version.data = env->version.base; + dm->d.version.len = env->version.length; + dm->d.has_version = true; + } +} + +static Dnstap__Message__Type +dnstap_type(dns_dtmsgtype_t msgtype) { + switch (msgtype) { + case DNS_DTTYPE_SQ: + return (DNSTAP__MESSAGE__TYPE__STUB_QUERY); + case DNS_DTTYPE_SR: + return (DNSTAP__MESSAGE__TYPE__STUB_RESPONSE); + case DNS_DTTYPE_CQ: + return (DNSTAP__MESSAGE__TYPE__CLIENT_QUERY); + case DNS_DTTYPE_CR: + return (DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE); + case DNS_DTTYPE_AQ: + return (DNSTAP__MESSAGE__TYPE__AUTH_QUERY); + case DNS_DTTYPE_AR: + return (DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE); + case DNS_DTTYPE_RQ: + return (DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY); + case DNS_DTTYPE_RR: + return (DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE); + case DNS_DTTYPE_FQ: + return (DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY); + case DNS_DTTYPE_FR: + return (DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE); + case DNS_DTTYPE_TQ: + return (DNSTAP__MESSAGE__TYPE__TOOL_QUERY); + case DNS_DTTYPE_TR: + return (DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE); + case DNS_DTTYPE_UQ: + return (DNSTAP__MESSAGE__TYPE__UPDATE_QUERY); + case DNS_DTTYPE_UR: + return (DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE); + default: + UNREACHABLE(); + } +} + +static void +cpbuf(isc_buffer_t *buf, ProtobufCBinaryData *p, protobuf_c_boolean *has) { + p->data = isc_buffer_base(buf); + p->len = isc_buffer_usedlength(buf); + *has = 1; +} + +static void +setaddr(dns_dtmsg_t *dm, isc_sockaddr_t *sa, bool tcp, + ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr, uint32_t *port, + protobuf_c_boolean *has_port) { + int family = isc_sockaddr_pf(sa); + + if (family != AF_INET6 && family != AF_INET) { + return; + } + + if (family == AF_INET6) { + dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6; + addr->data = sa->type.sin6.sin6_addr.s6_addr; + addr->len = 16; + *port = ntohs(sa->type.sin6.sin6_port); + } else { + dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET; + addr->data = (uint8_t *)&sa->type.sin.sin_addr.s_addr; + addr->len = 4; + *port = ntohs(sa->type.sin.sin_port); + } + + if (tcp) { + dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP; + } else { + dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP; + } + + dm->m.has_socket_protocol = 1; + dm->m.has_socket_family = 1; + *has_addr = 1; + *has_port = 1; +} + +/*% + * Invoke dns_dt_reopen() and re-allow dnstap output file rolling. This + * function is run in the context of the task stored in the 'reopen_task' field + * of the dnstap environment structure. + */ +static void +perform_reopen(isc_task_t *task, isc_event_t *event) { + dns_dtenv_t *env; + + REQUIRE(event != NULL); + REQUIRE(event->ev_type == DNS_EVENT_FREESTORAGE); + + env = (dns_dtenv_t *)event->ev_arg; + + REQUIRE(VALID_DTENV(env)); + REQUIRE(task == env->reopen_task); + + /* + * Roll output file in the context of env->reopen_task. + */ + dns_dt_reopen(env, env->rolls); + + /* + * Clean up. + */ + isc_event_free(&event); + isc_task_detach(&task); + + /* + * Re-allow output file rolling. + */ + LOCK(&env->reopen_lock); + env->reopen_queued = false; + UNLOCK(&env->reopen_lock); +} + +/*% + * Check whether a dnstap output file roll is due and if so, initiate it (the + * actual roll happens asynchronously). + */ +static void +check_file_size_and_maybe_reopen(dns_dtenv_t *env) { + isc_task_t *reopen_task = NULL; + isc_event_t *event; + struct stat statbuf; + + /* + * If the task from which the output file should be reopened was not + * specified, abort. + */ + if (env->reopen_task == NULL) { + return; + } + + /* + * If an output file roll is not currently queued, check the current + * size of the output file to see whether a roll is needed. Return if + * it is not. + */ + LOCK(&env->reopen_lock); + if (env->reopen_queued || stat(env->path, &statbuf) < 0 || + statbuf.st_size <= env->max_size) + { + goto unlock_and_return; + } + + /* + * We need to roll the output file, but it needs to be done in the + * context of env->reopen_task. Allocate and send an event to achieve + * that, then disallow output file rolling until the roll we queue is + * completed. + */ + event = isc_event_allocate(env->mctx, NULL, DNS_EVENT_FREESTORAGE, + perform_reopen, env, sizeof(*event)); + isc_task_attach(env->reopen_task, &reopen_task); + isc_task_send(reopen_task, &event); + env->reopen_queued = true; + +unlock_and_return: + UNLOCK(&env->reopen_lock); +} + +void +dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr, + isc_sockaddr_t *raddr, bool tcp, isc_region_t *zone, + isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf) { + isc_time_t now, *t; + dns_dtmsg_t dm; + + REQUIRE(DNS_VIEW_VALID(view)); + + if ((msgtype & view->dttypes) == 0) { + return; + } + + if (view->dtenv == NULL) { + return; + } + + REQUIRE(VALID_DTENV(view->dtenv)); + + if (view->dtenv->max_size != 0) { + check_file_size_and_maybe_reopen(view->dtenv); + } + + TIME_NOW(&now); + t = &now; + + init_msg(view->dtenv, &dm, dnstap_type(msgtype)); + + /* Query/response times */ + switch (msgtype) { + case DNS_DTTYPE_AR: + case DNS_DTTYPE_CR: + case DNS_DTTYPE_RR: + case DNS_DTTYPE_FR: + case DNS_DTTYPE_SR: + case DNS_DTTYPE_TR: + case DNS_DTTYPE_UR: + if (rtime != NULL) { + t = rtime; + } + + dm.m.response_time_sec = isc_time_seconds(t); + dm.m.has_response_time_sec = 1; + dm.m.response_time_nsec = isc_time_nanoseconds(t); + dm.m.has_response_time_nsec = 1; + + /* + * Types RR and FR can fall through and get the query + * time set as well. Any other response type, break. + */ + if (msgtype != DNS_DTTYPE_RR && msgtype != DNS_DTTYPE_FR) { + break; + } + + FALLTHROUGH; + case DNS_DTTYPE_AQ: + case DNS_DTTYPE_CQ: + case DNS_DTTYPE_FQ: + case DNS_DTTYPE_RQ: + case DNS_DTTYPE_SQ: + case DNS_DTTYPE_TQ: + case DNS_DTTYPE_UQ: + if (qtime != NULL) { + t = qtime; + } + + dm.m.query_time_sec = isc_time_seconds(t); + dm.m.has_query_time_sec = 1; + dm.m.query_time_nsec = isc_time_nanoseconds(t); + dm.m.has_query_time_nsec = 1; + break; + default: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, + DNS_LOGMODULE_DNSTAP, ISC_LOG_ERROR, + "invalid dnstap message type %d", msgtype); + return; + } + + /* Query and response messages */ + if ((msgtype & DNS_DTTYPE_QUERY) != 0) { + cpbuf(buf, &dm.m.query_message, &dm.m.has_query_message); + } else if ((msgtype & DNS_DTTYPE_RESPONSE) != 0) { + cpbuf(buf, &dm.m.response_message, &dm.m.has_response_message); + } + + /* Zone/bailiwick */ + switch (msgtype) { + case DNS_DTTYPE_AR: + case DNS_DTTYPE_RQ: + case DNS_DTTYPE_RR: + case DNS_DTTYPE_FQ: + case DNS_DTTYPE_FR: + if (zone != NULL && zone->base != NULL && zone->length != 0) { + dm.m.query_zone.data = zone->base; + dm.m.query_zone.len = zone->length; + dm.m.has_query_zone = 1; + } + break; + default: + break; + } + + if (qaddr != NULL) { + setaddr(&dm, qaddr, tcp, &dm.m.query_address, + &dm.m.has_query_address, &dm.m.query_port, + &dm.m.has_query_port); + } + if (raddr != NULL) { + setaddr(&dm, raddr, tcp, &dm.m.response_address, + &dm.m.has_response_address, &dm.m.response_port, + &dm.m.has_response_port); + } + + if (pack_dt(&dm.d, &dm.buf, &dm.len) == ISC_R_SUCCESS) { + send_dt(view->dtenv, dm.buf, dm.len); + } +} + +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + isc_result_t result; + + result = isc_buffer_reserve(b, strlen(str)); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putstr(*b, str); + return (ISC_R_SUCCESS); +} + +static isc_result_t +putaddr(isc_buffer_t **b, isc_region_t *ip) { + char buf[64]; + + if (ip->length == 4) { + if (!inet_ntop(AF_INET, ip->base, buf, sizeof(buf))) { + return (ISC_R_FAILURE); + } + } else if (ip->length == 16) { + if (!inet_ntop(AF_INET6, ip->base, buf, sizeof(buf))) { + return (ISC_R_FAILURE); + } + } else { + return (ISC_R_BADADDRESSFORM); + } + + return (putstr(b, buf)); +} + +static bool +dnstap_file(struct fstrm_reader *r) { + fstrm_res res; + const struct fstrm_control *control = NULL; + const uint8_t *rtype = NULL; + size_t dlen = strlen(DNSTAP_CONTENT_TYPE), rlen = 0; + size_t n = 0; + + res = fstrm_reader_get_control(r, FSTRM_CONTROL_START, &control); + if (res != fstrm_res_success) { + return (false); + } + + res = fstrm_control_get_num_field_content_type(control, &n); + if (res != fstrm_res_success) { + return (false); + } + if (n > 0) { + res = fstrm_control_get_field_content_type(control, 0, &rtype, + &rlen); + if (res != fstrm_res_success) { + return (false); + } + + if (rlen != dlen) { + return (false); + } + + if (memcmp(DNSTAP_CONTENT_TYPE, rtype, dlen) == 0) { + return (true); + } + } + + return (false); +} + +isc_result_t +dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx, + dns_dthandle_t **handlep) { + isc_result_t result; + struct fstrm_file_options *fopt = NULL; + fstrm_res res; + dns_dthandle_t *handle; + + REQUIRE(handlep != NULL && *handlep == NULL); + + handle = isc_mem_get(mctx, sizeof(*handle)); + + handle->mode = mode; + handle->mctx = NULL; + + switch (mode) { + case dns_dtmode_file: + fopt = fstrm_file_options_init(); + if (fopt == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + fstrm_file_options_set_file_path(fopt, filename); + + handle->reader = fstrm_file_reader_init(fopt, NULL); + if (handle->reader == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + res = fstrm_reader_open(handle->reader); + if (res != fstrm_res_success) { + CHECK(ISC_R_FAILURE); + } + + if (!dnstap_file(handle->reader)) { + CHECK(DNS_R_BADDNSTAP); + } + break; + case dns_dtmode_unix: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + default: + UNREACHABLE(); + } + + isc_mem_attach(mctx, &handle->mctx); + result = ISC_R_SUCCESS; + *handlep = handle; + handle = NULL; + +cleanup: + if (result != ISC_R_SUCCESS && handle->reader != NULL) { + fstrm_reader_destroy(&handle->reader); + handle->reader = NULL; + } + if (fopt != NULL) { + fstrm_file_options_destroy(&fopt); + } + if (handle != NULL) { + isc_mem_put(mctx, handle, sizeof(*handle)); + } + return (result); +} + +isc_result_t +dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep) { + const uint8_t *data; + fstrm_res res; + + REQUIRE(handle != NULL); + REQUIRE(bufp != NULL); + REQUIRE(sizep != NULL); + + data = (const uint8_t *)*bufp; + + res = fstrm_reader_read(handle->reader, &data, sizep); + switch (res) { + case fstrm_res_success: + if (data == NULL) { + return (ISC_R_FAILURE); + } + DE_CONST(data, *bufp); + return (ISC_R_SUCCESS); + case fstrm_res_stop: + return (ISC_R_NOMORE); + default: + return (ISC_R_FAILURE); + } +} + +void +dns_dt_close(dns_dthandle_t **handlep) { + dns_dthandle_t *handle; + + REQUIRE(handlep != NULL && *handlep != NULL); + + handle = *handlep; + *handlep = NULL; + + if (handle->reader != NULL) { + fstrm_reader_destroy(&handle->reader); + handle->reader = NULL; + } + isc_mem_putanddetach(&handle->mctx, handle, sizeof(*handle)); +} + +isc_result_t +dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp) { + isc_result_t result; + Dnstap__Dnstap *frame; + Dnstap__Message *m; + dns_dtdata_t *d = NULL; + isc_buffer_t b; + + REQUIRE(src != NULL); + REQUIRE(destp != NULL && *destp == NULL); + + d = isc_mem_get(mctx, sizeof(*d)); + + memset(d, 0, sizeof(*d)); + isc_mem_attach(mctx, &d->mctx); + + d->frame = dnstap__dnstap__unpack(NULL, src->length, src->base); + if (d->frame == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + frame = (Dnstap__Dnstap *)d->frame; + + if (frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) { + CHECK(DNS_R_BADDNSTAP); + } + + m = frame->message; + + /* Message type */ + switch (m->type) { + case DNSTAP__MESSAGE__TYPE__AUTH_QUERY: + d->type = DNS_DTTYPE_AQ; + break; + case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE: + d->type = DNS_DTTYPE_AR; + break; + case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY: + d->type = DNS_DTTYPE_CQ; + break; + case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE: + d->type = DNS_DTTYPE_CR; + break; + case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY: + d->type = DNS_DTTYPE_FQ; + break; + case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE: + d->type = DNS_DTTYPE_FR; + break; + case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY: + d->type = DNS_DTTYPE_RQ; + break; + case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE: + d->type = DNS_DTTYPE_RR; + break; + case DNSTAP__MESSAGE__TYPE__STUB_QUERY: + d->type = DNS_DTTYPE_SQ; + break; + case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE: + d->type = DNS_DTTYPE_SR; + break; + case DNSTAP__MESSAGE__TYPE__TOOL_QUERY: + d->type = DNS_DTTYPE_TQ; + break; + case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE: + d->type = DNS_DTTYPE_TR; + break; + case DNSTAP__MESSAGE__TYPE__UPDATE_QUERY: + d->type = DNS_DTTYPE_UQ; + break; + case DNSTAP__MESSAGE__TYPE__UPDATE_RESPONSE: + d->type = DNS_DTTYPE_UR; + break; + default: + CHECK(DNS_R_BADDNSTAP); + } + + /* Query? */ + if ((d->type & DNS_DTTYPE_QUERY) != 0) { + d->query = true; + } else { + d->query = false; + } + + /* Parse DNS message */ + if (d->query && m->has_query_message) { + d->msgdata.base = m->query_message.data; + d->msgdata.length = m->query_message.len; + } else if (!d->query && m->has_response_message) { + d->msgdata.base = m->response_message.data; + d->msgdata.length = m->response_message.len; + } + + isc_buffer_init(&b, d->msgdata.base, d->msgdata.length); + isc_buffer_add(&b, d->msgdata.length); + dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &d->msg); + result = dns_message_parse(d->msg, &b, 0); + if (result != ISC_R_SUCCESS) { + if (result != DNS_R_RECOVERABLE) { + dns_message_detach(&d->msg); + } + result = ISC_R_SUCCESS; + } + + /* Timestamp */ + if (d->query) { + if (m->has_query_time_sec && m->has_query_time_nsec) { + isc_time_set(&d->qtime, m->query_time_sec, + m->query_time_nsec); + } + } else { + if (m->has_response_time_sec && m->has_response_time_nsec) { + isc_time_set(&d->rtime, m->response_time_sec, + m->response_time_nsec); + } + } + + /* Peer address */ + if (m->has_query_address) { + d->qaddr.base = m->query_address.data; + d->qaddr.length = m->query_address.len; + } + if (m->has_query_port) { + d->qport = m->query_port; + } + + if (m->has_response_address) { + d->raddr.base = m->response_address.data; + d->raddr.length = m->response_address.len; + } + if (m->has_response_port) { + d->rport = m->response_port; + } + + /* Socket protocol */ + if (m->has_socket_protocol) { + const ProtobufCEnumValue *type = + protobuf_c_enum_descriptor_get_value( + &dnstap__socket_protocol__descriptor, + m->socket_protocol); + if (type != NULL && type->value == DNSTAP__SOCKET_PROTOCOL__TCP) + { + d->tcp = true; + } else { + d->tcp = false; + } + } + + /* Query tuple */ + if (d->msg != NULL) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + + CHECK(dns_message_firstname(d->msg, DNS_SECTION_QUESTION)); + dns_message_currentname(d->msg, DNS_SECTION_QUESTION, &name); + rdataset = ISC_LIST_HEAD(name->list); + + dns_name_format(name, d->namebuf, sizeof(d->namebuf)); + dns_rdatatype_format(rdataset->type, d->typebuf, + sizeof(d->typebuf)); + dns_rdataclass_format(rdataset->rdclass, d->classbuf, + sizeof(d->classbuf)); + } + + *destp = d; + +cleanup: + if (result != ISC_R_SUCCESS) { + dns_dtdata_free(&d); + } + + return (result); +} + +isc_result_t +dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest) { + isc_result_t result; + char buf[100]; + + REQUIRE(d != NULL); + REQUIRE(dest != NULL && *dest != NULL); + + memset(buf, 0, sizeof(buf)); + + /* Timestamp */ + if (d->query && !isc_time_isepoch(&d->qtime)) { + isc_time_formattimestamp(&d->qtime, buf, sizeof(buf)); + } else if (!d->query && !isc_time_isepoch(&d->rtime)) { + isc_time_formattimestamp(&d->rtime, buf, sizeof(buf)); + } + + if (buf[0] == '\0') { + CHECK(putstr(dest, "???\?-?\?-?? ??:??:??.??? ")); + } else { + CHECK(putstr(dest, buf)); + CHECK(putstr(dest, " ")); + } + + /* Type mnemonic */ + switch (d->type) { + case DNS_DTTYPE_AQ: + CHECK(putstr(dest, "AQ ")); + break; + case DNS_DTTYPE_AR: + CHECK(putstr(dest, "AR ")); + break; + case DNS_DTTYPE_CQ: + CHECK(putstr(dest, "CQ ")); + break; + case DNS_DTTYPE_CR: + CHECK(putstr(dest, "CR ")); + break; + case DNS_DTTYPE_FQ: + CHECK(putstr(dest, "FQ ")); + break; + case DNS_DTTYPE_FR: + CHECK(putstr(dest, "FR ")); + break; + case DNS_DTTYPE_RQ: + CHECK(putstr(dest, "RQ ")); + break; + case DNS_DTTYPE_RR: + CHECK(putstr(dest, "RR ")); + break; + case DNS_DTTYPE_SQ: + CHECK(putstr(dest, "SQ ")); + break; + case DNS_DTTYPE_SR: + CHECK(putstr(dest, "SR ")); + break; + case DNS_DTTYPE_TQ: + CHECK(putstr(dest, "TQ ")); + break; + case DNS_DTTYPE_TR: + CHECK(putstr(dest, "TR ")); + break; + case DNS_DTTYPE_UQ: + CHECK(putstr(dest, "UQ ")); + break; + case DNS_DTTYPE_UR: + CHECK(putstr(dest, "UR ")); + break; + default: + return (DNS_R_BADDNSTAP); + } + + /* Query and response addresses */ + if (d->qaddr.length != 0) { + CHECK(putaddr(dest, &d->qaddr)); + snprintf(buf, sizeof(buf), ":%u", d->qport); + CHECK(putstr(dest, buf)); + } else { + CHECK(putstr(dest, "?")); + } + if ((d->type & DNS_DTTYPE_QUERY) != 0) { + CHECK(putstr(dest, " -> ")); + } else { + CHECK(putstr(dest, " <- ")); + } + if (d->raddr.length != 0) { + CHECK(putaddr(dest, &d->raddr)); + snprintf(buf, sizeof(buf), ":%u", d->rport); + CHECK(putstr(dest, buf)); + } else { + CHECK(putstr(dest, "?")); + } + + CHECK(putstr(dest, " ")); + + /* Protocol */ + if (d->tcp) { + CHECK(putstr(dest, "TCP ")); + } else { + CHECK(putstr(dest, "UDP ")); + } + + /* Message size */ + if (d->msgdata.base != NULL) { + snprintf(buf, sizeof(buf), "%zub ", (size_t)d->msgdata.length); + CHECK(putstr(dest, buf)); + } else { + CHECK(putstr(dest, "0b ")); + } + + /* Query tuple */ + if (d->namebuf[0] == '\0') { + CHECK(putstr(dest, "?/")); + } else { + CHECK(putstr(dest, d->namebuf)); + CHECK(putstr(dest, "/")); + } + + if (d->classbuf[0] == '\0') { + CHECK(putstr(dest, "?/")); + } else { + CHECK(putstr(dest, d->classbuf)); + CHECK(putstr(dest, "/")); + } + + if (d->typebuf[0] == '\0') { + CHECK(putstr(dest, "?")); + } else { + CHECK(putstr(dest, d->typebuf)); + } + + CHECK(isc_buffer_reserve(dest, 1)); + isc_buffer_putuint8(*dest, 0); + +cleanup: + return (result); +} + +void +dns_dtdata_free(dns_dtdata_t **dp) { + dns_dtdata_t *d; + + REQUIRE(dp != NULL && *dp != NULL); + + d = *dp; + *dp = NULL; + + if (d->msg != NULL) { + dns_message_detach(&d->msg); + } + if (d->frame != NULL) { + dnstap__dnstap__free_unpacked(d->frame, NULL); + } + + isc_mem_putanddetach(&d->mctx, d, sizeof(*d)); +} diff --git a/lib/dns/dnstap.proto b/lib/dns/dnstap.proto new file mode 100644 index 0000000..9d0ac41 --- /dev/null +++ b/lib/dns/dnstap.proto @@ -0,0 +1,289 @@ +// Copyright (C) Internet Systems Consortium, Inc. ("ISC") +// +// SPDX-License-Identifier: MPL-2.0 +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// See the COPYRIGHT file distributed with this work for additional +// information regarding copyright ownership. + +// dnstap: flexible, structured event replication format for DNS software +// +// This file contains the protobuf schemas for the "dnstap" structured event +// replication format for DNS software. + +// Written in 2013-2014 by Farsight Security, Inc. +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this file to the public +// domain worldwide. This file is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this file. If not, see: +// +// . + +package dnstap; + +// "Dnstap": this is the top-level dnstap type, which is a "union" type that +// contains other kinds of dnstap payloads, although currently only one type +// of dnstap payload is defined. +// See: https://developers.google.com/protocol-buffers/docs/techniques#union +message Dnstap { + // DNS server identity. + // If enabled, this is the identity string of the DNS server which generated + // this message. Typically this would be the same string as returned by an + // "NSID" (RFC 5001) query. + optional bytes identity = 1; + + // DNS server version. + // If enabled, this is the version string of the DNS server which generated + // this message. Typically this would be the same string as returned by a + // "version.bind" query. + optional bytes version = 2; + + // Extra data for this payload. + // This field can be used for adding an arbitrary byte-string annotation to + // the payload. No encoding or interpretation is applied or enforced. + optional bytes extra = 3; + + // Identifies which field below is filled in. + enum Type { + MESSAGE = 1; + } + required Type type = 15; + + // One of the following will be filled in. + optional Message message = 14; +} + +// SocketFamily: the network protocol family of a socket. This specifies how +// to interpret "network address" fields. +enum SocketFamily { + INET = 1; // IPv4 (RFC 791) + INET6 = 2; // IPv6 (RFC 2460) +} + +// SocketProtocol: the transport protocol of a socket. This specifies how to +// interpret "transport port" fields. +enum SocketProtocol { + UDP = 1; // User Datagram Protocol (RFC 768) + TCP = 2; // Transmission Control Protocol (RFC 793) +} + +// Message: a wire-format (RFC 1035 section 4) DNS message and associated +// metadata. Applications generating "Message" payloads should follow +// certain requirements based on the MessageType, see below. +message Message { + + // There are eight types of "Message" defined that correspond to the + // four arrows in the following diagram, slightly modified from RFC 1035 + // section 2: + + // +---------+ +----------+ +--------+ + // | | query | | query | | + // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. | + // | Resolver| | Server | | Name | + // | |<-SR--------CR-| |<-RR----AR-| Server | + // +---------+ response | | response | | + // +----------+ +--------+ + + // Each arrow has two Type values each, one for each "end" of each arrow, + // because these are considered to be distinct events. Each end of each + // arrow on the diagram above has been marked with a two-letter Type + // mnemonic. Clockwise from upper left, these mnemonic values are: + // + // SQ: STUB_QUERY + // CQ: CLIENT_QUERY + // RQ: RESOLVER_QUERY + // AQ: AUTH_QUERY + // AR: AUTH_RESPONSE + // RR: RESOLVER_RESPONSE + // CR: CLIENT_RESPONSE + // SR: STUB_RESPONSE + + // Two additional types of "Message" have been defined for the + // "forwarding" case where an upstream DNS server is responsible for + // further recursion. These are not shown on the diagram above, but have + // the following mnemonic values: + + // FQ: FORWARDER_QUERY + // FR: FORWARDER_RESPONSE + + // The "Message" Type values are defined below. + + enum Type { + // AUTH_QUERY is a DNS query message received from a resolver by an + // authoritative name server, from the perspective of the authoritative + // name server. + AUTH_QUERY = 1; + + // AUTH_RESPONSE is a DNS response message sent from an authoritative + // name server to a resolver, from the perspective of the authoritative + // name server. + AUTH_RESPONSE = 2; + + // RESOLVER_QUERY is a DNS query message sent from a resolver to an + // authoritative name server, from the perspective of the resolver. + // Resolvers typically clear the RD (recursion desired) bit when + // sending queries. + RESOLVER_QUERY = 3; + + // RESOLVER_RESPONSE is a DNS response message received from an + // authoritative name server by a resolver, from the perspective of + // the resolver. + RESOLVER_RESPONSE = 4; + + // CLIENT_QUERY is a DNS query message sent from a client to a DNS + // server which is expected to perform further recursion, from the + // perspective of the DNS server. The client may be a stub resolver or + // forwarder or some other type of software which typically sets the RD + // (recursion desired) bit when querying the DNS server. The DNS server + // may be a simple forwarding proxy or it may be a full recursive + // resolver. + CLIENT_QUERY = 5; + + // CLIENT_RESPONSE is a DNS response message sent from a DNS server to + // a client, from the perspective of the DNS server. The DNS server + // typically sets the RA (recursion available) bit when responding. + CLIENT_RESPONSE = 6; + + // FORWARDER_QUERY is a DNS query message sent from a downstream DNS + // server to an upstream DNS server which is expected to perform + // further recursion, from the perspective of the downstream DNS + // server. + FORWARDER_QUERY = 7; + + // FORWARDER_RESPONSE is a DNS response message sent from an upstream + // DNS server performing recursion to a downstream DNS server, from the + // perspective of the downstream DNS server. + FORWARDER_RESPONSE = 8; + + // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS + // server, from the perspective of the stub resolver. + STUB_QUERY = 9; + + // STUB_RESPONSE is a DNS response message sent from a DNS server to a + // stub resolver, from the perspective of the stub resolver. + STUB_RESPONSE = 10; + + // TOOL_QUERY is a DNS query message sent from a DNS software tool to a + // DNS server, from the perspective of the tool. + TOOL_QUERY = 11; + + // TOOL_RESPONSE is a DNS response message received by a DNS software + // tool from a DNS server, from the perspective of the tool. + TOOL_RESPONSE = 12; + + // UPDATE_QUERY is a DNS update query message received from a resolver + // by an authoritative name server, from the perspective of the + // authoritative name server. + UPDATE_QUERY = 13; + + // UPDATE_RESPONSE is a DNS update response message sent from an + // authoritative name server to a resolver, from the perspective of the + // authoritative name server. + UPDATE_RESPONSE = 14; + } + + // One of the Type values described above. + required Type type = 1; + + // One of the SocketFamily values described above. + optional SocketFamily socket_family = 2; + + // One of the SocketProtocol values described above. + optional SocketProtocol socket_protocol = 3; + + // The network address of the message initiator. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes query_address = 4; + + // The network address of the message responder. + // For SocketFamily INET, this field is 4 octets (IPv4 address). + // For SocketFamily INET6, this field is 16 octets (IPv6 address). + optional bytes response_address = 5; + + // The transport port of the message initiator. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 query_port = 6; + + // The transport port of the message responder. + // This is a 16-bit UDP or TCP port number, depending on SocketProtocol. + optional uint32 response_port = 7; + + // The time at which the DNS query message was sent or received, depending + // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY. + // This is the number of seconds since the UNIX epoch. + optional uint64 query_time_sec = 8; + + // The time at which the DNS query message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 query_time_nsec = 9; + + // The initiator's original wire-format DNS query message, verbatim. + optional bytes query_message = 10; + + // The "zone" or "bailiwick" pertaining to the DNS query message. + // This is a wire-format DNS domain name. + optional bytes query_zone = 11; + + // The time at which the DNS response message was sent or received, + // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or + // CLIENT_RESPONSE. + // This is the number of seconds since the UNIX epoch. + optional uint64 response_time_sec = 12; + + // The time at which the DNS response message was sent or received. + // This is the seconds fraction, expressed as a count of nanoseconds. + optional fixed32 response_time_nsec = 13; + + // The responder's original wire-format DNS response message, verbatim. + optional bytes response_message = 14; +} + +// All fields except for 'type' in the Message schema are optional. +// It is recommended that at least the following fields be filled in for +// particular types of Messages. + +// AUTH_QUERY: +// socket_family, socket_protocol +// query_address, query_port +// query_message +// query_time_sec, query_time_nsec + +// AUTH_RESPONSE: +// socket_family, socket_protocol +// query_address, query_port +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec + +// RESOLVER_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port + +// RESOLVER_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// query_zone +// response_address, response_port +// response_message +// response_time_sec, response_time_nsec + +// CLIENT_QUERY: +// socket_family, socket_protocol +// query_message +// query_time_sec, query_time_nsec + +// CLIENT_RESPONSE: +// socket_family, socket_protocol +// query_time_sec, query_time_nsec +// response_message +// response_time_sec, response_time_nsec diff --git a/lib/dns/ds.c b/lib/dns/ds.c new file mode 100644 index 0000000..22f158b --- /dev/null +++ b/lib/dns/ds.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +isc_result_t +dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, + dns_dsdigest_t digest_type, unsigned char *digest, + dns_rdata_ds_t *dsrdata) { + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + unsigned int digestlen; + isc_region_t r; + isc_md_t *md; + const isc_md_type_t *md_type = NULL; + + REQUIRE(key != NULL); + REQUIRE(key->type == dns_rdatatype_dnskey || + key->type == dns_rdatatype_cdnskey); + + if (!dst_ds_digest_supported(digest_type)) { + return (ISC_R_NOTIMPLEMENTED); + } + + switch (digest_type) { + case DNS_DSDIGEST_SHA1: + md_type = ISC_MD_SHA1; + break; + + case DNS_DSDIGEST_SHA384: + md_type = ISC_MD_SHA384; + break; + + case DNS_DSDIGEST_SHA256: + md_type = ISC_MD_SHA256; + break; + + default: + UNREACHABLE(); + } + + name = dns_fixedname_initname(&fname); + (void)dns_name_downcase(owner, name, NULL); + + md = isc_md_new(); + if (md == NULL) { + return (ISC_R_NOMEMORY); + } + + result = isc_md_init(md, md_type); + if (result != ISC_R_SUCCESS) { + goto end; + } + + dns_name_toregion(name, &r); + + result = isc_md_update(md, r.base, r.length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + dns_rdata_toregion(key, &r); + INSIST(r.length >= 4); + + result = isc_md_update(md, r.base, r.length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_final(md, digest, &digestlen); + if (result != ISC_R_SUCCESS) { + goto end; + } + + dsrdata->mctx = NULL; + dsrdata->common.rdclass = key->rdclass; + dsrdata->common.rdtype = dns_rdatatype_ds; + dsrdata->algorithm = r.base[3]; + dsrdata->key_tag = dst_region_computeid(&r); + dsrdata->digest_type = digest_type; + dsrdata->digest = digest; + dsrdata->length = digestlen; + +end: + isc_md_free(md); + return (result); +} + +isc_result_t +dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key, + dns_dsdigest_t digest_type, unsigned char *buffer, + dns_rdata_t *rdata) { + isc_result_t result; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + isc_buffer_t b; + + result = dns_ds_fromkeyrdata(owner, key, digest_type, digest, &ds); + if (result != ISC_R_SUCCESS) { + return (result); + } + + memset(buffer, 0, DNS_DS_BUFFERSIZE); + isc_buffer_init(&b, buffer, DNS_DS_BUFFERSIZE); + result = dns_rdata_fromstruct(rdata, key->rdclass, dns_rdatatype_ds, + &ds, &b); + return (result); +} diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c new file mode 100644 index 0000000..f04ae2f --- /dev/null +++ b/lib/dns/dst_api.c @@ -0,0 +1,2831 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DST_KEY_INTERNAL + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "dst_internal.h" + +#define DST_AS_STR(t) ((t).value.as_textregion.base) + +#define NEXTTOKEN(lex, opt, token) \ + { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } + +#define NEXTTOKEN_OR_EOF(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type == isc_tokentype_eol); + +#define READLINE(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } while ((*token).type != isc_tokentype_eol) + +#define BADTOKEN() \ + { \ + ret = ISC_R_UNEXPECTEDTOKEN; \ + goto cleanup; \ + } + +#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) +static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", + "Lifetime:", "DSPubCount:", "DSRemCount:" +}; + +#define BOOLEAN_NTAGS (DST_MAX_BOOLEAN + 1) +static const char *booleantags[BOOLEAN_NTAGS] = { "KSK:", "ZSK:" }; + +#define TIMING_NTAGS (DST_MAX_TIMES + 1) +static const char *timingtags[TIMING_NTAGS] = { + "Generated:", "Published:", "Active:", "Revoked:", + "Retired:", "Removed:", + + "DSPublish:", "SyncPublish:", "SyncDelete:", + + "DNSKEYChange:", "ZRRSIGChange:", "KRRSIGChange:", "DSChange:", + + "DSRemoved:" +}; + +#define KEYSTATES_NTAGS (DST_MAX_KEYSTATES + 1) +static const char *keystatestags[KEYSTATES_NTAGS] = { + "DNSKEYState:", "ZRRSIGState:", "KRRSIGState:", "DSState:", "GoalState:" +}; + +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", + "rumoured", + "omnipresent", + "unretentive", +}; + +#define STATE_ALGORITHM_STR "Algorithm:" +#define STATE_LENGTH_STR "Length:" +#define MAX_NTAGS \ + (DST_MAX_NUMERIC + DST_MAX_BOOLEAN + DST_MAX_TIMES + DST_MAX_KEYSTATES) + +static dst_func_t *dst_t_func[DST_MAX_ALGS]; + +static bool dst_initialized = false; + +void +gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); + +/* + * Static functions. + */ +static dst_key_t * +get_key_struct(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, unsigned int bits, + dns_rdataclass_t rdclass, dns_ttl_t ttl, isc_mem_t *mctx); +static isc_result_t +write_public_key(const dst_key_t *key, int type, const char *directory); +static isc_result_t +write_key_state(const dst_key_t *key, int type, const char *directory); +static isc_result_t +buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, + unsigned int type, const char *directory, isc_buffer_t *out); +static isc_result_t +computeid(dst_key_t *key); +static isc_result_t +frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); + +static isc_result_t +algorithm_status(unsigned int alg); + +static isc_result_t +addsuffix(char *filename, int len, const char *dirname, const char *ofilename, + const char *suffix); + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto out; \ + } while (0) + +#define CHECKALG(alg) \ + do { \ + isc_result_t _r; \ + _r = algorithm_status(alg); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0); + +isc_result_t +dst_lib_init(isc_mem_t *mctx, const char *engine) { + isc_result_t result; + + REQUIRE(mctx != NULL); + REQUIRE(!dst_initialized); + + UNUSED(engine); + + memset(dst_t_func, 0, sizeof(dst_t_func)); + RETERR(dst__hmacmd5_init(&dst_t_func[DST_ALG_HMACMD5])); + RETERR(dst__hmacsha1_init(&dst_t_func[DST_ALG_HMACSHA1])); + RETERR(dst__hmacsha224_init(&dst_t_func[DST_ALG_HMACSHA224])); + RETERR(dst__hmacsha256_init(&dst_t_func[DST_ALG_HMACSHA256])); + RETERR(dst__hmacsha384_init(&dst_t_func[DST_ALG_HMACSHA384])); + RETERR(dst__hmacsha512_init(&dst_t_func[DST_ALG_HMACSHA512])); + RETERR(dst__openssl_init(engine)); + RETERR(dst__openssldh_init(&dst_t_func[DST_ALG_DH])); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA1], + DST_ALG_RSASHA1)); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1], + DST_ALG_NSEC3RSASHA1)); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA256], + DST_ALG_RSASHA256)); + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA512], + DST_ALG_RSASHA512)); + RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA256])); + RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA384])); +#ifdef HAVE_OPENSSL_ED25519 + RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED25519])); +#endif /* ifdef HAVE_OPENSSL_ED25519 */ +#ifdef HAVE_OPENSSL_ED448 + RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED448])); +#endif /* ifdef HAVE_OPENSSL_ED448 */ + +#if HAVE_GSSAPI + RETERR(dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI])); +#endif /* HAVE_GSSAPI */ + + dst_initialized = true; + return (ISC_R_SUCCESS); + +out: + /* avoid immediate crash! */ + dst_initialized = true; + dst_lib_destroy(); + return (result); +} + +void +dst_lib_destroy(void) { + int i; + RUNTIME_CHECK(dst_initialized); + dst_initialized = false; + + for (i = 0; i < DST_MAX_ALGS; i++) { + if (dst_t_func[i] != NULL && dst_t_func[i]->cleanup != NULL) { + dst_t_func[i]->cleanup(); + } + } + dst__openssl_destroy(); +} + +bool +dst_algorithm_supported(unsigned int alg) { + REQUIRE(dst_initialized); + + if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) { + return (false); + } + return (true); +} + +bool +dst_ds_digest_supported(unsigned int digest_type) { + return (digest_type == DNS_DSDIGEST_SHA1 || + digest_type == DNS_DSDIGEST_SHA256 || + digest_type == DNS_DSDIGEST_SHA384); +} + +isc_result_t +dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category, + bool useforsigning, int maxbits, dst_context_t **dctxp) { + dst_context_t *dctx; + isc_result_t result; + + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(mctx != NULL); + REQUIRE(dctxp != NULL && *dctxp == NULL); + + if (key->func->createctx == NULL && key->func->createctx2 == NULL) { + return (DST_R_UNSUPPORTEDALG); + } + if (key->keydata.generic == NULL) { + return (DST_R_NULLKEY); + } + + dctx = isc_mem_get(mctx, sizeof(dst_context_t)); + memset(dctx, 0, sizeof(*dctx)); + dst_key_attach(key, &dctx->key); + isc_mem_attach(mctx, &dctx->mctx); + dctx->category = category; + if (useforsigning) { + dctx->use = DO_SIGN; + } else { + dctx->use = DO_VERIFY; + } + if (key->func->createctx2 != NULL) { + result = key->func->createctx2(key, maxbits, dctx); + } else { + result = key->func->createctx(key, dctx); + } + if (result != ISC_R_SUCCESS) { + if (dctx->key != NULL) { + dst_key_free(&dctx->key); + } + isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t)); + return (result); + } + dctx->magic = CTX_MAGIC; + *dctxp = dctx; + return (ISC_R_SUCCESS); +} + +void +dst_context_destroy(dst_context_t **dctxp) { + dst_context_t *dctx; + + REQUIRE(dctxp != NULL && VALID_CTX(*dctxp)); + + dctx = *dctxp; + *dctxp = NULL; + INSIST(dctx->key->func->destroyctx != NULL); + dctx->key->func->destroyctx(dctx); + if (dctx->key != NULL) { + dst_key_free(&dctx->key); + } + dctx->magic = 0; + isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(dst_context_t)); +} + +isc_result_t +dst_context_adddata(dst_context_t *dctx, const isc_region_t *data) { + REQUIRE(VALID_CTX(dctx)); + REQUIRE(data != NULL); + INSIST(dctx->key->func->adddata != NULL); + + return (dctx->key->func->adddata(dctx, data)); +} + +isc_result_t +dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig) { + dst_key_t *key; + + REQUIRE(VALID_CTX(dctx)); + REQUIRE(sig != NULL); + + key = dctx->key; + CHECKALG(key->key_alg); + if (key->keydata.generic == NULL) { + return (DST_R_NULLKEY); + } + + if (key->func->sign == NULL) { + return (DST_R_NOTPRIVATEKEY); + } + if (key->func->isprivate == NULL || !key->func->isprivate(key)) { + return (DST_R_NOTPRIVATEKEY); + } + + return (key->func->sign(dctx, sig)); +} + +isc_result_t +dst_context_verify(dst_context_t *dctx, isc_region_t *sig) { + REQUIRE(VALID_CTX(dctx)); + REQUIRE(sig != NULL); + + CHECKALG(dctx->key->key_alg); + if (dctx->key->keydata.generic == NULL) { + return (DST_R_NULLKEY); + } + if (dctx->key->func->verify == NULL) { + return (DST_R_NOTPUBLICKEY); + } + + return (dctx->key->func->verify(dctx, sig)); +} + +isc_result_t +dst_context_verify2(dst_context_t *dctx, unsigned int maxbits, + isc_region_t *sig) { + REQUIRE(VALID_CTX(dctx)); + REQUIRE(sig != NULL); + + CHECKALG(dctx->key->key_alg); + if (dctx->key->keydata.generic == NULL) { + return (DST_R_NULLKEY); + } + if (dctx->key->func->verify == NULL && dctx->key->func->verify2 == NULL) + { + return (DST_R_NOTPUBLICKEY); + } + + return (dctx->key->func->verify2 != NULL + ? dctx->key->func->verify2(dctx, maxbits, sig) + : dctx->key->func->verify(dctx, sig)); +} + +isc_result_t +dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv, + isc_buffer_t *secret) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(pub) && VALID_KEY(priv)); + REQUIRE(secret != NULL); + + CHECKALG(pub->key_alg); + CHECKALG(priv->key_alg); + + if (pub->keydata.generic == NULL || priv->keydata.generic == NULL) { + return (DST_R_NULLKEY); + } + + if (pub->key_alg != priv->key_alg || pub->func->computesecret == NULL || + priv->func->computesecret == NULL) + { + return (DST_R_KEYCANNOTCOMPUTESECRET); + } + + if (!dst_key_isprivate(priv)) { + return (DST_R_NOTPRIVATEKEY); + } + + return (pub->func->computesecret(pub, priv, secret)); +} + +isc_result_t +dst_key_tofile(const dst_key_t *key, int type, const char *directory) { + isc_result_t ret = ISC_R_SUCCESS; + + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); + + CHECKALG(key->key_alg); + + if (key->func->tofile == NULL) { + return (DST_R_UNSUPPORTEDALG); + } + + if ((type & DST_TYPE_PUBLIC) != 0) { + ret = write_public_key(key, type, directory); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + } + + if ((type & DST_TYPE_STATE) != 0) { + ret = write_key_state(key, type, directory); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + } + + if (((type & DST_TYPE_PRIVATE) != 0) && + (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) + { + return (key->func->tofile(key, directory)); + } + return (ISC_R_SUCCESS); +} + +void +dst_key_setexternal(dst_key_t *key, bool value) { + REQUIRE(VALID_KEY(key)); + + key->external = value; +} + +bool +dst_key_isexternal(dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + return (key->external); +} + +void +dst_key_setmodified(dst_key_t *key, bool value) { + REQUIRE(VALID_KEY(key)); + + isc_mutex_lock(&key->mdlock); + key->modified = value; + isc_mutex_unlock(&key->mdlock); +} + +bool +dst_key_ismodified(const dst_key_t *key) { + bool modified; + + REQUIRE(VALID_KEY(key)); + + isc_mutex_lock(&(((dst_key_t *)key)->mdlock)); + modified = key->modified; + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + + return (modified); +} + +isc_result_t +dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, + int type, const char *directory, isc_mem_t *mctx, + isc_buffer_t *buf) { + isc_result_t result; + + REQUIRE(dst_initialized); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE((type & + (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE)) != 0); + REQUIRE(mctx != NULL); + REQUIRE(buf != NULL); + + CHECKALG(alg); + + result = buildfilename(name, id, alg, type, directory, buf); + if (result == ISC_R_SUCCESS) { + if (isc_buffer_availablelength(buf) > 0) { + isc_buffer_putuint8(buf, 0); + } else { + result = ISC_R_NOSPACE; + } + } + + return (result); +} + +isc_result_t +dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type, + const char *directory, isc_mem_t *mctx, dst_key_t **keyp) { + isc_result_t result; + char filename[NAME_MAX]; + isc_buffer_t buf; + dst_key_t *key; + + REQUIRE(dst_initialized); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + + CHECKALG(alg); + + key = NULL; + + isc_buffer_init(&buf, filename, NAME_MAX); + result = dst_key_getfilename(name, id, alg, type, NULL, mctx, &buf); + if (result != ISC_R_SUCCESS) { + goto out; + } + + result = dst_key_fromnamedfile(filename, directory, type, mctx, &key); + if (result != ISC_R_SUCCESS) { + goto out; + } + + result = computeid(key); + if (result != ISC_R_SUCCESS) { + goto out; + } + + if (!dns_name_equal(name, key->key_name) || id != key->key_id || + alg != key->key_alg) + { + result = DST_R_INVALIDPRIVATEKEY; + goto out; + } + + *keyp = key; + result = ISC_R_SUCCESS; + +out: + if ((key != NULL) && (result != ISC_R_SUCCESS)) { + dst_key_free(&key); + } + + return (result); +} + +isc_result_t +dst_key_fromnamedfile(const char *filename, const char *dirname, int type, + isc_mem_t *mctx, dst_key_t **keyp) { + isc_result_t result; + dst_key_t *pubkey = NULL, *key = NULL; + char *newfilename = NULL, *statefilename = NULL; + int newfilenamelen = 0, statefilenamelen = 0; + isc_lex_t *lex = NULL; + + REQUIRE(dst_initialized); + REQUIRE(filename != NULL); + REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + + /* If an absolute path is specified, don't use the key directory */ + if (filename[0] == '/') { + dirname = NULL; + } + + newfilenamelen = strlen(filename) + 5; + if (dirname != NULL) { + newfilenamelen += strlen(dirname) + 1; + } + newfilename = isc_mem_get(mctx, newfilenamelen); + result = addsuffix(newfilename, newfilenamelen, dirname, filename, + ".key"); + INSIST(result == ISC_R_SUCCESS); + + RETERR(dst_key_read_public(newfilename, type, mctx, &pubkey)); + isc_mem_put(mctx, newfilename, newfilenamelen); + + /* + * Read the state file, if requested by type. + */ + if ((type & DST_TYPE_STATE) != 0) { + statefilenamelen = strlen(filename) + 7; + if (dirname != NULL) { + statefilenamelen += strlen(dirname) + 1; + } + statefilename = isc_mem_get(mctx, statefilenamelen); + result = addsuffix(statefilename, statefilenamelen, dirname, + filename, ".state"); + INSIST(result == ISC_R_SUCCESS); + } + + pubkey->kasp = false; + if ((type & DST_TYPE_STATE) != 0) { + result = dst_key_read_state(statefilename, mctx, &pubkey); + if (result == ISC_R_SUCCESS) { + pubkey->kasp = true; + } else if (result == ISC_R_FILENOTFOUND) { + /* Having no state is valid. */ + result = ISC_R_SUCCESS; + } + RETERR(result); + } + + if ((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) == DST_TYPE_PUBLIC || + (pubkey->key_flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) + { + RETERR(computeid(pubkey)); + pubkey->modified = false; + *keyp = pubkey; + pubkey = NULL; + goto out; + } + + RETERR(algorithm_status(pubkey->key_alg)); + + key = get_key_struct(pubkey->key_name, pubkey->key_alg, + pubkey->key_flags, pubkey->key_proto, + pubkey->key_size, pubkey->key_class, + pubkey->key_ttl, mctx); + if (key == NULL) { + RETERR(ISC_R_NOMEMORY); + } + + if (key->func->parse == NULL) { + RETERR(DST_R_UNSUPPORTEDALG); + } + + newfilenamelen = strlen(filename) + 9; + if (dirname != NULL) { + newfilenamelen += strlen(dirname) + 1; + } + newfilename = isc_mem_get(mctx, newfilenamelen); + result = addsuffix(newfilename, newfilenamelen, dirname, filename, + ".private"); + INSIST(result == ISC_R_SUCCESS); + + RETERR(isc_lex_create(mctx, 1500, &lex)); + RETERR(isc_lex_openfile(lex, newfilename)); + isc_mem_put(mctx, newfilename, newfilenamelen); + + RETERR(key->func->parse(key, lex, pubkey)); + isc_lex_destroy(&lex); + + key->kasp = false; + if ((type & DST_TYPE_STATE) != 0) { + result = dst_key_read_state(statefilename, mctx, &key); + if (result == ISC_R_SUCCESS) { + key->kasp = true; + } else if (result == ISC_R_FILENOTFOUND) { + /* Having no state is valid. */ + result = ISC_R_SUCCESS; + } + RETERR(result); + } + + RETERR(computeid(key)); + + if (pubkey->key_id != key->key_id) { + RETERR(DST_R_INVALIDPRIVATEKEY); + } + + key->modified = false; + *keyp = key; + key = NULL; + +out: + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (newfilename != NULL) { + isc_mem_put(mctx, newfilename, newfilenamelen); + } + if (statefilename != NULL) { + isc_mem_put(mctx, statefilename, statefilenamelen); + } + if (lex != NULL) { + isc_lex_destroy(&lex); + } + if (key != NULL) { + dst_key_free(&key); + } + return (result); +} + +isc_result_t +dst_key_todns(const dst_key_t *key, isc_buffer_t *target) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(target != NULL); + + CHECKALG(key->key_alg); + + if (key->func->todns == NULL) { + return (DST_R_UNSUPPORTEDALG); + } + + if (isc_buffer_availablelength(target) < 4) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint16(target, (uint16_t)(key->key_flags & 0xffff)); + isc_buffer_putuint8(target, (uint8_t)key->key_proto); + isc_buffer_putuint8(target, (uint8_t)key->key_alg); + + if ((key->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { + if (isc_buffer_availablelength(target) < 2) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint16( + target, (uint16_t)((key->key_flags >> 16) & 0xffff)); + } + + if (key->keydata.generic == NULL) { /*%< NULL KEY */ + return (ISC_R_SUCCESS); + } + + return (key->func->todns(key, target)); +} + +isc_result_t +dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { + uint8_t alg, proto; + uint32_t flags, extflags; + dst_key_t *key = NULL; + dns_keytag_t id, rid; + isc_region_t r; + isc_result_t result; + + REQUIRE(dst_initialized); + + isc_buffer_remainingregion(source, &r); + + if (isc_buffer_remaininglength(source) < 4) { + return (DST_R_INVALIDPUBLICKEY); + } + flags = isc_buffer_getuint16(source); + proto = isc_buffer_getuint8(source); + alg = isc_buffer_getuint8(source); + + id = dst_region_computeid(&r); + rid = dst_region_computerid(&r); + + if ((flags & DNS_KEYFLAG_EXTENDED) != 0) { + if (isc_buffer_remaininglength(source) < 2) { + return (DST_R_INVALIDPUBLICKEY); + } + extflags = isc_buffer_getuint16(source); + flags |= (extflags << 16); + } + + result = frombuffer(name, alg, flags, proto, rdclass, source, mctx, + &key); + if (result != ISC_R_SUCCESS) { + return (result); + } + key->key_id = id; + key->key_rid = rid; + + *keyp = key; + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { + dst_key_t *key = NULL; + isc_result_t result; + + REQUIRE(dst_initialized); + + result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx, + &key); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = computeid(key); + if (result != ISC_R_SUCCESS) { + dst_key_free(&key); + return (result); + } + + *keyp = key; + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(target != NULL); + + CHECKALG(key->key_alg); + + if (key->func->todns == NULL) { + return (DST_R_UNSUPPORTEDALG); + } + + return (key->func->todns(key, target)); +} + +isc_result_t +dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer) { + isc_lex_t *lex = NULL; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(!dst_key_isprivate(key)); + REQUIRE(buffer != NULL); + + if (key->func->parse == NULL) { + RETERR(DST_R_UNSUPPORTEDALG); + } + + RETERR(isc_lex_create(key->mctx, 1500, &lex)); + RETERR(isc_lex_openbuffer(lex, buffer)); + RETERR(key->func->parse(key, lex, NULL)); +out: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + return (result); +} + +dns_gss_ctx_id_t +dst_key_getgssctx(const dst_key_t *key) { + REQUIRE(key != NULL); + + return (key->keydata.gssctx); +} + +isc_result_t +dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx, + isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken) { + dst_key_t *key; + isc_result_t result; + + REQUIRE(gssctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + + key = get_key_struct(name, DST_ALG_GSSAPI, 0, DNS_KEYPROTO_DNSSEC, 0, + dns_rdataclass_in, 0, mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + if (intoken != NULL) { + /* + * Keep the token for use by external ssu rules. They may need + * to examine the PAC in the kerberos ticket. + */ + isc_buffer_allocate(key->mctx, &key->key_tkeytoken, + intoken->length); + RETERR(isc_buffer_copyregion(key->key_tkeytoken, intoken)); + } + + key->keydata.gssctx = gssctx; + *keyp = key; + result = ISC_R_SUCCESS; +out: + if (result != ISC_R_SUCCESS) { + dst_key_free(&key); + } + return (result); +} + +FILE * +dst_key_open(char *tmpname, mode_t mode) { + /* Create public key file. */ + int fd = mkstemp(tmpname); + if (fd == -1) { + return (NULL); + } + + if (fchmod(fd, mode & ~isc_os_umask()) != 0) { + goto error; + } + + FILE *fp = fdopen(fd, "w"); + if (fp == NULL) { + goto error; + } + + return (fp); +error: + (void)close(fd); + (void)unlink(tmpname); + return (NULL); +} + +isc_result_t +dst_key_close(char *tmpname, FILE *fp, char *filename) { + if ((fflush(fp) != 0) || (ferror(fp) != 0)) { + return (dst_key_cleanup(tmpname, fp)); + } + + if (rename(tmpname, filename) != 0) { + return (dst_key_cleanup(tmpname, fp)); + } + + if (fclose(fp) != 0) { + /* + * This is in fact error, but we don't care at this point, + * as the file has been already flushed to disk. + */ + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_cleanup(char *tmpname, FILE *fp) { + if (ftruncate(fileno(fp), 0) != 0) { + /* + * ftruncate() result can't be ignored, but we don't care, as + * any sensitive data are protected by the permissions, and + * unlinked in the next step, this is just a good practice. + */ + } + + (void)unlink(tmpname); + (void)fclose(fp); + + return (DST_R_WRITEERROR); +} + +isc_result_t +dst_key_buildinternal(const dns_name_t *name, unsigned int alg, + unsigned int bits, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + void *data, isc_mem_t *mctx, dst_key_t **keyp) { + dst_key_t *key; + isc_result_t result; + + REQUIRE(dst_initialized); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + REQUIRE(data != NULL); + + CHECKALG(alg); + + key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0, + mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + key->keydata.generic = data; + + result = computeid(key); + if (result != ISC_R_SUCCESS) { + dst_key_free(&key); + return (result); + } + + *keyp = key; + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + const char *engine, const char *label, const char *pin, + isc_mem_t *mctx, dst_key_t **keyp) { + dst_key_t *key; + isc_result_t result; + + REQUIRE(dst_initialized); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + REQUIRE(label != NULL); + + CHECKALG(alg); + + key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + if (key->func->fromlabel == NULL) { + dst_key_free(&key); + return (DST_R_UNSUPPORTEDALG); + } + + result = key->func->fromlabel(key, engine, label, pin); + if (result != ISC_R_SUCCESS) { + dst_key_free(&key); + return (result); + } + + result = computeid(key); + if (result != ISC_R_SUCCESS) { + dst_key_free(&key); + return (result); + } + + *keyp = key; + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits, + unsigned int param, unsigned int flags, unsigned int protocol, + dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp, + void (*callback)(int)) { + dst_key_t *key; + isc_result_t ret; + + REQUIRE(dst_initialized); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + + CHECKALG(alg); + + key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0, + mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + if (bits == 0) { /*%< NULL KEY */ + key->key_flags |= DNS_KEYTYPE_NOKEY; + *keyp = key; + return (ISC_R_SUCCESS); + } + + if (key->func->generate == NULL) { + dst_key_free(&key); + return (DST_R_UNSUPPORTEDALG); + } + + ret = key->func->generate(key, param, callback); + if (ret != ISC_R_SUCCESS) { + dst_key_free(&key); + return (ret); + } + + ret = computeid(key); + if (ret != ISC_R_SUCCESS) { + dst_key_free(&key); + return (ret); + } + + *keyp = key; + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep) { + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_BOOLEAN); + + isc_mutex_lock(&(((dst_key_t *)key)->mdlock)); + if (!key->boolset[type]) { + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + return (ISC_R_NOTFOUND); + } + *valuep = key->bools[type]; + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + + return (ISC_R_SUCCESS); +} + +void +dst_key_setbool(dst_key_t *key, int type, bool value) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || !key->boolset[type] || + key->bools[type] != value; + key->bools[type] = value; + key->boolset[type] = true; + isc_mutex_unlock(&key->mdlock); +} + +void +dst_key_unsetbool(dst_key_t *key, int type) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_BOOLEAN); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || key->boolset[type]; + key->boolset[type] = false; + isc_mutex_unlock(&key->mdlock); +} + +isc_result_t +dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep) { + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_NUMERIC); + + isc_mutex_lock(&(((dst_key_t *)key)->mdlock)); + if (!key->numset[type]) { + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + return (ISC_R_NOTFOUND); + } + *valuep = key->nums[type]; + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + + return (ISC_R_SUCCESS); +} + +void +dst_key_setnum(dst_key_t *key, int type, uint32_t value) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_NUMERIC); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || !key->numset[type] || + key->nums[type] != value; + key->nums[type] = value; + key->numset[type] = true; + isc_mutex_unlock(&key->mdlock); +} + +void +dst_key_unsetnum(dst_key_t *key, int type) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_NUMERIC); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || key->numset[type]; + key->numset[type] = false; + isc_mutex_unlock(&key->mdlock); +} + +isc_result_t +dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) { + REQUIRE(VALID_KEY(key)); + REQUIRE(timep != NULL); + REQUIRE(type <= DST_MAX_TIMES); + + isc_mutex_lock(&(((dst_key_t *)key)->mdlock)); + if (!key->timeset[type]) { + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + return (ISC_R_NOTFOUND); + } + *timep = key->times[type]; + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + return (ISC_R_SUCCESS); +} + +void +dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_TIMES); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || !key->timeset[type] || + key->times[type] != when; + key->times[type] = when; + key->timeset[type] = true; + isc_mutex_unlock(&key->mdlock); +} + +void +dst_key_unsettime(dst_key_t *key, int type) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_TIMES); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || key->timeset[type]; + key->timeset[type] = false; + isc_mutex_unlock(&key->mdlock); +} + +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep) { + REQUIRE(VALID_KEY(key)); + REQUIRE(statep != NULL); + REQUIRE(type <= DST_MAX_KEYSTATES); + + isc_mutex_lock(&(((dst_key_t *)key)->mdlock)); + if (!key->keystateset[type]) { + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + return (ISC_R_NOTFOUND); + } + *statep = key->keystates[type]; + isc_mutex_unlock(&(((dst_key_t *)key)->mdlock)); + + return (ISC_R_SUCCESS); +} + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || !key->keystateset[type] || + key->keystates[type] != state; + key->keystates[type] = state; + key->keystateset[type] = true; + isc_mutex_unlock(&key->mdlock); +} + +void +dst_key_unsetstate(dst_key_t *key, int type) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_KEYSTATES); + + isc_mutex_lock(&key->mdlock); + key->modified = key->modified || key->keystateset[type]; + key->keystateset[type] = false; + isc_mutex_unlock(&key->mdlock); +} + +isc_result_t +dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp) { + REQUIRE(VALID_KEY(key)); + REQUIRE(majorp != NULL); + REQUIRE(minorp != NULL); + *majorp = key->fmt_major; + *minorp = key->fmt_minor; + return (ISC_R_SUCCESS); +} + +void +dst_key_setprivateformat(dst_key_t *key, int major, int minor) { + REQUIRE(VALID_KEY(key)); + key->fmt_major = major; + key->fmt_minor = minor; +} + +static bool +comparekeys(const dst_key_t *key1, const dst_key_t *key2, + bool match_revoked_key, + bool (*compare)(const dst_key_t *key1, const dst_key_t *key2)) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key1)); + REQUIRE(VALID_KEY(key2)); + + if (key1 == key2) { + return (true); + } + + if (key1->key_alg != key2->key_alg) { + return (false); + } + + if (key1->key_id != key2->key_id) { + if (!match_revoked_key) { + return (false); + } + if ((key1->key_flags & DNS_KEYFLAG_REVOKE) == + (key2->key_flags & DNS_KEYFLAG_REVOKE)) + { + return (false); + } + if (key1->key_id != key2->key_rid && + key1->key_rid != key2->key_id) + { + return (false); + } + } + + if (compare != NULL) { + return (compare(key1, key2)); + } else { + return (false); + } +} + +/* + * Compares only the public portion of two keys, by converting them + * both to wire format and comparing the results. + */ +static bool +pub_compare(const dst_key_t *key1, const dst_key_t *key2) { + isc_result_t result; + unsigned char buf1[DST_KEY_MAXSIZE], buf2[DST_KEY_MAXSIZE]; + isc_buffer_t b1, b2; + isc_region_t r1, r2; + + isc_buffer_init(&b1, buf1, sizeof(buf1)); + result = dst_key_todns(key1, &b1); + if (result != ISC_R_SUCCESS) { + return (false); + } + /* Zero out flags. */ + buf1[0] = buf1[1] = 0; + if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { + isc_buffer_subtract(&b1, 2); + } + + isc_buffer_init(&b2, buf2, sizeof(buf2)); + result = dst_key_todns(key2, &b2); + if (result != ISC_R_SUCCESS) { + return (false); + } + /* Zero out flags. */ + buf2[0] = buf2[1] = 0; + if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { + isc_buffer_subtract(&b2, 2); + } + + isc_buffer_usedregion(&b1, &r1); + /* Remove extended flags. */ + if ((key1->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { + memmove(&buf1[4], &buf1[6], r1.length - 6); + r1.length -= 2; + } + + isc_buffer_usedregion(&b2, &r2); + /* Remove extended flags. */ + if ((key2->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { + memmove(&buf2[4], &buf2[6], r2.length - 6); + r2.length -= 2; + } + return (isc_region_compare(&r1, &r2) == 0); +} + +bool +dst_key_compare(const dst_key_t *key1, const dst_key_t *key2) { + return (comparekeys(key1, key2, false, key1->func->compare)); +} + +bool +dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2, + bool match_revoked_key) { + return (comparekeys(key1, key2, match_revoked_key, pub_compare)); +} + +bool +dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key1)); + REQUIRE(VALID_KEY(key2)); + + if (key1 == key2) { + return (true); + } + if (key1->key_alg == key2->key_alg && + key1->func->paramcompare != NULL && + key1->func->paramcompare(key1, key2)) + { + return (true); + } else { + return (false); + } +} + +void +dst_key_attach(dst_key_t *source, dst_key_t **target) { + REQUIRE(dst_initialized); + REQUIRE(target != NULL && *target == NULL); + REQUIRE(VALID_KEY(source)); + + isc_refcount_increment(&source->refs); + *target = source; +} + +void +dst_key_free(dst_key_t **keyp) { + REQUIRE(dst_initialized); + REQUIRE(keyp != NULL && VALID_KEY(*keyp)); + dst_key_t *key = *keyp; + *keyp = NULL; + + if (isc_refcount_decrement(&key->refs) == 1) { + isc_refcount_destroy(&key->refs); + isc_mem_t *mctx = key->mctx; + if (key->keydata.generic != NULL) { + INSIST(key->func->destroy != NULL); + key->func->destroy(key); + } + if (key->engine != NULL) { + isc_mem_free(mctx, key->engine); + } + if (key->label != NULL) { + isc_mem_free(mctx, key->label); + } + dns_name_free(key->key_name, mctx); + isc_mem_put(mctx, key->key_name, sizeof(dns_name_t)); + if (key->key_tkeytoken) { + isc_buffer_free(&key->key_tkeytoken); + } + isc_mutex_destroy(&key->mdlock); + isc_safe_memwipe(key, sizeof(*key)); + isc_mem_putanddetach(&mctx, key, sizeof(*key)); + } +} + +bool +dst_key_isprivate(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + INSIST(key->func->isprivate != NULL); + return (key->func->isprivate(key)); +} + +isc_result_t +dst_key_buildfilename(const dst_key_t *key, int type, const char *directory, + isc_buffer_t *out) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type == DST_TYPE_PRIVATE || type == DST_TYPE_PUBLIC || + type == DST_TYPE_STATE || type == DST_TYPE_TEMPLATE || + type == 0); + + return (buildfilename(key->key_name, key->key_id, key->key_alg, type, + directory, out)); +} + +isc_result_t +dst_key_sigsize(const dst_key_t *key, unsigned int *n) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(n != NULL); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + *n = (key->key_size + 7) / 8; + break; + case DST_ALG_ECDSA256: + *n = DNS_SIG_ECDSA256SIZE; + break; + case DST_ALG_ECDSA384: + *n = DNS_SIG_ECDSA384SIZE; + break; + case DST_ALG_ED25519: + *n = DNS_SIG_ED25519SIZE; + break; + case DST_ALG_ED448: + *n = DNS_SIG_ED448SIZE; + break; + case DST_ALG_HMACMD5: + *n = isc_md_type_get_size(ISC_MD_MD5); + break; + case DST_ALG_HMACSHA1: + *n = isc_md_type_get_size(ISC_MD_SHA1); + break; + case DST_ALG_HMACSHA224: + *n = isc_md_type_get_size(ISC_MD_SHA224); + break; + case DST_ALG_HMACSHA256: + *n = isc_md_type_get_size(ISC_MD_SHA256); + break; + case DST_ALG_HMACSHA384: + *n = isc_md_type_get_size(ISC_MD_SHA384); + break; + case DST_ALG_HMACSHA512: + *n = isc_md_type_get_size(ISC_MD_SHA512); + break; + case DST_ALG_GSSAPI: + *n = 128; /*%< XXX */ + break; + case DST_ALG_DH: + default: + return (DST_R_UNSUPPORTEDALG); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dst_key_secretsize(const dst_key_t *key, unsigned int *n) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + REQUIRE(n != NULL); + + if (key->key_alg == DST_ALG_DH) { + *n = (key->key_size + 7) / 8; + return (ISC_R_SUCCESS); + } + return (DST_R_UNSUPPORTEDALG); +} + +/*% + * Set the flags on a key, then recompute the key ID + */ +isc_result_t +dst_key_setflags(dst_key_t *key, uint32_t flags) { + REQUIRE(VALID_KEY(key)); + key->key_flags = flags; + return (computeid(key)); +} + +void +dst_key_format(const dst_key_t *key, char *cp, unsigned int size) { + char namestr[DNS_NAME_FORMATSIZE]; + char algstr[DNS_NAME_FORMATSIZE]; + + dns_name_format(dst_key_name(key), namestr, sizeof(namestr)); + dns_secalg_format((dns_secalg_t)dst_key_alg(key), algstr, + sizeof(algstr)); + snprintf(cp, size, "%s/%s/%d", namestr, algstr, dst_key_id(key)); +} + +isc_result_t +dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) { + REQUIRE(buffer != NULL && *buffer == NULL); + REQUIRE(length != NULL && *length == 0); + REQUIRE(VALID_KEY(key)); + + if (key->func->dump == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return (key->func->dump(key, mctx, buffer, length)); +} + +isc_result_t +dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_mem_t *mctx, const char *keystr, dst_key_t **keyp) { + isc_result_t result; + dst_key_t *key; + + REQUIRE(dst_initialized); + REQUIRE(keyp != NULL && *keyp == NULL); + + if (alg >= DST_MAX_ALGS || dst_t_func[alg] == NULL) { + return (DST_R_UNSUPPORTEDALG); + } + + if (dst_t_func[alg]->restore == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + result = (dst_t_func[alg]->restore)(key, keystr); + if (result == ISC_R_SUCCESS) { + *keyp = key; + } else { + dst_key_free(&key); + } + + return (result); +} + +/*** + *** Static methods + ***/ + +/*% + * Allocates a key structure and fills in some of the fields. + */ +static dst_key_t * +get_key_struct(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, unsigned int bits, + dns_rdataclass_t rdclass, dns_ttl_t ttl, isc_mem_t *mctx) { + dst_key_t *key; + int i; + + key = isc_mem_get(mctx, sizeof(dst_key_t)); + + memset(key, 0, sizeof(dst_key_t)); + + key->key_name = isc_mem_get(mctx, sizeof(dns_name_t)); + + dns_name_init(key->key_name, NULL); + dns_name_dup(name, mctx, key->key_name); + + isc_refcount_init(&key->refs, 1); + isc_mem_attach(mctx, &key->mctx); + key->key_alg = alg; + key->key_flags = flags; + key->key_proto = protocol; + key->keydata.generic = NULL; + key->key_size = bits; + key->key_class = rdclass; + key->key_ttl = ttl; + key->func = dst_t_func[alg]; + key->fmt_major = 0; + key->fmt_minor = 0; + for (i = 0; i < (DST_MAX_TIMES + 1); i++) { + key->times[i] = 0; + key->timeset[i] = false; + } + isc_mutex_init(&key->mdlock); + key->inactive = false; + key->magic = KEY_MAGIC; + return (key); +} + +bool +dst_key_inactive(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + return (key->inactive); +} + +void +dst_key_setinactive(dst_key_t *key, bool inactive) { + REQUIRE(VALID_KEY(key)); + + key->inactive = inactive; +} + +/*% + * Reads a public key from disk. + */ +isc_result_t +dst_key_read_public(const char *filename, int type, isc_mem_t *mctx, + dst_key_t **keyp) { + u_char rdatabuf[DST_KEY_MAXSIZE]; + isc_buffer_t b; + dns_fixedname_t name; + isc_lex_t *lex = NULL; + isc_token_t token; + isc_result_t ret; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int opt = ISC_LEXOPT_DNSMULTILINE; + dns_rdataclass_t rdclass = dns_rdataclass_in; + isc_lexspecials_t specials; + uint32_t ttl = 0; + isc_result_t result; + dns_rdatatype_t keytype; + + /* + * Open the file and read its formatted contents + * File format: + * domain.name [ttl] [class] [KEY|DNSKEY] + * + */ + + /* 1500 should be large enough for any key */ + ret = isc_lex_create(mctx, 1500, &lex); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + memset(specials, 0, sizeof(specials)); + specials['('] = 1; + specials[')'] = 1; + specials['"'] = 1; + isc_lex_setspecials(lex, specials); + isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); + + ret = isc_lex_openfile(lex, filename); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Read the domain name */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + /* + * We don't support "@" in .key files. + */ + if (!strcmp(DST_AS_STR(token), "@")) { + BADTOKEN(); + } + + dns_fixedname_init(&name); + isc_buffer_init(&b, DST_AS_STR(token), strlen(DST_AS_STR(token))); + isc_buffer_add(&b, strlen(DST_AS_STR(token))); + ret = dns_name_fromtext(dns_fixedname_name(&name), &b, dns_rootname, 0, + NULL); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Read the next word: either TTL, class, or 'KEY' */ + NEXTTOKEN(lex, opt, &token); + + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + /* If it's a TTL, read the next one */ + result = dns_ttl_fromtext(&token.value.as_textregion, &ttl); + if (result == ISC_R_SUCCESS) { + NEXTTOKEN(lex, opt, &token); + } + + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = dns_rdataclass_fromtext(&rdclass, &token.value.as_textregion); + if (ret == ISC_R_SUCCESS) { + NEXTTOKEN(lex, opt, &token); + } + + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + if (strcasecmp(DST_AS_STR(token), "DNSKEY") == 0) { + keytype = dns_rdatatype_dnskey; + } else if (strcasecmp(DST_AS_STR(token), "KEY") == 0) { + keytype = dns_rdatatype_key; /*%< SIG(0), TKEY */ + } else { + BADTOKEN(); + } + + if (((type & DST_TYPE_KEY) != 0 && keytype != dns_rdatatype_key) || + ((type & DST_TYPE_KEY) == 0 && keytype != dns_rdatatype_dnskey)) + { + ret = DST_R_BADKEYTYPE; + goto cleanup; + } + + isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf)); + ret = dns_rdata_fromtext(&rdata, rdclass, keytype, lex, NULL, false, + mctx, &b, NULL); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + ret = dst_key_fromdns(dns_fixedname_name(&name), rdclass, &b, mctx, + keyp); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_setttl(*keyp, ttl); + +cleanup: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + return (ret); +} + +static int +find_metadata(const char *s, const char *tags[], int ntags) { + for (int i = 0; i < ntags; i++) { + if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) { + return (i); + } + } + return (-1); +} + +static int +find_numericdata(const char *s) { + return (find_metadata(s, numerictags, NUMERIC_NTAGS)); +} + +static int +find_booleandata(const char *s) { + return (find_metadata(s, booleantags, BOOLEAN_NTAGS)); +} + +static int +find_timingdata(const char *s) { + return (find_metadata(s, timingtags, TIMING_NTAGS)); +} + +static int +find_keystatedata(const char *s) { + return (find_metadata(s, keystatestags, KEYSTATES_NTAGS)); +} + +static isc_result_t +keystate_fromtext(const char *s, dst_key_state_t *state) { + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && strcasecmp(s, keystates[i]) == 0) { + *state = (dst_key_state_t)i; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +/*% + * Reads a key state from disk. + */ +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp) { + isc_lex_t *lex = NULL; + isc_token_t token; + isc_result_t ret; + unsigned int opt = ISC_LEXOPT_EOL; + + ret = isc_lex_create(mctx, 1500, &lex); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); + + ret = isc_lex_openfile(lex, filename); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Read the comment line. + */ + READLINE(lex, opt, &token); + + /* + * Read the algorithm line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_ALGORITHM_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long)dst_key_alg(*keyp)) + { + BADTOKEN(); + } + + READLINE(lex, opt, &token); + + /* + * Read the length line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), STATE_LENGTH_STR) != 0) + { + BADTOKEN(); + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long)dst_key_size(*keyp)) + { + BADTOKEN(); + } + + READLINE(lex, opt, &token); + + /* + * Read the metadata. + */ + for (int n = 0; n < MAX_NTAGS; n++) { + int tag; + + NEXTTOKEN_OR_EOF(lex, opt, &token); + if (ret == ISC_R_EOF) { + break; + } + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + /* Numeric metadata */ + tag = find_numericdata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < NUMERIC_NTAGS); + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number) { + BADTOKEN(); + } + + dst_key_setnum(*keyp, tag, token.value.as_ulong); + goto next; + } + + /* Boolean metadata */ + tag = find_booleandata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < BOOLEAN_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + if (strcmp(DST_AS_STR(token), "yes") == 0) { + dst_key_setbool(*keyp, tag, true); + } else if (strcmp(DST_AS_STR(token), "no") == 0) { + dst_key_setbool(*keyp, tag, false); + } else { + BADTOKEN(); + } + goto next; + } + + /* Timing metadata */ + tag = find_timingdata(DST_AS_STR(token)); + if (tag >= 0) { + uint32_t when; + + INSIST(tag < TIMING_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = dns_time32_fromtext(DST_AS_STR(token), &when); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_settime(*keyp, tag, when); + goto next; + } + + /* Keystate metadata */ + tag = find_keystatedata(DST_AS_STR(token)); + if (tag >= 0) { + dst_key_state_t state; + + INSIST(tag < KEYSTATES_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + BADTOKEN(); + } + + ret = keystate_fromtext(DST_AS_STR(token), &state); + if (ret != ISC_R_SUCCESS) { + goto cleanup; + } + + dst_key_setstate(*keyp, tag, state); + goto next; + } + + next: + READLINE(lex, opt, &token); + } + + /* Done, successfully parsed the whole file. */ + ret = ISC_R_SUCCESS; + +cleanup: + if (lex != NULL) { + isc_lex_destroy(&lex); + } + return (ret); +} + +static bool +issymmetric(const dst_key_t *key) { + REQUIRE(dst_initialized); + REQUIRE(VALID_KEY(key)); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + case DST_ALG_DH: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + return (false); + case DST_ALG_HMACMD5: + case DST_ALG_HMACSHA1: + case DST_ALG_HMACSHA224: + case DST_ALG_HMACSHA256: + case DST_ALG_HMACSHA384: + case DST_ALG_HMACSHA512: + case DST_ALG_GSSAPI: + return (true); + default: + return (false); + } +} + +/*% + * Write key boolean metadata to a file pointer, preceded by 'tag' + */ +static void +printbool(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + bool value = 0; + + result = dst_key_getbool(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, value ? "yes" : "no"); +} + +/*% + * Write key numeric metadata to a file pointer, preceded by 'tag' + */ +static void +printnum(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + uint32_t value = 0; + + result = dst_key_getnum(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %u\n", tag, value); +} + +/*% + * Write key timing metadata to a file pointer, preceded by 'tag' + */ +static void +printtime(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + char output[26]; /* Minimum buffer as per ctime_r() specification. */ + isc_stdtime_t when; + char utc[sizeof("YYYYMMDDHHSSMM")]; + isc_buffer_t b; + isc_region_t r; + + result = dst_key_gettime(key, type, &when); + if (result == ISC_R_NOTFOUND) { + return; + } + + isc_stdtime_tostring(when, output, sizeof(output)); + isc_buffer_init(&b, utc, sizeof(utc)); + result = dns_time32_totext(when, &b); + if (result != ISC_R_SUCCESS) { + goto error; + } + + isc_buffer_usedregion(&b, &r); + fprintf(stream, "%s: %.*s (%s)\n", tag, (int)r.length, r.base, output); + return; + +error: + fprintf(stream, "%s: (set, unable to display)\n", tag); +} + +/*% + * Write key state metadata to a file pointer, preceded by 'tag' + */ +static void +printstate(const dst_key_t *key, int type, const char *tag, FILE *stream) { + isc_result_t result; + dst_key_state_t value = 0; + + result = dst_key_getstate(key, type, &value); + if (result != ISC_R_SUCCESS) { + return; + } + fprintf(stream, "%s: %s\n", tag, keystates[value]); +} + +/*% + * Writes a key state to disk. + */ +static isc_result_t +write_key_state(const dst_key_t *key, int type, const char *directory) { + FILE *fp; + isc_buffer_t fileb; + isc_buffer_t tmpb; + char filename[NAME_MAX]; + char tmpname[NAME_MAX]; + isc_result_t result; + + REQUIRE(VALID_KEY(key)); + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + result = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_init(&tmpb, tmpname, sizeof(tmpname)); + result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory, + &tmpb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + mode_t mode = issymmetric(key) ? S_IRUSR | S_IWUSR + : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + /* Create temporary public key file. */ + fp = dst_key_open(tmpname, mode); + if (fp == NULL) { + return (DST_R_WRITEERROR); + } + + /* Write key state */ + if ((type & DST_TYPE_KEY) == 0) { + fprintf(fp, "; This is the state of key %d, for ", key->key_id); + result = dns_name_print(key->key_name, fp); + if (result != ISC_R_SUCCESS) { + return (dst_key_cleanup(tmpname, fp)); + } + fputc('\n', fp); + + fprintf(fp, "Algorithm: %u\n", key->key_alg); + fprintf(fp, "Length: %u\n", key->key_size); + + printnum(key, DST_NUM_LIFETIME, "Lifetime", fp); + printnum(key, DST_NUM_PREDECESSOR, "Predecessor", fp); + printnum(key, DST_NUM_SUCCESSOR, "Successor", fp); + + printbool(key, DST_BOOL_KSK, "KSK", fp); + printbool(key, DST_BOOL_ZSK, "ZSK", fp); + + printtime(key, DST_TIME_CREATED, "Generated", fp); + printtime(key, DST_TIME_PUBLISH, "Published", fp); + printtime(key, DST_TIME_ACTIVATE, "Active", fp); + printtime(key, DST_TIME_INACTIVE, "Retired", fp); + printtime(key, DST_TIME_REVOKE, "Revoked", fp); + printtime(key, DST_TIME_DELETE, "Removed", fp); + printtime(key, DST_TIME_DSPUBLISH, "DSPublish", fp); + printtime(key, DST_TIME_DSDELETE, "DSRemoved", fp); + printtime(key, DST_TIME_SYNCPUBLISH, "PublishCDS", fp); + printtime(key, DST_TIME_SYNCDELETE, "DeleteCDS", fp); + + printnum(key, DST_NUM_DSPUBCOUNT, "DSPubCount", fp); + printnum(key, DST_NUM_DSDELCOUNT, "DSDelCount", fp); + + printtime(key, DST_TIME_DNSKEY, "DNSKEYChange", fp); + printtime(key, DST_TIME_ZRRSIG, "ZRRSIGChange", fp); + printtime(key, DST_TIME_KRRSIG, "KRRSIGChange", fp); + printtime(key, DST_TIME_DS, "DSChange", fp); + + printstate(key, DST_KEY_DNSKEY, "DNSKEYState", fp); + printstate(key, DST_KEY_ZRRSIG, "ZRRSIGState", fp); + printstate(key, DST_KEY_KRRSIG, "KRRSIGState", fp); + printstate(key, DST_KEY_DS, "DSState", fp); + printstate(key, DST_KEY_GOAL, "GoalState", fp); + } + + return (dst_key_close(tmpname, fp, filename)); +} + +/*% + * Writes a public key to disk in DNS format. + */ +static isc_result_t +write_public_key(const dst_key_t *key, int type, const char *directory) { + FILE *fp; + isc_buffer_t keyb, tmpb, textb, fileb, classb; + isc_region_t r; + char tmpname[NAME_MAX]; + char filename[NAME_MAX]; + unsigned char key_array[DST_KEY_MAXSIZE]; + char text_array[DST_KEY_MAXTEXTSIZE]; + char class_array[10]; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + REQUIRE(VALID_KEY(key)); + + isc_buffer_init(&keyb, key_array, sizeof(key_array)); + isc_buffer_init(&textb, text_array, sizeof(text_array)); + isc_buffer_init(&classb, class_array, sizeof(class_array)); + + result = dst_key_todns(key, &keyb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_usedregion(&keyb, &r); + dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r); + + result = dns_rdata_totext(&rdata, (dns_name_t *)NULL, &textb); + if (result != ISC_R_SUCCESS) { + return (DST_R_INVALIDPUBLICKEY); + } + + result = dns_rdataclass_totext(key->key_class, &classb); + if (result != ISC_R_SUCCESS) { + return (DST_R_INVALIDPUBLICKEY); + } + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_init(&tmpb, tmpname, sizeof(tmpname)); + result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory, + &tmpb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* Create temporary public key file. */ + mode_t mode = issymmetric(key) ? S_IRUSR | S_IWUSR + : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + fp = dst_key_open(tmpname, mode); + if (fp == NULL) { + return (DST_R_WRITEERROR); + } + + /* Write key information in comments */ + if ((type & DST_TYPE_KEY) == 0) { + fprintf(fp, "; This is a %s%s-signing key, keyid %d, for ", + (key->key_flags & DNS_KEYFLAG_REVOKE) != 0 ? "revoked " + : "", + (key->key_flags & DNS_KEYFLAG_KSK) != 0 ? "key" + : "zone", + key->key_id); + result = dns_name_print(key->key_name, fp); + if (result != ISC_R_SUCCESS) { + return (dst_key_cleanup(tmpname, fp)); + } + fputc('\n', fp); + + printtime(key, DST_TIME_CREATED, "; Created", fp); + printtime(key, DST_TIME_PUBLISH, "; Publish", fp); + printtime(key, DST_TIME_ACTIVATE, "; Activate", fp); + printtime(key, DST_TIME_REVOKE, "; Revoke", fp); + printtime(key, DST_TIME_INACTIVE, "; Inactive", fp); + printtime(key, DST_TIME_DELETE, "; Delete", fp); + printtime(key, DST_TIME_SYNCPUBLISH, "; SyncPublish", fp); + printtime(key, DST_TIME_SYNCDELETE, "; SyncDelete", fp); + } + + /* Now print the actual key */ + result = dns_name_print(key->key_name, fp); + if (result != ISC_R_SUCCESS) { + return (dst_key_cleanup(tmpname, fp)); + } + fprintf(fp, " "); + + if (key->key_ttl != 0) { + fprintf(fp, "%u ", key->key_ttl); + } + + isc_buffer_usedregion(&classb, &r); + if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) { + return (dst_key_cleanup(tmpname, fp)); + } + + if ((type & DST_TYPE_KEY) != 0) { + fprintf(fp, " KEY "); + } else { + fprintf(fp, " DNSKEY "); + } + + isc_buffer_usedregion(&textb, &r); + if ((unsigned)fwrite(r.base, 1, r.length, fp) != r.length) { + return (dst_key_cleanup(tmpname, fp)); + } + + fputc('\n', fp); + + return (dst_key_close(tmpname, fp, filename)); +} + +static isc_result_t +buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, + unsigned int type, const char *directory, isc_buffer_t *out) { + const char *suffix = ""; + isc_result_t result; + + REQUIRE(out != NULL); + + if ((type & DST_TYPE_PRIVATE) != 0) { + suffix = ".private"; + } else if ((type & DST_TYPE_PUBLIC) != 0) { + suffix = ".key"; + } else if ((type & DST_TYPE_STATE) != 0) { + suffix = ".state"; + } else if ((type & DST_TYPE_TEMPLATE) != 0) { + suffix = ".XXXXXX"; + } + + if (directory != NULL) { + if (isc_buffer_availablelength(out) < strlen(directory)) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(out, directory); + if (strlen(directory) > 0U && + directory[strlen(directory) - 1] != '/') + { + isc_buffer_putstr(out, "/"); + } + } + if (isc_buffer_availablelength(out) < 1) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(out, "K"); + result = dns_name_tofilenametext(name, false, out); + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (isc_buffer_printf(out, "+%03d+%05d%s", alg, id, suffix)); +} + +static isc_result_t +computeid(dst_key_t *key) { + isc_buffer_t dnsbuf; + unsigned char dns_array[DST_KEY_MAXSIZE]; + isc_region_t r; + isc_result_t ret; + + isc_buffer_init(&dnsbuf, dns_array, sizeof(dns_array)); + ret = dst_key_todns(key, &dnsbuf); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + isc_buffer_usedregion(&dnsbuf, &r); + key->key_id = dst_region_computeid(&r); + key->key_rid = dst_region_computerid(&r); + return (ISC_R_SUCCESS); +} + +static isc_result_t +frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { + dst_key_t *key; + isc_result_t ret; + + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(source != NULL); + REQUIRE(mctx != NULL); + REQUIRE(keyp != NULL && *keyp == NULL); + + key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx); + if (key == NULL) { + return (ISC_R_NOMEMORY); + } + + if (isc_buffer_remaininglength(source) > 0) { + ret = algorithm_status(alg); + if (ret != ISC_R_SUCCESS) { + dst_key_free(&key); + return (ret); + } + if (key->func->fromdns == NULL) { + dst_key_free(&key); + return (DST_R_UNSUPPORTEDALG); + } + + ret = key->func->fromdns(key, source); + if (ret != ISC_R_SUCCESS) { + dst_key_free(&key); + return (ret); + } + } + + *keyp = key; + return (ISC_R_SUCCESS); +} + +static isc_result_t +algorithm_status(unsigned int alg) { + REQUIRE(dst_initialized); + + if (dst_algorithm_supported(alg)) { + return (ISC_R_SUCCESS); + } + return (DST_R_UNSUPPORTEDALG); +} + +static isc_result_t +addsuffix(char *filename, int len, const char *odirname, const char *ofilename, + const char *suffix) { + int olen = strlen(ofilename); + int n; + + if (olen > 1 && ofilename[olen - 1] == '.') { + olen -= 1; + } else if (olen > 8 && strcmp(ofilename + olen - 8, ".private") == 0) { + olen -= 8; + } else if (olen > 4 && strcmp(ofilename + olen - 4, ".key") == 0) { + olen -= 4; + } + + if (odirname == NULL) { + n = snprintf(filename, len, "%.*s%s", olen, ofilename, suffix); + } else { + n = snprintf(filename, len, "%s/%.*s%s", odirname, olen, + ofilename, suffix); + } + if (n < 0) { + return (ISC_R_FAILURE); + } + if (n >= len) { + return (ISC_R_NOSPACE); + } + return (ISC_R_SUCCESS); +} + +isc_buffer_t * +dst_key_tkeytoken(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_tkeytoken); +} + +/* + * A key is considered unused if it does not have any timing metadata set + * other than "Created". + * + */ +bool +dst_key_is_unused(dst_key_t *key) { + isc_stdtime_t val; + dst_key_state_t st; + int state_type; + bool state_type_set; + + REQUIRE(VALID_KEY(key)); + + /* + * None of the key timing metadata, except Created, may be set. Key + * state times may be set only if their respective state is HIDDEN. + */ + for (int i = 0; i < DST_MAX_TIMES + 1; i++) { + state_type_set = false; + + switch (i) { + case DST_TIME_CREATED: + break; + case DST_TIME_DNSKEY: + state_type = DST_KEY_DNSKEY; + state_type_set = true; + break; + case DST_TIME_ZRRSIG: + state_type = DST_KEY_ZRRSIG; + state_type_set = true; + break; + case DST_TIME_KRRSIG: + state_type = DST_KEY_KRRSIG; + state_type_set = true; + break; + case DST_TIME_DS: + state_type = DST_KEY_DS; + state_type_set = true; + break; + default: + break; + } + + /* Created is fine. */ + if (i == DST_TIME_CREATED) { + continue; + } + /* No such timing metadata found, that is fine too. */ + if (dst_key_gettime(key, i, &val) == ISC_R_NOTFOUND) { + continue; + } + /* + * Found timing metadata and it is not related to key states. + * This key is used. + */ + if (!state_type_set) { + return (false); + } + /* + * If the state is not HIDDEN, the key is in use. + * If the state is not set, this is odd and we default to NA. + */ + if (dst_key_getstate(key, state_type, &st) != ISC_R_SUCCESS) { + st = DST_KEY_STATE_NA; + } + if (st != DST_KEY_STATE_HIDDEN) { + return (false); + } + } + /* This key is unused. */ + return (true); +} + +isc_result_t +dst_key_role(dst_key_t *key, bool *ksk, bool *zsk) { + bool k = false, z = false; + isc_result_t result, ret = ISC_R_SUCCESS; + + if (ksk != NULL) { + result = dst_key_getbool(key, DST_BOOL_KSK, &k); + if (result == ISC_R_SUCCESS) { + *ksk = k; + } else { + *ksk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) != 0); + ret = result; + } + } + + if (zsk != NULL) { + result = dst_key_getbool(key, DST_BOOL_ZSK, &z); + if (result == ISC_R_SUCCESS) { + *zsk = z; + } else { + *zsk = ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0); + ret = result; + } + } + return (ret); +} + +/* Hints on key whether it can be published and/or used for signing. */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, + isc_stdtime_t *publish) { + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_PUBLISH, &when); + if (result == ISC_R_SUCCESS) { + *publish = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is RUMOURED or OMNIPRESENT, it means it + * should be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + } + + return (state_ok && time_ok); +} + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now) { + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool ds_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + time_ok = (when <= now); + } + + (void)dst_key_role(key, &ksk, &zsk); + + /* Check key states: + * KSK: If the DS is RUMOURED or OMNIPRESENT the key is considered + * active. + */ + if (ksk) { + result = dst_key_getstate(key, DST_KEY_DS, &state); + if (result == ISC_R_SUCCESS) { + ds_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + /* + * ZSK: If the ZRRSIG state is RUMOURED or OMNIPRESENT, it means the + * key is active. + */ + if (zsk) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return (ds_ok && zrrsig_ok && time_ok && !inactive); +} + +bool +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, + isc_stdtime_t *active) { + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool ksk = false, zsk = false, inactive = false; + bool krrsig_ok = true, zrrsig_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &when); + if (result == ISC_R_SUCCESS) { + inactive = (when <= now); + } + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &when); + if (result == ISC_R_SUCCESS) { + *active = when; + time_ok = (when <= now); + } + + (void)dst_key_role(key, &ksk, &zsk); + + /* Check key states: + * If the RRSIG state is RUMOURED or OMNIPRESENT, it means the key + * is active. + */ + if (ksk && role == DST_BOOL_KSK) { + result = dst_key_getstate(key, DST_KEY_KRRSIG, &state); + if (result == ISC_R_SUCCESS) { + krrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } else if (zsk && role == DST_BOOL_ZSK) { + result = dst_key_getstate(key, DST_KEY_ZRRSIG, &state); + if (result == ISC_R_SUCCESS) { + zrrsig_ok = ((state == DST_KEY_STATE_RUMOURED) || + (state == DST_KEY_STATE_OMNIPRESENT)); + /* + * Key states trump timing metadata. + * Ignore inactive time. + */ + time_ok = true; + inactive = false; + } + } + return (krrsig_ok && zrrsig_ok && time_ok && !inactive); +} + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke) { + isc_result_t result; + isc_stdtime_t when = 0; + bool time_ok = false; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_gettime(key, DST_TIME_REVOKE, &when); + if (result == ISC_R_SUCCESS) { + *revoke = when; + time_ok = (when <= now); + } + + return (time_ok); +} + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove) { + dst_key_state_t state; + isc_result_t result; + isc_stdtime_t when = 0; + bool state_ok = true, time_ok = false; + + REQUIRE(VALID_KEY(key)); + + if (dst_key_is_unused(key)) { + /* This key was never used. */ + return (false); + } + + result = dst_key_gettime(key, DST_TIME_DELETE, &when); + if (result == ISC_R_SUCCESS) { + *remove = when; + time_ok = (when <= now); + } + + /* Check key states: + * If the DNSKEY state is UNRETENTIVE or HIDDEN, it means the key + * should not be published. + */ + result = dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (result == ISC_R_SUCCESS) { + state_ok = ((state == DST_KEY_STATE_UNRETENTIVE) || + (state == DST_KEY_STATE_HIDDEN)); + /* + * Key states trump timing metadata. + * Ignore delete time. + */ + time_ok = true; + } + + return (state_ok && time_ok); +} + +dst_key_state_t +dst_key_goal(dst_key_t *key) { + dst_key_state_t state; + isc_result_t result; + + REQUIRE(VALID_KEY(key)); + + result = dst_key_getstate(key, DST_KEY_GOAL, &state); + if (result == ISC_R_SUCCESS) { + return (state); + } + return (DST_KEY_STATE_HIDDEN); +} + +bool +dst_key_haskasp(dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + return (key->kasp); +} + +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from) { + dst_key_state_t state; + isc_stdtime_t when; + uint32_t num; + bool yesno; + isc_result_t result; + + REQUIRE(VALID_KEY(to)); + REQUIRE(VALID_KEY(from)); + + for (int i = 0; i < DST_MAX_TIMES + 1; i++) { + result = dst_key_gettime(from, i, &when); + if (result == ISC_R_SUCCESS) { + dst_key_settime(to, i, when); + } else { + dst_key_unsettime(to, i); + } + } + + for (int i = 0; i < DST_MAX_NUMERIC + 1; i++) { + result = dst_key_getnum(from, i, &num); + if (result == ISC_R_SUCCESS) { + dst_key_setnum(to, i, num); + } else { + dst_key_unsetnum(to, i); + } + } + + for (int i = 0; i < DST_MAX_BOOLEAN + 1; i++) { + result = dst_key_getbool(from, i, &yesno); + if (result == ISC_R_SUCCESS) { + dst_key_setbool(to, i, yesno); + } else { + dst_key_unsetbool(to, i); + } + } + + for (int i = 0; i < DST_MAX_KEYSTATES + 1; i++) { + result = dst_key_getstate(from, i, &state); + if (result == ISC_R_SUCCESS) { + dst_key_setstate(to, i, state); + } else { + dst_key_unsetstate(to, i); + } + } + + dst_key_setmodified(to, dst_key_ismodified(from)); +} + +const char * +dst_hmac_algorithm_totext(dst_algorithm_t alg) { + switch (alg) { + case DST_ALG_HMACMD5: + return ("hmac-md5"); + case DST_ALG_HMACSHA1: + return ("hmac-sha1"); + case DST_ALG_HMACSHA224: + return ("hmac-sha224"); + case DST_ALG_HMACSHA256: + return ("hmac-sha256"); + case DST_ALG_HMACSHA384: + return ("hmac-sha384"); + case DST_ALG_HMACSHA512: + return ("hmac-sha512"); + default: + return ("unknown"); + } +} diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h new file mode 100644 index 0000000..e8eca94 --- /dev/null +++ b/lib/dns/dst_internal.h @@ -0,0 +1,254 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Portions Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +#define KEY_MAGIC ISC_MAGIC('D', 'S', 'T', 'K') +#define CTX_MAGIC ISC_MAGIC('D', 'S', 'T', 'C') + +#define VALID_KEY(x) ISC_MAGIC_VALID(x, KEY_MAGIC) +#define VALID_CTX(x) ISC_MAGIC_VALID(x, CTX_MAGIC) + +/*** + *** Types + ***/ + +typedef struct dst_func dst_func_t; + +typedef struct dst_hmac_key dst_hmac_key_t; + +/*% + * Indicate whether a DST context will be used for signing + * or for verification + */ +typedef enum { DO_SIGN, DO_VERIFY } dst_use_t; + +/*% DST Key Structure */ +struct dst_key { + unsigned int magic; + isc_refcount_t refs; + isc_mutex_t mdlock; /*%< lock for read/write metadata */ + dns_name_t *key_name; /*%< name of the key */ + unsigned int key_size; /*%< size of the key in bits */ + unsigned int key_proto; /*%< protocols this key is used for + * */ + unsigned int key_alg; /*%< algorithm of the key */ + uint32_t key_flags; /*%< flags of the public key */ + uint16_t key_id; /*%< identifier of the key */ + uint16_t key_rid; /*%< identifier of the key when + * revoked */ + uint16_t key_bits; /*%< hmac digest bits */ + dns_rdataclass_t key_class; /*%< class of the key record */ + dns_ttl_t key_ttl; /*%< default/initial dnskey ttl */ + isc_mem_t *mctx; /*%< memory context */ + char *engine; /*%< engine name (HSM) */ + char *label; /*%< engine label (HSM) */ + union { + void *generic; + dns_gss_ctx_id_t gssctx; + DH *dh; + EVP_PKEY *pkey; + dst_hmac_key_t *hmac_key; + } keydata; /*%< pointer to key in crypto pkg fmt */ + + isc_stdtime_t times[DST_MAX_TIMES + 1]; /*%< timing metadata */ + bool timeset[DST_MAX_TIMES + 1]; /*%< data set? */ + + uint32_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata + * */ + bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ + + bool bools[DST_MAX_BOOLEAN + 1]; /*%< boolean metadata + * */ + bool boolset[DST_MAX_BOOLEAN + 1]; /*%< data set? */ + + dst_key_state_t keystates[DST_MAX_KEYSTATES + 1]; /*%< key states + * */ + bool keystateset[DST_MAX_KEYSTATES + 1]; /*%< data + * set? */ + + bool kasp; /*%< key has kasp state */ + bool inactive; /*%< private key not present as it is + * inactive */ + bool external; /*%< external key */ + bool modified; /*%< set to true if key file metadata has changed */ + + int fmt_major; /*%< private key format, major version + * */ + int fmt_minor; /*%< private key format, minor version + * */ + + dst_func_t *func; /*%< crypto package specific functions */ + isc_buffer_t *key_tkeytoken; /*%< TKEY token data */ +}; + +struct dst_context { + unsigned int magic; + dst_use_t use; + dst_key_t *key; + isc_mem_t *mctx; + isc_logcategory_t *category; + union { + void *generic; + dst_gssapi_signverifyctx_t *gssctx; + isc_hmac_t *hmac_ctx; + EVP_MD_CTX *evp_md_ctx; + } ctxdata; +}; + +struct dst_func { + /* + * Context functions + */ + isc_result_t (*createctx)(dst_key_t *key, dst_context_t *dctx); + isc_result_t (*createctx2)(dst_key_t *key, int maxbits, + dst_context_t *dctx); + void (*destroyctx)(dst_context_t *dctx); + isc_result_t (*adddata)(dst_context_t *dctx, const isc_region_t *data); + + /* + * Key operations + */ + isc_result_t (*sign)(dst_context_t *dctx, isc_buffer_t *sig); + isc_result_t (*verify)(dst_context_t *dctx, const isc_region_t *sig); + isc_result_t (*verify2)(dst_context_t *dctx, int maxbits, + const isc_region_t *sig); + isc_result_t (*computesecret)(const dst_key_t *pub, + const dst_key_t *priv, + isc_buffer_t *secret); + bool (*compare)(const dst_key_t *key1, const dst_key_t *key2); + bool (*paramcompare)(const dst_key_t *key1, const dst_key_t *key2); + isc_result_t (*generate)(dst_key_t *key, int parms, + void (*callback)(int)); + bool (*isprivate)(const dst_key_t *key); + void (*destroy)(dst_key_t *key); + + /* conversion functions */ + isc_result_t (*todns)(const dst_key_t *key, isc_buffer_t *data); + isc_result_t (*fromdns)(dst_key_t *key, isc_buffer_t *data); + isc_result_t (*tofile)(const dst_key_t *key, const char *directory); + isc_result_t (*parse)(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub); + + /* cleanup */ + void (*cleanup)(void); + + isc_result_t (*fromlabel)(dst_key_t *key, const char *engine, + const char *label, const char *pin); + isc_result_t (*dump)(dst_key_t *key, isc_mem_t *mctx, char **buffer, + int *length); + isc_result_t (*restore)(dst_key_t *key, const char *keystr); +}; + +/*% + * Initializers + */ +isc_result_t +dst__openssl_init(const char *engine); + +isc_result_t +dst__hmacmd5_init(struct dst_func **funcp); +isc_result_t +dst__hmacsha1_init(struct dst_func **funcp); +isc_result_t +dst__hmacsha224_init(struct dst_func **funcp); +isc_result_t +dst__hmacsha256_init(struct dst_func **funcp); +isc_result_t +dst__hmacsha384_init(struct dst_func **funcp); +isc_result_t +dst__hmacsha512_init(struct dst_func **funcp); +isc_result_t +dst__openssldh_init(struct dst_func **funcp); +isc_result_t +dst__opensslrsa_init(struct dst_func **funcp, unsigned char algorithm); +isc_result_t +dst__opensslecdsa_init(struct dst_func **funcp); +#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 +isc_result_t +dst__openssleddsa_init(struct dst_func **funcp); +#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */ +#if HAVE_GSSAPI +isc_result_t +dst__gssapi_init(struct dst_func **funcp); +#endif /* HAVE_GSSAPI*/ + +/*% + * Destructors + */ +void +dst__openssl_destroy(void); + +/*% + * Memory allocators using the DST memory pool. + */ +void * +dst__mem_alloc(size_t size); +void +dst__mem_free(void *ptr); +void * +dst__mem_realloc(void *ptr, size_t size); + +/*% + * Secure private file handling + */ +FILE * +dst_key_open(char *tmpname, mode_t mode); +isc_result_t +dst_key_close(char *tmpname, FILE *fp, char *filename); +isc_result_t +dst_key_cleanup(char *tmpname, FILE *fp); + +ISC_LANG_ENDDECLS + +/*! \file */ diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h new file mode 100644 index 0000000..819af0f --- /dev/null +++ b/lib/dns/dst_openssl.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if !HAVE_BN_GENCB_NEW +/* + * These are new in OpenSSL 1.1.0. BN_GENCB _cb needs to be declared in + * the function like this before the BN_GENCB_new call: + * + * #if !HAVE_BN_GENCB_NEW + * _cb; + * #endif + */ +#define BN_GENCB_free(x) ((void)0) +#define BN_GENCB_new() (&_cb) +#define BN_GENCB_get_arg(x) ((x)->arg) +#endif /* !HAVE_BN_GENCB_NEW */ + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +/* + * EVP_dss1() is a version of EVP_sha1() that was needed prior to + * 1.1.0 because there was a link between digests and signing algorithms; + * the link has been eliminated and EVP_sha1() can be used now instead. + */ +#define EVP_dss1 EVP_sha1 +#endif /* if OPENSSL_VERSION_NUMBER >= 0x10100000L */ + +ISC_LANG_BEGINDECLS + +isc_result_t +dst__openssl_toresult(isc_result_t fallback); + +isc_result_t +dst__openssl_toresult2(const char *funcname, isc_result_t fallback); + +isc_result_t +dst__openssl_toresult3(isc_logcategory_t *category, const char *funcname, + isc_result_t fallback); + +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +ENGINE * +dst__openssl_getengine(const char *engine); +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c new file mode 100644 index 0000000..a3ff011 --- /dev/null +++ b/lib/dns/dst_parse.c @@ -0,0 +1,798 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dst_parse.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "isc/result.h" + +#define DST_AS_STR(t) ((t).value.as_textregion.base) + +#define PRIVATE_KEY_STR "Private-key-format:" +#define ALGORITHM_STR "Algorithm:" + +#define TIMING_NTAGS (DST_MAX_TIMES + 1) +static const char *timetags[TIMING_NTAGS] = { + "Created:", "Publish:", "Activate:", "Revoke:", + "Inactive:", "Delete:", "DSPublish:", "SyncPublish:", + "SyncDelete:", NULL, NULL, NULL, + NULL +}; + +#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) +static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", "Successor:", "MaxTTL:", "RollPeriod:", NULL, NULL, NULL +}; + +struct parse_map { + const int value; + const char *tag; +}; + +static struct parse_map map[] = { { TAG_RSA_MODULUS, "Modulus:" }, + { TAG_RSA_PUBLICEXPONENT, "PublicExponent:" }, + { TAG_RSA_PRIVATEEXPONENT, "PrivateExponent" + ":" }, + { TAG_RSA_PRIME1, "Prime1:" }, + { TAG_RSA_PRIME2, "Prime2:" }, + { TAG_RSA_EXPONENT1, "Exponent1:" }, + { TAG_RSA_EXPONENT2, "Exponent2:" }, + { TAG_RSA_COEFFICIENT, "Coefficient:" }, + { TAG_RSA_ENGINE, "Engine:" }, + { TAG_RSA_LABEL, "Label:" }, + + { TAG_DH_PRIME, "Prime(p):" }, + { TAG_DH_GENERATOR, "Generator(g):" }, + { TAG_DH_PRIVATE, "Private_value(x):" }, + { TAG_DH_PUBLIC, "Public_value(y):" }, + + { TAG_ECDSA_PRIVATEKEY, "PrivateKey:" }, + { TAG_ECDSA_ENGINE, "Engine:" }, + { TAG_ECDSA_LABEL, "Label:" }, + + { TAG_EDDSA_PRIVATEKEY, "PrivateKey:" }, + { TAG_EDDSA_ENGINE, "Engine:" }, + { TAG_EDDSA_LABEL, "Label:" }, + + { TAG_HMACMD5_KEY, "Key:" }, + { TAG_HMACMD5_BITS, "Bits:" }, + + { TAG_HMACSHA1_KEY, "Key:" }, + { TAG_HMACSHA1_BITS, "Bits:" }, + + { TAG_HMACSHA224_KEY, "Key:" }, + { TAG_HMACSHA224_BITS, "Bits:" }, + + { TAG_HMACSHA256_KEY, "Key:" }, + { TAG_HMACSHA256_BITS, "Bits:" }, + + { TAG_HMACSHA384_KEY, "Key:" }, + { TAG_HMACSHA384_BITS, "Bits:" }, + + { TAG_HMACSHA512_KEY, "Key:" }, + { TAG_HMACSHA512_BITS, "Bits:" }, + + { 0, NULL } }; + +static int +find_value(const char *s, const unsigned int alg) { + int i; + + for (i = 0; map[i].tag != NULL; i++) { + if (strcasecmp(s, map[i].tag) == 0 && + (TAG_ALG(map[i].value) == alg)) + { + return (map[i].value); + } + } + return (-1); +} + +static const char * +find_tag(const int value) { + int i; + + for (i = 0;; i++) { + if (map[i].tag == NULL) { + return (NULL); + } else if (value == map[i].value) { + return (map[i].tag); + } + } +} + +static int +find_metadata(const char *s, const char *tags[], int ntags) { + int i; + + for (i = 0; i < ntags; i++) { + if (tags[i] != NULL && strcasecmp(s, tags[i]) == 0) { + return (i); + } + } + + return (-1); +} + +static int +find_timedata(const char *s) { + return (find_metadata(s, timetags, TIMING_NTAGS)); +} + +static int +find_numericdata(const char *s) { + return (find_metadata(s, numerictags, NUMERIC_NTAGS)); +} + +static int +check_rsa(const dst_private_t *priv, bool external) { + int i, j; + bool have[RSA_NTAGS]; + bool ok; + unsigned int mask; + + if (external) { + return ((priv->nelements == 0) ? 0 : -1); + } + + for (i = 0; i < RSA_NTAGS; i++) { + have[i] = false; + } + + for (j = 0; j < priv->nelements; j++) { + for (i = 0; i < RSA_NTAGS; i++) { + if (priv->elements[j].tag == TAG(DST_ALG_RSA, i)) { + break; + } + } + if (i == RSA_NTAGS) { + return (-1); + } + have[i] = true; + } + + mask = (1ULL << TAG_SHIFT) - 1; + + if (have[TAG_RSA_ENGINE & mask]) { + ok = have[TAG_RSA_MODULUS & mask] && + have[TAG_RSA_PUBLICEXPONENT & mask] && + have[TAG_RSA_LABEL & mask]; + } else { + ok = have[TAG_RSA_MODULUS & mask] && + have[TAG_RSA_PUBLICEXPONENT & mask] && + have[TAG_RSA_PRIVATEEXPONENT & mask] && + have[TAG_RSA_PRIME1 & mask] && + have[TAG_RSA_PRIME2 & mask] && + have[TAG_RSA_EXPONENT1 & mask] && + have[TAG_RSA_EXPONENT2 & mask] && + have[TAG_RSA_COEFFICIENT & mask]; + } + return (ok ? 0 : -1); +} + +static int +check_dh(const dst_private_t *priv) { + int i, j; + if (priv->nelements != DH_NTAGS) { + return (-1); + } + for (i = 0; i < DH_NTAGS; i++) { + for (j = 0; j < priv->nelements; j++) { + if (priv->elements[j].tag == TAG(DST_ALG_DH, i)) { + break; + } + } + if (j == priv->nelements) { + return (-1); + } + } + return (0); +} + +static int +check_ecdsa(const dst_private_t *priv, bool external) { + int i, j; + bool have[ECDSA_NTAGS]; + bool ok; + unsigned int mask; + + if (external) { + return ((priv->nelements == 0) ? 0 : -1); + } + + for (i = 0; i < ECDSA_NTAGS; i++) { + have[i] = false; + } + for (j = 0; j < priv->nelements; j++) { + for (i = 0; i < ECDSA_NTAGS; i++) { + if (priv->elements[j].tag == TAG(DST_ALG_ECDSA256, i)) { + break; + } + } + if (i == ECDSA_NTAGS) { + return (-1); + } + have[i] = true; + } + + mask = (1ULL << TAG_SHIFT) - 1; + + if (have[TAG_ECDSA_ENGINE & mask]) { + ok = have[TAG_ECDSA_LABEL & mask]; + } else { + ok = have[TAG_ECDSA_PRIVATEKEY & mask]; + } + return (ok ? 0 : -1); +} + +static int +check_eddsa(const dst_private_t *priv, bool external) { + int i, j; + bool have[EDDSA_NTAGS]; + bool ok; + unsigned int mask; + + if (external) { + return ((priv->nelements == 0) ? 0 : -1); + } + + for (i = 0; i < EDDSA_NTAGS; i++) { + have[i] = false; + } + for (j = 0; j < priv->nelements; j++) { + for (i = 0; i < EDDSA_NTAGS; i++) { + if (priv->elements[j].tag == TAG(DST_ALG_ED25519, i)) { + break; + } + } + if (i == EDDSA_NTAGS) { + return (-1); + } + have[i] = true; + } + + mask = (1ULL << TAG_SHIFT) - 1; + + if (have[TAG_EDDSA_ENGINE & mask]) { + ok = have[TAG_EDDSA_LABEL & mask]; + } else { + ok = have[TAG_EDDSA_PRIVATEKEY & mask]; + } + return (ok ? 0 : -1); +} + +static int +check_hmac_md5(const dst_private_t *priv, bool old) { + int i, j; + + if (priv->nelements != HMACMD5_NTAGS) { + /* + * If this is a good old format and we are accepting + * the old format return success. + */ + if (old && priv->nelements == OLD_HMACMD5_NTAGS && + priv->elements[0].tag == TAG_HMACMD5_KEY) + { + return (0); + } + return (-1); + } + /* + * We must be new format at this point. + */ + for (i = 0; i < HMACMD5_NTAGS; i++) { + for (j = 0; j < priv->nelements; j++) { + if (priv->elements[j].tag == TAG(DST_ALG_HMACMD5, i)) { + break; + } + } + if (j == priv->nelements) { + return (-1); + } + } + return (0); +} + +static int +check_hmac_sha(const dst_private_t *priv, unsigned int ntags, + unsigned int alg) { + unsigned int i, j; + if (priv->nelements != ntags) { + return (-1); + } + for (i = 0; i < ntags; i++) { + for (j = 0; j < priv->nelements; j++) { + if (priv->elements[j].tag == TAG(alg, i)) { + break; + } + } + if (j == priv->nelements) { + return (-1); + } + } + return (0); +} + +static int +check_data(const dst_private_t *priv, const unsigned int alg, bool old, + bool external) { + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (alg) { + case DST_ALG_RSA: + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + return (check_rsa(priv, external)); + case DST_ALG_DH: + return (check_dh(priv)); + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + return (check_ecdsa(priv, external)); + case DST_ALG_ED25519: + case DST_ALG_ED448: + return (check_eddsa(priv, external)); + case DST_ALG_HMACMD5: + return (check_hmac_md5(priv, old)); + case DST_ALG_HMACSHA1: + return (check_hmac_sha(priv, HMACSHA1_NTAGS, alg)); + case DST_ALG_HMACSHA224: + return (check_hmac_sha(priv, HMACSHA224_NTAGS, alg)); + case DST_ALG_HMACSHA256: + return (check_hmac_sha(priv, HMACSHA256_NTAGS, alg)); + case DST_ALG_HMACSHA384: + return (check_hmac_sha(priv, HMACSHA384_NTAGS, alg)); + case DST_ALG_HMACSHA512: + return (check_hmac_sha(priv, HMACSHA512_NTAGS, alg)); + default: + return (DST_R_UNSUPPORTEDALG); + } +} + +void +dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx) { + int i; + + if (priv == NULL) { + return; + } + for (i = 0; i < priv->nelements; i++) { + if (priv->elements[i].data == NULL) { + continue; + } + memset(priv->elements[i].data, 0, MAXFIELDSIZE); + isc_mem_put(mctx, priv->elements[i].data, MAXFIELDSIZE); + } + priv->nelements = 0; +} + +isc_result_t +dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex, + isc_mem_t *mctx, dst_private_t *priv) { + int n = 0, major, minor, check; + isc_buffer_t b; + isc_token_t token; + unsigned char *data = NULL; + unsigned int opt = ISC_LEXOPT_EOL; + isc_stdtime_t when; + isc_result_t ret; + bool external = false; + + REQUIRE(priv != NULL); + + priv->nelements = 0; + memset(priv->elements, 0, sizeof(priv->elements)); + +#define NEXTTOKEN(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret != ISC_R_SUCCESS) \ + goto fail; \ + } while (0) + +#define READLINE(lex, opt, token) \ + do { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret == ISC_R_EOF) \ + break; \ + else if (ret != ISC_R_SUCCESS) \ + goto fail; \ + } while ((*token).type != isc_tokentype_eol) + + /* + * Read the description line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), PRIVATE_KEY_STR) != 0) + { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || (DST_AS_STR(token))[0] != 'v') + { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + if (sscanf(DST_AS_STR(token), "v%d.%d", &major, &minor) != 2) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + if (major > DST_MAJOR_VERSION) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + /* + * Store the private key format version number + */ + dst_key_setprivateformat(key, major, minor); + + READLINE(lex, opt, &token); + + /* + * Read the algorithm line. + */ + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string || + strcmp(DST_AS_STR(token), ALGORITHM_STR) != 0) + { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number || + token.value.as_ulong != (unsigned long)dst_key_alg(key)) + { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + READLINE(lex, opt, &token); + + /* + * Read the key data. + */ + for (n = 0; n < MAXFIELDS; n++) { + int tag; + isc_region_t r; + do { + ret = isc_lex_gettoken(lex, opt, &token); + if (ret == ISC_R_EOF) { + goto done; + } + if (ret != ISC_R_SUCCESS) { + goto fail; + } + } while (token.type == isc_tokentype_eol); + + if (token.type != isc_tokentype_string) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + if (strcmp(DST_AS_STR(token), "External:") == 0) { + external = true; + goto next; + } + + /* Numeric metadata */ + tag = find_numericdata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < NUMERIC_NTAGS); + + NEXTTOKEN(lex, opt | ISC_LEXOPT_NUMBER, &token); + if (token.type != isc_tokentype_number) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + dst_key_setnum(key, tag, token.value.as_ulong); + goto next; + } + + /* Timing metadata */ + tag = find_timedata(DST_AS_STR(token)); + if (tag >= 0) { + INSIST(tag < TIMING_NTAGS); + + NEXTTOKEN(lex, opt, &token); + if (token.type != isc_tokentype_string) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + ret = dns_time32_fromtext(DST_AS_STR(token), &when); + if (ret != ISC_R_SUCCESS) { + goto fail; + } + + dst_key_settime(key, tag, when); + + goto next; + } + + /* Key data */ + tag = find_value(DST_AS_STR(token), alg); + if (tag < 0 && minor > DST_MINOR_VERSION) { + goto next; + } else if (tag < 0) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + priv->elements[n].tag = tag; + + data = isc_mem_get(mctx, MAXFIELDSIZE); + + isc_buffer_init(&b, data, MAXFIELDSIZE); + ret = isc_base64_tobuffer(lex, &b, -1); + if (ret != ISC_R_SUCCESS) { + goto fail; + } + + isc_buffer_usedregion(&b, &r); + priv->elements[n].length = r.length; + priv->elements[n].data = r.base; + priv->nelements++; + + next: + READLINE(lex, opt, &token); + data = NULL; + } + +done: + if (external && priv->nelements != 0) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } + + check = check_data(priv, alg, true, external); + if (check < 0) { + ret = DST_R_INVALIDPRIVATEKEY; + goto fail; + } else if (check != ISC_R_SUCCESS) { + ret = check; + goto fail; + } + + key->external = external; + + return (ISC_R_SUCCESS); + +fail: + dst__privstruct_free(priv, mctx); + if (data != NULL) { + isc_mem_put(mctx, data, MAXFIELDSIZE); + } + + return (ret); +} + +isc_result_t +dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, + const char *directory) { + FILE *fp; + isc_result_t result; + char filename[NAME_MAX]; + char tmpname[NAME_MAX]; + char buffer[MAXFIELDSIZE * 2]; + isc_stdtime_t when; + uint32_t value; + isc_buffer_t b; + isc_buffer_t fileb; + isc_buffer_t tmpb; + isc_region_t r; + int major, minor; + mode_t mode; + int i, ret; + + REQUIRE(priv != NULL); + + ret = check_data(priv, dst_key_alg(key), false, key->external); + if (ret < 0) { + return (DST_R_INVALIDPRIVATEKEY); + } else if (ret != ISC_R_SUCCESS) { + return (ret); + } + + isc_buffer_init(&fileb, filename, sizeof(filename)); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, + &fileb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_file_mode(filename, &mode); + if (result == ISC_R_SUCCESS && mode != (S_IRUSR | S_IWUSR)) { + /* File exists; warn that we are changing its permissions */ + int level; + + level = ISC_LOG_WARNING; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, level, + "Permissions on the file %s " + "have changed from 0%o to 0600 as " + "a result of this operation.", + filename, (unsigned int)mode); + } + + isc_buffer_init(&tmpb, tmpname, sizeof(tmpname)); + result = dst_key_buildfilename(key, DST_TYPE_TEMPLATE, directory, + &tmpb); + if (result != ISC_R_SUCCESS) { + return (result); + } + + fp = dst_key_open(tmpname, S_IRUSR | S_IWUSR); + if (fp == NULL) { + return (DST_R_WRITEERROR); + } + + dst_key_getprivateformat(key, &major, &minor); + if (major == 0 && minor == 0) { + major = DST_MAJOR_VERSION; + minor = DST_MINOR_VERSION; + } + + /* XXXDCL return value should be checked for full filesystem */ + fprintf(fp, "%s v%d.%d\n", PRIVATE_KEY_STR, major, minor); + + fprintf(fp, "%s %u ", ALGORITHM_STR, dst_key_alg(key)); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (dst_key_alg(key)) { + case DST_ALG_DH: + fprintf(fp, "(DH)\n"); + break; + case DST_ALG_RSASHA1: + fprintf(fp, "(RSASHA1)\n"); + break; + case DST_ALG_NSEC3RSASHA1: + fprintf(fp, "(NSEC3RSASHA1)\n"); + break; + case DST_ALG_RSASHA256: + fprintf(fp, "(RSASHA256)\n"); + break; + case DST_ALG_RSASHA512: + fprintf(fp, "(RSASHA512)\n"); + break; + case DST_ALG_ECDSA256: + fprintf(fp, "(ECDSAP256SHA256)\n"); + break; + case DST_ALG_ECDSA384: + fprintf(fp, "(ECDSAP384SHA384)\n"); + break; + case DST_ALG_ED25519: + fprintf(fp, "(ED25519)\n"); + break; + case DST_ALG_ED448: + fprintf(fp, "(ED448)\n"); + break; + case DST_ALG_HMACMD5: + fprintf(fp, "(HMAC_MD5)\n"); + break; + case DST_ALG_HMACSHA1: + fprintf(fp, "(HMAC_SHA1)\n"); + break; + case DST_ALG_HMACSHA224: + fprintf(fp, "(HMAC_SHA224)\n"); + break; + case DST_ALG_HMACSHA256: + fprintf(fp, "(HMAC_SHA256)\n"); + break; + case DST_ALG_HMACSHA384: + fprintf(fp, "(HMAC_SHA384)\n"); + break; + case DST_ALG_HMACSHA512: + fprintf(fp, "(HMAC_SHA512)\n"); + break; + default: + fprintf(fp, "(?)\n"); + break; + } + + for (i = 0; i < priv->nelements; i++) { + const char *s; + + s = find_tag(priv->elements[i].tag); + + r.base = priv->elements[i].data; + r.length = priv->elements[i].length; + isc_buffer_init(&b, buffer, sizeof(buffer)); + result = isc_base64_totext(&r, sizeof(buffer), "", &b); + if (result != ISC_R_SUCCESS) { + return (dst_key_cleanup(tmpname, fp)); + } + isc_buffer_usedregion(&b, &r); + + fprintf(fp, "%s %.*s\n", s, (int)r.length, r.base); + } + + if (key->external) { + fprintf(fp, "External:\n"); + } + + /* Add the metadata tags */ + if (major > 1 || (major == 1 && minor >= 3)) { + for (i = 0; i < NUMERIC_NTAGS; i++) { + result = dst_key_getnum(key, i, &value); + if (result != ISC_R_SUCCESS) { + continue; + } + if (numerictags[i] != NULL) { + fprintf(fp, "%s %u\n", numerictags[i], value); + } + } + for (i = 0; i < TIMING_NTAGS; i++) { + result = dst_key_gettime(key, i, &when); + if (result != ISC_R_SUCCESS) { + continue; + } + + isc_buffer_init(&b, buffer, sizeof(buffer)); + result = dns_time32_totext(when, &b); + if (result != ISC_R_SUCCESS) { + return (dst_key_cleanup(tmpname, fp)); + } + + isc_buffer_usedregion(&b, &r); + + if (timetags[i] != NULL) { + fprintf(fp, "%s %.*s\n", timetags[i], + (int)r.length, r.base); + } + } + } + + result = dst_key_close(tmpname, fp, filename); + return (result); +} + +/*! \file */ diff --git a/lib/dns/dst_parse.h b/lib/dns/dst_parse.h new file mode 100644 index 0000000..cc12e9b --- /dev/null +++ b/lib/dns/dst_parse.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ +#pragma once + +#include + +#include + +#define MAXFIELDSIZE 512 + +/* + * Maximum number of fields in a private file is 18 (12 algorithm- + * specific fields for RSA, plus 6 generic fields). + */ +#define MAXFIELDS 12 + 6 + +#define TAG_SHIFT 4 +#define TAG_ALG(tag) ((unsigned int)(tag) >> TAG_SHIFT) +#define TAG(alg, off) (((alg) << TAG_SHIFT) + (off)) + +/* These are used by RSA-SHA1, RSASHA256 and RSASHA512 */ +#define RSA_NTAGS 11 +#define TAG_RSA_MODULUS ((DST_ALG_RSA << TAG_SHIFT) + 0) +#define TAG_RSA_PUBLICEXPONENT ((DST_ALG_RSA << TAG_SHIFT) + 1) +#define TAG_RSA_PRIVATEEXPONENT ((DST_ALG_RSA << TAG_SHIFT) + 2) +#define TAG_RSA_PRIME1 ((DST_ALG_RSA << TAG_SHIFT) + 3) +#define TAG_RSA_PRIME2 ((DST_ALG_RSA << TAG_SHIFT) + 4) +#define TAG_RSA_EXPONENT1 ((DST_ALG_RSA << TAG_SHIFT) + 5) +#define TAG_RSA_EXPONENT2 ((DST_ALG_RSA << TAG_SHIFT) + 6) +#define TAG_RSA_COEFFICIENT ((DST_ALG_RSA << TAG_SHIFT) + 7) +#define TAG_RSA_ENGINE ((DST_ALG_RSA << TAG_SHIFT) + 8) +#define TAG_RSA_LABEL ((DST_ALG_RSA << TAG_SHIFT) + 9) + +#define DH_NTAGS 4 +#define TAG_DH_PRIME ((DST_ALG_DH << TAG_SHIFT) + 0) +#define TAG_DH_GENERATOR ((DST_ALG_DH << TAG_SHIFT) + 1) +#define TAG_DH_PRIVATE ((DST_ALG_DH << TAG_SHIFT) + 2) +#define TAG_DH_PUBLIC ((DST_ALG_DH << TAG_SHIFT) + 3) + +#define ECDSA_NTAGS 4 +#define TAG_ECDSA_PRIVATEKEY ((DST_ALG_ECDSA256 << TAG_SHIFT) + 0) +#define TAG_ECDSA_ENGINE ((DST_ALG_ECDSA256 << TAG_SHIFT) + 1) +#define TAG_ECDSA_LABEL ((DST_ALG_ECDSA256 << TAG_SHIFT) + 2) + +#define EDDSA_NTAGS 4 +#define TAG_EDDSA_PRIVATEKEY ((DST_ALG_ED25519 << TAG_SHIFT) + 0) +#define TAG_EDDSA_ENGINE ((DST_ALG_ED25519 << TAG_SHIFT) + 1) +#define TAG_EDDSA_LABEL ((DST_ALG_ED25519 << TAG_SHIFT) + 2) + +#define OLD_HMACMD5_NTAGS 1 +#define HMACMD5_NTAGS 2 +#define TAG_HMACMD5_KEY ((DST_ALG_HMACMD5 << TAG_SHIFT) + 0) +#define TAG_HMACMD5_BITS ((DST_ALG_HMACMD5 << TAG_SHIFT) + 1) + +#define HMACSHA1_NTAGS 2 +#define TAG_HMACSHA1_KEY ((DST_ALG_HMACSHA1 << TAG_SHIFT) + 0) +#define TAG_HMACSHA1_BITS ((DST_ALG_HMACSHA1 << TAG_SHIFT) + 1) + +#define HMACSHA224_NTAGS 2 +#define TAG_HMACSHA224_KEY ((DST_ALG_HMACSHA224 << TAG_SHIFT) + 0) +#define TAG_HMACSHA224_BITS ((DST_ALG_HMACSHA224 << TAG_SHIFT) + 1) + +#define HMACSHA256_NTAGS 2 +#define TAG_HMACSHA256_KEY ((DST_ALG_HMACSHA256 << TAG_SHIFT) + 0) +#define TAG_HMACSHA256_BITS ((DST_ALG_HMACSHA256 << TAG_SHIFT) + 1) + +#define HMACSHA384_NTAGS 2 +#define TAG_HMACSHA384_KEY ((DST_ALG_HMACSHA384 << TAG_SHIFT) + 0) +#define TAG_HMACSHA384_BITS ((DST_ALG_HMACSHA384 << TAG_SHIFT) + 1) + +#define HMACSHA512_NTAGS 2 +#define TAG_HMACSHA512_KEY ((DST_ALG_HMACSHA512 << TAG_SHIFT) + 0) +#define TAG_HMACSHA512_BITS ((DST_ALG_HMACSHA512 << TAG_SHIFT) + 1) + +struct dst_private_element { + unsigned short tag; + unsigned short length; + unsigned char *data; +}; + +typedef struct dst_private_element dst_private_element_t; + +struct dst_private { + unsigned short nelements; + dst_private_element_t elements[MAXFIELDS]; +}; + +typedef struct dst_private dst_private_t; + +ISC_LANG_BEGINDECLS + +void +dst__privstruct_free(dst_private_t *priv, isc_mem_t *mctx); + +isc_result_t +dst__privstruct_parse(dst_key_t *key, unsigned int alg, isc_lex_t *lex, + isc_mem_t *mctx, dst_private_t *priv); + +isc_result_t +dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, + const char *directory); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/dyndb.c b/lib/dns/dyndb.c new file mode 100644 index 0000000..6e3c7c3 --- /dev/null +++ b/lib/dns/dyndb.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +typedef struct dyndb_implementation dyndb_implementation_t; +struct dyndb_implementation { + isc_mem_t *mctx; + uv_lib_t handle; + dns_dyndb_register_t *register_func; + dns_dyndb_destroy_t *destroy_func; + char *name; + void *inst; + LINK(dyndb_implementation_t) link; +}; + +/* + * List of dyndb implementations. Locked by dyndb_lock. + * + * These are stored here so they can be cleaned up on shutdown. + * (The order in which they are stored is not important.) + */ +static LIST(dyndb_implementation_t) dyndb_implementations; + +/* Locks dyndb_implementations. */ +static isc_mutex_t dyndb_lock; +static isc_once_t once = ISC_ONCE_INIT; + +static void +dyndb_initialize(void) { + isc_mutex_init(&dyndb_lock); + INIT_LIST(dyndb_implementations); +} + +static dyndb_implementation_t * +impfind(const char *name) { + dyndb_implementation_t *imp; + + for (imp = ISC_LIST_HEAD(dyndb_implementations); imp != NULL; + imp = ISC_LIST_NEXT(imp, link)) + { + if (strcasecmp(name, imp->name) == 0) { + return (imp); + } + } + return (NULL); +} + +static isc_result_t +load_symbol(uv_lib_t *handle, const char *filename, const char *symbol_name, + void **symbolp) { + void *symbol; + int r; + + REQUIRE(handle != NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + r = uv_dlsym(handle, symbol_name, &symbol); + if (r != 0) { + const char *errmsg = uv_dlerror(handle); + if (errmsg == NULL) { + errmsg = "returned function pointer is NULL"; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "failed to lookup symbol %s in " + "DynDB module '%s': %s", + symbol_name, filename, errmsg); + return (ISC_R_FAILURE); + } + + *symbolp = symbol; + + return (ISC_R_SUCCESS); +} + +static void +unload_library(dyndb_implementation_t **impp); + +static isc_result_t +load_library(isc_mem_t *mctx, const char *filename, const char *instname, + dyndb_implementation_t **impp) { + isc_result_t result; + dyndb_implementation_t *imp = NULL; + dns_dyndb_version_t *version_func = NULL; + int version; + int r; + + REQUIRE(impp != NULL && *impp == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, + ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'", + instname, filename); + + imp = isc_mem_get(mctx, sizeof(*imp)); + memset(imp, 0, sizeof(*imp)); + isc_mem_attach(mctx, &imp->mctx); + + imp->name = isc_mem_strdup(imp->mctx, instname); + + INIT_LINK(imp, link); + + r = uv_dlopen(filename, &imp->handle); + if (r != 0) { + const char *errmsg = uv_dlerror(&imp->handle); + if (errmsg == NULL) { + errmsg = "unknown error"; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "failed to dlopen() DynDB instance '%s' driver " + "'%s': %s", + instname, filename, errmsg); + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(&imp->handle, filename, "dyndb_version", + (void **)&version_func)); + + version = version_func(NULL); + if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) || + version > DNS_DYNDB_VERSION) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "driver API version mismatch: %d/%d", version, + DNS_DYNDB_VERSION); + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(&imp->handle, filename, "dyndb_init", + (void **)&imp->register_func)); + CHECK(load_symbol(&imp->handle, filename, "dyndb_destroy", + (void **)&imp->destroy_func)); + + *impp = imp; + + return (ISC_R_SUCCESS); + +cleanup: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, + ISC_LOG_ERROR, + "failed to dynamically load DynDB instance '%s' driver " + "'%s': %s", + instname, filename, isc_result_totext(result)); + + unload_library(&imp); + + return (result); +} + +static void +unload_library(dyndb_implementation_t **impp) { + dyndb_implementation_t *imp; + + REQUIRE(impp != NULL && *impp != NULL); + + imp = *impp; + *impp = NULL; + + /* + * This is a resource leak, but there is nothing we can currently do + * about it due to how configuration loading/reloading is designed. + */ + /* uv_dlclose(&imp->handle); */ + isc_mem_free(imp->mctx, imp->name); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(*imp)); +} + +isc_result_t +dns_dyndb_load(const char *libname, const char *name, const char *parameters, + const char *file, unsigned long line, isc_mem_t *mctx, + const dns_dyndbctx_t *dctx) { + isc_result_t result; + dyndb_implementation_t *implementation = NULL; + + REQUIRE(DNS_DYNDBCTX_VALID(dctx)); + REQUIRE(name != NULL); + + RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS); + + LOCK(&dyndb_lock); + + /* duplicate instance names are not allowed */ + if (impfind(name) != NULL) { + CHECK(ISC_R_EXISTS); + } + + CHECK(load_library(mctx, libname, name, &implementation)); + CHECK(implementation->register_func(mctx, name, parameters, file, line, + dctx, &implementation->inst)); + + APPEND(dyndb_implementations, implementation, link); + result = ISC_R_SUCCESS; + +cleanup: + if (result != ISC_R_SUCCESS) { + if (implementation != NULL) { + unload_library(&implementation); + } + } + + UNLOCK(&dyndb_lock); + return (result); +} + +void +dns_dyndb_cleanup(bool exiting) { + dyndb_implementation_t *elem; + dyndb_implementation_t *prev; + + RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS); + + LOCK(&dyndb_lock); + elem = TAIL(dyndb_implementations); + while (elem != NULL) { + prev = PREV(elem, link); + UNLINK(dyndb_implementations, elem, link); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_INFO, + "unloading DynDB instance '%s'", elem->name); + elem->destroy_func(&elem->inst); + ENSURE(elem->inst == NULL); + unload_library(&elem); + elem = prev; + } + UNLOCK(&dyndb_lock); + + if (exiting) { + isc_mutex_destroy(&dyndb_lock); + } +} + +isc_result_t +dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx, + dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task, + isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp) { + dns_dyndbctx_t *dctx; + + REQUIRE(dctxp != NULL && *dctxp == NULL); + + dctx = isc_mem_get(mctx, sizeof(*dctx)); + *dctx = (dns_dyndbctx_t){ + .timermgr = tmgr, + .hashinit = hashinit, + .lctx = lctx, + }; + + if (view != NULL) { + dns_view_attach(view, &dctx->view); + } + if (zmgr != NULL) { + dns_zonemgr_attach(zmgr, &dctx->zmgr); + } + if (task != NULL) { + isc_task_attach(task, &dctx->task); + } + + isc_mem_attach(mctx, &dctx->mctx); + dctx->magic = DNS_DYNDBCTX_MAGIC; + + *dctxp = dctx; + + return (ISC_R_SUCCESS); +} + +void +dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp) { + dns_dyndbctx_t *dctx; + + REQUIRE(dctxp != NULL && DNS_DYNDBCTX_VALID(*dctxp)); + + dctx = *dctxp; + *dctxp = NULL; + + dctx->magic = 0; + + if (dctx->view != NULL) { + dns_view_detach(&dctx->view); + } + if (dctx->zmgr != NULL) { + dns_zonemgr_detach(&dctx->zmgr); + } + if (dctx->task != NULL) { + isc_task_detach(&dctx->task); + } + dctx->timermgr = NULL; + dctx->lctx = NULL; + + isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx)); +} diff --git a/lib/dns/ecs.c b/lib/dns/ecs.c new file mode 100644 index 0000000..676c740 --- /dev/null +++ b/lib/dns/ecs.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void +dns_ecs_init(dns_ecs_t *ecs) { + isc_netaddr_unspec(&ecs->addr); + ecs->source = 0; + ecs->scope = 0xff; +} + +bool +dns_ecs_equals(const dns_ecs_t *ecs1, const dns_ecs_t *ecs2) { + const unsigned char *addr1, *addr2; + uint8_t mask; + size_t alen; + + REQUIRE(ecs1 != NULL && ecs2 != NULL); + + if (ecs1->source != ecs2->source || + ecs1->addr.family != ecs2->addr.family) + { + return (false); + } + + alen = (ecs1->source + 7) / 8; + if (alen == 0) { + return (true); + } + + switch (ecs1->addr.family) { + case AF_INET: + INSIST(alen <= 4); + addr1 = (const unsigned char *)&ecs1->addr.type.in; + addr2 = (const unsigned char *)&ecs2->addr.type.in; + break; + case AF_INET6: + INSIST(alen <= 16); + addr1 = (const unsigned char *)&ecs1->addr.type.in6; + addr2 = (const unsigned char *)&ecs2->addr.type.in6; + break; + default: + UNREACHABLE(); + } + + /* + * Compare all octets except the final octet of the address + * prefix. + */ + if (alen > 1 && memcmp(addr1, addr2, alen - 1) != 0) { + return (false); + } + + /* + * It should not be necessary to mask the final octet; all + * bits past the source prefix length are supposed to be 0. + * However, it seems prudent not to omit them from the + * comparison anyway. + */ + mask = (~0U << (8 - (ecs1->source % 8))) & 0xff; + if (mask == 0) { + mask = 0xff; + } + + if ((addr1[alen - 1] & mask) != (addr2[alen - 1] & mask)) { + return (false); + } + + return (true); +} + +void +dns_ecs_format(const dns_ecs_t *ecs, char *buf, size_t size) { + size_t len; + char *p; + + REQUIRE(ecs != NULL); + REQUIRE(buf != NULL); + REQUIRE(size >= DNS_ECS_FORMATSIZE); + + isc_netaddr_format(&ecs->addr, buf, size); + len = strlen(buf); + p = buf + len; + snprintf(p, size - len, "/%d/%d", ecs->source, + ecs->scope == 0xff ? 0 : ecs->scope); +} diff --git a/lib/dns/fixedname.c b/lib/dns/fixedname.c new file mode 100644 index 0000000..c6d899d --- /dev/null +++ b/lib/dns/fixedname.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +void +dns_fixedname_init(dns_fixedname_t *fixed) { + dns_name_init(&fixed->name, fixed->offsets); + isc_buffer_init(&fixed->buffer, fixed->data, DNS_NAME_MAXWIRE); + dns_name_setbuffer(&fixed->name, &fixed->buffer); +} + +void +dns_fixedname_invalidate(dns_fixedname_t *fixed) { + dns_name_invalidate(&fixed->name); +} + +dns_name_t * +dns_fixedname_name(dns_fixedname_t *fixed) { + return (&fixed->name); +} + +dns_name_t * +dns_fixedname_initname(dns_fixedname_t *fixed) { + dns_fixedname_init(fixed); + return (dns_fixedname_name(fixed)); +} diff --git a/lib/dns/forward.c b/lib/dns/forward.c new file mode 100644 index 0000000..8750569 --- /dev/null +++ b/lib/dns/forward.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct dns_fwdtable { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_rwlock_t rwlock; + /* Locked by lock. */ + dns_rbt_t *table; +}; + +#define FWDTABLEMAGIC ISC_MAGIC('F', 'w', 'd', 'T') +#define VALID_FWDTABLE(ft) ISC_MAGIC_VALID(ft, FWDTABLEMAGIC) + +static void +auto_detach(void *, void *); + +isc_result_t +dns_fwdtable_create(isc_mem_t *mctx, dns_fwdtable_t **fwdtablep) { + dns_fwdtable_t *fwdtable; + isc_result_t result; + + REQUIRE(fwdtablep != NULL && *fwdtablep == NULL); + + fwdtable = isc_mem_get(mctx, sizeof(*fwdtable)); + + fwdtable->table = NULL; + result = dns_rbt_create(mctx, auto_detach, fwdtable, &fwdtable->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_fwdtable; + } + + isc_rwlock_init(&fwdtable->rwlock, 0, 0); + fwdtable->mctx = NULL; + isc_mem_attach(mctx, &fwdtable->mctx); + fwdtable->magic = FWDTABLEMAGIC; + *fwdtablep = fwdtable; + + return (ISC_R_SUCCESS); + +cleanup_fwdtable: + isc_mem_put(mctx, fwdtable, sizeof(*fwdtable)); + + return (result); +} + +isc_result_t +dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, const dns_name_t *name, + dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t fwdpolicy) { + isc_result_t result; + dns_forwarders_t *forwarders; + dns_forwarder_t *fwd, *nfwd; + + REQUIRE(VALID_FWDTABLE(fwdtable)); + + forwarders = isc_mem_get(fwdtable->mctx, sizeof(*forwarders)); + + ISC_LIST_INIT(forwarders->fwdrs); + for (fwd = ISC_LIST_HEAD(*fwdrs); fwd != NULL; + fwd = ISC_LIST_NEXT(fwd, link)) + { + nfwd = isc_mem_get(fwdtable->mctx, sizeof(*nfwd)); + *nfwd = *fwd; + ISC_LINK_INIT(nfwd, link); + ISC_LIST_APPEND(forwarders->fwdrs, nfwd, link); + } + forwarders->fwdpolicy = fwdpolicy; + + RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + result = dns_rbt_addname(fwdtable->table, name, forwarders); + RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + return (ISC_R_SUCCESS); + +cleanup: + while (!ISC_LIST_EMPTY(forwarders->fwdrs)) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link); + isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders)); + return (result); +} + +isc_result_t +dns_fwdtable_add(dns_fwdtable_t *fwdtable, const dns_name_t *name, + isc_sockaddrlist_t *addrs, dns_fwdpolicy_t fwdpolicy) { + isc_result_t result; + dns_forwarders_t *forwarders; + dns_forwarder_t *fwd; + isc_sockaddr_t *sa; + + REQUIRE(VALID_FWDTABLE(fwdtable)); + + forwarders = isc_mem_get(fwdtable->mctx, sizeof(*forwarders)); + + ISC_LIST_INIT(forwarders->fwdrs); + for (sa = ISC_LIST_HEAD(*addrs); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + fwd = isc_mem_get(fwdtable->mctx, sizeof(*fwd)); + fwd->addr = *sa; + ISC_LINK_INIT(fwd, link); + ISC_LIST_APPEND(forwarders->fwdrs, fwd, link); + } + forwarders->fwdpolicy = fwdpolicy; + + RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + result = dns_rbt_addname(fwdtable->table, name, forwarders); + RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + return (ISC_R_SUCCESS); + +cleanup: + while (!ISC_LIST_EMPTY(forwarders->fwdrs)) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link); + isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders)); + return (result); +} + +isc_result_t +dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name) { + isc_result_t result; + + REQUIRE(VALID_FWDTABLE(fwdtable)); + + RWLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + result = dns_rbt_deletename(fwdtable->table, name, false); + RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name, + dns_name_t *foundname, dns_forwarders_t **forwardersp) { + isc_result_t result; + + REQUIRE(VALID_FWDTABLE(fwdtable)); + + RWLOCK(&fwdtable->rwlock, isc_rwlocktype_read); + result = dns_rbt_findname(fwdtable->table, name, 0, foundname, + (void **)forwardersp); + RWUNLOCK(&fwdtable->rwlock, isc_rwlocktype_read); + + return (result); +} + +void +dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep) { + dns_fwdtable_t *fwdtable; + + REQUIRE(fwdtablep != NULL && VALID_FWDTABLE(*fwdtablep)); + + fwdtable = *fwdtablep; + *fwdtablep = NULL; + + dns_rbt_destroy(&fwdtable->table); + isc_rwlock_destroy(&fwdtable->rwlock); + fwdtable->magic = 0; + + isc_mem_putanddetach(&fwdtable->mctx, fwdtable, sizeof(*fwdtable)); +} + +/*** + *** Private + ***/ + +static void +auto_detach(void *data, void *arg) { + dns_forwarders_t *forwarders = data; + dns_fwdtable_t *fwdtable = arg; + dns_forwarder_t *fwd; + + UNUSED(arg); + + while (!ISC_LIST_EMPTY(forwarders->fwdrs)) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + ISC_LIST_UNLINK(forwarders->fwdrs, fwd, link); + isc_mem_put(fwdtable->mctx, fwd, sizeof(*fwd)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(*forwarders)); +} diff --git a/lib/dns/gen.c b/lib/dns/gen.c new file mode 100644 index 0000000..7cbdf0a --- /dev/null +++ b/lib/dns/gen.c @@ -0,0 +1,1062 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif /* ifndef PATH_MAX */ + +#ifndef ULLONG_MAX +#define ULLONG_MAX (~0ULL) +#endif /* ifndef ULLONG_MAX */ + +#define INSIST(cond) \ + if (!(cond)) { \ + fprintf(stderr, "%s:%d: INSIST(%s)\n", __FILE__, __LINE__, \ + #cond); \ + abort(); \ + } + +#define FROMTEXTARGS "rdclass, type, lexer, origin, options, target, callbacks" +#define FROMTEXTCLASS "rdclass" +#define FROMTEXTTYPE "type" +#define FROMTEXTDEF "result = DNS_R_UNKNOWN" + +#define TOTEXTARGS "rdata, tctx, target" +#define TOTEXTCLASS "rdata->rdclass" +#define TOTEXTTYPE "rdata->type" +#define TOTEXTDEF "use_default = true" + +#define FROMWIREARGS "rdclass, type, source, dctx, options, target" +#define FROMWIRECLASS "rdclass" +#define FROMWIRETYPE "type" +#define FROMWIREDEF "use_default = true" + +#define TOWIREARGS "rdata, cctx, target" +#define TOWIRECLASS "rdata->rdclass" +#define TOWIRETYPE "rdata->type" +#define TOWIREDEF "use_default = true" + +#define FROMSTRUCTARGS "rdclass, type, source, target" +#define FROMSTRUCTCLASS "rdclass" +#define FROMSTRUCTTYPE "type" +#define FROMSTRUCTDEF "use_default = true" + +#define TOSTRUCTARGS "rdata, target, mctx" +#define TOSTRUCTCLASS "rdata->rdclass" +#define TOSTRUCTTYPE "rdata->type" +#define TOSTRUCTDEF "use_default = true" + +#define FREESTRUCTARGS "source" +#define FREESTRUCTCLASS "common->rdclass" +#define FREESTRUCTTYPE "common->rdtype" +#define FREESTRUCTDEF NULL + +#define COMPAREARGS "rdata1, rdata2" +#define COMPARECLASS "rdata1->rdclass" +#define COMPARETYPE "rdata1->type" +#define COMPAREDEF "use_default = true" + +#define ADDITIONALDATAARGS "rdata, owner, add, arg" +#define ADDITIONALDATACLASS "rdata->rdclass" +#define ADDITIONALDATATYPE "rdata->type" +#define ADDITIONALDATADEF "use_default = true" + +#define DIGESTARGS "rdata, digest, arg" +#define DIGESTCLASS "rdata->rdclass" +#define DIGESTTYPE "rdata->type" +#define DIGESTDEF "use_default = true" + +#define CHECKOWNERARGS "name, rdclass, type, wildcard" +#define CHECKOWNERCLASS "rdclass" +#define CHECKOWNERTYPE "type" +#define CHECKOWNERDEF "result = true" + +#define CHECKNAMESARGS "rdata, owner, bad" +#define CHECKNAMESCLASS "rdata->rdclass" +#define CHECKNAMESTYPE "rdata->type" +#define CHECKNAMESDEF "result = true" + +static const char copyright[] = "/*\n" + " * Copyright (C) 1998%s Internet Systems " + "Consortium, Inc. (\"ISC\")\n" + " *\n" + " * This Source Code Form is subject to the " + "terms of the Mozilla Public\n" + " * License, v. 2.0. If a copy of the MPL was " + "not distributed with this\n" + " * file, you can obtain one at " + "https://mozilla.org/MPL/2.0/.\n" + " */\n" + "\n" + "/***************\n" + " ***************\n" + " *************** THIS FILE IS AUTOMATICALLY " + "GENERATED BY gen.c.\n" + " *************** DO NOT EDIT!\n" + " ***************\n" + " ***************/\n" + "\n" + "/*! \\file */\n" + "\n"; + +#define STR_EXPAND(tok) #tok +#define STR(tok) STR_EXPAND(tok) + +#define TYPENAMES 256 +#define TYPECLASSLEN 20 /* DNS mnemonic size. Must be less than 100. */ +#define TYPECLASSBUF (TYPECLASSLEN + 1) +#define TYPECLASSFMT "%" STR(TYPECLASSLEN) "[-0-9a-z]_%u" +#define ATTRIBUTESIZE 256 + +static struct cc { + struct cc *next; + int rdclass; + char classbuf[TYPECLASSBUF]; +} *classes; + +static struct tt { + struct tt *next; + uint16_t rdclass; + uint16_t type; + char classbuf[TYPECLASSBUF]; + char typebuf[TYPECLASSBUF]; + char dirbuf[PATH_MAX - 30]; +} *types; + +static struct ttnam { + char typebuf[TYPECLASSBUF]; + char macroname[TYPECLASSBUF]; + char attr[ATTRIBUTESIZE]; + unsigned int sorted; + uint16_t type; +} typenames[TYPENAMES]; + +static int maxtype = -1; + +typedef struct { + DIR *handle; + char *filename; +} isc_dir_t; + +static char * +upper(char *); +static char * +funname(const char *, char *); +static void +doswitch(const char *, const char *, const char *, const char *, const char *, + const char *); +static void +add(unsigned int, const char *, int, const char *, const char *); +static void +sd(unsigned int, const char *, const char *, char); +static void +insert_into_typenames(int, const char *, const char *); + +static bool +start_directory(const char *path, isc_dir_t *dir) { + dir->handle = opendir(path); + + if (dir->handle != NULL) { + return (true); + } else { + return (false); + } +} + +static bool +next_file(isc_dir_t *dir) { + struct dirent *dirent; + + dir->filename = NULL; + + if (dir->handle != NULL) { + errno = 0; + dirent = readdir(dir->handle); + if (dirent != NULL) { + dir->filename = dirent->d_name; + } else { + if (errno != 0) { + fprintf(stderr, + "Error: reading directory: %s\n", + strerror(errno)); + exit(1); + } + } + } + + if (dir->filename != NULL) { + return (true); + } else { + return (false); + } +} + +static void +end_directory(isc_dir_t *dir) { + if (dir->handle != NULL) { + (void)closedir(dir->handle); + } + + dir->handle = NULL; +} + +/*% + * If you use more than 10 of these in, say, a printf(), you'll have problems. + */ +static char * +upper(char *s) { + static int buf_to_use = 0; + static char buf[10][256]; + char *b; + int c; + + buf_to_use++; + if (buf_to_use > 9) { + buf_to_use = 0; + } + + b = buf[buf_to_use]; + memset(b, 0, 256); + + while ((c = (*s++) & 0xff)) { + *b++ = islower(c) ? toupper(c) : c; + } + *b = '\0'; + return (buf[buf_to_use]); +} + +static char * +funname(const char *s, char *buf) { + char *b = buf; + char c; + + INSIST(strlen(s) < TYPECLASSBUF); + while ((c = *s++)) { + *b++ = (c == '-') ? '_' : c; + } + *b = '\0'; + return (buf); +} + +static void +doswitch(const char *name, const char *function, const char *args, + const char *tsw, const char *csw, const char *res) { + struct tt *tt; + int first = 1; + int lasttype = 0; + int subswitch = 0; + char buf1[TYPECLASSBUF], buf2[TYPECLASSBUF]; + const char *result = " result ="; + + if (res == NULL) { + result = ""; + } + + for (tt = types; tt != NULL; tt = tt->next) { + if (first) { + printf("\n#define %s \\\n", name); + printf("\tswitch (%s) { \\\n" /*}*/, tsw); + first = 0; + } + if (tt->type != lasttype && subswitch) { + if (res == NULL) { + printf("\t\tdefault: break; \\\n"); + } else { + printf("\t\tdefault: %s; break; \\\n", res); + } + printf("\t\t} \\\n"); + printf("\t\tbreak; \\\n"); + subswitch = 0; + } + if (tt->rdclass && tt->type != lasttype) { + printf("\tcase %d: switch (%s) { \\\n" /*}*/, tt->type, + csw); + subswitch = 1; + } + if (tt->rdclass == 0) { + printf("\tcase %d:%s %s_%s(%s); break;", tt->type, + result, function, funname(tt->typebuf, buf1), + args); + } else { + printf("\t\tcase %d:%s %s_%s_%s(%s); break;", + tt->rdclass, result, function, + funname(tt->classbuf, buf1), + funname(tt->typebuf, buf2), args); + } + printf(" \\\n"); + lasttype = tt->type; + } + if (subswitch) { + if (res == NULL) { + printf("\t\tdefault: break; \\\n"); + } else { + printf("\t\tdefault: %s; break; \\\n", res); + } + printf("\t\t} \\\n"); + printf("\t\tbreak; \\\n"); + } + if (first) { + if (res == NULL) { + printf("\n#define %s\n", name); + } else { + printf("\n#define %s %s;\n", name, res); + } + } else { + if (res == NULL) { + printf("\tdefault: break; \\\n"); + } else { + printf("\tdefault: %s; break; \\\n", res); + } + printf("\t}\n"); + } +} + +static struct ttnam * +find_typename(int type) { + int i; + + for (i = 0; i < TYPENAMES; i++) { + if (typenames[i].typebuf[0] != 0 && typenames[i].type == type) { + return (&typenames[i]); + } + } + return (NULL); +} + +static void +insert_into_typenames(int type, const char *typebuf, const char *attr) { + struct ttnam *ttn = NULL; + size_t c; + int i, n; + char tmp[256]; + + INSIST(strlen(typebuf) < TYPECLASSBUF); + for (i = 0; i < TYPENAMES; i++) { + if (typenames[i].typebuf[0] != 0 && typenames[i].type == type && + strcmp(typebuf, typenames[i].typebuf) != 0) + { + fprintf(stderr, + "Error: type %d has two names: %s, %s\n", type, + typenames[i].typebuf, typebuf); + exit(1); + } + if (typenames[i].typebuf[0] == 0 && ttn == NULL) { + ttn = &typenames[i]; + } + } + if (ttn == NULL) { + fprintf(stderr, "Error: typenames array too small\n"); + exit(1); + } + + /* XXXMUKS: This is redundant due to the INSIST above. */ + if (strlen(typebuf) > sizeof(ttn->typebuf) - 1) { + fprintf(stderr, "Error: type name %s is too long\n", typebuf); + exit(1); + } + + strncpy(ttn->typebuf, typebuf, sizeof(ttn->typebuf)); + ttn->typebuf[sizeof(ttn->typebuf) - 1] = '\0'; + + strncpy(ttn->macroname, ttn->typebuf, sizeof(ttn->macroname)); + ttn->macroname[sizeof(ttn->macroname) - 1] = '\0'; + + ttn->type = type; + c = strlen(ttn->macroname); + while (c > 0) { + if (ttn->macroname[c - 1] == '-') { + ttn->macroname[c - 1] = '_'; + } + c--; + } + + if (attr == NULL) { + n = snprintf(tmp, sizeof(tmp), "RRTYPE_%s_ATTRIBUTES", + upper(ttn->macroname)); + INSIST(n > 0 && (unsigned)n < sizeof(tmp)); + attr = tmp; + } + + if (ttn->attr[0] != 0 && strcmp(attr, ttn->attr) != 0) { + fprintf(stderr, + "Error: type %d has different attributes: " + "%s, %s\n", + type, ttn->attr, attr); + exit(1); + } + + if (strlen(attr) > sizeof(ttn->attr) - 1) { + fprintf(stderr, "Error: attr (%s) [name %s] is too long\n", + attr, typebuf); + exit(1); + } + + strncpy(ttn->attr, attr, sizeof(ttn->attr)); + ttn->attr[sizeof(ttn->attr) - 1] = '\0'; + + ttn->sorted = 0; + if (maxtype < type) { + maxtype = type; + } +} + +static void +add(unsigned int rdclass, const char *classbuf, int type, const char *typebuf, + const char *dirbuf) { + struct tt *newtt = (struct tt *)malloc(sizeof(*newtt)); + struct tt *tt, *oldtt; + struct cc *newcc; + struct cc *cc, *oldcc; + + INSIST(strlen(typebuf) < TYPECLASSBUF); + INSIST(strlen(classbuf) < TYPECLASSBUF); + INSIST(strlen(dirbuf) < PATH_MAX); + + insert_into_typenames(type, typebuf, NULL); + + if (newtt == NULL) { + fprintf(stderr, "malloc() failed\n"); + exit(1); + } + + newtt->next = NULL; + newtt->rdclass = rdclass; + newtt->type = type; + + strncpy(newtt->classbuf, classbuf, sizeof(newtt->classbuf)); + newtt->classbuf[sizeof(newtt->classbuf) - 1] = '\0'; + + strncpy(newtt->typebuf, typebuf, sizeof(newtt->typebuf)); + newtt->typebuf[sizeof(newtt->typebuf) - 1] = '\0'; + + if (strncmp(dirbuf, "./", 2) == 0) { + dirbuf += 2; + } + strncpy(newtt->dirbuf, dirbuf, sizeof(newtt->dirbuf)); + newtt->dirbuf[sizeof(newtt->dirbuf) - 1] = '\0'; + + tt = types; + oldtt = NULL; + + while ((tt != NULL) && (tt->type < type)) { + oldtt = tt; + tt = tt->next; + } + + while ((tt != NULL) && (tt->type == type) && (tt->rdclass < rdclass)) { + if (strcmp(tt->typebuf, typebuf) != 0) { + exit(1); + } + oldtt = tt; + tt = tt->next; + } + + if ((tt != NULL) && (tt->type == type) && (tt->rdclass == rdclass)) { + exit(1); + } + + newtt->next = tt; + if (oldtt != NULL) { + oldtt->next = newtt; + } else { + types = newtt; + } + + /* + * Do a class switch for this type. + */ + if (rdclass == 0) { + return; + } + + newcc = (struct cc *)malloc(sizeof(*newcc)); + if (newcc == NULL) { + fprintf(stderr, "malloc() failed\n"); + exit(1); + } + newcc->rdclass = rdclass; + strncpy(newcc->classbuf, classbuf, sizeof(newcc->classbuf)); + newcc->classbuf[sizeof(newcc->classbuf) - 1] = '\0'; + cc = classes; + oldcc = NULL; + + while ((cc != NULL) && (cc->rdclass < rdclass)) { + oldcc = cc; + cc = cc->next; + } + + if ((cc != NULL) && cc->rdclass == rdclass) { + free((char *)newcc); + return; + } + + newcc->next = cc; + if (oldcc != NULL) { + oldcc->next = newcc; + } else { + classes = newcc; + } +} + +static void +sd(unsigned int rdclass, const char *classbuf, const char *dirbuf, + char filetype) { + char buf[TYPECLASSLEN + sizeof("_4294967295.h")]; + char typebuf[TYPECLASSBUF]; + unsigned int type; + int n; + isc_dir_t dir; + + if (!start_directory(dirbuf, &dir)) { + return; + } + + while (next_file(&dir)) { + if (sscanf(dir.filename, TYPECLASSFMT, typebuf, &type) != 2) { + continue; + } + + /* + * sscanf accepts leading sign and zeros before type so + * compare the scanned items against the filename. Filter + * out mismatches. Also filter out bad file extensions. + */ + n = snprintf(buf, sizeof(buf), "%s_%u.%c", typebuf, type, + filetype); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf, dir.filename) != 0) { + continue; + } + if (type > 65535) { + fprintf(stderr, "Error: type value > 65535 (%s)\n", + dir.filename); + exit(1); + } + add(rdclass, classbuf, type, typebuf, dirbuf); + } + + end_directory(&dir); +} + +static unsigned int +HASH(char *string) { + size_t n; + unsigned char a, b; + + n = strlen(string); + if (n == 0) { + fprintf(stderr, "n == 0?\n"); + exit(1); + } + a = tolower((unsigned char)string[0]); + b = tolower((unsigned char)string[n - 1]); + + return (((a + n) * b) % 256); +} + +int +main(int argc, char **argv) { + char buf[PATH_MAX]; + char srcdir[PATH_MAX]; + unsigned int rdclass; + char classbuf[TYPECLASSBUF]; + struct tt *tt; + struct cc *cc; + struct ttnam *ttn, *ttn2; + unsigned int hash; + time_t now; + char year[11]; + int lasttype; + int code = 1; + int class_enum = 0; + int type_enum = 0; + int structs = 0; + int depend = 0; + int c, i, j, n; + char buf1[TYPECLASSBUF]; + char filetype = 'c'; + FILE *fd; + char *prefix = NULL; + char *suffix = NULL; + char *file = NULL; + char *source_date_epoch; + unsigned long long epoch; + char *endptr; + isc_dir_t dir; + + for (i = 0; i < TYPENAMES; i++) { + memset(&typenames[i], 0, sizeof(typenames[i])); + } + + srcdir[0] = '\0'; + while ((c = getopt(argc, argv, "cdits:F:P:S:")) != -1) { + switch (c) { + case 'c': + code = 0; + depend = 0; + type_enum = 0; + class_enum = 1; + filetype = 'c'; + structs = 0; + break; + case 'd': + code = 0; + depend = 1; + class_enum = 0; + type_enum = 0; + structs = 0; + filetype = 'h'; + break; + case 't': + code = 0; + depend = 0; + class_enum = 0; + type_enum = 1; + filetype = 'c'; + structs = 0; + break; + case 'i': + code = 0; + depend = 0; + class_enum = 0; + type_enum = 0; + structs = 1; + filetype = 'h'; + break; + case 's': + if (strlen(optarg) > + PATH_MAX - 2 * TYPECLASSLEN - + sizeof("/rdata/_65535_65535")) + { + fprintf(stderr, "\"%s\" too long\n", optarg); + exit(1); + } + n = snprintf(srcdir, sizeof(srcdir), "%s/", optarg); + INSIST(n > 0 && (unsigned)n < sizeof(srcdir)); + break; + case 'F': + file = optarg; + break; + case 'P': + prefix = optarg; + break; + case 'S': + suffix = optarg; + break; + case '?': + exit(1); + } + } + + n = snprintf(buf, sizeof(buf), "%srdata", srcdir); + INSIST(n > 0 && (unsigned)n < sizeof(srcdir)); + + if (!start_directory(buf, &dir)) { + exit(1); + } + + while (next_file(&dir)) { + if (sscanf(dir.filename, TYPECLASSFMT, classbuf, &rdclass) != 2) + { + continue; + } + + /* + * sscanf accepts leading sign and zeros before type so + * compare the scanned items against the filename. Filter + * out mismatches. + */ + n = snprintf(buf, sizeof(buf), "%srdata/%s_%u", srcdir, + classbuf, rdclass); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf + 6 + strlen(srcdir), dir.filename) != 0) { + continue; + } + if (rdclass > 65535) { + fprintf(stderr, "Error: class value > 65535 (%s)\n", + dir.filename); + exit(1); + } + sd(rdclass, classbuf, buf, filetype); + } + end_directory(&dir); + n = snprintf(buf, sizeof(buf), "%srdata/generic", srcdir); + INSIST(n > 0 && (unsigned)n < sizeof(srcdir)); + sd(0, "", buf, filetype); + + source_date_epoch = getenv("SOURCE_DATE_EPOCH"); + if (source_date_epoch) { + errno = 0; + epoch = strtoull(source_date_epoch, &endptr, 10); + if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) || + (errno != 0 && epoch == 0)) + { + fprintf(stderr, + "Environment variable " + "$SOURCE_DATE_EPOCH: strtoull: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + if (endptr == source_date_epoch) { + fprintf(stderr, + "Environment variable " + "$SOURCE_DATE_EPOCH: " + "No digits were found: %s\n", + endptr); + exit(EXIT_FAILURE); + } + if (*endptr != '\0') { + fprintf(stderr, + "Environment variable " + "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n", + endptr); + exit(EXIT_FAILURE); + } + if (epoch > ULONG_MAX) { + fprintf(stderr, + "Environment variable " + "$SOURCE_DATE_EPOCH: value must be " + "smaller than or equal to: %lu but " + "was found to be: %llu \n", + ULONG_MAX, epoch); + exit(EXIT_FAILURE); + } + now = epoch; + } else { + time(&now); + } + + if (now != -1) { + struct tm t, *tm = gmtime_r(&now, &t); + + if (tm != NULL && tm->tm_year > 104) { + n = snprintf(year, sizeof(year), "-%d", + tm->tm_year + 1900); + INSIST(n > 0 && (unsigned)n < sizeof(year)); + } else { + snprintf(year, sizeof(year), "-2016"); + } + } else { + snprintf(year, sizeof(year), "-2016"); + } + + if (!depend) { + printf(copyright, year); + } + + if (code) { + printf("#pragma once\n"); + + printf("#include \n"); + printf("#include \n\n"); + printf("#include \n\n"); + + for (tt = types; tt != NULL; tt = tt->next) { + printf("#include \"%s/%s_%d.c\"\n", tt->dirbuf, + tt->typebuf, tt->type); + } + + printf("\n\n"); + + doswitch("FROMTEXTSWITCH", "fromtext", FROMTEXTARGS, + FROMTEXTTYPE, FROMTEXTCLASS, FROMTEXTDEF); + doswitch("TOTEXTSWITCH", "totext", TOTEXTARGS, TOTEXTTYPE, + TOTEXTCLASS, TOTEXTDEF); + doswitch("FROMWIRESWITCH", "fromwire", FROMWIREARGS, + FROMWIRETYPE, FROMWIRECLASS, FROMWIREDEF); + doswitch("TOWIRESWITCH", "towire", TOWIREARGS, TOWIRETYPE, + TOWIRECLASS, TOWIREDEF); + doswitch("COMPARESWITCH", "compare", COMPAREARGS, COMPARETYPE, + COMPARECLASS, COMPAREDEF); + doswitch("CASECOMPARESWITCH", "casecompare", COMPAREARGS, + COMPARETYPE, COMPARECLASS, COMPAREDEF); + doswitch("FROMSTRUCTSWITCH", "fromstruct", FROMSTRUCTARGS, + FROMSTRUCTTYPE, FROMSTRUCTCLASS, FROMSTRUCTDEF); + doswitch("TOSTRUCTSWITCH", "tostruct", TOSTRUCTARGS, + TOSTRUCTTYPE, TOSTRUCTCLASS, TOSTRUCTDEF); + doswitch("FREESTRUCTSWITCH", "freestruct", FREESTRUCTARGS, + FREESTRUCTTYPE, FREESTRUCTCLASS, FREESTRUCTDEF); + doswitch("ADDITIONALDATASWITCH", "additionaldata", + ADDITIONALDATAARGS, ADDITIONALDATATYPE, + ADDITIONALDATACLASS, ADDITIONALDATADEF); + doswitch("DIGESTSWITCH", "digest", DIGESTARGS, DIGESTTYPE, + DIGESTCLASS, DIGESTDEF); + doswitch("CHECKOWNERSWITCH", "checkowner", CHECKOWNERARGS, + CHECKOWNERTYPE, CHECKOWNERCLASS, CHECKOWNERDEF); + doswitch("CHECKNAMESSWITCH", "checknames", CHECKNAMESARGS, + CHECKNAMESTYPE, CHECKNAMESCLASS, CHECKNAMESDEF); + + /* + * From here down, we are processing the rdata names and + * attributes. + */ + +#define PRINT_COMMA(x) (x == maxtype ? "" : ",") + +#define METANOTQUESTION \ + "DNS_RDATATYPEATTR_META | " \ + "DNS_RDATATYPEATTR_NOTQUESTION" +#define METAQUESTIONONLY \ + "DNS_RDATATYPEATTR_META | " \ + "DNS_RDATATYPEATTR_QUESTIONONLY" +#define RESERVEDNAME "0" +#define RESERVED "DNS_RDATATYPEATTR_RESERVED" + + /* + * Add in reserved/special types. This will let us + * sort them without special cases. + */ + insert_into_typenames(100, "uinfo", RESERVEDNAME); + insert_into_typenames(101, "uid", RESERVEDNAME); + insert_into_typenames(102, "gid", RESERVEDNAME); + insert_into_typenames(103, "unspec", RESERVEDNAME); + insert_into_typenames(251, "ixfr", METAQUESTIONONLY); + insert_into_typenames(252, "axfr", METAQUESTIONONLY); + insert_into_typenames(253, "mailb", METAQUESTIONONLY); + insert_into_typenames(254, "maila", METAQUESTIONONLY); + insert_into_typenames(255, "any", METAQUESTIONONLY); + + /* + * Spit out a quick and dirty hash function. Here, + * we walk through the list of type names, and calculate + * a hash. This isn't perfect, but it will generate "pretty + * good" estimates. Lowercase the characters before + * computing in all cases. + * + * Here, walk the list from top to bottom, calculating + * the hash (mod 256) for each name. + */ + printf("#define RDATATYPE_COMPARE(_s, _d, _tn, _n, _tp) \\\n"); + printf("\tdo { \\\n"); + printf("\t\tif (sizeof(_s) - 1 == _n && \\\n" + "\t\t strncasecmp(_s,(_tn)," + "(sizeof(_s) - 1)) == 0) { \\\n"); + printf("\t\t\tif ((dns_rdatatype_attributes(_d) & " + "DNS_RDATATYPEATTR_RESERVED) != 0) \\\n"); + printf("\t\t\t\treturn (ISC_R_NOTIMPLEMENTED); \\\n"); + printf("\t\t\t*(_tp) = _d; \\\n"); + printf("\t\t\treturn (ISC_R_SUCCESS); \\\n"); + printf("\t\t} \\\n"); + printf("\t} while (0)\n\n"); + + printf("#define RDATATYPE_FROMTEXT_SW(_hash," + "_typename,_length,_typep) \\\n"); + printf("\tswitch (_hash) { \\\n"); + for (i = 0; i <= maxtype; i++) { + ttn = find_typename(i); + if (ttn == NULL) { + continue; + } + + /* + * Skip entries we already processed. + */ + if (ttn->sorted != 0) { + continue; + } + + hash = HASH(ttn->typebuf); + printf("\t\tcase %u: \\\n", hash); + + /* + * Find all other entries that happen to match + * this hash. + */ + for (j = 0; j <= maxtype; j++) { + ttn2 = find_typename(j); + if (ttn2 == NULL) { + continue; + } + if (hash == HASH(ttn2->typebuf)) { + printf("\t\t\tRDATATYPE_COMPARE" + "(\"%s\", %d, _typename, " + " _length, _typep); \\\n", + ttn2->typebuf, ttn2->type); + ttn2->sorted = 1; + } + } + printf("\t\t\tbreak; \\\n"); + } + printf("\t}\n"); + + printf("#define RDATATYPE_ATTRIBUTE_SW \\\n"); + printf("\tswitch (type) { \\\n"); + for (i = 0; i <= maxtype; i++) { + ttn = find_typename(i); + if (ttn == NULL) { + continue; + } + printf("\tcase %d: return (%s); \\\n", i, + upper(ttn->attr)); + } + printf("\t}\n"); + + printf("#define RDATATYPE_TOTEXT_SW \\\n"); + printf("\tswitch (type) { \\\n"); + for (i = 0; i <= maxtype; i++) { + ttn = find_typename(i); + if (ttn == NULL) { + continue; + } + /* + * Remove KEYDATA (65533) from the type to memonic + * translation as it is internal use only. This + * stops the tools from displaying KEYDATA instead + * of TYPE65533. + */ + if (i == 65533U) { + continue; + } + printf("\tcase %d: return " + "(str_totext(\"%s\", target)); \\\n", + i, upper(ttn->typebuf)); + } + printf("\t}\n"); + } else if (type_enum) { + char *s; + + printf("#pragma once\n"); + + printf("enum {\n"); + printf("\tdns_rdatatype_none = 0,\n"); + + lasttype = 0; + for (tt = types; tt != NULL; tt = tt->next) { + if (tt->type != lasttype) { + printf("\tdns_rdatatype_%s = %d,\n", + funname(tt->typebuf, buf1), + lasttype = tt->type); + } + } + + printf("\tdns_rdatatype_ixfr = 251,\n"); + printf("\tdns_rdatatype_axfr = 252,\n"); + printf("\tdns_rdatatype_mailb = 253,\n"); + printf("\tdns_rdatatype_maila = 254,\n"); + printf("\tdns_rdatatype_any = 255\n"); + + printf("};\n\n"); + + printf("#define dns_rdatatype_none\t" + "((dns_rdatatype_t)dns_rdatatype_none)\n"); + + for (tt = types; tt != NULL; tt = tt->next) { + if (tt->type != lasttype) { + s = funname(tt->typebuf, buf1); + printf("#define dns_rdatatype_%s\t%s" + "((dns_rdatatype_t)dns_rdatatype_%s)\n", + s, strlen(s) < 2U ? "\t" : "", s); + lasttype = tt->type; + } + } + + printf("#define dns_rdatatype_ixfr\t" + "((dns_rdatatype_t)dns_rdatatype_ixfr)\n"); + printf("#define dns_rdatatype_axfr\t" + "((dns_rdatatype_t)dns_rdatatype_axfr)\n"); + printf("#define dns_rdatatype_mailb\t" + "((dns_rdatatype_t)dns_rdatatype_mailb)\n"); + printf("#define dns_rdatatype_maila\t" + "((dns_rdatatype_t)dns_rdatatype_maila)\n"); + printf("#define dns_rdatatype_any\t" + "((dns_rdatatype_t)dns_rdatatype_any)\n"); + } else if (class_enum) { + char *s; + int classnum; + + printf("#pragma once\n"); + + printf("enum {\n"); + + printf("\tdns_rdataclass_reserved0 = 0,\n"); + printf("#define dns_rdataclass_reserved0 \\\n\t\t\t\t" + "((dns_rdataclass_t)dns_rdataclass_reserved0)\n"); + +#define PRINTCLASS(name, num) \ + do { \ + s = funname(name, buf1); \ + classnum = num; \ + printf("\tdns_rdataclass_%s = %d%s\n", s, classnum, \ + classnum != 255 ? "," : ""); \ + printf("#define dns_rdataclass_%s\t" \ + "((dns_rdataclass_t)dns_rdataclass_%s)\n", \ + s, s); \ + } while (0) + + for (cc = classes; cc != NULL; cc = cc->next) { + if (cc->rdclass == 3) { + PRINTCLASS("chaos", 3); + } else if (cc->rdclass == 255) { + PRINTCLASS("none", 254); + } + PRINTCLASS(cc->classbuf, cc->rdclass); + } + +#undef PRINTCLASS + + printf("};\n\n"); + } else if (structs) { + if (prefix != NULL) { + if ((fd = fopen(prefix, "r")) != NULL) { + while (fgets(buf, sizeof(buf), fd) != NULL) { + printf("%s", buf); + } + fclose(fd); + } + } + for (tt = types; tt != NULL; tt = tt->next) { + snprintf(buf, sizeof(buf), "%s/%s_%d.h", tt->dirbuf, + tt->typebuf, tt->type); + if ((fd = fopen(buf, "r")) != NULL) { + while (fgets(buf, sizeof(buf), fd) != NULL) { + printf("%s", buf); + } + fclose(fd); + } + } + if (suffix != NULL) { + if ((fd = fopen(suffix, "r")) != NULL) { + while (fgets(buf, sizeof(buf), fd) != NULL) { + printf("%s", buf); + } + fclose(fd); + } + } + } else if (depend) { + for (tt = types; tt != NULL; tt = tt->next) { + printf("%s:\t%s/%s_%d.h\n", file, tt->dirbuf, + tt->typebuf, tt->type); + } + } + + if (ferror(stdout) != 0) { + exit(1); + } + + return (0); +} diff --git a/lib/dns/geoip2.c b/lib/dns/geoip2.c new file mode 100644 index 0000000..a3b2206 --- /dev/null +++ b/lib/dns/geoip2.c @@ -0,0 +1,382 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +/* + * This file is only built and linked if GeoIP2 has been configured. + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * This structure preserves state from the previous GeoIP lookup, + * so that successive lookups for the same data from the same IP + * address will not require repeated database lookups. + * This should improve performance somewhat. + * + * For all lookups we preserve pointers to the MMDB_lookup_result_s + * and MMDB_entry_s structures, a pointer to the database from which + * the lookup was answered, and a copy of the request address. + * + * If the next geoip ACL lookup is for the same database and from the + * same address, we can reuse the MMDB entry without repeating the lookup. + * This is for the case when a single query has to process multiple + * geoip ACLs: for example, when there are multiple views with + * match-clients statements that search for different countries. + * + * (XXX: Currently the persistent state is stored in thread specific + * memory, but it could more simply be stored in the client object. + * Also multiple entries could be stored in case the ACLs require + * searching in more than one GeoIP database.) + */ + +typedef struct geoip_state { + uint16_t subtype; + const MMDB_s *db; + isc_netaddr_t addr; + MMDB_lookup_result_s mmresult; + MMDB_entry_s entry; +} geoip_state_t; + +static thread_local geoip_state_t geoip_state = { 0 }; + +static void +set_state(const MMDB_s *db, const isc_netaddr_t *addr, + MMDB_lookup_result_s mmresult, MMDB_entry_s entry) { + geoip_state.db = db; + geoip_state.addr = *addr; + geoip_state.mmresult = mmresult; + geoip_state.entry = entry; +} + +static geoip_state_t * +get_entry_for(MMDB_s *const db, const isc_netaddr_t *addr) { + isc_sockaddr_t sa; + MMDB_lookup_result_s match; + int err; + + if (db == geoip_state.db && isc_netaddr_equal(addr, &geoip_state.addr)) + { + return (&geoip_state); + } + + isc_sockaddr_fromnetaddr(&sa, addr, 0); + match = MMDB_lookup_sockaddr(db, &sa.type.sa, &err); + if (err != MMDB_SUCCESS || !match.found_entry) { + return (NULL); + } + + set_state(db, addr, match, match.entry); + + return (&geoip_state); +} + +static dns_geoip_subtype_t +fix_subtype(const dns_geoip_databases_t *geoip, dns_geoip_subtype_t subtype) { + dns_geoip_subtype_t ret = subtype; + + switch (subtype) { + case dns_geoip_countrycode: + if (geoip->city != NULL) { + ret = dns_geoip_city_countrycode; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_code; + } + break; + case dns_geoip_countryname: + if (geoip->city != NULL) { + ret = dns_geoip_city_countryname; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_name; + } + break; + case dns_geoip_continentcode: + if (geoip->city != NULL) { + ret = dns_geoip_city_continentcode; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_continentcode; + } + break; + case dns_geoip_continent: + if (geoip->city != NULL) { + ret = dns_geoip_city_continent; + } else if (geoip->country != NULL) { + ret = dns_geoip_country_continent; + } + break; + case dns_geoip_region: + if (geoip->city != NULL) { + ret = dns_geoip_city_region; + } + break; + case dns_geoip_regionname: + if (geoip->city != NULL) { + ret = dns_geoip_city_regionname; + } + default: + break; + } + + return (ret); +} + +static MMDB_s * +geoip2_database(const dns_geoip_databases_t *geoip, + dns_geoip_subtype_t subtype) { + switch (subtype) { + case dns_geoip_country_code: + case dns_geoip_country_name: + case dns_geoip_country_continentcode: + case dns_geoip_country_continent: + return (geoip->country); + + case dns_geoip_city_countrycode: + case dns_geoip_city_countryname: + case dns_geoip_city_continentcode: + case dns_geoip_city_continent: + case dns_geoip_city_region: + case dns_geoip_city_regionname: + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_timezonecode: + case dns_geoip_city_metrocode: + case dns_geoip_city_areacode: + return (geoip->city); + + case dns_geoip_isp_name: + return (geoip->isp); + + case dns_geoip_as_asnum: + case dns_geoip_org_name: + return (geoip->as); + + case dns_geoip_domain_name: + return (geoip->domain); + + default: + /* + * All other subtypes are unavailable in GeoIP2. + */ + return (NULL); + } +} + +static bool +match_string(MMDB_entry_data_s *value, const char *str) { + REQUIRE(str != NULL); + + if (value == NULL || !value->has_data || + value->type != MMDB_DATA_TYPE_UTF8_STRING || + value->utf8_string == NULL) + { + return (false); + } + + return (strncasecmp(value->utf8_string, str, value->data_size) == 0); +} + +static bool +match_int(MMDB_entry_data_s *value, const uint32_t ui32) { + if (value == NULL || !value->has_data || + (value->type != MMDB_DATA_TYPE_UINT32 && + value->type != MMDB_DATA_TYPE_UINT16)) + { + return (false); + } + + return (value->uint32 == ui32); +} + +bool +dns_geoip_match(const isc_netaddr_t *reqaddr, + const dns_geoip_databases_t *geoip, + const dns_geoip_elem_t *elt) { + MMDB_s *db = NULL; + MMDB_entry_data_s value; + geoip_state_t *state = NULL; + dns_geoip_subtype_t subtype; + const char *s = NULL; + int ret; + + REQUIRE(reqaddr != NULL); + REQUIRE(elt != NULL); + REQUIRE(geoip != NULL); + + subtype = fix_subtype(geoip, elt->subtype); + db = geoip2_database(geoip, subtype); + if (db == NULL) { + return (false); + } + + state = get_entry_for(db, reqaddr); + if (state == NULL) { + return (false); + } + + switch (subtype) { + case dns_geoip_country_code: + case dns_geoip_city_countrycode: + ret = MMDB_get_value(&state->entry, &value, "country", + "iso_code", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_name: + case dns_geoip_city_countryname: + ret = MMDB_get_value(&state->entry, &value, "country", "names", + "en", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_continentcode: + case dns_geoip_city_continentcode: + ret = MMDB_get_value(&state->entry, &value, "continent", "code", + (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_country_continent: + case dns_geoip_city_continent: + ret = MMDB_get_value(&state->entry, &value, "continent", + "names", "en", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_region: + case dns_geoip_city_region: + ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0", + "iso_code", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_regionname: + case dns_geoip_city_regionname: + ret = MMDB_get_value(&state->entry, &value, "subdivisions", "0", + "names", "en", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_name: + ret = MMDB_get_value(&state->entry, &value, "city", "names", + "en", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_postalcode: + ret = MMDB_get_value(&state->entry, &value, "postal", "code", + (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_timezonecode: + ret = MMDB_get_value(&state->entry, &value, "location", + "time_zone", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_city_metrocode: + ret = MMDB_get_value(&state->entry, &value, "location", + "metro_code", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_isp_name: + ret = MMDB_get_value(&state->entry, &value, "isp", (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_as_asnum: + INSIST(elt->as_string != NULL); + + ret = MMDB_get_value(&state->entry, &value, + "autonomous_system_number", (char *)0); + if (ret == MMDB_SUCCESS) { + int i; + s = elt->as_string; + if (strncasecmp(s, "AS", 2) == 0) { + s += 2; + } + i = strtol(s, NULL, 10); + return (match_int(&value, i)); + } + break; + + case dns_geoip_org_name: + ret = MMDB_get_value(&state->entry, &value, + "autonomous_system_organization", + (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + case dns_geoip_domain_name: + ret = MMDB_get_value(&state->entry, &value, "domain", + (char *)0); + if (ret == MMDB_SUCCESS) { + return (match_string(&value, elt->as_string)); + } + break; + + default: + /* + * For any other subtype, we assume the database was + * unavailable and return false. + */ + return (false); + } + + /* + * No database matched: return false. + */ + return (false); +} diff --git a/lib/dns/gssapi_link.c b/lib/dns/gssapi_link.c new file mode 100644 index 0000000..a3fbb6e --- /dev/null +++ b/lib/dns/gssapi_link.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include /* IWYU pragma: keep */ +#include +#include /* IWYU pragma: keep */ + +#if HAVE_GSSAPI_GSSAPI_H +#include +#elif HAVE_GSSAPI_H +#include +#endif + +#if HAVE_GSSAPI_GSSAPI_KRB5_H +#include +#elif HAVE_GSSAPI_KRB5_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" + +#define INITIAL_BUFFER_SIZE 1024 +#define BUFFER_EXTRA 1024 + +#define REGION_TO_GBUFFER(r, gb) \ + do { \ + (gb).length = (r).length; \ + (gb).value = (r).base; \ + } while (0) + +#define GBUFFER_TO_REGION(gb, r) \ + do { \ + (r).length = (unsigned int)(gb).length; \ + (r).base = (gb).value; \ + } while (0) + +struct dst_gssapi_signverifyctx { + isc_buffer_t *buffer; +}; + +/*% + * Allocate a temporary "context" for use in gathering data for signing + * or verifying. + */ +static isc_result_t +gssapi_create_signverify_ctx(dst_key_t *key, dst_context_t *dctx) { + dst_gssapi_signverifyctx_t *ctx; + + UNUSED(key); + + ctx = isc_mem_get(dctx->mctx, sizeof(dst_gssapi_signverifyctx_t)); + ctx->buffer = NULL; + isc_buffer_allocate(dctx->mctx, &ctx->buffer, INITIAL_BUFFER_SIZE); + + dctx->ctxdata.gssctx = ctx; + + return (ISC_R_SUCCESS); +} + +/*% + * Destroy the temporary sign/verify context. + */ +static void +gssapi_destroy_signverify_ctx(dst_context_t *dctx) { + dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx; + + if (ctx != NULL) { + if (ctx->buffer != NULL) { + isc_buffer_free(&ctx->buffer); + } + isc_mem_put(dctx->mctx, ctx, + sizeof(dst_gssapi_signverifyctx_t)); + dctx->ctxdata.gssctx = NULL; + } +} + +/*% + * Add data to our running buffer of data we will be signing or verifying. + * This code will see if the new data will fit in our existing buffer, and + * copy it in if it will. If not, it will attempt to allocate a larger + * buffer and copy old+new into it, and free the old buffer. + */ +static isc_result_t +gssapi_adddata(dst_context_t *dctx, const isc_region_t *data) { + dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx; + isc_buffer_t *newbuffer = NULL; + isc_region_t r; + unsigned int length; + isc_result_t result; + + result = isc_buffer_copyregion(ctx->buffer, data); + if (result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + length = isc_buffer_length(ctx->buffer) + data->length + BUFFER_EXTRA; + + isc_buffer_allocate(dctx->mctx, &newbuffer, length); + + isc_buffer_usedregion(ctx->buffer, &r); + (void)isc_buffer_copyregion(newbuffer, &r); + (void)isc_buffer_copyregion(newbuffer, data); + + isc_buffer_free(&ctx->buffer); + ctx->buffer = newbuffer; + + return (ISC_R_SUCCESS); +} + +/*% + * Sign. + */ +static isc_result_t +gssapi_sign(dst_context_t *dctx, isc_buffer_t *sig) { + dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx; + isc_region_t message; + gss_buffer_desc gmessage, gsig; + OM_uint32 minor, gret; + gss_ctx_id_t gssctx = dctx->key->keydata.gssctx; + char buf[1024]; + + /* + * Convert the data we wish to sign into a structure gssapi can + * understand. + */ + isc_buffer_usedregion(ctx->buffer, &message); + REGION_TO_GBUFFER(message, gmessage); + + /* + * Generate the signature. + */ + gret = gss_get_mic(&minor, gssctx, GSS_C_QOP_DEFAULT, &gmessage, &gsig); + + /* + * If it did not complete, we log the result and return a generic + * failure code. + */ + if (gret != GSS_S_COMPLETE) { + gss_log(3, "GSS sign error: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + return (ISC_R_FAILURE); + } + + /* + * If it will not fit in our allocated buffer, return that we need + * more space. + */ + if (gsig.length > isc_buffer_availablelength(sig)) { + gss_release_buffer(&minor, &gsig); + return (ISC_R_NOSPACE); + } + + /* + * Copy the output into our buffer space, and release the gssapi + * allocated space. + */ + isc_buffer_putmem(sig, gsig.value, (unsigned int)gsig.length); + if (gsig.length != 0U) { + gss_release_buffer(&minor, &gsig); + } + + return (ISC_R_SUCCESS); +} + +/*% + * Verify. + */ +static isc_result_t +gssapi_verify(dst_context_t *dctx, const isc_region_t *sig) { + dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx; + isc_region_t message; + gss_buffer_desc gmessage, gsig; + OM_uint32 minor, gret; + gss_ctx_id_t gssctx = dctx->key->keydata.gssctx; + char err[1024]; + + /* + * Convert the data we wish to sign into a structure gssapi can + * understand. + */ + isc_buffer_usedregion(ctx->buffer, &message); + REGION_TO_GBUFFER(message, gmessage); + REGION_TO_GBUFFER(*sig, gsig); + + /* + * Verify the data. + */ + gret = gss_verify_mic(&minor, gssctx, &gmessage, &gsig, NULL); + + /* + * Convert return codes into something useful to us. + */ + if (gret != GSS_S_COMPLETE) { + gss_log(3, "GSS verify error: %s", + gss_error_tostring(gret, minor, err, sizeof(err))); + if (gret == GSS_S_DEFECTIVE_TOKEN || gret == GSS_S_BAD_SIG || + gret == GSS_S_DUPLICATE_TOKEN || gret == GSS_S_OLD_TOKEN || + gret == GSS_S_UNSEQ_TOKEN || gret == GSS_S_GAP_TOKEN || + gret == GSS_S_CONTEXT_EXPIRED || gret == GSS_S_NO_CONTEXT || + gret == GSS_S_FAILURE) + { + return (DST_R_VERIFYFAILURE); + } else { + return (ISC_R_FAILURE); + } + } + + return (ISC_R_SUCCESS); +} + +static bool +gssapi_compare(const dst_key_t *key1, const dst_key_t *key2) { + gss_ctx_id_t gsskey1 = key1->keydata.gssctx; + gss_ctx_id_t gsskey2 = key2->keydata.gssctx; + + /* No idea */ + return (gsskey1 == gsskey2); +} + +static isc_result_t +gssapi_generate(dst_key_t *key, int unused, void (*callback)(int)) { + UNUSED(key); + UNUSED(unused); + UNUSED(callback); + + /* No idea */ + return (ISC_R_FAILURE); +} + +static bool +gssapi_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +gssapi_destroy(dst_key_t *key) { + REQUIRE(key != NULL); + dst_gssapi_deletectx(key->mctx, &key->keydata.gssctx); + key->keydata.gssctx = NULL; +} + +static isc_result_t +gssapi_restore(dst_key_t *key, const char *keystr) { + OM_uint32 major, minor; + unsigned int len; + isc_buffer_t *b = NULL; + isc_region_t r; + gss_buffer_desc gssbuffer; + isc_result_t result; + + len = strlen(keystr); + if ((len % 4) != 0U) { + return (ISC_R_BADBASE64); + } + + len = (len / 4) * 3; + + isc_buffer_allocate(key->mctx, &b, len); + + result = isc_base64_decodestring(keystr, b); + if (result != ISC_R_SUCCESS) { + isc_buffer_free(&b); + return (result); + } + + isc_buffer_remainingregion(b, &r); + REGION_TO_GBUFFER(r, gssbuffer); + major = gss_import_sec_context(&minor, &gssbuffer, + (gss_ctx_id_t *)&key->keydata.gssctx); + if (major != GSS_S_COMPLETE) { + isc_buffer_free(&b); + return (ISC_R_FAILURE); + } + + isc_buffer_free(&b); + return (ISC_R_SUCCESS); +} + +static isc_result_t +gssapi_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) { + OM_uint32 major, minor; + gss_buffer_desc gssbuffer; + size_t len; + char *buf; + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + major = gss_export_sec_context( + &minor, (gss_ctx_id_t *)&key->keydata.gssctx, &gssbuffer); + if (major != GSS_S_COMPLETE) { + fprintf(stderr, "gss_export_sec_context -> %u, %u\n", major, + minor); + return (ISC_R_FAILURE); + } + if (gssbuffer.length == 0U) { + return (ISC_R_FAILURE); + } + len = ((gssbuffer.length + 2) / 3) * 4; + buf = isc_mem_get(mctx, len); + isc_buffer_init(&b, buf, (unsigned int)len); + GBUFFER_TO_REGION(gssbuffer, r); + result = isc_base64_totext(&r, 0, "", &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + gss_release_buffer(&minor, &gssbuffer); + *buffer = buf; + *length = (int)len; + return (ISC_R_SUCCESS); +} + +static dst_func_t gssapi_functions = { + gssapi_create_signverify_ctx, + NULL, /*%< createctx2 */ + gssapi_destroy_signverify_ctx, + gssapi_adddata, + gssapi_sign, + gssapi_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + gssapi_compare, + NULL, /*%< paramcompare */ + gssapi_generate, + gssapi_isprivate, + gssapi_destroy, + NULL, /*%< todns */ + NULL, /*%< fromdns */ + NULL, /*%< tofile */ + NULL, /*%< parse */ + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + gssapi_dump, + gssapi_restore, +}; + +isc_result_t +dst__gssapi_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + *funcp = &gssapi_functions; + } + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c new file mode 100644 index 0000000..6eed756 --- /dev/null +++ b/lib/dns/gssapictx.c @@ -0,0 +1,962 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include +#include +#include + +#if HAVE_GSSAPI_GSSAPI_H +#include +#elif HAVE_GSSAPI_H +#include +#endif + +#if HAVE_GSSAPI_GSSAPI_KRB5_H +#include +#elif HAVE_GSSAPI_KRB5_H +#include +#endif + +#if HAVE_KRB5_KRB5_H +#include +#elif HAVE_KRB5_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" + +#if HAVE_GSSAPI + +#ifndef GSS_KRB5_MECHANISM +static unsigned char krb5_mech_oid_bytes[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x12, 0x01, 0x02, 0x02 }; +static gss_OID_desc __gss_krb5_mechanism_oid_desc = { + sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes +}; +#define GSS_KRB5_MECHANISM (&__gss_krb5_mechanism_oid_desc) +#endif /* ifndef GSS_KRB5_MECHANISM */ + +#ifndef GSS_SPNEGO_MECHANISM +static unsigned char spnego_mech_oid_bytes[] = { 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x02 }; +static gss_OID_desc __gss_spnego_mechanism_oid_desc = { + sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes +}; +#define GSS_SPNEGO_MECHANISM (&__gss_spnego_mechanism_oid_desc) +#endif /* ifndef GSS_SPNEGO_MECHANISM */ + +#define REGION_TO_GBUFFER(r, gb) \ + do { \ + (gb).length = (r).length; \ + (gb).value = (r).base; \ + } while (0) + +#define GBUFFER_TO_REGION(gb, r) \ + do { \ + (r).length = (unsigned int)(gb).length; \ + (r).base = (gb).value; \ + } while (0) + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto out; \ + } while (0) + +static void +name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer, + gss_buffer_desc *gbuffer) { + dns_name_t tname; + const dns_name_t *namep; + isc_region_t r; + isc_result_t result; + + if (!dns_name_isabsolute(name)) { + namep = name; + } else { + unsigned int labels; + dns_name_init(&tname, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 0, labels - 1, &tname); + namep = &tname; + } + + result = dns_name_toprincipal(namep, buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(buffer, 0); + isc_buffer_usedregion(buffer, &r); + REGION_TO_GBUFFER(r, *gbuffer); +} + +static void +log_cred(const gss_cred_id_t cred) { + OM_uint32 gret, minor, lifetime; + gss_name_t gname; + gss_buffer_desc gbuffer; + gss_cred_usage_t usage; + const char *usage_text; + char buf[1024]; + + gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_inquire_cred: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + return; + } + + gret = gss_display_name(&minor, gname, &gbuffer, NULL); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_display_name: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + } else { + switch (usage) { + case GSS_C_BOTH: + usage_text = "GSS_C_BOTH"; + break; + case GSS_C_INITIATE: + usage_text = "GSS_C_INITIATE"; + break; + case GSS_C_ACCEPT: + usage_text = "GSS_C_ACCEPT"; + break; + default: + usage_text = "???"; + } + gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value, + usage_text, (unsigned long)lifetime); + } + + if (gret == GSS_S_COMPLETE) { + if (gbuffer.length != 0U) { + gret = gss_release_buffer(&minor, &gbuffer); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_buffer: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + } + + gret = gss_release_name(&minor, &gname); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_name: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + } +} + +/* + * check for the most common configuration errors. + * + * The errors checked for are: + * - tkey-gssapi-credential doesn't start with DNS/ + * - the default realm in /etc/krb5.conf and the + * tkey-gssapi-credential bind config option don't match + * + * Note that if tkey-gssapi-keytab is set then these configure checks + * are not performed, and runtime errors from gssapi are used instead + */ +static void +check_config(const char *gss_name) { + const char *p; + krb5_context krb5_ctx; + char *krb5_realm_name = NULL; + + if (strncasecmp(gss_name, "DNS/", 4) != 0) { + gss_log(ISC_LOG_ERROR, + "tkey-gssapi-credential (%s) " + "should start with 'DNS/'", + gss_name); + return; + } + + if (krb5_init_context(&krb5_ctx) != 0) { + gss_log(ISC_LOG_ERROR, "Unable to initialise krb5 context"); + return; + } + if (krb5_get_default_realm(krb5_ctx, &krb5_realm_name) != 0) { + gss_log(ISC_LOG_ERROR, "Unable to get krb5 default realm"); + krb5_free_context(krb5_ctx); + return; + } + p = strchr(gss_name, '@'); + if (p == NULL) { + gss_log(ISC_LOG_ERROR, + "badly formatted " + "tkey-gssapi-credentials (%s)", + gss_name); + krb5_free_context(krb5_ctx); + return; + } + if (strcasecmp(p + 1, krb5_realm_name) != 0) { + gss_log(ISC_LOG_ERROR, + "default realm from krb5.conf (%s) " + "does not match tkey-gssapi-credential (%s)", + krb5_realm_name, gss_name); + krb5_free_context(krb5_ctx); + return; + } + krb5_free_context(krb5_ctx); +} + +static OM_uint32 +mech_oid_set_create(OM_uint32 *minor, gss_OID_set *mech_oid_set) { + OM_uint32 gret; + + gret = gss_create_empty_oid_set(minor, mech_oid_set); + if (gret != GSS_S_COMPLETE) { + return (gret); + } + + gret = gss_add_oid_set_member(minor, GSS_KRB5_MECHANISM, mech_oid_set); + if (gret != GSS_S_COMPLETE) { + goto release; + } + + gret = gss_add_oid_set_member(minor, GSS_SPNEGO_MECHANISM, + mech_oid_set); + if (gret != GSS_S_COMPLETE) { + goto release; + } + +release: + REQUIRE(gss_release_oid_set(minor, mech_oid_set) == GSS_S_COMPLETE); + + return (gret); +} + +static void +mech_oid_set_release(gss_OID_set *mech_oid_set) { + OM_uint32 minor; + + REQUIRE(gss_release_oid_set(&minor, mech_oid_set) == GSS_S_COMPLETE); +} + +isc_result_t +dst_gssapi_acquirecred(const dns_name_t *name, bool initiate, + dns_gss_cred_id_t *cred) { + isc_result_t result; + isc_buffer_t namebuf; + gss_name_t gname; + gss_buffer_desc gnamebuf; + unsigned char array[DNS_NAME_MAXTEXT + 1]; + OM_uint32 gret, minor; + OM_uint32 lifetime; + gss_cred_usage_t usage; + char buf[1024]; + gss_OID_set mech_oid_set; + + REQUIRE(cred != NULL && *cred == NULL); + + /* + * XXXSRA In theory we could use GSS_C_NT_HOSTBASED_SERVICE + * here when we're in the acceptor role, which would let us + * default the hostname and use a compiled in default service + * name of "DNS", giving one less thing to configure in + * named.conf. Unfortunately, this creates a circular + * dependency due to DNS-based realm lookup in at least one + * GSSAPI implementation (Heimdal). Oh well. + */ + if (name != NULL) { + isc_buffer_init(&namebuf, array, sizeof(array)); + name_to_gbuffer(name, &namebuf, &gnamebuf); + gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname); + if (gret != GSS_S_COMPLETE) { + check_config((char *)array); + + gss_log(3, "failed gss_import_name: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + return (ISC_R_FAILURE); + } + } else { + gname = NULL; + } + + /* Get the credentials. */ + if (gname != NULL) { + gss_log(3, "acquiring credentials for %s", + (char *)gnamebuf.value); + } else { + /* XXXDCL does this even make any sense? */ + gss_log(3, "acquiring credentials for ?"); + } + + if (initiate) { + usage = GSS_C_INITIATE; + } else { + usage = GSS_C_ACCEPT; + } + + gret = mech_oid_set_create(&minor, &mech_oid_set); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed to create OID_set: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + return (ISC_R_FAILURE); + } + + gret = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, mech_oid_set, + usage, (gss_cred_id_t *)cred, NULL, &lifetime); + + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed to acquire %s credentials for %s: %s", + initiate ? "initiate" : "accept", + (gname != NULL) ? (char *)gnamebuf.value : "?", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + if (gname != NULL) { + check_config((char *)array); + } + result = ISC_R_FAILURE; + goto cleanup; + } + + gss_log(4, "acquired %s credentials for %s", + initiate ? "initiate" : "accept", + (gname != NULL) ? (char *)gnamebuf.value : "?"); + + log_cred(*cred); + result = ISC_R_SUCCESS; + +cleanup: + mech_oid_set_release(&mech_oid_set); + + if (gname != NULL) { + gret = gss_release_name(&minor, &gname); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_name: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + + return (result); +} + +bool +dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain) { + char sbuf[DNS_NAME_FORMATSIZE]; + char rbuf[DNS_NAME_FORMATSIZE]; + char *sname; + char *rname; + isc_buffer_t buffer; + isc_result_t result; + + /* + * It is far, far easier to write the names we are looking at into + * a string, and do string operations on them. + */ + isc_buffer_init(&buffer, sbuf, sizeof(sbuf)); + result = dns_name_toprincipal(signer, &buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(&buffer, 0); + dns_name_format(realm, rbuf, sizeof(rbuf)); + + /* + * Find the realm portion. This is the part after the @. If it + * does not exist, we don't have something we like, so we fail our + * compare. + */ + rname = strchr(sbuf, '@'); + if (rname == NULL) { + return (false); + } + *rname = '\0'; + rname++; + + if (strcmp(rname, rbuf) != 0) { + return (false); + } + + /* + * Find the host portion of the signer's name. We do this by + * searching for the first / character. We then check to make + * certain the instance name is "host" + * + * This will work for + * host/example.com@EXAMPLE.COM + */ + sname = strchr(sbuf, '/'); + if (sname == NULL) { + return (false); + } + *sname = '\0'; + sname++; + if (strcmp(sbuf, "host") != 0) { + return (false); + } + + /* + * If name is non NULL check that it matches against the + * machine name as expected. + */ + if (name != NULL) { + dns_fixedname_t fixed; + dns_name_t *machine; + + machine = dns_fixedname_initname(&fixed); + result = dns_name_fromstring(machine, sname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (false); + } + if (subdomain) { + return (dns_name_issubdomain(name, machine)); + } + return (dns_name_equal(name, machine)); + } + + return (true); +} + +bool +dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain) { + char sbuf[DNS_NAME_FORMATSIZE]; + char rbuf[DNS_NAME_FORMATSIZE]; + char *sname; + char *rname; + isc_buffer_t buffer; + isc_result_t result; + + /* + * It is far, far easier to write the names we are looking at into + * a string, and do string operations on them. + */ + isc_buffer_init(&buffer, sbuf, sizeof(sbuf)); + result = dns_name_toprincipal(signer, &buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(&buffer, 0); + dns_name_format(realm, rbuf, sizeof(rbuf)); + + /* + * Find the realm portion. This is the part after the @. If it + * does not exist, we don't have something we like, so we fail our + * compare. + */ + rname = strchr(sbuf, '@'); + if (rname == NULL) { + return (false); + } + sname = strchr(sbuf, '$'); + if (sname == NULL) { + return (false); + } + + /* + * Verify that the $ and @ follow one another. + */ + if (rname - sname != 1) { + return (false); + } + + /* + * Find the host portion of the signer's name. Zero out the $ so + * it terminates the signer's name, and skip past the @ for + * the realm. + * + * All service principals in Microsoft format seem to be in + * machinename$@EXAMPLE.COM + * format. + */ + rname++; + *sname = '\0'; + + if (strcmp(rname, rbuf) != 0) { + return (false); + } + + /* + * Now, we check that the realm matches (case sensitive) and that + * 'name' matches against 'machinename' qualified with 'realm'. + */ + if (name != NULL) { + dns_fixedname_t fixed; + dns_name_t *machine; + + machine = dns_fixedname_initname(&fixed); + result = dns_name_fromstring2(machine, sbuf, realm, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (false); + } + if (subdomain) { + return (dns_name_issubdomain(name, machine)); + } + return (dns_name_equal(name, machine)); + } + + return (true); +} + +isc_result_t +dst_gssapi_releasecred(dns_gss_cred_id_t *cred) { + OM_uint32 gret, minor; + char buf[1024]; + + REQUIRE(cred != NULL && *cred != NULL); + + gret = gss_release_cred(&minor, (gss_cred_id_t *)cred); + if (gret != GSS_S_COMPLETE) { + /* Log the error, but still free the credential's memory */ + gss_log(3, "failed releasing credential: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + } + *cred = NULL; + + return (ISC_R_SUCCESS); +} + +/* + * Format a gssapi error message info into a char ** on the given memory + * context. This is used to return gssapi error messages back up the + * call chain for reporting to the user. + */ +static void +gss_err_message(isc_mem_t *mctx, uint32_t major, uint32_t minor, + char **err_message) { + char buf[1024]; + char *estr; + + if (err_message == NULL || mctx == NULL) { + /* the caller doesn't want any error messages */ + return; + } + + estr = gss_error_tostring(major, minor, buf, sizeof(buf)); + if (estr != NULL) { + (*err_message) = isc_mem_strdup(mctx, estr); + } +} + +isc_result_t +dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, + isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx, + isc_mem_t *mctx, char **err_message) { + isc_region_t r; + isc_buffer_t namebuf; + gss_name_t gname; + OM_uint32 gret, minor, ret_flags, flags; + gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER; + isc_result_t result; + gss_buffer_desc gnamebuf; + unsigned char array[DNS_NAME_MAXTEXT + 1]; + + /* Client must pass us a valid gss_ctx_id_t here */ + REQUIRE(gssctx != NULL); + REQUIRE(mctx != NULL); + + isc_buffer_init(&namebuf, array, sizeof(array)); + name_to_gbuffer(name, &namebuf, &gnamebuf); + + /* Get the name as a GSS name */ + gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname); + if (gret != GSS_S_COMPLETE) { + gss_err_message(mctx, gret, minor, err_message); + result = ISC_R_FAILURE; + goto out; + } + + if (intoken != NULL) { + /* Don't call gss_release_buffer for gintoken! */ + REGION_TO_GBUFFER(*intoken, gintoken); + gintokenp = &gintoken; + } else { + gintokenp = NULL; + } + + /* + * Note that we don't set GSS_C_SEQUENCE_FLAG as Windows DNS + * servers don't like it. + */ + flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + + gret = gss_init_sec_context( + &minor, GSS_C_NO_CREDENTIAL, (gss_ctx_id_t *)gssctx, gname, + GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL, + &gouttoken, &ret_flags, NULL); + + if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) { + gss_err_message(mctx, gret, minor, err_message); + if (err_message != NULL && *err_message != NULL) { + gss_log(3, "Failure initiating security context: %s", + *err_message); + } else { + gss_log(3, "Failure initiating security context"); + } + + result = ISC_R_FAILURE; + goto out; + } + + /* + * XXXSRA Not handled yet: RFC 3645 3.1.1: check ret_flags + * MUTUAL and INTEG flags, fail if either not set. + */ + + /* + * RFC 2744 states the a valid output token has a non-zero length. + */ + if (gouttoken.length != 0U) { + GBUFFER_TO_REGION(gouttoken, r); + RETERR(isc_buffer_copyregion(outtoken, &r)); + } + + if (gret == GSS_S_COMPLETE) { + result = ISC_R_SUCCESS; + } else { + result = DNS_R_CONTINUE; + } + +out: + if (gouttoken.length != 0U) { + (void)gss_release_buffer(&minor, &gouttoken); + } + (void)gss_release_name(&minor, &gname); + return (result); +} + +isc_result_t +dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + isc_region_t *intoken, isc_buffer_t **outtoken, + dns_gss_ctx_id_t *ctxout, dns_name_t *principal, + isc_mem_t *mctx) { + isc_region_t r; + isc_buffer_t namebuf; + gss_buffer_desc gnamebuf = GSS_C_EMPTY_BUFFER, gintoken, + gouttoken = GSS_C_EMPTY_BUFFER; + OM_uint32 gret, minor; + gss_ctx_id_t context = GSS_C_NO_CONTEXT; + gss_name_t gname = NULL; + isc_result_t result; + char buf[1024]; + + REQUIRE(outtoken != NULL && *outtoken == NULL); + + REGION_TO_GBUFFER(*intoken, gintoken); + + if (*ctxout == NULL) { + context = GSS_C_NO_CONTEXT; + } else { + context = *ctxout; + } + + if (gssapi_keytab != NULL) { +#if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H + gret = gsskrb5_register_acceptor_identity(gssapi_keytab); + if (gret != GSS_S_COMPLETE) { + gss_log(3, + "failed " + "gsskrb5_register_acceptor_identity(%s): %s", + gssapi_keytab, + gss_error_tostring(gret, 0, buf, sizeof(buf))); + return (DNS_R_INVALIDTKEY); + } +#else + /* + * Minimize memory leakage by only setting KRB5_KTNAME + * if it needs to change. + */ + const char *old = getenv("KRB5_KTNAME"); + if (old == NULL || strcmp(old, gssapi_keytab) != 0) { + size_t size; + char *kt; + + size = strlen(gssapi_keytab) + 13; + kt = malloc(size); + if (kt == NULL) { + return (ISC_R_NOMEMORY); + } + snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab); + if (putenv(kt) != 0) { + return (ISC_R_NOMEMORY); + } + } +#endif + } + + log_cred(cred); + + gret = gss_accept_sec_context(&minor, &context, cred, &gintoken, + GSS_C_NO_CHANNEL_BINDINGS, &gname, NULL, + &gouttoken, NULL, NULL, NULL); + + result = ISC_R_FAILURE; + + switch (gret) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + break; + case GSS_S_DEFECTIVE_TOKEN: + case GSS_S_DEFECTIVE_CREDENTIAL: + case GSS_S_BAD_SIG: + case GSS_S_DUPLICATE_TOKEN: + case GSS_S_OLD_TOKEN: + case GSS_S_NO_CRED: + case GSS_S_CREDENTIALS_EXPIRED: + case GSS_S_BAD_BINDINGS: + case GSS_S_NO_CONTEXT: + case GSS_S_BAD_MECH: + case GSS_S_FAILURE: + result = DNS_R_INVALIDTKEY; + /* fall through */ + default: + gss_log(3, "failed gss_accept_sec_context: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + if (gouttoken.length > 0U) { + (void)gss_release_buffer(&minor, &gouttoken); + } + return (result); + } + + if (gouttoken.length > 0U) { + isc_buffer_allocate(mctx, outtoken, + (unsigned int)gouttoken.length); + GBUFFER_TO_REGION(gouttoken, r); + RETERR(isc_buffer_copyregion(*outtoken, &r)); + (void)gss_release_buffer(&minor, &gouttoken); + } + + if (gret == GSS_S_COMPLETE) { + gret = gss_display_name(&minor, gname, &gnamebuf, NULL); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_display_name: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + RETERR(ISC_R_FAILURE); + } + + /* + * Compensate for a bug in Solaris8's implementation + * of gss_display_name(). Should be harmless in any + * case, since principal names really should not + * contain null characters. + */ + if (gnamebuf.length > 0U && + ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0') + { + gnamebuf.length--; + } + + gss_log(3, "gss-api source name (accept) is %.*s", + (int)gnamebuf.length, (char *)gnamebuf.value); + + GBUFFER_TO_REGION(gnamebuf, r); + isc_buffer_init(&namebuf, r.base, r.length); + isc_buffer_add(&namebuf, r.length); + + RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, + NULL)); + + if (gnamebuf.length != 0U) { + gret = gss_release_buffer(&minor, &gnamebuf); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_buffer: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + } else { + result = DNS_R_CONTINUE; + } + + *ctxout = context; + +out: + if (gname != NULL) { + gret = gss_release_name(&minor, &gname); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_release_name: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + + return (result); +} + +isc_result_t +dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) { + OM_uint32 gret, minor; + char buf[1024]; + + UNUSED(mctx); + + REQUIRE(gssctx != NULL && *gssctx != NULL); + + /* Delete the context from the GSS provider */ + gret = gss_delete_sec_context(&minor, (gss_ctx_id_t *)gssctx, + GSS_C_NO_BUFFER); + if (gret != GSS_S_COMPLETE) { + /* Log the error, but still free the context's memory */ + gss_log(3, "Failure deleting security context %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + } + return (ISC_R_SUCCESS); +} + +char * +gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) { + gss_buffer_desc msg_minor = GSS_C_EMPTY_BUFFER, + msg_major = GSS_C_EMPTY_BUFFER; + OM_uint32 msg_ctx, minor_stat; + + /* Handle major status */ + msg_ctx = 0; + (void)gss_display_status(&minor_stat, major, GSS_C_GSS_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg_major); + + /* Handle minor status */ + msg_ctx = 0; + (void)gss_display_status(&minor_stat, minor, GSS_C_MECH_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg_minor); + + snprintf(buf, buflen, "GSSAPI error: Major = %s, Minor = %s.", + (char *)msg_major.value, (char *)msg_minor.value); + + if (msg_major.length != 0U) { + (void)gss_release_buffer(&minor_stat, &msg_major); + } + if (msg_minor.length != 0U) { + (void)gss_release_buffer(&minor_stat, &msg_minor); + } + return (buf); +} + +#else + +isc_result_t +dst_gssapi_acquirecred(const dns_name_t *name, bool initiate, + dns_gss_cred_id_t *cred) { + REQUIRE(cred != NULL && *cred == NULL); + + UNUSED(name); + UNUSED(initiate); + UNUSED(cred); + + return (ISC_R_NOTIMPLEMENTED); +} + +bool +dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain) { + UNUSED(signer); + UNUSED(name); + UNUSED(realm); + UNUSED(subdomain); + + return (false); +} + +bool +dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain) { + UNUSED(signer); + UNUSED(name); + UNUSED(realm); + UNUSED(subdomain); + + return (false); +} + +isc_result_t +dst_gssapi_releasecred(dns_gss_cred_id_t *cred) { + UNUSED(cred); + + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, + isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx, + isc_mem_t *mctx, char **err_message) { + UNUSED(name); + UNUSED(intoken); + UNUSED(outtoken); + UNUSED(gssctx); + UNUSED(mctx); + UNUSED(err_message); + + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + isc_region_t *intoken, isc_buffer_t **outtoken, + dns_gss_ctx_id_t *ctxout, dns_name_t *principal, + isc_mem_t *mctx) { + UNUSED(cred); + UNUSED(gssapi_keytab); + UNUSED(intoken); + UNUSED(outtoken); + UNUSED(ctxout); + UNUSED(principal); + UNUSED(mctx); + + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx) { + UNUSED(mctx); + UNUSED(gssctx); + return (ISC_R_NOTIMPLEMENTED); +} + +char * +gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen) { + snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", major, + minor); + + return (buf); +} + +#endif + +void +gss_log(int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_TKEY, + ISC_LOG_DEBUG(level), fmt, ap); + va_end(ap); +} diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c new file mode 100644 index 0000000..9f8e94b --- /dev/null +++ b/lib/dns/hmac_link.c @@ -0,0 +1,527 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dst_internal.h" +#ifdef HAVE_FIPS_MODE +#include "dst_openssl.h" /* FIPS_mode() prototype */ +#endif /* ifdef HAVE_FIPS_MODE */ +#include "dst_parse.h" + +#define ISC_MD_md5 ISC_MD_MD5 +#define ISC_MD_sha1 ISC_MD_SHA1 +#define ISC_MD_sha224 ISC_MD_SHA224 +#define ISC_MD_sha256 ISC_MD_SHA256 +#define ISC_MD_sha384 ISC_MD_SHA384 +#define ISC_MD_sha512 ISC_MD_SHA512 + +#define hmac_register_algorithm(alg) \ + static isc_result_t hmac##alg##_createctx(dst_key_t *key, \ + dst_context_t *dctx) { \ + return (hmac_createctx(ISC_MD_##alg, key, dctx)); \ + } \ + static void hmac##alg##_destroyctx(dst_context_t *dctx) { \ + hmac_destroyctx(dctx); \ + } \ + static isc_result_t hmac##alg##_adddata(dst_context_t *dctx, \ + const isc_region_t *data) { \ + return (hmac_adddata(dctx, data)); \ + } \ + static isc_result_t hmac##alg##_sign(dst_context_t *dctx, \ + isc_buffer_t *sig) { \ + return (hmac_sign(dctx, sig)); \ + } \ + static isc_result_t hmac##alg##_verify(dst_context_t *dctx, \ + const isc_region_t *sig) { \ + return (hmac_verify(dctx, sig)); \ + } \ + static bool hmac##alg##_compare(const dst_key_t *key1, \ + const dst_key_t *key2) { \ + return (hmac_compare(ISC_MD_##alg, key1, key2)); \ + } \ + static isc_result_t hmac##alg##_generate( \ + dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { \ + UNUSED(pseudorandom_ok); \ + UNUSED(callback); \ + return (hmac_generate(ISC_MD_##alg, key)); \ + } \ + static bool hmac##alg##_isprivate(const dst_key_t *key) { \ + return (hmac_isprivate(key)); \ + } \ + static void hmac##alg##_destroy(dst_key_t *key) { hmac_destroy(key); } \ + static isc_result_t hmac##alg##_todns(const dst_key_t *key, \ + isc_buffer_t *data) { \ + return (hmac_todns(key, data)); \ + } \ + static isc_result_t hmac##alg##_fromdns(dst_key_t *key, \ + isc_buffer_t *data) { \ + return (hmac_fromdns(ISC_MD_##alg, key, data)); \ + } \ + static isc_result_t hmac##alg##_tofile(const dst_key_t *key, \ + const char *directory) { \ + return (hmac_tofile(ISC_MD_##alg, key, directory)); \ + } \ + static isc_result_t hmac##alg##_parse( \ + dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { \ + const char *file = isc_lex_getsourcename(lexer); \ + isc_result_t result; \ + result = hmac_parse(ISC_MD_##alg, key, lexer, pub); \ + if (result == ISC_R_SUCCESS && file != NULL) { \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, \ + DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING, \ + "%s: Use of K* file pairs for HMAC is " \ + "deprecated\n", \ + file); \ + } \ + return (result); \ + } \ + static dst_func_t hmac##alg##_functions = { \ + hmac##alg##_createctx, \ + NULL, /*%< createctx2 */ \ + hmac##alg##_destroyctx, \ + hmac##alg##_adddata, \ + hmac##alg##_sign, \ + hmac##alg##_verify, \ + NULL, /*%< verify2 */ \ + NULL, /*%< computesecret */ \ + hmac##alg##_compare, \ + NULL, /*%< paramcompare */ \ + hmac##alg##_generate, \ + hmac##alg##_isprivate, \ + hmac##alg##_destroy, \ + hmac##alg##_todns, \ + hmac##alg##_fromdns, \ + hmac##alg##_tofile, \ + hmac##alg##_parse, \ + NULL, /*%< cleanup */ \ + NULL, /*%< fromlabel */ \ + NULL, /*%< dump */ \ + NULL, /*%< restore */ \ + }; \ + isc_result_t dst__hmac##alg##_init(dst_func_t **funcp) { \ + REQUIRE(funcp != NULL); \ + if (*funcp == NULL) { \ + *funcp = &hmac##alg##_functions; \ + } \ + return (ISC_R_SUCCESS); \ + } + +static isc_result_t +hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data); + +struct dst_hmac_key { + uint8_t key[ISC_MAX_BLOCK_SIZE]; +}; + +static isc_result_t +getkeybits(dst_key_t *key, struct dst_private_element *element) { + uint16_t *bits = (uint16_t *)element->data; + + if (element->length != 2) { + return (DST_R_INVALIDPRIVATEKEY); + } + + key->key_bits = ntohs(*bits); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_createctx(const isc_md_type_t *type, const dst_key_t *key, + dst_context_t *dctx) { + isc_result_t result; + const dst_hmac_key_t *hkey = key->keydata.hmac_key; + isc_hmac_t *ctx = isc_hmac_new(); /* Either returns or abort()s */ + + result = isc_hmac_init(ctx, hkey->key, isc_md_type_get_block_size(type), + type); + if (result != ISC_R_SUCCESS) { + isc_hmac_free(ctx); + return (DST_R_UNSUPPORTEDALG); + } + + dctx->ctxdata.hmac_ctx = ctx; + return (ISC_R_SUCCESS); +} + +static void +hmac_destroyctx(dst_context_t *dctx) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + REQUIRE(ctx != NULL); + + isc_hmac_free(ctx); + dctx->ctxdata.hmac_ctx = NULL; +} + +static isc_result_t +hmac_adddata(const dst_context_t *dctx, const isc_region_t *data) { + isc_result_t result; + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + + REQUIRE(ctx != NULL); + + result = isc_hmac_update(ctx, data->base, data->length); + if (result != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_sign(const dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + REQUIRE(ctx != NULL); + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen = sizeof(digest); + + if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_buffer_availablelength(sig) < digestlen) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem(sig, digest, digestlen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_verify(const dst_context_t *dctx, const isc_region_t *sig) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen = sizeof(digest); + + REQUIRE(ctx != NULL); + + if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (sig->length > digestlen) { + return (DST_R_VERIFYFAILURE); + } + + return (isc_safe_memequal(digest, sig->base, sig->length) + ? ISC_R_SUCCESS + : DST_R_VERIFYFAILURE); +} + +static bool +hmac_compare(const isc_md_type_t *type, const dst_key_t *key1, + const dst_key_t *key2) { + dst_hmac_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmac_key; + hkey2 = key2->keydata.hmac_key; + + if (hkey1 == NULL && hkey2 == NULL) { + return (true); + } else if (hkey1 == NULL || hkey2 == NULL) { + return (false); + } + + return (isc_safe_memequal(hkey1->key, hkey2->key, + isc_md_type_get_block_size(type))); +} + +static isc_result_t +hmac_generate(const isc_md_type_t *type, dst_key_t *key) { + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes, len; + unsigned char data[ISC_MAX_MD_SIZE] = { 0 }; + + len = isc_md_type_get_block_size(type); + + bytes = (key->key_size + 7) / 8; + + if (bytes > len) { + bytes = len; + key->key_size = len * 8; + } + + isc_nonce_buf(data, bytes); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + + ret = hmac_fromdns(type, key, &b); + + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmac_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmac_destroy(dst_key_t *key) { + dst_hmac_key_t *hkey = key->keydata.hmac_key; + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmac_key = NULL; +} + +static isc_result_t +hmac_todns(const dst_key_t *key, isc_buffer_t *data) { + REQUIRE(key != NULL && key->keydata.hmac_key != NULL); + dst_hmac_key_t *hkey = key->keydata.hmac_key; + unsigned int bytes; + + bytes = (key->key_size + 7) / 8; + if (isc_buffer_availablelength(data) < bytes) { + return (ISC_R_NOSPACE); + } + isc_buffer_putmem(data, hkey->key, bytes); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data) { + dst_hmac_key_t *hkey; + unsigned int keylen; + isc_region_t r; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmac_key_t)); + + memset(hkey->key, 0, sizeof(hkey->key)); + + /* Hash the key if the key is longer then chosen MD block size */ + if (r.length > (unsigned int)isc_md_type_get_block_size(type)) { + if (isc_md(type, r.base, r.length, hkey->key, &keylen) != + ISC_R_SUCCESS) + { + isc_mem_put(key->mctx, hkey, sizeof(dst_hmac_key_t)); + return (DST_R_OPENSSLFAILURE); + } + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmac_key = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static int +hmac__get_tag_key(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (TAG_HMACMD5_KEY); + } else if (type == ISC_MD_SHA1) { + return (TAG_HMACSHA1_KEY); + } else if (type == ISC_MD_SHA224) { + return (TAG_HMACSHA224_KEY); + } else if (type == ISC_MD_SHA256) { + return (TAG_HMACSHA256_KEY); + } else if (type == ISC_MD_SHA384) { + return (TAG_HMACSHA384_KEY); + } else if (type == ISC_MD_SHA512) { + return (TAG_HMACSHA512_KEY); + } else { + UNREACHABLE(); + } +} + +static int +hmac__get_tag_bits(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (TAG_HMACMD5_BITS); + } else if (type == ISC_MD_SHA1) { + return (TAG_HMACSHA1_BITS); + } else if (type == ISC_MD_SHA224) { + return (TAG_HMACSHA224_BITS); + } else if (type == ISC_MD_SHA256) { + return (TAG_HMACSHA256_BITS); + } else if (type == ISC_MD_SHA384) { + return (TAG_HMACSHA384_BITS); + } else if (type == ISC_MD_SHA512) { + return (TAG_HMACSHA512_BITS); + } else { + UNREACHABLE(); + } +} + +static isc_result_t +hmac_tofile(const isc_md_type_t *type, const dst_key_t *key, + const char *directory) { + dst_hmac_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + uint16_t bits; + + if (key->keydata.hmac_key == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + return (DST_R_EXTERNALKEY); + } + + hkey = key->keydata.hmac_key; + + priv.elements[0].tag = hmac__get_tag_key(type); + priv.elements[0].length = bytes; + priv.elements[0].data = hkey->key; + + bits = htons(key->key_bits); + + priv.elements[1].tag = hmac__get_tag_bits(type); + priv.elements[1].length = sizeof(bits); + priv.elements[1].data = (uint8_t *)&bits; + + priv.nelements = 2; + + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static int +hmac__to_dst_alg(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (DST_ALG_HMACMD5); + } else if (type == ISC_MD_SHA1) { + return (DST_ALG_HMACSHA1); + } else if (type == ISC_MD_SHA224) { + return (DST_ALG_HMACSHA224); + } else if (type == ISC_MD_SHA256) { + return (DST_ALG_HMACSHA256); + } else if (type == ISC_MD_SHA384) { + return (DST_ALG_HMACSHA384); + } else if (type == ISC_MD_SHA512) { + return (DST_ALG_HMACSHA512); + } else { + UNREACHABLE(); + } +} + +static isc_result_t +hmac_parse(const isc_md_type_t *type, dst_key_t *key, isc_lex_t *lexer, + dst_key_t *pub) { + dst_private_t priv; + isc_result_t result, tresult; + isc_buffer_t b; + isc_mem_t *mctx = key->mctx; + unsigned int i; + + UNUSED(pub); + /* read private key file */ + result = dst__privstruct_parse(key, hmac__to_dst_alg(type), lexer, mctx, + &priv); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (key->external) { + result = DST_R_EXTERNALKEY; + } + + key->key_bits = 0; + for (i = 0; i < priv.nelements && result == ISC_R_SUCCESS; i++) { + switch (priv.elements[i].tag) { + case TAG_HMACMD5_KEY: + case TAG_HMACSHA1_KEY: + case TAG_HMACSHA224_KEY: + case TAG_HMACSHA256_KEY: + case TAG_HMACSHA384_KEY: + case TAG_HMACSHA512_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmac_fromdns(type, key, &b); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + } + break; + case TAG_HMACMD5_BITS: + case TAG_HMACSHA1_BITS: + case TAG_HMACSHA224_BITS: + case TAG_HMACSHA256_BITS: + case TAG_HMACSHA384_BITS: + case TAG_HMACSHA512_BITS: + tresult = getkeybits(key, &priv.elements[i]); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + } + break; + default: + result = DST_R_INVALIDPRIVATEKEY; + break; + } + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (result); +} + +hmac_register_algorithm(md5); +hmac_register_algorithm(sha1); +hmac_register_algorithm(sha224); +hmac_register_algorithm(sha256); +hmac_register_algorithm(sha384); +hmac_register_algorithm(sha512); + +/*! \file */ diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h new file mode 100644 index 0000000..a2f303b --- /dev/null +++ b/lib/dns/include/dns/acl.h @@ -0,0 +1,337 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/acl.h + * \brief + * Address match list handling. + */ + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*** + *** Types + ***/ + +typedef enum { + dns_aclelementtype_ipprefix, + dns_aclelementtype_keyname, + dns_aclelementtype_nestedacl, + dns_aclelementtype_localhost, + dns_aclelementtype_localnets, +#if defined(HAVE_GEOIP2) + dns_aclelementtype_geoip, +#endif /* HAVE_GEOIP2 */ + dns_aclelementtype_any +} dns_aclelementtype_t; + +typedef struct dns_acl_port_transports { + in_port_t port; + uint32_t transports; + bool encrypted; /* for protocols with optional encryption (e.g. HTTP) */ + bool negative; + ISC_LINK(struct dns_acl_port_transports) link; +} dns_acl_port_transports_t; + +typedef struct dns_aclipprefix dns_aclipprefix_t; + +struct dns_aclipprefix { + isc_netaddr_t address; /* IP4/IP6 */ + unsigned int prefixlen; +}; + +struct dns_aclelement { + dns_aclelementtype_t type; + bool negative; + dns_name_t keyname; +#if defined(HAVE_GEOIP2) + dns_geoip_elem_t geoip_elem; +#endif /* HAVE_GEOIP2 */ + dns_acl_t *nestedacl; + int node_num; +}; + +#define dns_acl_node_count(acl) acl->iptable->radix->num_added_node + +struct dns_acl { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + dns_iptable_t *iptable; + dns_aclelement_t *elements; + bool has_negatives; + unsigned int alloc; /*%< Elements allocated */ + unsigned int length; /*%< Elements initialized */ + char *name; /*%< Temporary use only */ + ISC_LINK(dns_acl_t) nextincache; /*%< Ditto */ + ISC_LIST(dns_acl_port_transports_t) ports_and_transports; + size_t port_proto_entries; +}; + +struct dns_aclenv { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + + isc_rwlock_t rwlock; /*%< Locks localhost and localnets */ + dns_acl_t *localhost; + dns_acl_t *localnets; + + bool match_mapped; +#if defined(HAVE_GEOIP2) + dns_geoip_databases_t *geoip; +#endif /* HAVE_GEOIP2 */ +}; + +#define DNS_ACL_MAGIC ISC_MAGIC('D', 'a', 'c', 'l') +#define DNS_ACL_VALID(a) ISC_MAGIC_VALID(a, DNS_ACL_MAGIC) + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target); +/*%< + * Create a new ACL, including an IP table and an array with room + * for 'n' ACL elements. The elements are uninitialized and the + * length is 0. + */ + +isc_result_t +dns_acl_any(isc_mem_t *mctx, dns_acl_t **target); +/*%< + * Create a new ACL that matches everything. + */ + +isc_result_t +dns_acl_none(isc_mem_t *mctx, dns_acl_t **target); +/*%< + * Create a new ACL that matches nothing. + */ + +bool +dns_acl_isany(dns_acl_t *acl); +/*%< + * Test whether ACL is set to "{ any; }" + */ + +bool +dns_acl_isnone(dns_acl_t *acl); +/*%< + * Test whether ACL is set to "{ none; }" + */ + +isc_result_t +dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos); +/*%< + * Merge the contents of one ACL into another. Call dns_iptable_merge() + * for the IP tables, then concatenate the element arrays. + * + * If pos is set to false, then the nested ACL is to be negated. This + * means reverse the sense of each *positive* element or IP table node, + * but leave negatives alone, so as to prevent a double-negative causing + * an unexpected positive match in the parent ACL. + */ + +void +dns_acl_attach(dns_acl_t *source, dns_acl_t **target); +/*%< + * Attach to acl 'source'. + * + * Requires: + *\li 'source' to be a valid acl. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_acl_detach(dns_acl_t **aclp); +/*%< + * Detach the acl. On final detach the acl must not be linked on any + * list. + * + * Requires: + *\li '*aclp' to be a valid acl. + * + * Insists: + *\li '*aclp' is not linked on final detach. + */ + +bool +dns_acl_isinsecure(const dns_acl_t *a); +/*%< + * Return #true iff the acl 'a' is considered insecure, that is, + * if it contains IP addresses other than those of the local host. + * This is intended for applications such as printing warning + * messages for suspect ACLs; it is not intended for making access + * control decisions. We make no guarantee that an ACL for which + * this function returns #false is safe. + */ + +bool +dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl, + dns_aclenv_t *aclenv); +/*%< + * Return #true iff the 'addr', 'signer', or ECS values are + * permitted by 'acl' in environment 'aclenv'. + */ + +isc_result_t +dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp); +/*%< + * Create ACL environment, setting up localhost and localnets ACLs + */ + +void +dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s); +/*%< + * Copy the ACLs from one ACL environment object to another. + * + * Requires: + *\li both 's' and 't' are valid ACL environments. + */ + +void +dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets); +/*%< + * Attach the 'localhost' and 'localnets' arguments to 'env' ACL environment + */ + +void +dns_aclenv_attach(dns_aclenv_t *source, dns_aclenv_t **targetp); +/*%< + * Attach '*targetp' to ACL environment 'source'. + * + * Requires: + *\li 'source' is a valid ACL environment. + *\li 'targetp' is not NULL and '*targetp' is NULL. + */ + +void +dns_aclenv_detach(dns_aclenv_t **aclenvp); +/*%< + * Detach an ACL environment; on final detach, destroy it. + * + * Requires: + *\li '*aclenvp' to be a valid ACL environment + */ + +isc_result_t +dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, + const dns_acl_t *acl, dns_aclenv_t *env, int *match, + const dns_aclelement_t **matchelt); +/*%< + * General, low-level ACL matching. This is expected to + * be useful even for weird stuff like the topology and sortlist statements. + * + * Match the address 'reqaddr', and optionally the key name 'reqsigner', + * against 'acl'. 'reqsigner' may be NULL. + * + * If there is a match, '*match' will be set to an integer whose absolute + * value corresponds to the order in which the matching value was inserted + * into the ACL. For a positive match, this value will be positive; for a + * negative match, it will be negative. + * + * If there is no match, *match will be set to zero. + * + * If there is a match in the element list (either positive or negative) + * and 'matchelt' is non-NULL, *matchelt will be pointed to the matching + * element. + * + * 'env' points to the current ACL environment, including the + * current values of localhost and localnets and (if applicable) + * the GeoIP context. + * + * Returns: + *\li #ISC_R_SUCCESS Always succeeds. + */ + +bool +dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, + const dns_aclelement_t *e, dns_aclenv_t *env, + const dns_aclelement_t **matchelt); +/*%< + * Like dns_acl_match, but matches against the single ACL element 'e' + * rather than a complete ACL, and returns true iff it matched. + * + * To determine whether the match was positive or negative, the + * caller should examine e->negative. Since the element 'e' may be + * a reference to a named ACL or a nested ACL, a matching element + * returned through 'matchelt' is not necessarily 'e' itself. + */ + +isc_result_t +dns_acl_match_port_transport(const isc_netaddr_t *reqaddr, + const in_port_t local_port, + const isc_nmsocket_type_t transport, + const bool encrypted, const dns_name_t *reqsigner, + const dns_acl_t *acl, dns_aclenv_t *env, + int *match, const dns_aclelement_t **matchelt); +/*%< + * Like dns_acl_match, but able to match the server port and + * transport, as well as encryption status. + * + * Requires: + *\li 'reqaddr' is not 'NULL'; + *\li 'acl' is a valid ACL object. + */ + +void +dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port, + const uint32_t transports, const bool encrypted, + const bool negative); +/*%< + * Adds a "port-transports" entry to the specified ACL. Transports + * are specified as a bit-set 'transports' consisting of entries + * defined in the isc_nmsocket_type enumeration. + * + * Requires: + *\li 'acl' is a valid ACL object; + *\li either 'port' or 'transports' is not equal to 0. + */ + +void +dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos); +/*%< + * Merges "port-transports" entries from the 'dest' ACL into + * the 'source' ACL. The 'pos' parameter works in a way similar to + * 'dns_acl_merge()'. + * + * Requires: + *\li 'dest' is a valid ACL object; + *\li 'source' is a valid ACL object. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h new file mode 100644 index 0000000..6310a8c --- /dev/null +++ b/lib/dns/include/dns/adb.h @@ -0,0 +1,805 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/adb.h + *\brief + * DNS Address Database + * + * This module implements an address database (ADB) for mapping a name + * to an isc_sockaddr_t. It also provides statistical information on + * how good that address might be. + * + * A client will pass in a dns_name_t, and the ADB will walk through + * the rdataset looking up addresses associated with the name. If it + * is found on the internal lists, a structure is filled in with the + * address information and stats for found addresses. + * + * If the name cannot be found on the internal lists, a new entry will + * be created for a name if all the information needed can be found + * in the zone table or cache. This new address will then be returned. + * + * If a request must be made to remote servers to satisfy a name lookup, + * this module will start fetches to try to complete these addresses. When + * at least one more completes, an event is sent to the caller. If none of + * them resolve before the fetch times out, an event indicating this is + * sent instead. + * + * Records are stored internally until a timer expires. The timer is the + * smaller of the TTL or signature validity period. + * + * Lameness is stored per tuple, and this data hangs off each + * address field. When an address is marked lame for a given tuple the address + * will not be returned to a caller. + * + * + * MP: + * + *\li The ADB takes care of all necessary locking. + * + *\li Only the task which initiated the name lookup can cancel the lookup. + * + * + * Security: + * + *\li None, since all data stored is required to be pre-filtered. + * (Cache needs to be sane, fetches return bounds-checked and sanity- + * checked data, caller passes a good dns_name_t for the zone, etc) + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Magic number checks + ***/ + +#define DNS_ADBFIND_MAGIC ISC_MAGIC('a', 'd', 'b', 'H') +#define DNS_ADBFIND_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBFIND_MAGIC) +#define DNS_ADBADDRINFO_MAGIC ISC_MAGIC('a', 'd', 'A', 'I') +#define DNS_ADBADDRINFO_VALID(x) ISC_MAGIC_VALID(x, DNS_ADBADDRINFO_MAGIC) + +/*** + *** TYPES + ***/ + +typedef struct dns_adbname dns_adbname_t; + +/*! + *\brief + * Represents a lookup for a single name. + * + * On return, the client can safely use "list", and can reorder the list. + * Items may not be _deleted_ from this list, however, or added to it + * other than by using the dns_adb_*() API. + */ +struct dns_adbfind { + /* Public */ + unsigned int magic; /*%< RO: magic */ + dns_adbaddrinfolist_t list; /*%< RO: list of addrs */ + unsigned int query_pending; /*%< RO: partial list */ + unsigned int partial_result; /*%< RO: addrs missing */ + unsigned int options; /*%< RO: options */ + isc_result_t result_v4; /*%< RO: v4 result */ + isc_result_t result_v6; /*%< RO: v6 result */ + ISC_LINK(dns_adbfind_t) publink; /*%< RW: client use */ + + /* Private */ + isc_mutex_t lock; /* locks all below */ + in_port_t port; + int name_bucket; + unsigned int flags; + dns_adbname_t *adbname; + dns_adb_t *adb; + isc_event_t event; + ISC_LINK(dns_adbfind_t) plink; +}; + +/* + * _INET: + * _INET6: + * return addresses of that type. + * + * _EMPTYEVENT: + * Only schedule an event if no addresses are known. + * Must set _WANTEVENT for this to be meaningful. + * + * _WANTEVENT: + * An event is desired. Check this bit in the returned find to see + * if one will actually be generated. + * + * _AVOIDFETCHES: + * If set, fetches will not be generated unless no addresses are + * available in any of the address families requested. + * + * _STARTATZONE: + * Fetches will start using the closest zone data or use the root servers. + * This is useful for reestablishing glue that has expired. + * + * _GLUEOK: + * _HINTOK: + * Glue or hints are ok. These are used when matching names already + * in the adb, and when dns databases are searched. + * + * _RETURNLAME: + * Return lame servers in a find, so that all addresses are returned. + * + * _LAMEPRUNED: + * At least one address was omitted from the list because it was lame. + * This bit will NEVER be set if _RETURNLAME is set in the createfind(). + */ +/*% Return addresses of type INET. */ +#define DNS_ADBFIND_INET 0x00000001 +/*% Return addresses of type INET6. */ +#define DNS_ADBFIND_INET6 0x00000002 +#define DNS_ADBFIND_ADDRESSMASK 0x00000003 +/*% + * Only schedule an event if no addresses are known. + * Must set _WANTEVENT for this to be meaningful. + */ +#define DNS_ADBFIND_EMPTYEVENT 0x00000004 +/*% + * An event is desired. Check this bit in the returned find to see + * if one will actually be generated. + */ +#define DNS_ADBFIND_WANTEVENT 0x00000008 +/*% + * If set, fetches will not be generated unless no addresses are + * available in any of the address families requested. + */ +#define DNS_ADBFIND_AVOIDFETCHES 0x00000010 +/*% + * Fetches will start using the closest zone data or use the root servers. + * This is useful for reestablishing glue that has expired. + */ +#define DNS_ADBFIND_STARTATZONE 0x00000020 +/*% + * Glue or hints are ok. These are used when matching names already + * in the adb, and when dns databases are searched. + */ +#define DNS_ADBFIND_GLUEOK 0x00000040 +/*% + * Glue or hints are ok. These are used when matching names already + * in the adb, and when dns databases are searched. + */ +#define DNS_ADBFIND_HINTOK 0x00000080 +/*% + * Return lame servers in a find, so that all addresses are returned. + */ +#define DNS_ADBFIND_RETURNLAME 0x00000100 +/*% + * Only schedule an event if no addresses are known. + * Must set _WANTEVENT for this to be meaningful. + */ +#define DNS_ADBFIND_LAMEPRUNED 0x00000200 +/*% + * The server's fetch quota is exceeded; it will be treated as + * lame for this query. + */ +#define DNS_ADBFIND_OVERQUOTA 0x00000400 +/*% + * Don't perform a fetch even if there are no address records available. + */ +#define DNS_ADBFIND_NOFETCH 0x00000800 + +/*% + * The answers to queries come back as a list of these. + */ +struct dns_adbaddrinfo { + unsigned int magic; /*%< private */ + + isc_sockaddr_t sockaddr; /*%< [rw] */ + unsigned int srtt; /*%< [rw] microsecs */ + + unsigned int flags; /*%< [rw] */ + dns_adbentry_t *entry; /*%< private */ + ISC_LINK(dns_adbaddrinfo_t) publink; +}; + +/*!< + * The event sent to the caller task is just a plain old isc_event_t. It + * contains no data other than a simple status, passed in the "type" field + * to indicate that another address resolved, or all partially resolved + * addresses have failed to resolve. + * + * "sender" is the dns_adbfind_t used to issue this query. + * + * This is simply a standard event, with the "type" set to: + * + *\li #DNS_EVENT_ADBMOREADDRESSES -- another address resolved. + *\li #DNS_EVENT_ADBNOMOREADDRESSES -- all pending addresses failed, + * were canceled, or otherwise will + * not be usable. + *\li #DNS_EVENT_ADBCANCELED -- The request was canceled by a + * 3rd party. + *\li #DNS_EVENT_ADBNAMEDELETED -- The name was deleted, so this request + * was canceled. + * + * In each of these cases, the addresses returned by the initial call + * to dns_adb_createfind() can still be used until they are no longer needed. + */ + +/**** +**** FUNCTIONS +****/ + +isc_result_t +dns_adb_create(isc_mem_t *mem, dns_view_t *view, isc_timermgr_t *tmgr, + isc_taskmgr_t *taskmgr, dns_adb_t **newadb); +/*%< + * Create a new ADB. + * + * Notes: + * + *\li Generally, applications should not create an ADB directly, but + * should instead call dns_view_createresolver(). + * + * Requires: + * + *\li 'mem' must be a valid memory context. + * + *\li 'view' be a pointer to a valid view. + * + *\li 'tmgr' be a pointer to a valid timer manager. + * + *\li 'taskmgr' be a pointer to a valid task manager. + * + *\li 'newadb' != NULL && '*newadb' == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS after happiness. + *\li #ISC_R_NOMEMORY after resource allocation failure. + */ + +void +dns_adb_attach(dns_adb_t *adb, dns_adb_t **adbp); +/*% + * Attach to an 'adb' to 'adbp'. + * + * Requires: + *\li 'adb' to be a valid dns_adb_t, created via dns_adb_create(). + *\li 'adbp' to be a valid pointer to a *dns_adb_t which is initialized + * to NULL. + */ + +void +dns_adb_detach(dns_adb_t **adb); +/*% + * Delete the ADB. Sets *ADB to NULL. Cancels any outstanding requests. + * + * Requires: + * + *\li 'adb' be non-NULL and '*adb' be a valid dns_adb_t, created via + * dns_adb_create(). + */ + +void +dns_adb_whenshutdown(dns_adb_t *adb, isc_task_t *task, isc_event_t **eventp); +/*% + * Send '*eventp' to 'task' when 'adb' has shutdown. + * + * Requires: + * + *\li '*adb' is a valid dns_adb_t. + * + *\li eventp != NULL && *eventp is a valid event. + * + * Ensures: + * + *\li *eventp == NULL + * + *\li The event's sender field is set to the value of adb when the event + * is sent. + */ + +void +dns_adb_shutdown(dns_adb_t *adb); +/*%< + * Shutdown 'adb'. + * + * Requires: + * + * \li '*adb' is a valid dns_adb_t. + */ + +isc_result_t +dns_adb_createfind(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action, + void *arg, const dns_name_t *name, const dns_name_t *qname, + dns_rdatatype_t qtype, unsigned int options, + isc_stdtime_t now, dns_name_t *target, in_port_t port, + unsigned int depth, isc_counter_t *qc, dns_adbfind_t **find); +/*%< + * Main interface for clients. The adb will look up the name given in + * "name" and will build up a list of found addresses, and perhaps start + * internal fetches to resolve names that are unknown currently. + * + * If other addresses resolve after this call completes, an event will + * be sent to the with the sender of that event + * set to a pointer to the dns_adbfind_t returned by this function. + * + * If no events will be generated, the *find->result_v4 and/or result_v6 + * members may be examined for address lookup status. The usual #ISC_R_SUCCESS, + * #ISC_R_FAILURE, #DNS_R_NXDOMAIN, and #DNS_R_NXRRSET are returned, along with + * #ISC_R_NOTFOUND meaning the ADB has not _yet_ found the values. In this + * latter case, retrying may produce more addresses. + * + * If events will be returned, the result_v[46] members are only valid + * when that event is actually returned. + * + * The list of addresses returned is unordered. The caller must impose + * any ordering required. The list will not contain "known bad" addresses, + * however. For instance, it will not return hosts that are known to be + * lame for the zone in question. + * + * The caller cannot (directly) modify the contents of the address list's + * fields other than the "link" field. All values can be read at any + * time, however. + * + * The "now" parameter is used only for determining which entries that + * have a specific time to live or expire time should be removed from + * the running database. If specified as zero, the current time will + * be retrieved and used. + * + * If 'target' is not NULL and 'name' is an alias (i.e. the name is + * CNAME'd or DNAME'd to another name), then 'target' will be updated with + * the domain name that 'name' is aliased to. + * + * All addresses returned will have the sockaddr's port set to 'port.' + * The caller may change them directly in the dns_adbaddrinfo_t since + * they are copies of the internal address only. + * + * XXXMLG Document options, especially the flags which control how + * events are sent. + * + * Requires: + * + *\li *adb be a valid isc_adb_t object. + * + *\li If events are to be sent, *task be a valid task, + * and isc_taskaction_t != NULL. + * + *\li *name is a valid dns_name_t. + * + *\li qname != NULL and *qname be a valid dns_name_t. + * + *\li target == NULL or target is a valid name with a buffer. + * + *\li find != NULL && *find == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS Addresses might have been returned, and events will be + * delivered for unresolved addresses. + *\li #ISC_R_NOMORE Addresses might have been returned, but no events + * will ever be posted for this context. This is only + * returned if task != NULL. + *\li #ISC_R_NOMEMORY insufficient resources + *\li #DNS_R_ALIAS 'name' is an alias for another name. + * + * Calls, and returns error codes from: + * + *\li isc_stdtime_get() + * + * Notes: + * + *\li No internal reference to "name" exists after this function + * returns. + */ + +void +dns_adb_cancelfind(dns_adbfind_t *find); +/*%< + * Cancels the find, and sends the event off to the caller. + * + * It is an error to call dns_adb_cancelfind() on a find where + * no event is wanted, or will ever be sent. + * + * Note: + * + *\li It is possible that the real completion event was posted just + * before the dns_adb_cancelfind() call was made. In this case, + * dns_adb_cancelfind() will do nothing. The event callback needs + * to be prepared to find this situation (i.e. result is valid but + * the caller expects it to be canceled). + * + * Requires: + * + *\li 'find' be a valid dns_adbfind_t pointer. + * + *\li events would have been posted to the task. This can be checked + * with (find->options & DNS_ADBFIND_WANTEVENT). + * + * Ensures: + * + *\li The event was posted to the task. + */ + +void +dns_adb_destroyfind(dns_adbfind_t **find); +/*%< + * Destroys the find reference. + * + * Note: + * + *\li This can only be called after the event was delivered for a + * find. Additionally, the event MUST have been freed via + * isc_event_free() BEFORE this function is called. + * + * Requires: + * + *\li 'find' != NULL and *find be valid dns_adbfind_t pointer. + * + * Ensures: + * + *\li No "address found" events will be posted to the originating task + * after this function returns. + */ + +void +dns_adb_dump(dns_adb_t *adb, FILE *f); +/*%< + * This function is only used for debugging. It will dump as much of the + * state of the running system as possible. + * + * Requires: + * + *\li adb be valid. + * + *\li f != NULL, and is a file open for writing. + */ + +void +dns_adb_dumpfind(dns_adbfind_t *find, FILE *f); +/*%< + * This function is only used for debugging. Dump the data associated + * with a find. + * + * Requires: + * + *\li find is valid. + * + * \li f != NULL, and is a file open for writing. + */ + +isc_result_t +dns_adb_marklame(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + const dns_name_t *qname, dns_rdatatype_t type, + isc_stdtime_t expire_time); +/*%< + * Mark the given address as lame for the . expire_time should + * be set to the time when the entry should expire. That is, if it is to + * expire 10 minutes in the future, it should set it to (now + 10 * 60). + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + * + *\li qname be the qname used in the dns_adb_createfind() call. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOMEMORY -- could not mark address as lame. + */ + +/* + * Reasonable defaults for RTT adjustments + * + * (Note: these values function both as scaling factors and as + * indicators of the type of RTT adjustment operation taking place. + * Adjusting the scaling factors is fine, as long as they all remain + * unique values.) + */ +#define DNS_ADB_RTTADJDEFAULT 7 /*%< default scale */ +#define DNS_ADB_RTTADJREPLACE 0 /*%< replace with our rtt */ +#define DNS_ADB_RTTADJAGE 10 /*%< age this rtt */ + +void +dns_adb_adjustsrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int rtt, + unsigned int factor); +/*%< + * Mix the round trip time into the existing smoothed rtt. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + * + *\li 0 <= factor <= 10 + * + * Note: + * + *\li The srtt in addr will be updated to reflect the new global + * srtt value. This may include changes made by others. + */ + +void +dns_adb_agesrtt(dns_adb_t *adb, dns_adbaddrinfo_t *addr, isc_stdtime_t now); +/* + * dns_adb_agesrtt is equivalent to dns_adb_adjustsrtt with factor + * equal to DNS_ADB_RTTADJAGE and the current time passed in. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + * + * Note: + * + *\li The srtt in addr will be updated to reflect the new global + * srtt value. This may include changes made by others. + */ + +void +dns_adb_changeflags(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int bits, + unsigned int mask); +/*% + * Change Flags. + * + * Set the flags as given by: + * + *\li newflags = (oldflags & ~mask) | (bits & mask); + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +void +dns_adb_setudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, unsigned int size); +/*% + * Update seen UDP response size. The largest seen will be returned by + * dns_adb_getudpsize(). + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +unsigned int +dns_adb_getudpsize(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Return the largest seen UDP response size. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +void +dns_adb_plainresponse(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Record a successful plain DNS response. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +void +dns_adb_timeout(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Record a plain DNS UDP query failed. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +void +dns_adb_ednsto(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Record a EDNS UDP query failed. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +isc_result_t +dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *sa, + dns_adbaddrinfo_t **addrp, isc_stdtime_t now); +/*%< + * Return a dns_adbaddrinfo_t that is associated with address 'sa'. + * + * Requires: + * + *\li adb is valid. + * + *\li sa is valid. + * + *\li addrp != NULL && *addrp == NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SHUTTINGDOWN + */ + +void +dns_adb_freeaddrinfo(dns_adb_t *adb, dns_adbaddrinfo_t **addrp); +/*%< + * Free a dns_adbaddrinfo_t allocated by dns_adb_findaddrinfo(). + * + * Requires: + * + *\li adb is valid. + * + *\li *addrp is a valid dns_adbaddrinfo_t *. + */ + +void +dns_adb_flush(dns_adb_t *adb); +/*%< + * Flushes all cached data from the adb. + * + * Requires: + *\li adb is valid. + */ + +void +dns_adb_setadbsize(dns_adb_t *adb, size_t size); +/*%< + * Set a target memory size. If memory usage exceeds the target + * size entries will be removed before they would have expired on + * a random basis. + * + * If 'size' is 0 then memory usage is unlimited. + * + * Requires: + *\li 'adb' is valid. + */ + +void +dns_adb_flushname(dns_adb_t *adb, const dns_name_t *name); +/*%< + * Flush 'name' from the adb cache. + * + * Requires: + *\li 'adb' is valid. + *\li 'name' is valid. + */ + +void +dns_adb_flushnames(dns_adb_t *adb, const dns_name_t *name); +/*%< + * Flush 'name' and all subdomains from the adb cache. + * + * Requires: + *\li 'adb' is valid. + *\li 'name' is valid. + */ + +void +dns_adb_setcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + const unsigned char *cookie, size_t len); +/*%< + * Record the COOKIE associated with this address. If + * cookie is NULL or len is zero the recorded COOKIE is cleared. + * + * Requires: + *\li 'adb' is valid. + *\li 'addr' is valid. + */ + +size_t +dns_adb_getcookie(dns_adb_t *adb, dns_adbaddrinfo_t *addr, + unsigned char *cookie, size_t len); +/* + * Retrieve the saved COOKIE value and store it in 'cookie' which has + * size 'len'. + * + * Requires: + *\li 'adb' is valid. + *\li 'addr' is valid. + * + * Returns: + * The size of the cookie or zero if it doesn't fit in the buffer + * or it doesn't exist. + */ + +void +dns_adb_setquota(dns_adb_t *adb, uint32_t quota, uint32_t freq, double low, + double high, double discount); +/*%< + * Set the baseline ADB quota, and configure parameters for the + * quota adjustment algorithm. + * + * If the number of fetches currently waiting for responses from this + * address exceeds the current quota, then additional fetches are spilled. + * + * 'quota' is the highest permissible quota; it will adjust itself + * downward in response to detected congestion. + * + * After every 'freq' fetches have either completed or timed out, an + * exponentially weighted moving average of the ratio of timeouts + * to responses is calculated. If the EWMA goes above a 'high' + * threshold, then the quota is adjusted down one step; if it drops + * below a 'low' threshold, then the quota is adjusted back up one + * step. + * + * The quota adjustment is based on the function (1 / 1 + (n/10)^(3/2)), + * for values of n from 0 to 99. It starts at 100% of the baseline + * quota, and descends after 100 steps to 2%. + * + * 'discount' represents the discount rate of the moving average. Higher + * values cause older values to be discounted sooner, providing a faster + * response to changes in the timeout ratio. + * + * Requires: + *\li 'adb' is valid. + */ + +bool +dns_adbentry_overquota(dns_adbentry_t *entry); +/*%< + * Returns true if the specified ADB has too many active fetches. + * + * Requires: + *\li 'entry' is valid. + */ + +void +dns_adb_beginudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +void +dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Begin/end a UDP fetch on a particular address. + * + * These functions increment or decrement the fetch counter for + * the ADB entry so that the fetch quota can be enforced. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/badcache.h b/lib/dns/include/dns/badcache.h new file mode 100644 index 0000000..1021ba3 --- /dev/null +++ b/lib/dns/include/dns/badcache.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/badcache.h + * \brief + * Defines dns_badcache_t, the "bad cache" object. + * + * Notes: + *\li A bad cache object is a hash table of name/type tuples, + * indicating whether a given tuple known to be "bad" in some + * sense (e.g., queries for that name and type have been + * returning SERVFAIL). This is used for both the "bad server + * cache" in the resolver and for the "servfail cache" in + * the view. + * + * Reliability: + * + * Resources: + * + * Security: + * + * Standards: + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp); +/*% + * Allocate and initialize a badcache and store it in '*bcp'. + * + * Requires: + * \li mctx != NULL + * \li bcp != NULL + * \li *bcp == NULL + */ + +void +dns_badcache_destroy(dns_badcache_t **bcp); +/*% + * Flush and then free badcache in 'bcp'. '*bcp' is set to NULL on return. + * + * Requires: + * \li '*bcp' to be a valid badcache + */ + +void +dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name, + dns_rdatatype_t type, bool update, uint32_t flags, + isc_time_t *expire); +/*% + * Adds a badcache entry to the badcache 'bc' for name 'name' and + * type 'type'. If an entry already exists, then it will be updated if + * 'update' is true. The entry will be stored with flags 'flags' + * and expiration date 'expire'. + * + * Requires: + * \li bc to be a valid badcache. + * \li name != NULL + * \li expire != NULL + */ + +bool +dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name, + dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now); +/*% + * Returns true if a record is found in the badcache 'bc' matching + * 'name' and 'type', with an expiration date later than 'now'. + * If 'flagp' is not NULL, then '*flagp' is updated to the flags + * that were stored in the badcache entry. Returns false if + * no matching record is found. + * + * Requires: + * \li bc to be a valid badcache. + * \li name != NULL + * \li now != NULL + */ + +void +dns_badcache_flush(dns_badcache_t *bc); +/*% + * Flush the entire bad cache. + * + * Requires: + * \li bc to be a valid badcache + */ + +void +dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name); +/*% + * Flush the bad cache of all entries at 'name'. + * + * Requires: + * \li bc to be a valid badcache + * \li name != NULL + */ + +void +dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name); +/*% + * Flush the bad cache of all entries at or below 'name'. + * + * Requires: + * \li bc to be a valid badcache + * \li name != NULL + */ + +void +dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp); +/*% + * Print the contents of badcache 'bc' (headed by the title 'cachename') + * to file pointer 'fp'. + * + * Requires: + * \li bc to be a valid badcache + * \li cachename != NULL + * \li fp != NULL + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/bit.h b/lib/dns/include/dns/bit.h new file mode 100644 index 0000000..dc7c195 --- /dev/null +++ b/lib/dns/include/dns/bit.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/bit.h */ + +#include + +typedef uint64_t dns_bitset_t; + +#define DNS_BIT_SET(bit, bitset) (*(bitset) |= ((dns_bitset_t)1 << (bit))) +#define DNS_BIT_CLEAR(bit, bitset) (*(bitset) &= ~((dns_bitset_t)1 << (bit))) +#define DNS_BIT_CHECK(bit, bitset) \ + ((*(bitset) & ((dns_bitset_t)1 << (bit))) == ((dns_bitset_t)1 << (bit))) diff --git a/lib/dns/include/dns/byaddr.h b/lib/dns/include/dns/byaddr.h new file mode 100644 index 0000000..b92213b --- /dev/null +++ b/lib/dns/include/dns/byaddr.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/byaddr.h + * \brief + * The byaddr module provides reverse lookup services for IPv4 and IPv6 + * addresses. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li RFCs: 1034, 1035, 2181, TBS + *\li Drafts: TBS + */ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * A 'dns_byaddrevent_t' is returned when a byaddr completes. + * The sender field will be set to the byaddr that completed. If 'result' + * is ISC_R_SUCCESS, then 'names' will contain a list of names associated + * with the address. The recipient of the event must not change the list + * and must not refer to any of the name data after the event is freed. + */ +typedef struct dns_byaddrevent { + ISC_EVENT_COMMON(struct dns_byaddrevent); + isc_result_t result; + dns_namelist_t names; +} dns_byaddrevent_t; + +isc_result_t +dns_byaddr_create(isc_mem_t *mctx, const isc_netaddr_t *address, + dns_view_t *view, unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_byaddr_t **byaddrp); +/*%< + * Find the domain name of 'address'. + * + * Notes: + * + *\li There is a reverse lookup format for IPv6 addresses, 'nibble' + * + *\li The 'nibble' format for that address is + * + * \code + * 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa. + * \endcode + * + * Requires: + * + *\li 'mctx' is a valid mctx. + * + *\li 'address' is a valid IPv4 or IPv6 address. + * + *\li 'view' is a valid view which has a resolver. + * + *\li 'task' is a valid task. + * + *\li byaddrp != NULL && *byaddrp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Any resolver-related error (e.g. #ISC_R_SHUTTINGDOWN) may also be + * returned. + */ + +void +dns_byaddr_cancel(dns_byaddr_t *byaddr); +/*%< + * Cancel 'byaddr'. + * + * Notes: + * + *\li If 'byaddr' has not completed, post its #DNS_EVENT_BYADDRDONE + * event with a result code of #ISC_R_CANCELED. + * + * Requires: + * + *\li 'byaddr' is a valid byaddr. + */ + +void +dns_byaddr_destroy(dns_byaddr_t **byaddrp); +/*%< + * Destroy 'byaddr'. + * + * Requires: + * + *\li '*byaddrp' is a valid byaddr. + * + *\li The caller has received the #DNS_EVENT_BYADDRDONE event (either because + * the byaddr completed or because dns_byaddr_cancel() was called). + * + * Ensures: + * + *\li *byaddrp == NULL. + */ + +isc_result_t +dns_byaddr_createptrname(const isc_netaddr_t *address, unsigned int options, + dns_name_t *name); +/*%< + * Creates a name that would be used in a PTR query for this address. The + * nibble flag indicates that the 'nibble' format is to be used if an IPv6 + * address is provided, instead of the 'bitstring' format. Since we dropped + * the support of the bitstring labels, it is expected that the flag is always + * set. 'options' are the same as for dns_byaddr_create(). + * + * Requires: + * + * \li 'address' is a valid address. + * \li 'name' is a valid name with a dedicated buffer. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h new file mode 100644 index 0000000..8fc9657 --- /dev/null +++ b/lib/dns/include/dns/cache.h @@ -0,0 +1,302 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/cache.h + * \brief + * Defines dns_cache_t, the cache object. + * + * Notes: + *\li A cache object contains DNS data of a single class. + * Multiple classes will be handled by creating multiple + * views, each with a different class and its own cache. + * + * MP: + *\li See notes at the individual functions. + * + * Reliability: + * + * Resources: + * + * Security: + * + * Standards: + */ + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ +isc_result_t +dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *cachename, const char *db_type, + unsigned int db_argc, char **db_argv, dns_cache_t **cachep); +/*%< + * Create a new DNS cache. + * + * dns_cache_create2() will create a named cache. + * + * dns_cache_create3() will create a named cache using two separate memory + * contexts, one for cache data which can be cleaned and a separate one for + * memory allocated for the heap (which can grow without an upper limit and + * has no mechanism for shrinking). + * + * dns_cache_create() is a backward compatible version that internally + * specifies an empty cache name and a single memory context. + * + * Requires: + * + *\li 'cmctx' (and 'hmctx' if applicable) is a valid memory context. + * + *\li 'taskmgr' is a valid task manager and 'timermgr' is a valid timer + * manager, or both are NULL. If NULL, no periodic cleaning of the + * cache will take place. + * + *\li 'cachename' is a valid string. This must not be NULL. + * + *\li 'cachep' is a valid pointer, and *cachep == NULL + * + * Ensures: + * + *\li '*cachep' is attached to the newly created cache + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp); +/*%< + * Attach *targetp to cache. + * + * Requires: + * + *\li 'cache' is a valid cache. + * + *\li 'targetp' points to a NULL dns_cache_t *. + * + * Ensures: + * + *\li *targetp is attached to cache. + */ + +void +dns_cache_detach(dns_cache_t **cachep); +/*%< + * Detach *cachep from its cache. + * + * Requires: + * + *\li 'cachep' points to a valid cache. + * + * Ensures: + * + *\li *cachep is NULL. + * + *\li If '*cachep' is the last reference to the cache, + * all resources used by the cache will be freed + */ + +void +dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp); +/*%< + * Attach *dbp to the cache's database. + * + * Notes: + * + *\li This may be used to get a reference to the database for + * the purpose of cache lookups (XXX currently it is also + * the way to add data to the cache, but having a + * separate dns_cache_add() interface instead would allow + * more control over memory usage). + * The caller should call dns_db_detach() on the reference + * when it is no longer needed. + * + * Requires: + * + *\li 'cache' is a valid cache. + * + *\li 'dbp' points to a NULL dns_db *. + * + * Ensures: + * + *\li *dbp is attached to the database. + */ + +isc_result_t +dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now); +/*%< + * Force immediate cleaning of the cache, freeing all rdatasets + * whose TTL has expired as of 'now' and that have no pending + * references. + */ + +const char * +dns_cache_getname(dns_cache_t *cache); +/*%< + * Get the cache name. + */ + +void +dns_cache_setcachesize(dns_cache_t *cache, size_t size); +/*%< + * Set the maximum cache size. 0 means unlimited. + */ + +size_t +dns_cache_getcachesize(dns_cache_t *cache); +/*%< + * Get the maximum cache size. + */ + +void +dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl); +/*%< + * Sets the maximum length of time that cached answers may be retained + * past their normal TTL. Default value for the library is 0, disabling + * the use of stale data. + * + * Requires: + *\li 'cache' to be valid. + */ + +dns_ttl_t +dns_cache_getservestalettl(dns_cache_t *cache); +/*%< + * Gets the maximum length of time that cached answers may be kept past + * normal expiry. + * + * Requires: + *\li 'cache' to be valid. + */ + +void +dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval); +/*%< + * Sets the length of time to wait before attempting to refresh a rrset + * if a previous attempt in doing so has failed. + * During this time window if stale rrset are available in cache they + * will be directly returned to client. + * + * Requires: + *\li 'cache' to be valid. + */ + +dns_ttl_t +dns_cache_getservestalerefresh(dns_cache_t *cache); +/*%< + * Gets the 'stale-refresh-time' value, set by a previous call to + * 'dns_cache_setservestalerefresh'. + * + * Requires: + *\li 'cache' to be valid. + */ + +isc_result_t +dns_cache_flush(dns_cache_t *cache); +/*%< + * Flushes all data from the cache. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree); +/* + * Flush a given name from the cache. If 'tree' is true, then + * also flush all names under 'name'. + * + * Requires: + *\li 'cache' to be valid. + *\li 'name' to be valid. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li other error returns. + */ + +isc_result_t +dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name); +/* + * Flush a given name from the cache. Equivalent to + * dns_cache_flushpartial(cache, name, false). + * + * Requires: + *\li 'cache' to be valid. + *\li 'name' to be valid. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li other error returns. + */ + +isc_stats_t * +dns_cache_getstats(dns_cache_t *cache); +/* + * Return a pointer to the stats collection object for 'cache' + */ + +void +dns_cache_dumpstats(dns_cache_t *cache, FILE *fp); +/* + * Dump cache statistics and status in text to 'fp' + */ + +void +dns_cache_updatestats(dns_cache_t *cache, isc_result_t result); +/* + * Update cache statistics based on result code in 'result' + */ + +#ifdef HAVE_LIBXML2 +int +dns_cache_renderxml(dns_cache_t *cache, void *writer0); +/* + * Render cache statistics and status in XML for 'writer'. + */ +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +isc_result_t +dns_cache_renderjson(dns_cache_t *cache, void *cstats0); +/* + * Render cache statistics and status in JSON + */ +#endif /* HAVE_JSON_C */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/callbacks.h b/lib/dns/include/dns/callbacks.h new file mode 100644 index 0000000..e908a9f --- /dev/null +++ b/lib/dns/include/dns/callbacks.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/callbacks.h */ + +/*** + *** Imports + ***/ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +#define DNS_CALLBACK_MAGIC ISC_MAGIC('C', 'L', 'L', 'B') +#define DNS_CALLBACK_VALID(cb) ISC_MAGIC_VALID(cb, DNS_CALLBACK_MAGIC) + +struct dns_rdatacallbacks { + unsigned int magic; + + /*% + * dns_load_master calls this when it has rdatasets to commit. + */ + dns_addrdatasetfunc_t add; + + /*% + * dns_master_load*() call this when loading a raw zonefile, + * to pass back information obtained from the file header + */ + dns_rawdatafunc_t rawdata; + dns_zone_t *zone; + + /*% + * dns_load_master / dns_rdata_fromtext call this to issue a error. + */ + void (*error)(struct dns_rdatacallbacks *, const char *, ...); + /*% + * dns_load_master / dns_rdata_fromtext call this to issue a warning. + */ + void (*warn)(struct dns_rdatacallbacks *, const char *, ...); + /*% + * Private data handles for use by the above callback functions. + */ + void *add_private; + void *error_private; + void *warn_private; +}; + +/*** + *** Initialization + ***/ + +void +dns_rdatacallbacks_init(dns_rdatacallbacks_t *callbacks); +/*%< + * Initialize 'callbacks'. + * + * \li 'magic' is set to DNS_CALLBACK_MAGIC + * + * \li 'error' and 'warn' are set to default callbacks that print the + * error message through the DNS library log context. + * + *\li All other elements are initialized to NULL. + * + * Requires: + * \li 'callbacks' is a valid dns_rdatacallbacks_t, + */ + +void +dns_rdatacallbacks_init_stdio(dns_rdatacallbacks_t *callbacks); +/*%< + * Like dns_rdatacallbacks_init, but logs to stdio. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/catz.h b/lib/dns/include/dns/catz.h new file mode 100644 index 0000000..1401380 --- /dev/null +++ b/lib/dns/include/dns/catz.h @@ -0,0 +1,461 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* + * Define this for reference count tracing in the unit + */ +#undef DNS_CATZ_TRACE + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +#define DNS_CATZ_ERROR_LEVEL ISC_LOG_WARNING +#define DNS_CATZ_INFO_LEVEL ISC_LOG_INFO +#define DNS_CATZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1) +#define DNS_CATZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2) +#define DNS_CATZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3) +#define DNS_CATZ_DEBUG_QUIET (DNS_CATZ_DEBUG_LEVEL3 + 1) + +/* + * Catalog Zones functions and structures. + */ + +/* + * Options for a member zone in a catalog + */ +struct dns_catz_entry_options { + /* + * Options that can be overridden in catalog zone + */ + /* default-masters/default-primaries definition */ + dns_ipkeylist_t masters; + + /* both as text in config format, NULL if none */ + isc_buffer_t *allow_query; + isc_buffer_t *allow_transfer; + + /* + * Options that are only set in named.conf + */ + /* zone-directory definition */ + char *zonedir; + + /* zone should not be stored on disk (no 'file' statement in def */ + bool in_memory; + /* + * Minimal interval between catalog zone updates, if a new version + * of catalog zone is received before this time the update will be + * postponed. This is a global option for the whole catalog zone. + */ + uint32_t min_update_interval; +}; + +void +dns_catz_options_init(dns_catz_options_t *options); +/*%< + * Initialize 'options' to NULL values. + * + * Requires: + * \li 'options' to be non NULL. + */ + +void +dns_catz_options_free(dns_catz_options_t *options, isc_mem_t *mctx); +/*%< + * Free 'options' contents into 'mctx'. ('options' itself is not freed.) + * + * Requires: + * \li 'options' to be non NULL. + * \li 'mctx' to be a valid memory context. + */ + +void +dns_catz_options_copy(isc_mem_t *mctx, const dns_catz_options_t *opts, + dns_catz_options_t *nopts); +/*%< + * Duplicate 'opts' into 'nopts', allocating space from 'mctx'. + * + * Requires: + * \li 'mctx' to be a valid memory context. + * \li 'options' to be non NULL and valid options. + * \li 'nopts' to be non NULL. + */ + +void +dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults, + dns_catz_options_t *opts); +/*%< + * Replace empty values in 'opts' with values from 'defaults' + * + * Requires: + * \li 'mctx' to be a valid memory context. + * \li 'defaults' to be non NULL and valid options. + * \li 'opts' to be non NULL. + */ + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *entry); +/*%< + * Get domain name for 'entry' + * + * Requires: + * \li 'entry' to be non NULL. + * + * Returns: + * \li domain name for entry. + */ + +void +dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_entry_t **nentryp); +/*%< + * Allocate a new catz_entry on 'mctx', with the name 'domain' + * + * Requires: + * \li 'mctx' to be a valid memory context. + * \li 'domain' to be valid dns_name or NULL. + * \li 'nentryp' to be non NULL, *nentryp to be NULL. + * + * Returns: + * \li ISC_R_SUCCESS on success + * \li ISC_R_NOMEMORY on allocation failure + */ + +void +dns_catz_entry_copy(dns_catz_zone_t *catz, const dns_catz_entry_t *entry, + dns_catz_entry_t **nentryp); +/*%< + * Allocate a new catz_entry and deep copy 'entry' into 'nentryp'. + * + * Requires: + * \li 'mctx' to be a valid memory context. + * \li 'entry' to be non NULL. + * \li 'nentryp' to be non NULL, *nentryp to be NULL. + * + * Returns: + * \li ISC_R_SUCCESS on success + * \li ISC_R_NOMEMORY on allocation failure + */ + +void +dns_catz_entry_attach(dns_catz_entry_t *entry, dns_catz_entry_t **entryp); +/*%< + * Attach an entry + * + * Requires: + * \li 'entry' is a valid dns_catz_entry_t. + * \li 'entryp' is not NULL and '*entryp' is NULL. + */ + +void +dns_catz_entry_detach(dns_catz_zone_t *catz, dns_catz_entry_t **entryp); +/*%< + * Detach an entry, free if no further references + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + * \li 'entryp' is not NULL and '*entryp' is not NULL. + */ + +bool +dns_catz_entry_validate(const dns_catz_entry_t *entry); +/*%< + * Validate whether entry is correct. + * (NOT YET IMPLEMENTED: always returns true) + * + * Requires: + *\li 'entry' is a valid dns_catz_entry_t. + */ + +bool +dns_catz_entry_cmp(const dns_catz_entry_t *ea, const dns_catz_entry_t *eb); +/*%< + * Deep compare two entries + * + * Requires: + * \li 'ea' is a valid dns_catz_entry_t. + * \li 'eb' is a valid dns_catz_entry_t. + * + * Returns: + * \li 'true' if entries are the same. + * \li 'false' if the entries differ. + */ + +isc_result_t +dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **catzp, + const dns_name_t *name); +/*%< + * Allocate a new catz zone on catzs mctx + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'catzp' is not NULL and '*zonep' is NULL. + * \li 'name' is a valid dns_name_t. + * + */ + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *catz); +/*%< + * Get catalog zone name + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + */ + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *catz); +/*%< + * Get default member zone options for catalog zone 'catz' + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + */ + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *catz); +/*%< + * Reset the default member zone options for catalog zone 'catz' to + * the default values. + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + */ + +isc_result_t +dns_catz_generate_masterfilename(dns_catz_zone_t *catz, dns_catz_entry_t *entry, + isc_buffer_t **buffer); +/*%< + * Generate master file name and put it into *buffer (might be reallocated). + * The general format of the file name is: + * __catz__catalog.zone.name__member_zone_name.db + * But if it's too long it's shortened to: + * __catz__unique_hash_generated_from_the_above.db + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + * \li 'entry' is a valid dns_catz_entry_t. + * \li 'buffer' is not NULL and '*buffer' is not NULL. + */ + +isc_result_t +dns_catz_generate_zonecfg(dns_catz_zone_t *catz, dns_catz_entry_t *entry, + isc_buffer_t **buf); +/*%< + * Generate a zone config entry (in text form) from dns_catz_entry and puts + * it into *buf. buf might be reallocated. + * + * Requires: + * \li 'catz' is a valid dns_catz_zone_t. + * \li 'entry' is a valid dns_catz_entry_t. + * \li 'buf' is not NULL and '*buf' is NULL. + * + */ + +/* Methods provided by named to dynamically modify the member zones */ +/* xxxwpk TODO config! */ +typedef isc_result_t (*dns_catz_zoneop_fn_t)(dns_catz_entry_t *entry, + dns_catz_zone_t *origin, + dns_view_t *view, + isc_taskmgr_t *taskmgr, + void *udata); +struct dns_catz_zonemodmethods { + dns_catz_zoneop_fn_t addzone; + dns_catz_zoneop_fn_t modzone; + dns_catz_zoneop_fn_t delzone; + void *udata; +}; + +isc_result_t +dns_catz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_catz_zones_t **catzsp, + dns_catz_zonemodmethods_t *zmm); +/*%< + * Allocate a new catz_zones object, a collection storing all catalog zones + * for a view. + * + * Requires: + * \li 'mctx' is not NULL. + * \li 'taskmgr' is not NULL. + * \li 'timermgr' is not NULL. + * \li 'catzsp' is not NULL and '*catzsp' is NULL. + * \li 'zmm' is not NULL. + * + */ + +isc_result_t +dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name, + dns_catz_zone_t **catzp); +/*%< + * Allocate a new catz named 'name' and put it in 'catzs' collection. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'name' is a valid dns_name_t. + * \li 'catzp' is not NULL and *catzp is NULL. + * + */ + +dns_catz_zone_t * +dns_catz_get_zone(dns_catz_zones_t *catzs, const dns_name_t *name); +/*%< + * Returns a zone named 'name' from collection 'catzs' + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'name' is a valid dns_name_t. + */ + +void +dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view); +/*%< + * Set a view for 'catzs'. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'catzs->view' is NULL or 'catzs->view' == 'view'. + */ + +isc_result_t +dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg); +/*%< + * Callback for update of catalog zone database. + * If there was no catalog zone update recently it launches an + * update_taskaction immediately. + * If there was an update recently it schedules update_taskaction for some time + * in the future. + * If there is an update scheduled it replaces old db version with a new one. + * + * Requires: + * \li 'db' is a valid database. + * \li 'fn_arg' is not NULL (casted to dns_catz_zones_t*). + */ + +void +dns_catz_dbupdate_unregister(dns_db_t *db, dns_catz_zones_t *catzs); +/*%< + * Register the catalog zone database update notify callback. + * + * Requires: + * \li 'db' is a valid database. + * \li 'catzs' is valid. + */ + +void +dns_catz_dbupdate_register(dns_db_t *db, dns_catz_zones_t *catzs); +/*%< + * Unregister the catalog zone database update notify callback. + * + * Requires: + * \li 'db' is a valid database. + * \li 'catzs' is valid. + */ + +void +dns_catz_prereconfig(dns_catz_zones_t *catzs); +/*%< + * Called before reconfig, clears 'active' flag on all the zones in set + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * + */ + +void +dns_catz_postreconfig(dns_catz_zones_t *catzs); +/*%< + * Called after reconfig, walks through all zones in set, removes those + * inactive and force reload of those with changed configuration. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + */ + +void +dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp); +/*%< + * Get the hashtable iterator on catalog zone members, point '*itp' to it. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'itp' is not NULL and '*itp' is NULL. + * + */ + +void +dns_catz_shutdown_catzs(dns_catz_zones_t *catzs); +/*%< + * Shut down the catalog zones. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * + */ + +#ifdef DNS_CATZ_TRACE +/* Compatibility macros */ +#define dns_catz_attach_catz(catz, catzp) \ + dns_catz_zone__attach(catz, catzp, __func__, __FILE__, __LINE__) +#define dns_catz_detach_catz(catzp) \ + dns_catz_zone__detach(catzp, __func__, __FILE__, __LINE__) +#define dns_catz_ref_catz(ptr) \ + dns_catz_zone__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_catz_unref_catz(ptr) \ + dns_catz_zone__unref(ptr, __func__, __FILE__, __LINE__) + +#define dns_catz_attach_catzs(catzs, catzsp) \ + dns_catz_zones__attach(catzs, catzsp, __func__, __FILE__, __LINE__) +#define dns_catz_detach_catzs(catzsp) \ + dns_catz_zones__detach(catzsp, __func__, __FILE__, __LINE__) +#define dns_catz_ref_catzs(ptr) \ + dns_catz_zones__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_catz_unref_catzs(ptr) \ + dns_catz_zones__unref(ptr, __func__, __FILE__, __LINE__) + +ISC_REFCOUNT_TRACE_DECL(dns_catz_zone); +ISC_REFCOUNT_TRACE_DECL(dns_catz_zones); +#else +/* Compatibility macros */ +#define dns_catz_attach_catz(catz, catzp) dns_catz_zone_attach(catz, catzp) +#define dns_catz_detach_catz(catzp) dns_catz_zone_detach(catzp) +#define dns_catz_ref_catz(ptr) dns_catz_zone_ref(ptr) +#define dns_catz_unref_catz(ptr) dns_catz_zone_unref(ptr) + +#define dns_catz_attach_catzs(catzs, catzsp) \ + dns_catz_zones_attach(catzs, catzsp) +#define dns_catz_detach_catzs(catzsp) dns_catz_zones_detach(catzsp) +#define dns_catz_ref_catzs(ptr) dns_catz_zones_ref(ptr) +#define dns_catz_unref_catzs(ptr) dns_catz_zones_unref(ptr) + +ISC_REFCOUNT_DECL(dns_catz_zone); +ISC_REFCOUNT_DECL(dns_catz_zones); +#endif /* DNS_CATZ_TRACE */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/cert.h b/lib/dns/include/dns/cert.h new file mode 100644 index 0000000..c1a0a50 --- /dev/null +++ b/lib/dns/include/dns/cert.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/cert.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_cert_fromtext(dns_cert_t *certp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a certificate type. + * The text may contain either a mnemonic type name or a decimal type number. + * + * Requires: + *\li 'certp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_RANGE numeric type is out of range + *\li #DNS_R_UNKNOWN mnemonic type is unknown + */ + +isc_result_t +dns_cert_totext(dns_cert_t cert, isc_buffer_t *target); +/*%< + * Put a textual representation of certificate type 'cert' into 'target'. + * + * Requires: + *\li 'cert' is a valid cert. + * + *\li 'target' is a valid text buffer. + * + * Ensures: + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h new file mode 100644 index 0000000..ec70f92 --- /dev/null +++ b/lib/dns/include/dns/client.h @@ -0,0 +1,293 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * + * \brief + * The DNS client module provides convenient programming interfaces to various + * DNS services, such as name resolution with or without DNSSEC validation or + * dynamic DNS update. This module is primarily expected to be used by other + * applications than BIND9-related ones that need such advanced DNS features. + * + * MP: + *\li In the typical usage of this module, application threads will not share + * the same data structures created and manipulated in this module. + * However, the module still ensures appropriate synchronization of such + * data structures. + * + * Resources: + *\li TBS + * + * Security: + *\li This module does not handle any low-level data directly, and so no + * security issue specific to this module is anticipated. + */ + +#include +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% + * Optional flags for dns_client_(start)resolve. + */ +/*%< Do not return DNSSEC data (e.g. RRSIGS) with response. */ +#define DNS_CLIENTRESOPT_NODNSSEC 0x01 +/*%< Allow running external context. */ +#define DNS_CLIENTRESOPT_RESERVED 0x02 +/*%< Don't validate responses. */ +#define DNS_CLIENTRESOPT_NOVALIDATE 0x04 +/*%< Don't set the CD flag on upstream queries. */ +#define DNS_CLIENTRESOPT_NOCDFLAG 0x08 +/*%< Use TCP transport. */ +#define DNS_CLIENTRESOPT_TCP 0x10 + +/*% + * View name used in dns_client. + */ +#define DNS_CLIENTVIEW_NAME "_dnsclient" + +/*% + * A dns_clientresevent_t is sent when name resolution performed by a client + * completes. 'result' stores the result code of the entire resolution + * procedure. 'vresult' specifically stores the result code of DNSSEC + * validation if it is performed. When name resolution successfully completes, + * 'answerlist' is typically non empty, containing answer names along with + * RRsets. It is the receiver's responsibility to free this list by calling + * dns_client_freeresanswer() before freeing the event structure. + */ +typedef struct dns_clientresevent { + ISC_EVENT_COMMON(struct dns_clientresevent); + isc_result_t result; + isc_result_t vresult; + dns_namelist_t answerlist; +} dns_clientresevent_t; /* too long? */ + +isc_result_t +dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, + isc_nm_t *nm, isc_timermgr_t *timermgr, unsigned int options, + dns_client_t **clientp, const isc_sockaddr_t *localaddr4, + const isc_sockaddr_t *localaddr6); +/*%< + * Create a DNS client object with minimal internal resources, such as + * a default view for the IN class and IPv4/IPv6 dispatches for the view. + * + * dns_client_create() takes 'manager' arguments so that the caller can + * control the behavior of the client through the underlying event framework. + * 'localaddr4' and 'localaddr6' specify the local addresses to use for + * each address family; if both are set to NULL, then wildcard addresses + * will be used for both families. If only one is NULL, then the other + * address will be used as the local address, and the NULL protocol family + * will not be used. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'actx' is a valid application context. + * + *\li 'taskmgr' is a valid task manager. + * + *\li 'nm' is a valid network manager. + * + *\li 'timermgr' is a valid timer manager. + * + *\li clientp != NULL && *clientp == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +void +dns_client_detach(dns_client_t **clientp); +/*%< + * Detach 'client' and destroy it if there are no more references. + * + * Requires: + * + *\li '*clientp' is a valid client. + * + * Ensures: + * + *\li *clientp == NULL. + */ + +isc_result_t +dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space, isc_sockaddrlist_t *addrs); +/*%< + * Specify a list of addresses of recursive name servers that the client will + * use for name resolution. A view for the 'rdclass' class must be created + * beforehand. If 'name_space' is non NULL, the specified server will be used + * if and only if the query name is a subdomain of 'name_space'. When servers + * for multiple 'name_space's are provided, and a query name is covered by + * more than one 'name_space', the servers for the best (longest) matching + * name_space will be used. If 'name_space' is NULL, it works as if + * dns_rootname (.) were specified. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'name_space' is NULL or a valid name. + * + *\li 'addrs' != NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +isc_result_t +dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space); +/*%< + * Remove configured recursive name servers for the 'rdclass' and 'name_space' + * from the client. See the description of dns_client_setservers() for + * the requirements about 'rdclass' and 'name_space'. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'name_space' is NULL or a valid name. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +isc_result_t +dns_client_resolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, dns_namelist_t *namelist); + +isc_result_t +dns_client_startresolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_clientrestrans_t **transp); +/*%< + * Perform name resolution for 'name', 'rdclass', and 'type'. + * + * If any trusted keys are configured and the query name is considered to + * belong to a secure zone, these functions also validate the responses + * using DNSSEC by default. If the DNS_CLIENTRESOPT_NOVALIDATE flag is set + * in 'options', DNSSEC validation is disabled regardless of the configured + * trusted keys or the query name. With DNS_CLIENTRESOPT_NODNSSEC + * DNSSEC data is not returned with response. DNS_CLIENTRESOPT_NOCDFLAG + * disables the CD flag on queries, DNS_CLIENTRESOPT_TCP switches to + * the TCP (vs. UDP) transport. + * + * dns_client_resolve() provides a synchronous service. This function starts + * name resolution internally and blocks until it completes. On success, + * 'namelist' will contain a list of answer names, each of which has + * corresponding RRsets. The caller must provide a valid empty list, and + * is responsible for freeing the list content via dns_client_freeresanswer(). + * If the name resolution fails due to an error in DNSSEC validation, + * dns_client_resolve() returns the result code indicating the validation + * error. Otherwise, it returns the result code of the entire resolution + * process, either success or failure. + * + * It is expected that the client object passed to dns_client_resolve() was + * created via dns_client_create() and has external managers and contexts. + * + * dns_client_startresolve() is an asynchronous version of dns_client_resolve() + * and does not block. When name resolution is completed, 'action' will be + * called with the argument of a 'dns_clientresevent_t' object, which contains + * the resulting list of answer names (on success). On return, '*transp' is + * set to an opaque transaction ID so that the caller can cancel this + * resolution process. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'addrs' != NULL. + * + *\li 'name' is a valid name. + * + *\li 'namelist' != NULL and is not empty. + * + *\li 'task' is a valid task. + * + *\li 'transp' != NULL && *transp == NULL; + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +void +dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist); +/*%< + * Free resources allocated for the content of 'namelist'. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'namelist' != NULL. + */ + +isc_result_t +dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, const dns_name_t *keyname, + isc_buffer_t *keydatabuf); +/*%< + * Add a DNSSEC trusted key for the 'rdclass' class. A view for the 'rdclass' + * class must be created beforehand. 'rdtype' is the type of the RR data + * for the key, either DNSKEY or DS. 'keyname' is the DNS name of the key, + * and 'keydatabuf' stores the RR data. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'keyname' is a valid name. + * + *\li 'keydatabuf' is a valid buffer. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/clientinfo.h b/lib/dns/include/dns/clientinfo.h new file mode 100644 index 0000000..b6dd601 --- /dev/null +++ b/lib/dns/include/dns/clientinfo.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/clientinfo.h + * \brief + * The DNS clientinfo interface allows libdns to retrieve information + * about the client from the caller. + * + * The clientinfo interface is used by the DNS DB and DLZ interfaces; + * it allows databases to modify their answers on the basis of information + * about the client, such as source IP address. + * + * dns_clientinfo_t contains a pointer to an opaque structure containing + * client information in some form. dns_clientinfomethods_t contains a + * list of methods which operate on that opaque structure to return + * potentially useful data. Both structures also contain versioning + * information. + */ + +/***** +***** Imports +*****/ + +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/***** +***** Types +*****/ + +#define DNS_CLIENTINFO_VERSION 3 +/* + * Any updates to this structure should also be applied in + * contrib/modules/dlz/dlz_minmal.h. + */ +typedef struct dns_clientinfo { + uint16_t version; + void *data; + void *dbversion; + dns_ecs_t ecs; +} dns_clientinfo_t; + +typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client, + isc_sockaddr_t **addrp); + +#define DNS_CLIENTINFOMETHODS_VERSION 2 +#define DNS_CLIENTINFOMETHODS_AGE 1 + +/* + * Any updates to this structure should also be applied in + * contrib/modules/dlz/dlz_minmal.h. + */ +typedef struct dns_clientinfomethods { + uint16_t version; + uint16_t age; + dns_clientinfo_sourceip_t sourceip; +} dns_clientinfomethods_t; + +/***** +***** Methods +*****/ +void +dns_clientinfomethods_init(dns_clientinfomethods_t *methods, + dns_clientinfo_sourceip_t sourceip); + +void +dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp); +/*%< + * Initialize a clientinfo object, setting the data to 'data' and the + * database version to 'versionp'. ECS data is initialized to 0/0/0. + */ + +void +dns_clientinfo_setecs(dns_clientinfo_t *ci, dns_ecs_t *ecs); +/*%< + * Set the ECS client data associated with a clientinfo object 'ci'. + * If 'ecs' is NULL, initialize ci->ecs to 0/0/0; otherwise copy it. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/compress.h b/lib/dns/include/dns/compress.h new file mode 100644 index 0000000..5476dd9 --- /dev/null +++ b/lib/dns/include/dns/compress.h @@ -0,0 +1,299 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*! \file dns/compress.h + * Direct manipulation of the structures is strongly discouraged. + * + * A name compression context handles compression of multiple DNS names + * in relation to a single DNS message. The context can be used to + * selectively turn on/off compression for specific names (depending on + * the RR type) by using \c dns_compress_setmethods(). Alternately, + * compression can be disabled completely using \c + * dns_compress_disable(). + * + * \c dns_compress_setmethods() is intended for use by RDATA towire() + * implementations, whereas \c dns_compress_disable() is intended to be + * used by a nameserver's configuration manager. + */ + +#define DNS_COMPRESS_NONE 0x00 /*%< no compression */ +#define DNS_COMPRESS_GLOBAL14 0x01 /*%< "normal" compression. */ +#define DNS_COMPRESS_ALL 0x01 /*%< all compression. */ +#define DNS_COMPRESS_CASESENSITIVE 0x02 /*%< case sensitive compression. */ +#define DNS_COMPRESS_ENABLED 0x04 + +/* + * DNS_COMPRESS_TABLESIZE must be a power of 2. The compress code + * utilizes this assumption. + */ +#define DNS_COMPRESS_TABLEBITS 6 +#define DNS_COMPRESS_TABLESIZE (1U << DNS_COMPRESS_TABLEBITS) +#define DNS_COMPRESS_TABLEMASK (DNS_COMPRESS_TABLESIZE - 1) +#define DNS_COMPRESS_INITIALNODES 24 +#define DNS_COMPRESS_ARENA_SIZE 640 + +typedef struct dns_compressnode dns_compressnode_t; + +struct dns_compressnode { + dns_compressnode_t *next; + uint16_t offset; + uint16_t count; + isc_region_t r; + dns_name_t name; +}; + +struct dns_compress { + unsigned int magic; /*%< Magic number. */ + unsigned int allowed; /*%< Allowed methods. */ + int edns; /*%< Edns version or -1. */ + /*% Global compression table. */ + dns_compressnode_t *table[DNS_COMPRESS_TABLESIZE]; + /*% Preallocated arena for names. */ + unsigned char arena[DNS_COMPRESS_ARENA_SIZE]; + off_t arena_off; + /*% Preallocated nodes for the table. */ + dns_compressnode_t initialnodes[DNS_COMPRESS_INITIALNODES]; + uint16_t count; /*%< Number of nodes. */ + isc_mem_t *mctx; /*%< Memory context. */ +}; + +typedef enum { + DNS_DECOMPRESS_ANY, /*%< Any compression */ + DNS_DECOMPRESS_STRICT, /*%< Allowed compression */ + DNS_DECOMPRESS_NONE /*%< No compression */ +} dns_decompresstype_t; + +struct dns_decompress { + unsigned int magic; /*%< Magic number. */ + unsigned int allowed; /*%< Allowed methods. */ + int edns; /*%< Edns version or -1. */ + dns_decompresstype_t type; /*%< Strict checking */ +}; + +isc_result_t +dns_compress_init(dns_compress_t *cctx, int edns, isc_mem_t *mctx); +/*%< + * Initialise the compression context structure pointed to by + * 'cctx'. A freshly initialized context has name compression + * enabled, but no methods are set. Please use \c + * dns_compress_setmethods() to set a compression method. + * + * Requires: + * \li 'cctx' is a valid dns_compress_t structure. + * \li 'mctx' is an initialized memory context. + * Ensures: + * \li cctx->global is initialized. + * + * Returns: + * \li #ISC_R_SUCCESS + */ + +void +dns_compress_invalidate(dns_compress_t *cctx); + +/*%< + * Invalidate the compression structure pointed to by cctx. + * + * Requires: + *\li 'cctx' to be initialized. + */ + +void +dns_compress_setmethods(dns_compress_t *cctx, unsigned int allowed); + +/*%< + * Sets allowed compression methods. + * + * Requires: + *\li 'cctx' to be initialized. + */ + +unsigned int +dns_compress_getmethods(dns_compress_t *cctx); + +/*%< + * Gets allowed compression methods. + * + * Requires: + *\li 'cctx' to be initialized. + * + * Returns: + *\li allowed compression bitmap. + */ + +void +dns_compress_disable(dns_compress_t *cctx); +/*%< + * Disables all name compression in the context. Once disabled, + * name compression cannot currently be re-enabled. + * + * Requires: + *\li 'cctx' to be initialized. + * + */ + +void +dns_compress_setsensitive(dns_compress_t *cctx, bool sensitive); + +/* + * Preserve the case of compressed domain names. + * + * Requires: + * 'cctx' to be initialized. + */ + +bool +dns_compress_getsensitive(dns_compress_t *cctx); +/* + * Return whether case is to be preserved when compressing + * domain names. + * + * Requires: + * 'cctx' to be initialized. + */ + +int +dns_compress_getedns(dns_compress_t *cctx); + +/*%< + * Gets edns value. + * + * Requires: + *\li 'cctx' to be initialized. + * + * Returns: + *\li -1 .. 255 + */ + +bool +dns_compress_findglobal(dns_compress_t *cctx, const dns_name_t *name, + dns_name_t *prefix, uint16_t *offset); +/*%< + * Finds longest possible match of 'name' in the global compression table. + * + * Requires: + *\li 'cctx' to be initialized. + *\li 'name' to be a absolute name. + *\li 'prefix' to be initialized. + *\li 'offset' to point to an uint16_t. + * + * Ensures: + *\li 'prefix' and 'offset' are valid if true is returned. + * + * Returns: + *\li #true / #false + */ + +void +dns_compress_add(dns_compress_t *cctx, const dns_name_t *name, + const dns_name_t *prefix, uint16_t offset); +/*%< + * Add compression pointers for 'name' to the compression table, + * not replacing existing pointers. + * + * Requires: + *\li 'cctx' initialized + * + *\li 'name' must be initialized and absolute, and must remain + * valid until the message compression is complete. + * + *\li 'prefix' must be a prefix returned by + * dns_compress_findglobal(), or the same as 'name'. + */ + +void +dns_compress_rollback(dns_compress_t *cctx, uint16_t offset); + +/*%< + * Remove any compression pointers from global table >= offset. + * + * Requires: + *\li 'cctx' is initialized. + */ + +void +dns_decompress_init(dns_decompress_t *dctx, int edns, + dns_decompresstype_t type); + +/*%< + * Initializes 'dctx'. + * Records 'edns' and 'type' into the structure. + * + * Requires: + *\li 'dctx' to be a valid pointer. + */ + +void +dns_decompress_invalidate(dns_decompress_t *dctx); + +/*%< + * Invalidates 'dctx'. + * + * Requires: + *\li 'dctx' to be initialized + */ + +void +dns_decompress_setmethods(dns_decompress_t *dctx, unsigned int allowed); + +/*%< + * Sets 'dctx->allowed' to 'allowed'. + * + * Requires: + *\li 'dctx' to be initialized + */ + +unsigned int +dns_decompress_getmethods(dns_decompress_t *dctx); + +/*%< + * Returns 'dctx->allowed' + * + * Requires: + *\li 'dctx' to be initialized + */ + +int +dns_decompress_edns(dns_decompress_t *dctx); + +/*%< + * Returns 'dctx->edns' + * + * Requires: + *\li 'dctx' to be initialized + */ + +dns_decompresstype_t +dns_decompress_type(dns_decompress_t *dctx); + +/*%< + * Returns 'dctx->type' + * + * Requires: + *\li 'dctx' to be initialized + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h new file mode 100644 index 0000000..9b53f04 --- /dev/null +++ b/lib/dns/include/dns/db.h @@ -0,0 +1,1759 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/db.h + * \brief + * The DNS DB interface allows named rdatasets to be stored and retrieved. + * + * The dns_db_t type is like a "virtual class". To actually use + * DBs, an implementation of the class is required. + * + * XXX more XXX + * + * MP: + * \li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + * \li No anticipated impact. + * + * Resources: + * \li TBS + * + * Security: + * \li No anticipated impact. + * + * Standards: + * \li None. + */ + +/***** +***** Imports +*****/ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +/*% + * Tuning: external query load in packets per seconds. + */ +extern unsigned int dns_pps; + +/***** +***** Types +*****/ + +typedef struct dns_dbmethods { + void (*attach)(dns_db_t *source, dns_db_t **targetp); + void (*detach)(dns_db_t **dbp); + isc_result_t (*beginload)(dns_db_t *db, + dns_rdatacallbacks_t *callbacks); + isc_result_t (*endload)(dns_db_t *db, dns_rdatacallbacks_t *callbacks); + isc_result_t (*dump)(dns_db_t *db, dns_dbversion_t *version, + const char *filename, + dns_masterformat_t masterformat); + void (*currentversion)(dns_db_t *db, dns_dbversion_t **versionp); + isc_result_t (*newversion)(dns_db_t *db, dns_dbversion_t **versionp); + void (*attachversion)(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp); + void (*closeversion)(dns_db_t *db, dns_dbversion_t **versionp, + bool commit); + isc_result_t (*findnode)(dns_db_t *db, const dns_name_t *name, + bool create, dns_dbnode_t **nodep); + isc_result_t (*find)(dns_db_t *db, const dns_name_t *name, + dns_dbversion_t *version, dns_rdatatype_t type, + unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); + isc_result_t (*findzonecut)(dns_db_t *db, const dns_name_t *name, + unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_name_t *dcname, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); + void (*attachnode)(dns_db_t *db, dns_dbnode_t *source, + dns_dbnode_t **targetp); + void (*detachnode)(dns_db_t *db, dns_dbnode_t **targetp); + isc_result_t (*expirenode)(dns_db_t *db, dns_dbnode_t *node, + isc_stdtime_t now); + void (*printnode)(dns_db_t *db, dns_dbnode_t *node, FILE *out); + isc_result_t (*createiterator)(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp); + isc_result_t (*findrdataset)(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, + dns_rdatatype_t type, + dns_rdatatype_t covers, isc_stdtime_t now, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); + isc_result_t (*allrdatasets)(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp); + isc_result_t (*addrdataset)(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, isc_stdtime_t now, + dns_rdataset_t *rdataset, + unsigned int options, + dns_rdataset_t *addedrdataset); + isc_result_t (*subtractrdataset)(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, + dns_rdataset_t *rdataset, + unsigned int options, + dns_rdataset_t *newrdataset); + isc_result_t (*deleterdataset)(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, + dns_rdatatype_t type, + dns_rdatatype_t covers); + bool (*issecure)(dns_db_t *db); + unsigned int (*nodecount)(dns_db_t *db, dns_dbtree_t); + bool (*ispersistent)(dns_db_t *db); + void (*overmem)(dns_db_t *db, bool overmem); + void (*settask)(dns_db_t *db, isc_task_t *); + isc_result_t (*getoriginnode)(dns_db_t *db, dns_dbnode_t **nodep); + void (*transfernode)(dns_db_t *db, dns_dbnode_t **sourcep, + dns_dbnode_t **targetp); + isc_result_t (*getnsec3parameters)(dns_db_t *db, + dns_dbversion_t *version, + dns_hash_t *hash, uint8_t *flags, + uint16_t *iterations, + unsigned char *salt, + size_t *salt_len); + isc_result_t (*findnsec3node)(dns_db_t *db, const dns_name_t *name, + bool create, dns_dbnode_t **nodep); + isc_result_t (*setsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset, + isc_stdtime_t resign); + isc_result_t (*getsigningtime)(dns_db_t *db, dns_rdataset_t *rdataset, + dns_name_t *name); + void (*resigned)(dns_db_t *db, dns_rdataset_t *rdataset, + dns_dbversion_t *version); + bool (*isdnssec)(dns_db_t *db); + dns_stats_t *(*getrrsetstats)(dns_db_t *db); + void (*rpz_attach)(dns_db_t *db, void *rpzs, uint8_t rpz_num); + isc_result_t (*rpz_ready)(dns_db_t *db); + isc_result_t (*findnodeext)(dns_db_t *db, const dns_name_t *name, + bool create, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, + dns_dbnode_t **nodep); + isc_result_t (*findext)(dns_db_t *db, const dns_name_t *name, + dns_dbversion_t *version, dns_rdatatype_t type, + unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); + isc_result_t (*setcachestats)(dns_db_t *db, isc_stats_t *stats); + size_t (*hashsize)(dns_db_t *db); + isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node, + dns_name_t *name); + isc_result_t (*getsize)(dns_db_t *db, dns_dbversion_t *version, + uint64_t *records, uint64_t *bytes); + isc_result_t (*setservestalettl)(dns_db_t *db, dns_ttl_t ttl); + isc_result_t (*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl); + isc_result_t (*setservestalerefresh)(dns_db_t *db, uint32_t interval); + isc_result_t (*getservestalerefresh)(dns_db_t *db, uint32_t *interval); + isc_result_t (*setgluecachestats)(dns_db_t *db, isc_stats_t *stats); +} dns_dbmethods_t; + +typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx, + const dns_name_t *name, + dns_dbtype_t type, + dns_rdataclass_t rdclass, + unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp); + +typedef isc_result_t (*dns_dbupdate_callback_t)(dns_db_t *db, void *fn_arg); + +#define DNS_DB_MAGIC ISC_MAGIC('D', 'N', 'S', 'D') +#define DNS_DB_VALID(db) ISC_MAGIC_VALID(db, DNS_DB_MAGIC) + +/*% + * This structure is actually just the common prefix of a DNS db + * implementation's version of a dns_db_t. + * \brief + * Direct use of this structure by clients is forbidden. DB implementations + * may change the structure. 'magic' must be DNS_DB_MAGIC for any of the + * dns_db_ routines to work. DB implementations must maintain all DB + * invariants. + */ +struct dns_db { + unsigned int magic; + unsigned int impmagic; + dns_dbmethods_t *methods; + uint16_t attributes; + dns_rdataclass_t rdclass; + dns_name_t origin; + isc_mem_t *mctx; + ISC_LIST(dns_dbonupdatelistener_t) update_listeners; +}; + +#define DNS_DBATTR_CACHE 0x01 +#define DNS_DBATTR_STUB 0x02 + +struct dns_dbonupdatelistener { + dns_dbupdate_callback_t onupdate; + void *onupdate_arg; + ISC_LINK(dns_dbonupdatelistener_t) link; +}; + +/*@{*/ +/*% + * Options that can be specified for dns_db_find(). + */ +#define DNS_DBFIND_GLUEOK 0x0001 +#define DNS_DBFIND_VALIDATEGLUE 0x0002 +#define DNS_DBFIND_NOWILD 0x0004 +#define DNS_DBFIND_PENDINGOK 0x0008 +#define DNS_DBFIND_NOEXACT 0x0010 +#define DNS_DBFIND_FORCENSEC 0x0020 +#define DNS_DBFIND_COVERINGNSEC 0x0040 +#define DNS_DBFIND_FORCENSEC3 0x0080 +#define DNS_DBFIND_ADDITIONALOK 0x0100 +#define DNS_DBFIND_NOZONECUT 0x0200 + +/* + * DNS_DBFIND_STALEOK: This flag is set when BIND fails to refresh a RRset due + * to timeout (resolver-query-timeout). Its intent is to try to look for stale + * data in cache as a fallback, but only if stale answers are enabled in + * configuration. + */ +#define DNS_DBFIND_STALEOK 0x0400 + +/* + * DNS_DBFIND_STALEENABLED: This flag is used as a hint to the database that + * it may use stale data. It is always set during query lookup if stale + * answers are enabled, but only effectively used during stale-refresh-time + * window. Also during this window, the resolver will not try to resolve the + * query, in other words no attempt to refresh the data in cache is made when + * the stale-refresh-time window is active. + */ +#define DNS_DBFIND_STALEENABLED 0x0800 + +/* + * DNS_DBFIND_STALETIMEOUT: This flag is used when we want stale data from the + * database, but not due to a failure in resolution, it also doesn't require + * stale-refresh-time window timer to be active. As long as there is stale + * data available, it should be returned. + */ +#define DNS_DBFIND_STALETIMEOUT 0x1000 + +/* + * DNS_DBFIND_STALESTART: This flag is used to activate stale-refresh-time + * window. + */ +#define DNS_DBFIND_STALESTART 0x2000 +/*@}*/ + +/*@{*/ +/*% + * Options that can be specified for dns_db_addrdataset(). + */ +#define DNS_DBADD_MERGE 0x01 +#define DNS_DBADD_FORCE 0x02 +#define DNS_DBADD_EXACT 0x04 +#define DNS_DBADD_EXACTTTL 0x08 +#define DNS_DBADD_PREFETCH 0x10 +/*@}*/ + +/*% + * Options that can be specified for dns_db_subtractrdataset(). + */ +#define DNS_DBSUB_EXACT 0x01 +#define DNS_DBSUB_WANTOLD 0x02 + +/*@{*/ +/*% + * Iterator options + */ +#define DNS_DB_RELATIVENAMES 0x1 +#define DNS_DB_NSEC3ONLY 0x2 +#define DNS_DB_NONSEC3 0x4 +/*@}*/ + +#define DNS_DB_STALEOK 0x01 +#define DNS_DB_EXPIREDOK 0x02 + +/***** +***** Methods +*****/ + +/*** + *** Basic DB Methods + ***/ + +isc_result_t +dns_db_create(isc_mem_t *mctx, const char *db_type, const dns_name_t *origin, + dns_dbtype_t type, dns_rdataclass_t rdclass, unsigned int argc, + char *argv[], dns_db_t **dbp); +/*%< + * Create a new database using implementation 'db_type'. + * + * Notes: + * \li All names in the database must be subdomains of 'origin' and in class + * 'rdclass'. The database makes its own copy of the origin, so the + * caller may do whatever they like with 'origin' and its storage once the + * call returns. + * + * \li DB implementation-specific parameters are passed using argc and argv. + * + * Requires: + * + * \li dbp != NULL and *dbp == NULL + * + * \li 'origin' is a valid absolute domain name. + * + * \li mctx is a valid memory context + * + * Ensures: + * + * \li A copy of 'origin' has been made for the databases use, and the + * caller is free to do whatever they want with the name and storage + * associated with 'origin'. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * \li #ISC_R_NOTFOUND db_type not found + * + * \li Many other errors are possible, depending on what db_type was + * specified. + */ + +void +dns_db_attach(dns_db_t *source, dns_db_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + * \li 'source' is a valid database. + * + * \li 'targetp' points to a NULL dns_db_t *. + * + * Ensures: + * + * \li *targetp is attached to source. + */ + +void +dns_db_detach(dns_db_t **dbp); +/*%< + * Detach *dbp from its database. + * + * Requires: + * + * \li 'dbp' points to a valid database. + * + * Ensures: + * + * \li *dbp is NULL. + * + * \li If '*dbp' is the last reference to the database, + * all resources used by the database will be freed + */ + +bool +dns_db_iscache(dns_db_t *db); +/*%< + * Does 'db' have cache semantics? + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li #true 'db' has cache semantics + * \li #false otherwise + */ + +bool +dns_db_iszone(dns_db_t *db); +/*%< + * Does 'db' have zone semantics? + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li #true 'db' has zone semantics + * \li #false otherwise + */ + +bool +dns_db_isstub(dns_db_t *db); +/*%< + * Does 'db' have stub semantics? + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li #true 'db' has zone semantics + * \li #false otherwise + */ + +bool +dns_db_issecure(dns_db_t *db); +/*%< + * Is 'db' secure? + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * Returns: + * \li #true 'db' is secure. + * \li #false 'db' is not secure. + */ + +bool +dns_db_isdnssec(dns_db_t *db); +/*%< + * Is 'db' secure or partially secure? + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * Returns: + * \li #true 'db' is secure or is partially. + * \li #false 'db' is not secure. + */ + +dns_name_t * +dns_db_origin(dns_db_t *db); +/*%< + * The origin of the database. + * + * Note: caller must not try to change this name. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * + * \li The origin of the database. + */ + +dns_rdataclass_t +dns_db_class(dns_db_t *db); +/*%< + * The class of the database. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * + * \li The class of the database. + */ + +isc_result_t +dns_db_beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Begin loading 'db'. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li This is the first attempt to load 'db'. + * + * \li 'callbacks' is a pointer to an initialized dns_rdatacallbacks_t + * structure. + * + * Ensures: + * + * \li On success, callbacks->add will be a valid dns_addrdatasetfunc_t + * suitable for loading records into 'db' from a raw or text zone + * file. callbacks->add_private will be a valid DB load context + * which should be used as 'arg' when callbacks->add is called. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * + * \li Other results are possible, depending upon the database + * implementation used, syntax errors in the master file, etc. + */ + +isc_result_t +dns_db_endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks); +/*%< + * Finish loading 'db'. + * + * Requires: + * + * \li 'db' is a valid database that is being loaded. + * + * \li 'callbacks' is a valid dns_rdatacallbacks_t structure. + * + * \li callbacks->add_private is not NULL and is a valid database load context. + * + * Ensures: + * + * \li 'callbacks' is returned to its state prior to calling dns_db_beginload() + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * + * \li Other results are possible, depending upon the database + * implementation used, syntax errors in the master file, etc. + */ + +isc_result_t +dns_db_load(dns_db_t *db, const char *filename, dns_masterformat_t format, + unsigned int options); +/*%< + * Load master file 'filename' into 'db'. + * + * Notes: + * \li This routine is equivalent to calling + * + *\code + * dns_db_beginload(); + * dns_master_loadfile(); + * dns_db_endload(); + *\endcode + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li This is the first attempt to load 'db'. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * + * \li Other results are possible, depending upon the database + * implementation used, syntax errors in the master file, etc. + */ + +isc_result_t +dns_db_dump(dns_db_t *db, dns_dbversion_t *version, const char *filename); +/*%< + * Dump version 'version' of 'db' to master file 'filename'. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'version' is a valid version. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * + * \li Other results are possible, depending upon the database + * implementation used, OS file errors, etc. + */ + +/*** + *** Version Methods + ***/ + +void +dns_db_currentversion(dns_db_t *db, dns_dbversion_t **versionp); +/*%< + * Open the current version for reading. + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * \li versionp != NULL && *verisonp == NULL + * + * Ensures: + * + * \li On success, '*versionp' is attached to the current version. + * + */ + +isc_result_t +dns_db_newversion(dns_db_t *db, dns_dbversion_t **versionp); +/*%< + * Open a new version for reading and writing. + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * \li versionp != NULL && *verisonp == NULL + * + * Ensures: + * + * \li On success, '*versionp' is attached to the current version. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +void +dns_db_attachversion(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * \li source is a valid open version + * + * \li targetp != NULL && *targetp == NULL + * + * Ensures: + * + * \li '*targetp' is attached to source. + */ + +void +dns_db_closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit); +/*%< + * Close version '*versionp'. + * + * Note: if '*versionp' is a read-write version and 'commit' is true, + * then all changes made in the version will take effect, otherwise they + * will be rolled back. The value of 'commit' is ignored for read-only + * versions. + * + * Requires: + * + * \li 'db' is a valid database with zone semantics. + * + * \li '*versionp' refers to a valid version. + * + * \li If committing a writable version, then there must be no other + * outstanding references to the version (e.g. an active rdataset + * iterator). + * + * Ensures: + * + * \li *versionp == NULL + * + * \li If *versionp is a read-write version, and commit is true, then + * the version will become the current version. If !commit, then all + * changes made in the version will be undone, and the version will + * not become the current version. + */ + +/*** + *** Node Methods + ***/ + +isc_result_t +dns_db_findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep); + +isc_result_t +dns_db_findnodeext(dns_db_t *db, const dns_name_t *name, bool create, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep); +/*%< + * Find the node with name 'name'. + * + * dns_db_findnodeext() (findnode extended) also accepts parameters + * 'methods' and 'clientinfo', which, when provided, enable the database to + * retrieve information about the client from the caller, and modify its + * response on the basis of that information. + * + * Notes: + * \li If 'create' is true and no node with name 'name' exists, then + * such a node will be created. + * + * \li This routine is for finding or creating a node with the specified + * name. There are no partial matches. It is not suitable for use + * in building responses to ordinary DNS queries; clients which wish + * to do that should use dns_db_find() instead. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'name' is a valid, non-empty, absolute name. + * + * \li nodep != NULL && *nodep == NULL + * + * Ensures: + * + * \li On success, *nodep is attached to the node with name 'name'. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND If !create and name not found. + * \li #ISC_R_NOMEMORY Can only happen if create is true. + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); + +isc_result_t +dns_db_findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +/*%< + * Find the best match for 'name' and 'type' in version 'version' of 'db'. + * + * dns_db_findext() (find extended) also accepts parameters 'methods' + * and 'clientinfo', which when provided enable the database to retrieve + * information about the client from the caller, and modify its response + * on the basis of this information. + * + * Notes: + * + * \li If type == dns_rdataset_any, then rdataset will not be bound. + * + * \li If 'options' does not have #DNS_DBFIND_GLUEOK set, then no glue will + * be returned. For zone databases, glue is as defined in RFC2181. + * For cache databases, glue is any rdataset with a trust of + * dns_trust_glue. + * + * \li If 'options' does not have #DNS_DBFIND_ADDITIONALOK set, then no + * additional records will be returned. Only caches can have + * rdataset with trust dns_trust_additional. + * + * \li If 'options' does not have #DNS_DBFIND_PENDINGOK set, then no + * pending data will be returned. This option is only meaningful for + * cache databases. + * + * \li If the #DNS_DBFIND_NOWILD option is set, then wildcard matching will + * be disabled. This option is only meaningful for zone databases. + * + * \li If the #DNS_DBFIND_NOZONECUT option is set, the database is + * assumed to contain no zone cuts above 'name'. An implementation + * may therefore choose to search for a match beginning at 'name' + * rather than walking down the tree to check check for delegations. + * If #DNS_DBFIND_NOWILD is not set, wildcard matching will be + * attempted at each node starting at the direct ancestor of 'name' + * and working up to the zone origin. This option is only meaningful + * when querying redirect zones. + * + * \li If the #DNS_DBFIND_FORCENSEC option is set, the database is assumed to + * have NSEC records, and these will be returned when appropriate. This + * is only necessary when querying a database that was not secure + * when created. + * + * \li If the DNS_DBFIND_COVERINGNSEC option is set, then look for a + * NSEC record that potentially covers 'name' if a answer cannot + * be found. Note the returned NSEC needs to be checked to ensure + * that it is correct. This only affects answers returned from the + * cache. + * + * \li If the #DNS_DBFIND_FORCENSEC3 option is set, then we are looking + * in the NSEC3 tree and not the main tree. Without this option being + * set NSEC3 records will not be found. + * + * \li To respond to a query for SIG records, the caller should create a + * rdataset iterator and extract the signatures from each rdataset. + * + * \li Making queries of type ANY with #DNS_DBFIND_GLUEOK is not recommended, + * because the burden of determining whether a given rdataset is valid + * glue or not falls upon the caller. + * + * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a + * cache database, an rdataset will not be found unless it expires after + * 'now'. Any ANY query will not match unless at least one rdataset at + * the node expires after 'now'. If 'now' is zero, then the current time + * will be used. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'type' is not SIG, or a meta-RR type other than 'ANY' (e.g. 'OPT'). + * + * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL. + * + * \li 'foundname' is a valid name with a dedicated buffer. + * + * \li 'rdataset' is NULL, or is a valid unassociated rdataset. + * + * Ensures, + * on a non-error completion: + * + * \li If nodep != NULL, then it is bound to the found node. + * + * \li If foundname != NULL, then it contains the full name of the + * found node. + * + * \li If rdataset != NULL and type != dns_rdatatype_any, then + * rdataset is bound to the found rdataset. + * + * Non-error results are: + * + * \li #ISC_R_SUCCESS The desired node and type were + * found. + * + * \li #DNS_R_GLUE The desired node and type were + * found, but are glue. This + * result can only occur if + * the DNS_DBFIND_GLUEOK option + * is set. This result can only + * occur if 'db' is a zone + * database. If type == + * dns_rdatatype_any, then the + * node returned may contain, or + * consist entirely of invalid + * glue (i.e. data occluded by a + * zone cut). The caller must + * take care not to return invalid + * glue to a client. + * + * \li #DNS_R_DELEGATION The data requested is beneath + * a zone cut. node, foundname, + * and rdataset reference the + * NS RRset of the zone cut. + * If 'db' is a cache database, + * then this is the deepest known + * delegation. + * + * \li #DNS_R_ZONECUT type == dns_rdatatype_any, and + * the desired node is a zonecut. + * The caller must take care not + * to return inappropriate glue + * to a client. This result can + * only occur if 'db' is a zone + * database and DNS_DBFIND_GLUEOK + * is set. + * + * \li #DNS_R_DNAME The data requested is beneath + * a DNAME. node, foundname, + * and rdataset reference the + * DNAME RRset. + * + * \li #DNS_R_CNAME The rdataset requested was not + * found, but there is a CNAME + * at the desired name. node, + * foundname, and rdataset + * reference the CNAME RRset. + * + * \li #DNS_R_NXDOMAIN The desired name does not + * exist. + * + * \li #DNS_R_NXRRSET The desired name exists, but + * the desired type does not. + * + * \li #ISC_R_NOTFOUND The desired name does not + * exist, and no delegation could + * be found. This result can only + * occur if 'db' is a cache + * database. The caller should + * use its nameserver(s) of last + * resort (e.g. root hints). + * + * \li #DNS_R_NCACHENXDOMAIN The desired name does not + * exist. 'node' is bound to the + * cache node with the desired + * name, and 'rdataset' contains + * the negative caching proof. + * + * \li #DNS_R_NCACHENXRRSET The desired type does not + * exist. 'node' is bound to the + * cache node with the desired + * name, and 'rdataset' contains + * the negative caching proof. + * + * \li #DNS_R_EMPTYNAME The name exists but there is + * no data at the name. + * + * \li #DNS_R_COVERINGNSEC The returned data is a NSEC + * that potentially covers 'name'. + * + * \li #DNS_R_EMPTYWILD The name is a wildcard without + * resource records. + * + * Error results: + * + * \li #ISC_R_NOMEMORY + * + * \li #DNS_R_BADDB Data that is required to be + * present in the DB, e.g. an NSEC + * record in a secure zone, is not + * present. + * + * \li Other results are possible, and should all be treated as + * errors. + */ + +isc_result_t +dns_db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_name_t *dcname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +/*%< + * Find the deepest known zonecut which encloses 'name' in 'db'. + * + * Notes: + * + * \li If the #DNS_DBFIND_NOEXACT option is set, then the zonecut returned + * (if any) will be the deepest known ancestor of 'name'. + * + * \li If 'now' is zero, then the current time will be used. + * + * Requires: + * + * \li 'db' is a valid database with cache semantics. + * + * \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL. + * + * \li 'foundname' is a valid name with a dedicated buffer. + * + * \li 'dcname' is a valid name with a dedicated buffer. + * + * \li 'rdataset' is NULL, or is a valid unassociated rdataset. + * + * Ensures, on a non-error completion: + * + * \li If nodep != NULL, then it is bound to the found node. + * + * \li If foundname != NULL, then it contains the full name of the + * found node. + * + * \li If dcname != NULL, then it contains the deepest cached name + * that exists in the database. + * + * \li If rdataset != NULL and type != dns_rdatatype_any, then + * rdataset is bound to the found rdataset. + * + * Non-error results are: + * + * \li #ISC_R_SUCCESS + * + * \li #ISC_R_NOTFOUND + * + * \li Other results are possible, and should all be treated as + * errors. + */ + +void +dns_db_attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'source' is a valid node. + * + * \li 'targetp' points to a NULL dns_dbnode_t *. + * + * Ensures: + * + * \li *targetp is attached to source. + */ + +void +dns_db_detachnode(dns_db_t *db, dns_dbnode_t **nodep); +/*%< + * Detach *nodep from its node. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'nodep' points to a valid node. + * + * Ensures: + * + * \li *nodep is NULL. + */ + +void +dns_db_transfernode(dns_db_t *db, dns_dbnode_t **sourcep, + dns_dbnode_t **targetp); +/*%< + * Transfer a node between pointer. + * + * This is equivalent to calling dns_db_attachnode() then dns_db_detachnode(). + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li '*sourcep' is a valid node. + * + * \li 'targetp' points to a NULL dns_dbnode_t *. + * + * Ensures: + * + * \li '*sourcep' is NULL. + */ + +isc_result_t +dns_db_expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now); +/*%< + * Mark as stale all records at 'node' which expire at or before 'now'. + * + * Note: if 'now' is zero, then the current time will be used. + * + * Requires: + * + * \li 'db' is a valid cache database. + * + * \li 'node' is a valid node. + */ + +void +dns_db_printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out); +/*%< + * Print a textual representation of the contents of the node to + * 'out'. + * + * Note: this function is intended for debugging, not general use. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + */ + +/*** + *** DB Iterator Creation + ***/ + +isc_result_t +dns_db_createiterator(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp); +/*%< + * Create an iterator for version 'version' of 'db'. + * + * Notes: + * + * \li One or more of the following options can be set. + * #DNS_DB_RELATIVENAMES + * #DNS_DB_NSEC3ONLY + * #DNS_DB_NONSEC3 + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li iteratorp != NULL && *iteratorp == NULL + * + * Ensures: + * + * \li On success, *iteratorp will be a valid database iterator. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +/*** + *** Rdataset Methods + ***/ + +/* + * XXXRTH Should we check for glue and pending data in dns_db_findrdataset()? + */ + +isc_result_t +dns_db_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, + isc_stdtime_t now, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); + +/*%< + * Search for an rdataset of type 'type' at 'node' that are in version + * 'version' of 'db'. If found, make 'rdataset' refer to it. + * + * Notes: + * + * \li If 'version' is NULL, then the current version will be used. + * + * \li Care must be used when using this routine to build a DNS response: + * 'node' should have been found with dns_db_find(), not + * dns_db_findnode(). No glue checking is done. No checking for + * pending data is done. + * + * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a + * cache database, an rdataset will not be found unless it expires after + * 'now'. If 'now' is zero, then the current time will be used. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + * + * \li 'rdataset' is a valid, disassociated rdataset. + * + * \li 'sigrdataset' is a valid, disassociated rdataset, or it is NULL. + * + * \li If 'covers' != 0, 'type' must be RRSIG. + * + * \li 'type' is not a meta-RR type such as 'ANY' or 'OPT'. + * + * Ensures: + * + * \li On success, 'rdataset' is associated with the found rdataset. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp); +/*%< + * Make '*iteratorp' an rdataset iterator for all rdatasets at 'node' in + * version 'version' of 'db'. + * + * Notes: + * + * \li If 'version' is NULL, then the current version will be used. + * + * \li 'options' controls which rdatasets are selected when interating over + * the node. + * 'DNS_DB_STALEOK' return stale rdatasets as well as current rdatasets. + * 'DNS_DB_EXPIREDOK' return expired rdatasets as well as current + * rdatasets. + * + * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is a + * cache database, an rdataset will not be found unless it expires after + * 'now'. Any ANY query will not match unless at least one rdataset at + * the node expires after 'now'. If 'now' is zero, then the current time + * will be used. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + * + * \li iteratorp != NULL && *iteratorp == NULL + * + * Ensures: + * + * \li On success, '*iteratorp' is a valid rdataset iterator. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *addedrdataset); +/*%< + * Add 'rdataset' to 'node' in version 'version' of 'db'. + * + * Notes: + * + * \li If the database has zone semantics, the #DNS_DBADD_MERGE option is set, + * and an rdataset of the same type as 'rdataset' already exists at + * 'node' then the contents of 'rdataset' will be merged with the existing + * rdataset. If the option is not set, then rdataset will replace any + * existing rdataset of the same type. If not merging and the + * #DNS_DBADD_FORCE option is set, then the data will update the database + * without regard to trust levels. If not forcing the data, then the + * rdataset will only be added if its trust level is >= the trust level of + * any existing rdataset. Forcing is only meaningful for cache databases. + * If #DNS_DBADD_EXACT is set then there must be no rdata in common between + * the old and new rdata sets. If #DNS_DBADD_EXACTTTL is set then both + * the old and new rdata sets must have the same ttl. + * + * \li The 'now' field is ignored if 'db' is a zone database. If 'db' is + * a cache database, then the added rdataset will expire no later than + * now + rdataset->ttl. + * + * \li If 'addedrdataset' is not NULL, then it will be attached to the + * resulting new rdataset in the database, or to the existing data if + * the existing data was better. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + * + * \li 'rdataset' is a valid, associated rdataset with the same class + * as 'db'. + * + * \li 'addedrdataset' is NULL, or a valid, unassociated rdataset. + * + * \li The database has zone semantics and 'version' is a valid + * read-write version, or the database has cache semantics + * and version is NULL. + * + * \li If the database has cache semantics, the #DNS_DBADD_MERGE option must + * not be set. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #DNS_R_UNCHANGED The operation did not change + * anything. \li #ISC_R_NOMEMORY \li #DNS_R_NOTEXACT + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_subtractrdataset(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdataset_t *rdataset, + unsigned int options, dns_rdataset_t *newrdataset); +/*%< + * Remove any rdata in 'rdataset' from 'node' in version 'version' of + * 'db'. + * + * Notes: + * + * \li If 'newrdataset' is not NULL, then it will be attached to the + * resulting new rdataset in the database, unless the rdataset has + * become nonexistent. If DNS_DBSUB_EXACT is set then all elements + * of 'rdataset' must exist at 'node'. + * + *\li If DNS_DBSUB_WANTOLD is set and the entire rdataset was deleted + * then return the original rdatatset in newrdataset if that existed. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + * + * \li 'rdataset' is a valid, associated rdataset with the same class + * as 'db'. + * + * \li 'newrdataset' is NULL, or a valid, unassociated rdataset. + * + * \li The database has zone semantics and 'version' is a valid + * read-write version. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #DNS_R_UNCHANGED The operation did not change + * anything. \li #DNS_R_NXRRSET All rdata of the same + *type as + * those in 'rdataset' have been deleted. \li #DNS_R_NOTEXACT + * Some part of 'rdataset' did not exist and DNS_DBSUB_EXACT was set. + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_deleterdataset(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdatatype_t type, + dns_rdatatype_t covers); +/*%< + * Make it so that no rdataset of type 'type' exists at 'node' in version + * version 'version' of 'db'. + * + * Notes: + * + * \li If 'type' is dns_rdatatype_any, then no rdatasets will exist in + * 'version' (provided that the dns_db_deleterdataset() isn't followed + * by one or more dns_db_addrdataset() calls). + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'node' is a valid node. + * + * \li The database has zone semantics and 'version' is a valid + * read-write version, or the database has cache semantics + * and version is NULL. + * + * \li 'type' is not a meta-RR type, except for dns_rdatatype_any, which is + * allowed. + * + * \li If 'covers' != 0, 'type' must be SIG. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #DNS_R_UNCHANGED No rdatasets of 'type' existed + * before the operation was attempted. + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_getsoaserial(dns_db_t *db, dns_dbversion_t *ver, uint32_t *serialp); +/*%< + * Get the current SOA serial number from a zone database. + * + * Requires: + * \li 'db' is a valid database with zone semantics. + * \li 'ver' is a valid version. + */ + +void +dns_db_overmem(dns_db_t *db, bool overmem); +/*%< + * Enable / disable aggressive cache cleaning. + */ + +unsigned int +dns_db_nodecount(dns_db_t *db, dns_dbtree_t tree); +/*%< + * Count the number of nodes in 'db' or its auxiliary trees. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li The number of nodes in the database + */ + +size_t +dns_db_hashsize(dns_db_t *db); +/*%< + * For database implementations using a hash table, report the + * current number of buckets. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li The number of buckets in the database's hash table, or + * 0 if not implemented. + */ + +void +dns_db_settask(dns_db_t *db, isc_task_t *task); +/*%< + * If task is set then the final detach maybe performed asynchronously. + * + * Requires: + * \li 'db' is a valid database. + * \li 'task' to be valid or NULL. + */ + +bool +dns_db_ispersistent(dns_db_t *db); +/*%< + * Is 'db' persistent? A persistent database does not need to be loaded + * from disk or written to disk. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * \li #true 'db' is persistent. + * \li #false 'db' is not persistent. + */ + +isc_result_t +dns_db_register(const char *name, dns_dbcreatefunc_t create, void *driverarg, + isc_mem_t *mctx, dns_dbimplementation_t **dbimp); + +/*%< + * Register a new database implementation and add it to the list of + * supported implementations. + * + * Requires: + * + * \li 'name' is not NULL + * \li 'order' is a valid function pointer + * \li 'mctx' is a valid memory context + * \li dbimp != NULL && *dbimp == NULL + * + * Returns: + * \li #ISC_R_SUCCESS The registration succeeded + * \li #ISC_R_NOMEMORY Out of memory + * \li #ISC_R_EXISTS A database implementation with the same name exists + * + * Ensures: + * + * \li *dbimp points to an opaque structure which must be passed to + * dns_db_unregister(). + */ + +void +dns_db_unregister(dns_dbimplementation_t **dbimp); +/*%< + * Remove a database implementation from the list of supported + * implementations. No databases of this type can be active when this + * is called. + * + * Requires: + * \li dbimp != NULL && *dbimp == NULL + * + * Ensures: + * + * \li Any memory allocated in *dbimp will be freed. + */ + +isc_result_t +dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep); +/*%< + * Get the origin DB node corresponding to the DB's zone. This function + * should typically succeed unless the underlying DB implementation doesn't + * support the feature. + * + * Requires: + * + * \li 'db' is a valid zone database. + * \li 'nodep' != NULL && '*nodep' == NULL + * + * Ensures: + * \li On success, '*nodep' will point to the DB node of the zone's origin. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature. + */ + +isc_result_t +dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, + dns_hash_t *hash, uint8_t *flags, + uint16_t *iterations, unsigned char *salt, + size_t *salt_length); +/*%< + * Get the NSEC3 parameters that are associated with this zone. + * + * Requires: + * \li 'db' is a valid zone database. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND - the DB implementation does not support this feature + * or this zone does not have NSEC3 records. + */ + +isc_result_t +dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, + uint64_t *xfrsize); +/*%< + * On success if 'records' is not NULL, it is set to the number of records + * in the given version of the database. If 'xfrisize' is not NULL, it is + * set to the approximate number of bytes needed to transfer the records, + * counting name, TTL, type, class, and rdata for each RR. (This is meant + * to be a rough approximation of the size of a full zone transfer, though + * it does not take into account DNS message overhead or name compression.) + * + * Requires: + * \li 'db' is a valid zone database. + * \li 'version' is NULL or a valid version. + * \li 'records' is NULL or a pointer to return the record count in. + * \li 'xfrsize' is NULL or a pointer to return the byte count in. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +dns_db_findnsec3node(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep); +/*%< + * Find the NSEC3 node with name 'name'. + * + * Notes: + * \li If 'create' is true and no node with name 'name' exists, then + * such a node will be created. + * + * Requires: + * + * \li 'db' is a valid database. + * + * \li 'name' is a valid, non-empty, absolute name. + * + * \li nodep != NULL && *nodep == NULL + * + * Ensures: + * + * \li On success, *nodep is attached to the node with name 'name'. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND If !create and name not found. + * \li #ISC_R_NOMEMORY Can only happen if create is true. + * + * \li Other results are possible, depending upon the database + * implementation used. + */ + +isc_result_t +dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, + isc_stdtime_t resign); +/*%< + * Sets the re-signing time associated with 'rdataset' to 'resign'. + * + * Requires: + * \li 'db' is a valid zone database. + * \li 'rdataset' is or is to be associated with 'db'. + * \li 'rdataset' is not pending removed from the heap via an + * uncommitted call to dns_db_resigned(). + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *name); +/*%< + * Return the rdataset with the earliest signing time in the zone. + * Note: the rdataset is version agnostic. + * + * Requires: + * \li 'db' is a valid zone database. + * \li 'rdataset' to be initialized but not associated. + * \li 'name' to be NULL or have a buffer associated with it. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND - No dataset exists. + */ + +void +dns_db_resigned(dns_db_t *db, dns_rdataset_t *rdataset, + dns_dbversion_t *version); +/*%< + * Mark 'rdataset' as not being available to be returned by + * dns_db_getsigningtime(). If the changes associated with 'version' + * are committed this will be permanent. If the version is not committed + * this change will be rolled back when the version is closed. Until + * 'version' is either committed or rolled back, 'rdataset' can no longer + * be acted upon by dns_db_setsigningtime(). + * + * Requires: + * \li 'db' is a valid zone database. + * \li 'rdataset' to be associated with 'db'. + * \li 'version' to be open for writing. + */ + +dns_stats_t * +dns_db_getrrsetstats(dns_db_t *db); +/*%< + * Get statistics information counting RRsets stored in the DB, when available. + * The statistics may not be available depending on the DB implementation. + * + * Requires: + * + * \li 'db' is a valid database (cache only). + * + * Returns: + * \li when available, a pointer to a statistics object created by + * dns_rdatasetstats_create(); otherwise NULL. + */ + +isc_result_t +dns_db_setcachestats(dns_db_t *db, isc_stats_t *stats); +/*%< + * Set the location in which to collect cache statistics. + * This option may not exist depending on the DB implementation. + * + * Requires: + * + * \li 'db' is a valid database (cache only). + * + * Returns: + * \li when available, a pointer to a statistics object created by + * dns_rdatasetstats_create(); otherwise NULL. + */ + +void +dns_db_rpz_attach(dns_db_t *db, void *rpzs, uint8_t rpz_num) ISC_DEPRECATED; +/*%< + * Attach the response policy information for a view to a database for a + * zone for the view. + */ + +isc_result_t +dns_db_rpz_ready(dns_db_t *db) ISC_DEPRECATED; +/*%< + * Finish loading a response policy zone. + */ + +isc_result_t +dns_db_updatenotify_register(dns_db_t *db, dns_dbupdate_callback_t fn, + void *fn_arg); +/*%< + * Register a notify-on-update callback function to a database. + * Duplicate callbacks are suppressed. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'fn' is not NULL + * + */ + +isc_result_t +dns_db_updatenotify_unregister(dns_db_t *db, dns_dbupdate_callback_t fn, + void *fn_arg); +/*%< + * Unregister a notify-on-update callback. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'db' has update callback registered + * + */ + +isc_result_t +dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); +/*%< + * Get the name associated with a database node. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'node' and 'name' are not NULL + */ + +isc_result_t +dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl); +/*%< + * Sets the maximum length of time that cached answers may be retained + * past their normal TTL. Default value for the library is 0, disabling + * the use of stale data. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'ttl' is the number of seconds to retain data past its normal expiry. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl); +/*%< + * Gets maximum length of time that cached answers may be kept past + * normal TTL expiration. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'ttl' is the number of seconds to retain data past its normal expiry. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_setservestalerefresh(dns_db_t *db, uint32_t interval); +/*%< + * Sets the length of time to wait before attempting to refresh a rrset + * if a previous attempt in doing so has failed. + * During this time window if stale rrset are available in cache they + * will be directly returned to client. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'interval' is number of seconds before attempting to refresh data. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_getservestalerefresh(dns_db_t *db, uint32_t *interval); +/*%< + * Gets the length of time in which stale answers are directly returned from + * cache before attempting to refresh them, in case a previous attempt in + * doing so has failed. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'interval' is number of seconds before attempting to refresh data. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_setgluecachestats(dns_db_t *db, isc_stats_t *stats); +/*%< + * Set the location in which to collect glue cache statistics. + * This option may not exist depending on the DB implementation. + * + * Requires: + * + * \li 'db' is a valid database (cache only). + * + * Returns: + * \li when available, a pointer to a statistics object created by + * dns_rdatasetstats_create(); otherwise NULL. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dbiterator.h b/lib/dns/include/dns/dbiterator.h new file mode 100644 index 0000000..dcec59a --- /dev/null +++ b/lib/dns/include/dns/dbiterator.h @@ -0,0 +1,290 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/dbiterator.h + * \brief + * The DNS DB Iterator interface allows iteration of all of the nodes in a + * database. + * + * The dns_dbiterator_t type is like a "virtual class". To actually use + * it, an implementation of the class is required. This implementation is + * supplied by the database. + * + * It is the client's responsibility to call dns_db_detachnode() on all + * nodes returned. + * + * XXX <more> XXX + * + * MP: + *\li The iterator itself is not locked. The caller must ensure + * synchronization. + * + *\li The iterator methods ensure appropriate database locking. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +/***** +***** Imports +*****/ + +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/***** +***** Types +*****/ + +typedef struct dns_dbiteratormethods { + void (*destroy)(dns_dbiterator_t **iteratorp); + isc_result_t (*first)(dns_dbiterator_t *iterator); + isc_result_t (*last)(dns_dbiterator_t *iterator); + isc_result_t (*seek)(dns_dbiterator_t *iterator, + const dns_name_t *name); + isc_result_t (*prev)(dns_dbiterator_t *iterator); + isc_result_t (*next)(dns_dbiterator_t *iterator); + isc_result_t (*current)(dns_dbiterator_t *iterator, + dns_dbnode_t **nodep, dns_name_t *name); + isc_result_t (*pause)(dns_dbiterator_t *iterator); + isc_result_t (*origin)(dns_dbiterator_t *iterator, dns_name_t *name); +} dns_dbiteratormethods_t; + +#define DNS_DBITERATOR_MAGIC ISC_MAGIC('D', 'N', 'S', 'I') +#define DNS_DBITERATOR_VALID(dbi) ISC_MAGIC_VALID(dbi, DNS_DBITERATOR_MAGIC) +/*% + * This structure is actually just the common prefix of a DNS db + * implementation's version of a dns_dbiterator_t. + * + * Clients may use the 'db' field of this structure. Except for that field, + * direct use of this structure by clients is forbidden. DB implementations + * may change the structure. 'magic' must be DNS_DBITERATOR_MAGIC for any of + * the dns_dbiterator routines to work. DB iterator implementations must + * maintain all DB iterator invariants. + */ +struct dns_dbiterator { + /* Unlocked. */ + unsigned int magic; + dns_dbiteratormethods_t *methods; + dns_db_t *db; + bool relative_names; + bool cleaning; +}; + +void +dns_dbiterator_destroy(dns_dbiterator_t **iteratorp); +/*%< + * Destroy '*iteratorp'. + * + * Requires: + * + *\li '*iteratorp' is a valid iterator. + * + * Ensures: + * + *\li All resources used by the iterator are freed. + * + *\li *iteratorp == NULL. + */ + +isc_result_t +dns_dbiterator_first(dns_dbiterator_t *iterator); +/*%< + * Move the node cursor to the first node in the database (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no nodes in the database. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_last(dns_dbiterator_t *iterator); +/*%< + * Move the node cursor to the last node in the database (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no nodes in the database. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name); +/*%< + * Move the node cursor to the node with name 'name'. + * + * Requires: + *\li 'iterator' is a valid iterator. + * + *\li 'name' is a valid name. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTFOUND + *\li #DNS_R_PARTIALMATCH + * (node is at name above requested named when name has children) + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_prev(dns_dbiterator_t *iterator); +/*%< + * Move the node cursor to the previous node in the database (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no more nodes in the + * database. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_next(dns_dbiterator_t *iterator); +/*%< + * Move the node cursor to the next node in the database (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no more nodes in the + * database. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name); +/*%< + * Return the current node. + * + * Notes: + *\li If 'name' is not NULL, it will be set to the name of the node. + * + * Requires: + *\li 'iterator' is a valid iterator. + * + *\li nodep != NULL && *nodep == NULL + * + *\li The node cursor of 'iterator' is at a valid location (i.e. the + * result of last call to a cursor movement command was ISC_R_SUCCESS). + * + *\li 'name' is NULL, or is a valid name with a dedicated buffer. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #DNS_R_NEWORIGIN If this iterator was created + * with 'relative_names' set to true, then #DNS_R_NEWORIGIN will be returned + *when + * the origin the names are relative to changes. This result can occur only + *when + *'name' is not NULL. This is also a successful result. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_pause(dns_dbiterator_t *iterator); +/*%< + * Pause iteration. + * + * Calling a cursor movement method or dns_dbiterator_current() may cause + * database locks to be acquired. Rather than reacquire these locks every + * time one of these routines is called, the locks may simply be held. + * Calling dns_dbiterator_pause() releases any such locks. Iterator clients + * should call this routine any time they are not going to execute another + * iterator method in the immediate future. + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Ensures: + *\li Any database locks being held for efficiency of iterator access are + * released. + * + * Returns: + *\li #ISC_R_SUCCESS + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name); +/*%< + * Return the origin to which returned node names are relative. + * + * Requires: + * + *\li 'iterator' is a valid relative_names iterator. + * + *\li 'name' is a valid name with a dedicated buffer. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + * + *\li Other results are possible, depending on the DB implementation. + */ + +void +dns_dbiterator_setcleanmode(dns_dbiterator_t *iterator, bool mode); +/*%< + * Indicate that the given iterator is/is not cleaning the DB. + * + * Notes: + *\li When 'mode' is true, + * + * Requires: + *\li 'iterator' is a valid iterator. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/diff.h b/lib/dns/include/dns/diff.h new file mode 100644 index 0000000..e4677dd --- /dev/null +++ b/lib/dns/include/dns/diff.h @@ -0,0 +1,279 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/diff.h + * \brief + * A diff is a convenience type representing a list of changes to be + * made to a database. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include + +/*** + *** Types + ***/ + +/*% + * A dns_difftuple_t represents a single RR being added or deleted. + * The RR type and class are in the 'rdata' member; the class is always + * the real one, not a DynDNS meta-class, so that the rdatas can be + * compared using dns_rdata_compare(). The TTL is significant + * even for deletions, because a deletion/addition pair cannot + * be canceled out if the TTL differs (it might be an explicit + * TTL update). + * + * Tuples are also used to represent complete RRs with owner + * names for a couple of other purposes, such as the + * individual RRs of a "RRset exists (value dependent)" + * prerequisite set. In this case, op==DNS_DIFFOP_EXISTS, + * and the TTL is ignored. + * + * DNS_DIFFOP_*RESIGN will cause the 'resign' attribute of the resulting + * RRset to be recomputed to be 'resign' seconds before the earliest RRSIG + * timeexpire. + */ + +typedef enum { + DNS_DIFFOP_ADD = 0, /*%< Add an RR. */ + DNS_DIFFOP_DEL = 1, /*%< Delete an RR. */ + DNS_DIFFOP_EXISTS = 2, /*%< Assert RR existence. */ + DNS_DIFFOP_ADDRESIGN = 4, /*%< ADD + RESIGN. */ + DNS_DIFFOP_DELRESIGN = 5 /*%< DEL + RESIGN. */ +} dns_diffop_t; + +typedef struct dns_difftuple dns_difftuple_t; +typedef ISC_LIST(dns_difftuple_t) dns_difftuplelist_t; + +#define DNS_DIFFTUPLE_MAGIC ISC_MAGIC('D', 'I', 'F', 'T') +#define DNS_DIFFTUPLE_VALID(t) ISC_MAGIC_VALID(t, DNS_DIFFTUPLE_MAGIC) + +struct dns_difftuple { + unsigned int magic; + isc_mem_t *mctx; + dns_diffop_t op; + dns_name_t name; + dns_ttl_t ttl; + dns_rdata_t rdata; + ISC_LINK(dns_difftuple_t) link; + /* Variable-size name data and rdata follows. */ +}; + +/*% + * A dns_diff_t represents a set of changes being applied to + * a zone. Diffs are also used to represent "RRset exists + * (value dependent)" prerequisites. + */ +typedef struct dns_diff dns_diff_t; + +#define DNS_DIFF_MAGIC ISC_MAGIC('D', 'I', 'F', 'F') +#define DNS_DIFF_VALID(t) ISC_MAGIC_VALID(t, DNS_DIFF_MAGIC) + +struct dns_diff { + unsigned int magic; + isc_mem_t *mctx; + dns_difftuplelist_t tuples; +}; + +/* Type of comparison function for sorting diffs. */ +typedef int +dns_diff_compare_func(const void *, const void *); + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +/**************************************************************************/ +/* + * Manipulation of diffs and tuples. + */ + +isc_result_t +dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp); +/*%< + * Create a tuple. Deep copies are made of the name and rdata, so + * they need not remain valid after the call. + * + * Requires: + *\li *tp != NULL && *tp == NULL. + * + * Returns: + *\li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + */ + +void +dns_difftuple_free(dns_difftuple_t **tp); +/*%< + * Free a tuple. + * + * Requires: + * \li **tp is a valid tuple. + * + * Ensures: + * \li *tp == NULL + * \li All memory used by the tuple is freed. + */ + +isc_result_t +dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp); +/*%< + * Copy a tuple. + * + * Requires: + * \li 'orig' points to a valid tuple + *\li copyp != NULL && *copyp == NULL + */ + +void +dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff); +/*%< + * Initialize a diff. + * + * Requires: + * \li 'diff' points to an uninitialized dns_diff_t + * \li allocated by the caller. + * + * Ensures: + * \li '*diff' is a valid, empty diff. + */ + +void +dns_diff_clear(dns_diff_t *diff); +/*%< + * Clear a diff, destroying all its tuples. + * + * Requires: + * \li 'diff' points to a valid dns_diff_t. + * + * Ensures: + * \li Any tuples in the diff are destroyed. + * The diff now empty, but it is still valid + * and may be reused without calling dns_diff_init + * again. The only memory used is that of the + * dns_diff_t structure itself. + * + * Notes: + * \li Managing the memory of the dns_diff_t structure itself + * is the caller's responsibility. + */ + +void +dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuple); +/*%< + * Append a single tuple to a diff. + * + *\li 'diff' is a valid diff. + * \li '*tuple' is a valid tuple. + * + * Ensures: + *\li *tuple is NULL. + *\li The tuple has been freed, or will be freed when the diff is cleared. + */ + +void +dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuple); +/*%< + * Append 'tuple' to 'diff', removing any duplicate + * or conflicting updates as needed to create a minimal diff. + * + * Requires: + *\li 'diff' is a minimal diff. + * + * Ensures: + *\li 'diff' is still a minimal diff. + * \li *tuple is NULL. + * \li The tuple has been freed, or will be freed when the diff is + * cleared. + * + */ + +isc_result_t +dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare); +/*%< + * Sort 'diff' in-place according to the comparison function 'compare'. + */ + +isc_result_t +dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver); +isc_result_t +dns_diff_applysilently(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver); +/*%< + * Apply 'diff' to the database 'db'. + * + * dns_diff_apply() logs warnings about updates with no effect or + * with inconsistent TTLs; dns_diff_applysilently() does not. + * + * For efficiency, the diff should be sorted by owner name. + * If it is not sorted, operation will still be correct, + * but less efficient. + * + * Requires: + *\li *diff is a valid diff (possibly empty), containing + * tuples of type #DNS_DIFFOP_ADD and/or + * For #DNS_DIFFOP_DEL tuples, the TTL is ignored. + * + */ + +isc_result_t +dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc, + void *add_private); +/*%< + * Like dns_diff_apply, but for use when loading a new database + * instead of modifying an existing one. This bypasses the + * database transaction mechanisms. + * + * Requires: + *\li 'addfunc' is a valid dns_addradatasetfunc_t obtained from + * dns_db_beginload() + * + *\li 'add_private' points to a corresponding dns_dbload_t * + * (XXX why is it a void pointer, then?) + */ + +isc_result_t +dns_diff_print(dns_diff_t *diff, FILE *file); + +/*%< + * Print the differences to 'file' or if 'file' is NULL via the + * logging system. + * + * Require: + *\li 'diff' to be valid. + *\li 'file' to refer to a open file or NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + *\li any error from dns_rdataset_totext() + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dispatch.h b/lib/dns/include/dns/dispatch.h new file mode 100644 index 0000000..96be0f4 --- /dev/null +++ b/lib/dns/include/dns/dispatch.h @@ -0,0 +1,390 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +/***** +***** Module Info +*****/ + +/*! \file dns/dispatch.h + * \brief + * DNS Dispatch Management + * Shared UDP and single-use TCP dispatches for queries and responses. + * + * MP: + * + *\li All locking is performed internally to each dispatch. + * Restrictions apply to dns_dispatch_done(). + * + * Reliability: + * + * Resources: + * + * Security: + * + *\li Depends on dns_message_t for prevention of buffer overruns. + * + * Standards: + * + *\li None. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#undef DNS_DISPATCH_TRACE + +ISC_LANG_BEGINDECLS + +/*% + * This is a set of one or more dispatches which can be retrieved + * round-robin fashion. + */ +struct dns_dispatchset { + isc_mem_t *mctx; + dns_dispatch_t **dispatches; + int ndisp; + int cur; + isc_mutex_t lock; +}; + +/* + */ +#define DNS_DISPATCHOPT_FIXEDID 0x00000001U + +isc_result_t +dns_dispatchmgr_create(isc_mem_t *mctx, isc_nm_t *nm, dns_dispatchmgr_t **mgrp); +/*%< + * Creates a new dispatchmgr object, and sets the available ports + * to the default range (1024-65535). + * + * Requires: + *\li 'mctx' be a valid memory context. + * + *\li 'nm' is a valid network manager. + + *\li mgrp != NULL && *mgrp == NULL + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +#if DNS_DISPATCH_TRACE +#define dns_dispatchmgr_ref(ptr) \ + dns_dispatchmgr__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispatchmgr_unref(ptr) \ + dns_dispatchmgr__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispatchmgr_attach(ptr, ptrp) \ + dns_dispatchmgr__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define dns_dispatchmgr_detach(ptrp) \ + dns_dispatchmgr__detach(ptrp, __func__, __FILE__, __LINE__) +ISC_REFCOUNT_TRACE_DECL(dns_dispatchmgr); +#else +ISC_REFCOUNT_DECL(dns_dispatchmgr); +#endif + +/*%< + * Attach/Detach to a dispatch manager. + */ + +void +dns_dispatchmgr_setblackhole(dns_dispatchmgr_t *mgr, dns_acl_t *blackhole); +/*%< + * Sets the dispatcher's "blackhole list," a list of addresses that will + * be ignored by all dispatchers created by the dispatchmgr. + * + * Requires: + * \li mgrp is a valid dispatchmgr + * \li blackhole is a valid acl + */ + +dns_acl_t * +dns_dispatchmgr_getblackhole(dns_dispatchmgr_t *mgr); +/*%< + * Gets a pointer to the dispatcher's current blackhole list, + * without incrementing its reference count. + * + * Requires: + *\li mgr is a valid dispatchmgr + * Returns: + *\li A pointer to the current blackhole list, or NULL. + */ + +isc_result_t +dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, + isc_portset_t *v6portset); +/*%< + * Sets a list of UDP ports that can be used for outgoing UDP messages. + * + * Requires: + *\li mgr is a valid dispatchmgr + *\li v4portset is NULL or a valid port set + *\li v6portset is NULL or a valid port set + */ + +void +dns_dispatchmgr_setstats(dns_dispatchmgr_t *mgr, isc_stats_t *stats); +/*%< + * Sets statistics counter for the dispatchmgr. This function is expected to + * be called only on zone creation (when necessary). + * Once installed, it cannot be removed or replaced. Also, there is no + * interface to get the installed stats from the zone; the caller must keep the + * stats to reference (e.g. dump) it later. + * + * Requires: + *\li mgr is a valid dispatchmgr with no managed dispatch. + *\li stats is a valid statistics supporting resolver statistics counters + * (see dns/stats.h). + */ + +isc_result_t +dns_dispatch_createudp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + dns_dispatch_t **dispp); +/*%< + * Create a new UDP dispatch. + * + * Requires: + *\li All pointer parameters be valid for their respective types. + * + *\li dispp != NULL && *disp == NULL + * + * Returns: + *\li ISC_R_SUCCESS -- success. + * + *\li Anything else -- failure. + */ + +isc_result_t +dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr, + const isc_sockaddr_t *destaddr, dns_dispatch_t **dispp); +/*%< + * Create a new TCP dns_dispatch. + * + * Requires: + * + *\li mgr is a valid dispatch manager. + * + *\li sock is a valid. + * + * Returns: + *\li ISC_R_SUCCESS -- success. + * + *\li Anything else -- failure. + */ + +#if DNS_DISPATCH_TRACE +#define dns_dispatch_ref(ptr) \ + dns_dispatch__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispatch_unref(ptr) \ + dns_dispatch__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_dispatch_attach(ptr, ptrp) \ + dns_dispatch__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define dns_dispatch_detach(ptrp) \ + dns_dispatch__detach(ptrp, __func__, __FILE__, __LINE__) +ISC_REFCOUNT_TRACE_DECL(dns_dispatch); +#else +ISC_REFCOUNT_DECL(dns_dispatch); +#endif +/*%< + * Attach/Detach to a dispatch handle. + * + * Requires: + *\li disp is valid. + * + *\li dispp != NULL && *dispp == NULL + */ + +isc_result_t +dns_dispatch_connect(dns_dispentry_t *resp); +/*%< + * Connect to the remote server configured in 'resp' and run the + * connect callback that was set up via dns_dispatch_add(). + * + * Requires: + *\li 'resp' is valid. + */ + +void +dns_dispatch_send(dns_dispentry_t *resp, isc_region_t *r); +/*%< + * Send region 'r' using the socket in 'resp', then run the specified + * callback. + * + * Requires: + *\li 'resp' is valid. + */ + +void +dns_dispatch_resume(dns_dispentry_t *resp, uint16_t timeout); +/*%< + * Reset the read timeout in the socket associated with 'resp' and + * continue reading. + * + * Requires: + *\li 'resp' is valid. + */ + +isc_result_t +dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr, + const isc_sockaddr_t *localaddr, dns_dispatch_t **dispp); +/* + * Attempt to connect to a existing TCP connection. + */ + +typedef void (*dispatch_cb_t)(isc_result_t eresult, isc_region_t *region, + void *cbarg); + +isc_result_t +dns_dispatch_add(dns_dispatch_t *disp, unsigned int options, + unsigned int timeout, const isc_sockaddr_t *dest, + dispatch_cb_t connected, dispatch_cb_t sent, + dispatch_cb_t response, void *arg, dns_messageid_t *idp, + dns_dispentry_t **resp); +/*%< + * Add a response entry for this dispatch. + * + * "*idp" is filled in with the assigned message ID, and *resp is filled in + * with the dispatch entry object. + * + * The 'connected' and 'sent' callbacks are run to inform the caller when + * the connect and send functions are complete. The 'timedout' callback + * is run to inform the caller that a read has timed out; it may optionally + * reset the read timer. The 'response' callback is run for recv results + * (response packets, timeouts, or cancellations). + * + * All the callback functions are sent 'arg' as a parameter. + * + * Requires: + *\li "idp" be non-NULL. + * + *\li "response" and "arg" be set as appropriate. + * + *\li "dest" be non-NULL and valid. + * + *\li "resp" be non-NULL and *resp be NULL + * + * Ensures: + * + *\li <id, dest> is a unique tuple. That means incoming messages + * are identifiable. + * + * Returns: + * + *\li ISC_R_SUCCESS -- all is well. + *\li ISC_R_NOMEMORY -- memory could not be allocated. + *\li ISC_R_NOMORE -- no more message ids can be allocated + * for this destination. + */ + +void +dns_dispatch_done(dns_dispentry_t **respp); +/*< + * Disconnect a dispatch response entry from its dispatch, cancel all + * pending connects and reads in a dispatch entry and shut it down. + + * + * Requires: + *\li "resp" != NULL and "*resp" contain a value previously allocated + * by dns_dispatch_add(); + */ + +isc_result_t +dns_dispatch_getlocaladdress(dns_dispatch_t *disp, isc_sockaddr_t *addrp); +/*%< + * Return the local address for this dispatch. + * This currently only works for dispatches using UDP sockets. + * + * Requires: + *\li disp is valid. + *\li addrp to be non NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +dns_dispentry_getlocaladdress(dns_dispentry_t *resp, isc_sockaddr_t *addrp); +/*%< + * Return the local address for this dispatch entry. + * + * Requires: + *\li resp is valid. + *\li addrp to be non NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTIMPLEMENTED + */ + +dns_dispatch_t * +dns_dispatchset_get(dns_dispatchset_t *dset); +/*%< + * Retrieve the next dispatch from dispatch set 'dset', and increment + * the round-robin counter. + * + * Requires: + *\li dset != NULL + */ + +isc_result_t +dns_dispatchset_create(isc_mem_t *mctx, dns_dispatch_t *source, + dns_dispatchset_t **dsetp, int n); +/*%< + * Given a valid dispatch 'source', create a dispatch set containing + * 'n' UDP dispatches, with the remainder filled out by clones of the + * source. + * + * Requires: + *\li source is a valid UDP dispatcher + *\li dsetp != NULL, *dsetp == NULL + */ + +void +dns_dispatchset_destroy(dns_dispatchset_t **dsetp); +/*%< + * Dereference all the dispatches in '*dsetp', free the dispatchset + * memory, and set *dsetp to NULL. + * + * Requires: + *\li dset is valid + */ + +isc_result_t +dns_dispatch_getnext(dns_dispentry_t *resp); +/*%< + * Trigger the sending of the next item off the dispatch queue if present. + * + * Requires: + *\li resp is valid + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dlz.h b/lib/dns/include/dns/dlz.h new file mode 100644 index 0000000..4b61411 --- /dev/null +++ b/lib/dns/include/dns/dlz.h @@ -0,0 +1,333 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file dns/dlz.h */ + +#pragma once + +/***** +***** Module Info +*****/ + +/* + * DLZ Interface + * + * The DLZ interface allows zones to be looked up using a driver instead of + * Bind's default in memory zone table. + * + * + * Reliability: + * No anticipated impact. + * + * Resources: + * + * Security: + * No anticipated impact. + * + * Standards: + * None. + */ + +/***** +***** Imports +*****/ + +#include + +#include + +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +#define DNS_DLZ_MAGIC ISC_MAGIC('D', 'L', 'Z', 'D') +#define DNS_DLZ_VALID(dlz) ISC_MAGIC_VALID(dlz, DNS_DLZ_MAGIC) + +typedef isc_result_t (*dns_dlzallowzonexfr_t)(void *driverarg, void *dbdata, + isc_mem_t *mctx, + dns_rdataclass_t rdclass, + const dns_name_t *name, + const isc_sockaddr_t *clientaddr, + dns_db_t **dbp); + +/*%< + * Method prototype. Drivers implementing the DLZ interface MUST + * supply an allow zone transfer method. This method is called when + * the DNS server is performing a zone transfer query. The driver's + * method should return ISC_R_SUCCESS and a database pointer to the + * name server if the zone is supported by the database, and zone + * transfer is allowed. If the view's transfer acl should be used, + * then the driver's method should return ISC_R_DEFAULT. Otherwise, + * it should return ISC_R_NOTFOUND if the zone is not supported by + * the database, or ISC_R_NOPERM if zone transfers are not allowed. + * If an error occurs, the result code should indicate the type of error. + */ + +typedef isc_result_t (*dns_dlzcreate_t)(isc_mem_t *mctx, const char *dlzname, + unsigned int argc, char *argv[], + void *driverarg, void **dbdata); + +/*%< + * Method prototype. Drivers implementing the DLZ interface MUST + * supply a create method. This method is called when the DNS server + * is starting up and creating drivers for use later. + */ + +typedef void (*dns_dlzdestroy_t)(void *driverarg, void **dbdata); + +/*%< + * Method prototype. Drivers implementing the DLZ interface MUST + * supply a destroy method. This method is called when the DNS server + * is shutting down and no longer needs the driver. + */ + +typedef isc_result_t (*dns_dlzfindzone_t)(void *driverarg, void *dbdata, + isc_mem_t *mctx, + dns_rdataclass_t rdclass, + const dns_name_t *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, + dns_db_t **dbp); + +/*%< + * Method prototype. Drivers implementing the DLZ interface MUST + * supply a find zone method. This method is called when the DNS + * server is performing a query. The find zone method will be called + * with the longest possible name first, and continue to be called + * with successively shorter domain names, until any of the following + * occur: + * + * \li 1) a match is found, and the function returns (ISC_R_SUCCESS) + * + * \li 2) a problem occurs, and the functions returns anything other + * than (ISC_R_NOTFOUND) + * \li 3) we run out of domain name labels. I.E. we have tried the + * shortest domain name + * \li 4) the number of labels in the domain name is less than + * min_labels for dns_dlzfindzone + * + * The driver's find zone method should return ISC_R_SUCCESS and a + * database pointer to the name server if the zone is supported by the + * database. Otherwise it will return ISC_R_NOTFOUND, and a null + * pointer if the zone is not supported. If an error occurs it should + * return a result code indicating the type of error. + */ + +typedef isc_result_t (*dns_dlzconfigure_t)(void *driverarg, void *dbdata, + dns_view_t *view, + dns_dlzdb_t *dlzdb); +/*%< + * Method prototype. Drivers implementing the DLZ interface may + * optionally supply a configure method. If supplied, this will be + * called immediately after the create method is called. The driver + * may call configuration functions during the configure call + */ + +typedef bool (*dns_dlzssumatch_t)(const dns_name_t *signer, + const dns_name_t *name, + const isc_netaddr_t *tcpaddr, + dns_rdatatype_t type, const dst_key_t *key, + void *driverarg, void *dbdata); +/*%< + * Method prototype. Drivers implementing the DLZ interface may + * optionally supply a ssumatch method. If supplied, this will be + * called to authorize update requests + */ + +/*% the methods supplied by a DLZ driver */ +typedef struct dns_dlzmethods { + dns_dlzcreate_t create; + dns_dlzdestroy_t destroy; + dns_dlzfindzone_t findzone; + dns_dlzallowzonexfr_t allowzonexfr; + dns_dlzconfigure_t configure; + dns_dlzssumatch_t ssumatch; +} dns_dlzmethods_t; + +/*% information about a DLZ driver */ +struct dns_dlzimplementation { + const char *name; + const dns_dlzmethods_t *methods; + isc_mem_t *mctx; + void *driverarg; + ISC_LINK(dns_dlzimplementation_t) link; +}; + +typedef isc_result_t (*dlzconfigure_callback_t)(dns_view_t *, dns_dlzdb_t *, + dns_zone_t *); + +/*% An instance of a DLZ driver */ +struct dns_dlzdb { + unsigned int magic; + isc_mem_t *mctx; + dns_dlzimplementation_t *implementation; + void *dbdata; + dlzconfigure_callback_t configure_callback; + bool search; + char *dlzname; + ISC_LINK(dns_dlzdb_t) link; + dns_ssutable_t *ssutable; +}; + +/*** + *** Method declarations + ***/ + +isc_result_t +dns_dlzallowzonexfr(dns_view_t *view, const dns_name_t *name, + const isc_sockaddr_t *clientaddr, dns_db_t **dbp); + +/*%< + * This method is called when the DNS server is performing a zone + * transfer query. It will call the DLZ driver's allow zone transfer + * method. + */ + +isc_result_t +dns_dlzcreate(isc_mem_t *mctx, const char *dlzname, const char *drivername, + unsigned int argc, char *argv[], dns_dlzdb_t **dbp); + +/*%< + * This method is called when the DNS server is starting up and + * creating drivers for use later. It will search the DLZ driver list + * for 'drivername' and return a DLZ driver via dbp if a match is + * found. If the DLZ driver supplies a create method, this function + * will call it. + */ + +void +dns_dlzdestroy(dns_dlzdb_t **dbp); + +/*%< + * This method is called when the DNS server is shutting down and no + * longer needs the driver. If the DLZ driver supplies a destroy + * methods, this function will call it. + */ + +isc_result_t +dns_dlzregister(const char *drivername, const dns_dlzmethods_t *methods, + void *driverarg, isc_mem_t *mctx, + dns_dlzimplementation_t **dlzimp); + +/*%< + * Register a dynamically loadable zones (DLZ) driver for the database + * type 'drivername', implemented by the functions in '*methods'. + * + * dlzimp must point to a NULL dlz_implementation_t pointer. That is, + * dlzimp != NULL && *dlzimp == NULL. It will be assigned a value that + * will later be used to identify the driver when deregistering it. + */ + +isc_result_t +dns_dlzstrtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp); + +/*%< + * This method is called when the name server is starting up to parse + * the DLZ driver command line from named.conf. Basically it splits + * up a string into and argc / argv. The primary difference of this + * method is items between braces { } are considered only 1 word. for + * example the command line "this is { one grouped phrase } and this + * isn't" would be parsed into: + * + * \li argv[0]: "this" + * \li argv[1]: "is" + * \li argv{2]: " one grouped phrase " + * \li argv[3]: "and" + * \li argv[4]: "this" + * \li argv{5}: "isn't" + * + * braces should NOT be nested, more than one grouping in the command + * line is allowed. Notice, argv[2] has an extra space at the + * beginning and end. Extra spaces are not stripped between a + * grouping. You can do so in your driver if needed, or be sure not + * to put extra spaces before / after the braces. + */ + +void +dns_dlzunregister(dns_dlzimplementation_t **dlzimp); + +/*%< + * Removes the dlz driver from the list of registered dlz drivers. + * There must be no active dlz drivers of this type when this function + * is called. + */ + +typedef isc_result_t +dns_dlz_writeablezone_t(dns_view_t *view, dns_dlzdb_t *dlzdb, + const char *zone_name); +dns_dlz_writeablezone_t dns_dlz_writeablezone; +/*%< + * creates a writeable DLZ zone. Must be called from within the + * configure() method of a DLZ driver. + */ + +isc_result_t +dns_dlzconfigure(dns_view_t *view, dns_dlzdb_t *dlzdb, + dlzconfigure_callback_t callback); +/*%< + * call a DLZ drivers configure method, if supplied + */ + +bool +dns_dlz_ssumatch(dns_dlzdb_t *dlzdatabase, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *tcpaddr, + dns_rdatatype_t type, const dst_key_t *key); +/*%< + * call a DLZ drivers ssumatch method, if supplied. Otherwise return false + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dlz_dlopen.h b/lib/dns/include/dns/dlz_dlopen.h new file mode 100644 index 0000000..a0059ff --- /dev/null +++ b/lib/dns/include/dns/dlz_dlopen.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file dns/dlz_dlopen.h */ + +#pragma once + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/* + * This header provides a minimal set of defines and typedefs needed + * for the entry points of an external DLZ module for bind9. + */ + +#define DLZ_DLOPEN_VERSION 3 +#define DLZ_DLOPEN_AGE 0 + +/* + * dlz_dlopen_version() is required for all DLZ external drivers. It + * should return DLZ_DLOPEN_VERSION + */ +typedef int +dlz_dlopen_version_t(unsigned int *flags); + +/* + * dlz_dlopen_create() is required for all DLZ external drivers. + */ +typedef isc_result_t +dlz_dlopen_create_t(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...); + +/* + * dlz_dlopen_destroy() is optional, and will be called when the + * driver is unloaded if supplied + */ +typedef void +dlz_dlopen_destroy_t(void *dbdata); + +/* + * dlz_dlopen_findzonedb() is required for all DLZ external drivers + */ +typedef isc_result_t +dlz_dlopen_findzonedb_t(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +/* + * dlz_dlopen_lookup() is required for all DLZ external drivers + */ +typedef isc_result_t +dlz_dlopen_lookup_t(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +/* + * dlz_dlopen_authority is optional() if dlz_dlopen_lookup() + * supplies authority information for the dns record + */ +typedef isc_result_t +dlz_dlopen_authority_t(const char *zone, void *dbdata, + dns_sdlzlookup_t *lookup); + +/* + * dlz_dlopen_allowzonexfr() is optional, and should be supplied if + * you want to support zone transfers + */ +typedef isc_result_t +dlz_dlopen_allowzonexfr_t(void *dbdata, const char *name, const char *client); + +/* + * dlz_dlopen_allnodes() is optional, but must be supplied if supply a + * dlz_dlopen_allowzonexfr() function + */ +typedef isc_result_t +dlz_dlopen_allnodes_t(const char *zone, void *dbdata, + dns_sdlzallnodes_t *allnodes); + +/* + * dlz_dlopen_newversion() is optional. It should be supplied if you + * want to support dynamic updates. + */ +typedef isc_result_t +dlz_dlopen_newversion_t(const char *zone, void *dbdata, void **versionp); + +/* + * dlz_closeversion() is optional, but must be supplied if you supply + * a dlz_newversion() function + */ +typedef void +dlz_dlopen_closeversion_t(const char *zone, bool commit, void *dbdata, + void **versionp); + +/* + * dlz_dlopen_configure() is optional, but must be supplied if you + * want to support dynamic updates + */ +typedef isc_result_t +dlz_dlopen_configure_t(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata); + +/* + * dlz_dlopen_setclientcallback() is optional, but must be supplied if you + * want to retrieve information about the client (e.g., source address) + * before sending a replay. + */ +typedef isc_result_t +dlz_dlopen_setclientcallback_t(dns_view_t *view, void *dbdata); + +/* + * dlz_dlopen_ssumatch() is optional, but must be supplied if you want + * to support dynamic updates + */ +typedef bool +dlz_dlopen_ssumatch_t(const char *signer, const char *name, const char *tcpaddr, + const char *type, const char *key, uint32_t keydatalen, + unsigned char *keydata, void *dbdata); + +/* + * dlz_dlopen_addrdataset() is optional, but must be supplied if you + * want to support dynamic updates + */ +typedef isc_result_t +dlz_dlopen_addrdataset_t(const char *name, const char *rdatastr, void *dbdata, + void *version); + +/* + * dlz_dlopen_subrdataset() is optional, but must be supplied if you + * want to support dynamic updates + */ +typedef isc_result_t +dlz_dlopen_subrdataset_t(const char *name, const char *rdatastr, void *dbdata, + void *version); + +/* + * dlz_dlopen_delrdataset() is optional, but must be supplied if you + * want to support dynamic updates + */ +typedef isc_result_t +dlz_dlopen_delrdataset_t(const char *name, const char *type, void *dbdata, + void *version); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h new file mode 100644 index 0000000..8dd0103 --- /dev/null +++ b/lib/dns/include/dns/dns64.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +/* + * dns_dns64_create() flags. + */ +#define DNS_DNS64_RECURSIVE_ONLY \ + 0x01 /* If set then this record \ + * only applies to recursive \ + * queries. \ + */ +#define DNS_DNS64_BREAK_DNSSEC \ + 0x02 /* If set then still perform \ + * DNSSEC synthesis even \ + * though the result would \ + * fail validation. \ + */ + +/* + * dns_dns64_aaaaok() and dns_dns64_aaaafroma() flags. + */ +#define DNS_DNS64_RECURSIVE 0x01 /* Recursive query. */ +#define DNS_DNS64_DNSSEC 0x02 /* DNSSEC sensitive query. */ + +isc_result_t +dns_dns64_create(isc_mem_t *mctx, const isc_netaddr_t *prefix, + unsigned int prefixlen, const isc_netaddr_t *suffix, + dns_acl_t *client, dns_acl_t *mapped, dns_acl_t *excluded, + unsigned int flags, dns_dns64_t **dns64); +/* + * Create a dns64 record which is used to identify the set of clients + * it applies to and how to perform the DNS64 synthesis. + * + * 'prefix' and 'prefixlen' defined the leading bits of the AAAA records + * to be synthesised. 'suffix' defines the bits after the A records bits. + * If suffix is NULL zeros will be used for these bits. 'client' defines + * for which clients this record applies. If 'client' is NULL then all + * clients apply. 'mapped' defines which A records are candidated for + * mapping. If 'mapped' is NULL then all A records will be mapped. + * 'excluded' defines which AAAA are to be treated as non-existent for the + * purposed of determining whether to perform synthesis. If 'excluded' is + * NULL then no AAAA records prevent synthesis. + * + * If DNS_DNS64_RECURSIVE_ONLY is set then the record will only match if + * DNS_DNS64_RECURSIVE is set when calling dns_dns64_aaaaok() and + * dns_dns64_aaaafroma(). + * + * If DNS_DNS64_BREAK_DNSSEC is set then the record will still apply if + * DNS_DNS64_DNSSEC is set when calling dns_dns64_aaaaok() and + * dns_dns64_aaaafroma() otherwise the record will be ignored. + * + * Requires: + * 'mctx' to be valid. + * 'prefix' to be valid and the address family to AF_INET6. + * 'prefixlen' to be one of 32, 40, 48, 56, 72 and 96. + * the bits not covered by prefixlen in prefix to + * be zero. + * 'suffix' to be NULL or the address family be set to AF_INET6 + * and the leading 'prefixlen' + 32 bits of the 'suffix' + * to be zero. If 'prefixlen' is 40, 48 or 56 then the + * the leading 'prefixlen' + 40 bits of 'suffix' must be + * zero. + * 'client' to be NULL or a valid acl. + * 'mapped' to be NULL or a valid acl. + * 'excluded' to be NULL or a valid acl. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +void +dns_dns64_destroy(dns_dns64_t **dns64p); +/* + * Destroys a dns64 record. + * + * Requires the record to not be linked. + */ + +isc_result_t +dns_dns64_aaaafroma(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, dns_aclenv_t *env, + unsigned int flags, unsigned char *a, unsigned char *aaaa); +/* + * dns_dns64_aaaafroma() determines whether to perform a DNS64 address + * synthesis from 'a' based on 'dns64', 'reqaddr', 'reqsigner', 'env', + * 'flags' and 'aaaa'. If synthesis is performed then the result is + * written to '*aaaa'. + * + * The synthesised address will be of the form: + * + * + * + * If straddle bits 64-71 of the AAAA record, then 8 zero bits will + * be inserted at bits 64-71. + * + * Requires: + * 'dns64' to be valid. + * 'reqaddr' to be valid. + * 'reqsigner' to be NULL or valid. + * 'env' to be valid. + * 'a' to point to a IPv4 address in network order. + * 'aaaa' to point to a IPv6 address buffer in network order. + * + * Returns: + * ISC_R_SUCCESS if synthesis was performed. + * DNS_R_DISALLOWED if there is no match. + */ + +dns_dns64_t * +dns_dns64_next(dns_dns64_t *dns64); +/* + * Return the next dns64 record in the list. + */ + +void +dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64); +/* + * Append the dns64 record to the list. + */ + +void +dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64); +/* + * Unlink the dns64 record from the list. + */ + +bool +dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, dns_aclenv_t *env, + unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok, + size_t aaaaoklen); +/* + * Determine if there are any non-excluded AAAA records in from the + * matching dns64 records in the list starting at 'dns64'. If there + * is a non-excluded address return true. If all addresses are + * excluded in the matched records return false. If no records + * match then return true. + * + * If aaaaok is defined then dns_dns64_aaaaok() return a array of which + * addresses in 'rdataset' were deemed to not be exclude by any matching + * record. If there are no matching records then all entries are set + * to true. + * + * Requires + * 'rdataset' to be valid and to be for type AAAA and class IN. + * 'aaaaoklen' must match the number of records in 'rdataset' + * if 'aaaaok' in non NULL. + */ + +isc_result_t +dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix, + size_t *len); +/* + * Look through 'rdataset' for AAAA pairs which define encoded DNS64 prefixes. + * 'len' should be set to the number of entries in 'prefix' and returns + * the number of prefixes discovered. This may be bigger than those that + * can fit in 'prefix'. + * + * Requires + * 'rdataset' to be valid and to be for type AAAA and class IN. + * 'prefix' to be non NULL. + * 'len' to be non NULL and non zero. + * + * Returns + * ISC_R_SUCCESS + * ISC_R_NOSPACE if there are more prefixes discovered than can fit + * into 'prefix'. + * ISC_R_NOTFOUND no prefixes where found. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dnsrps.h b/lib/dns/include/dns/dnsrps.h new file mode 100644 index 0000000..15066f0 --- /dev/null +++ b/lib/dns/include/dns/dnsrps.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include + +#include + +#include + +#ifdef USE_DNSRPS + +#include +#include + +/* + * Error message if dlopen(librpz) failed. + */ +extern librpz_emsg_t librpz_lib_open_emsg; + +/* + * These shim BIND9 database, node, and rdataset are handles on RRs from librpz. + * + * All of these structures are used by a single thread and so need no locks. + * + * rpsdb_t holds the state for a set of RPZ queries. + * + * rpsnode_t is a link to the rpsdb_t for the set of RPZ queries + * and a flag saying whether it is pretending to be a node with RRs for + * the qname or the node with the SOA for the zone containing the rewritten + * RRs or justifying NXDOMAIN. + */ +typedef struct { + uint8_t unused; +} rpsnode_t; +typedef struct rpsdb { + dns_db_t common; + int ref_cnt; + librpz_result_id_t hit_id; + librpz_result_t result; + librpz_rsp_t *rsp; + librpz_domain_buf_t origin_buf; + const dns_name_t *qname; + rpsnode_t origin_node; + rpsnode_t data_node; +} rpsdb_t; + +/* + * Convert a dnsrps policy to a classic BIND9 RPZ policy. + */ +dns_rpz_policy_t +dns_dnsrps_2policy(librpz_policy_t rps_policy); + +/* + * Convert a dnsrps trigger to a classic BIND9 RPZ rewrite or trigger type. + */ +dns_rpz_type_t +dns_dnsrps_trig2type(librpz_trig_t trig); + +/* + * Convert a classic BIND9 RPZ rewrite or trigger type to a librpz trigger type. + */ +librpz_trig_t +dns_dnsrps_type2trig(dns_rpz_type_t type); + +/* + * Start dnsrps for the entire server. + */ +isc_result_t +dns_dnsrps_server_create(void); + +/* + * Stop dnsrps for the entire server. + */ +void +dns_dnsrps_server_destroy(void); + +/* + * Ready dnsrps for a view. + */ +isc_result_t +dns_dnsrps_view_init(dns_rpz_zones_t *new, char *rps_cstr); + +/* + * Connect to and start the dnsrps daemon, dnsrpzd. + */ +isc_result_t +dns_dnsrps_connect(dns_rpz_zones_t *rpzs); + +/* + * Get ready to try dnsrps rewriting. + */ +isc_result_t +dns_dnsrps_rewrite_init(librpz_emsg_t *emsg, dns_rpz_st_t *st, + dns_rpz_zones_t *rpzs, const dns_name_t *qname, + isc_mem_t *mctx, bool have_rd); + +#endif /* USE_DNSRPS */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h new file mode 100644 index 0000000..6add7d5 --- /dev/null +++ b/lib/dns/include/dns/dnssec.h @@ -0,0 +1,399 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/dnssec.h */ + +#include + +#include +#include +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +extern isc_stats_t *dns_dnssec_stats; + +/*%< Maximum number of keys supported in a zone. */ +#define DNS_MAXZONEKEYS 32 + +/* + * Indicates how the signer found this key: in the key repository, at the + * zone apex, or specified by the user. + */ +typedef enum { + dns_keysource_unknown, + dns_keysource_repository, + dns_keysource_zoneapex, + dns_keysource_user +} dns_keysource_t; + +/* + * A DNSSEC key and hints about its intended use gleaned from metadata + */ +struct dns_dnsseckey { + dst_key_t *key; + bool hint_publish; /*% metadata says to publish */ + bool force_publish; /*% publish regardless of metadata + * */ + bool hint_sign; /*% metadata says to sign with this + * key */ + bool force_sign; /*% sign with key regardless of + * metadata */ + bool hint_revoke; /*% metadata says revoke key */ + bool hint_remove; /*% metadata says *don't* publish */ + bool is_active; /*% key is already active */ + bool first_sign; /*% key is newly becoming active */ + bool purge; /*% remove key files */ + unsigned int prepublish; /*% how long until active? */ + dns_keysource_t source; /*% how the key was found */ + bool ksk; /*% this is a key-signing key */ + bool zsk; /*% this is a zone-signing key */ + bool legacy; /*% this is old-style key with no + * metadata (possibly generated by + * an older version of BIND9) and + * should be ignored when searching + * for keys to import into the zone */ + unsigned int index; /*% position in list */ + ISC_LINK(dns_dnsseckey_t) link; +}; + +isc_result_t +dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata, + isc_mem_t *mctx, dst_key_t **key); +/*%< + * Creates a DST key from a DNS record. Basically a wrapper around + * dst_key_fromdns(). + * + * Requires: + *\li 'name' is not NULL + *\li 'rdata' is not NULL + *\li 'mctx' is not NULL + *\li 'key' is not NULL + *\li '*key' is NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li DST_R_INVALIDPUBLICKEY + *\li various errors from dns_name_totext + */ + +isc_result_t +dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_stdtime_t *inception, isc_stdtime_t *expire, + isc_mem_t *mctx, isc_buffer_t *buffer, dns_rdata_t *sigrdata); +/*%< + * Generates a RRSIG record covering this rdataset. This has no effect + * on existing RRSIG records. + * + * Requires: + *\li 'name' (the owner name of the record) is a valid name + *\li 'set' is a valid rdataset + *\li 'key' is a valid key + *\li 'inception' is not NULL + *\li 'expire' is not NULL + *\li 'mctx' is not NULL + *\li 'buffer' is not NULL + *\li 'sigrdata' is not NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_NOSPACE + *\li #DNS_R_INVALIDTIME - the expiration is before the inception + *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either + * it is not a zone key or its flags prevent + * authentication) + *\li DST_R_* + */ + +isc_result_t +dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, unsigned int maxbits, isc_mem_t *mctx, + dns_rdata_t *sigrdata, dns_name_t *wild); +/*%< + * Verifies the RRSIG record covering this rdataset signed by a specific + * key. This does not determine if the key's owner is authorized to sign + * this record, as this requires a resolver or database. + * If 'ignoretime' is true, temporal validity will not be checked. + * + * 'maxbits' specifies the maximum number of rsa exponent bits accepted. + * + * Requires: + *\li 'name' (the owner name of the record) is a valid name + *\li 'set' is a valid rdataset + *\li 'key' is a valid key + *\li 'mctx' is not NULL + *\li 'sigrdata' is a valid rdata containing a SIG record + *\li 'wild' if non-NULL then is a valid and has a buffer. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #DNS_R_FROMWILDCARD - the signature is valid and is from + * a wildcard expansion. dns_dnssec_verify2() only. + * 'wild' contains the name of the wildcard if non-NULL. + *\li #DNS_R_SIGINVALID - the signature fails to verify + *\li #DNS_R_SIGEXPIRED - the signature has expired + *\li #DNS_R_SIGFUTURE - the signature's validity period has not begun + *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either + * it is not a zone key or its flags prevent + * authentication) + *\li DST_R_* + */ + +/*@{*/ +isc_result_t +dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, + const dns_name_t *name, const char *directory, + isc_stdtime_t now, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys); + +/*%< + * Finds a set of zone keys. + * XXX temporary - this should be handled in dns_zone_t. + */ +/*@}*/ + +bool +dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now); +/*%< + * + * Returns true if 'key' is active as of the time specified + * in 'now' (i.e., if the activation date has passed, inactivation or + * deletion date has not yet been reached, and the key is not revoked + * -- or if it is a legacy key without metadata). Otherwise returns + * false. + * + * Requires: + *\li 'key' is a valid key + */ + +isc_result_t +dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key); +/*%< + * Signs a message with a SIG(0) record. This is implicitly called by + * dns_message_renderend() if msg->sig0key is not NULL. + * + * Requires: + *\li 'msg' is a valid message + *\li 'key' is a valid key that can be used for signing + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li DST_R_* + */ + +isc_result_t +dns_dnssec_verifymessage(isc_buffer_t *source, dns_message_t *msg, + dst_key_t *key); +/*%< + * Verifies a message signed by a SIG(0) record. This is not + * called implicitly by dns_message_parse(). If dns_message_signer() + * is called before dns_dnssec_verifymessage(), it will return + * #DNS_R_NOTVERIFIEDYET. dns_dnssec_verifymessage() will set + * the verified_sig0 flag in msg if the verify succeeds, and + * the sig0status field otherwise. + * + * Requires: + *\li 'source' is a valid buffer containing the unparsed message + *\li 'msg' is a valid message + *\li 'key' is a valid key + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_NOTFOUND - no SIG(0) was found + *\li #DNS_R_SIGINVALID - the SIG record is not well-formed or + * was not generated by the key. + *\li DST_R_* + */ + +bool +dns_dnssec_selfsigns(dns_rdata_t *rdata, const dns_name_t *name, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + bool ignoretime, isc_mem_t *mctx); + +bool +dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + bool ignoretime, isc_mem_t *mctx); +/*%< + * Verify that 'rdataset' is validly signed in 'sigrdataset' by + * the key in 'rdata'. + * + * dns_dnssec_selfsigns() requires that rdataset be a DNSKEY or KEY + * rrset. dns_dnssec_signs() works on any rrset. + */ + +isc_result_t +dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey, + dns_dnsseckey_t **dkp); +/*%< + * Create and initialize a dns_dnsseckey_t structure. + * + * Requires: + *\li 'dkp' is not NULL and '*dkp' is NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +dns_dnsseckey_destroy(isc_mem_t *mctx, dns_dnsseckey_t **dkp); +/*%< + * Reclaim a dns_dnsseckey_t structure. + * + * Requires: + *\li 'dkp' is not NULL and '*dkp' is not NULL. + * + * Ensures: + *\li '*dkp' is NULL. + */ + +void +dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now); +/*%< + * Get hints on DNSSEC key whether this key can be published + * and/or is active. Timing metadata is compared to 'now'. + * + * Requires: + *\li 'key' is a pointer to a DNSSEC key and is not NULL. + */ + +isc_result_t +dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, + isc_stdtime_t now, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist); +/*%< + * Search 'directory' for K* key files matching the name in 'origin'. + * Append all such keys, along with use hints gleaned from their + * metadata, onto 'keylist'. Skip any unsupported algorithms. + * + * Requires: + *\li 'keylist' is not NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTFOUND + *\li #ISC_R_NOMEMORY + *\li any error returned by dns_name_totext(), isc_dir_open(), or + * dst_key_fromnamedfile() + * + * Ensures: + *\li On error, keylist is unchanged + */ + +isc_result_t +dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, + isc_mem_t *mctx, dns_rdataset_t *keyset, + dns_rdataset_t *keysigs, dns_rdataset_t *soasigs, + bool savekeys, bool publickey, + dns_dnsseckeylist_t *keylist); +/*%< + * Append the contents of a DNSKEY rdataset 'keyset' to 'keylist'. + * Omit duplicates. If 'publickey' is false, search 'directory' for + * matching key files, and load the private keys that go with + * the public ones. If 'savekeys' is true, mark the keys so + * they will not be deleted or inactivated regardless of metadata. + * + * 'keysigs' and 'soasigs', if not NULL and associated, contain the + * RRSIGS for the DNSKEY and SOA records respectively and are used to mark + * whether a key is already active in the zone. + */ + +isc_result_t +dns_dnssec_updatekeys(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *newkeys, + dns_dnsseckeylist_t *removed, const dns_name_t *origin, + dns_ttl_t hint_ttl, dns_diff_t *diff, isc_mem_t *mctx, + void (*report)(const char *, ...) + ISC_FORMAT_PRINTF(1, 2)); +/*%< + * Update the list of keys in 'keys' with new key information in 'newkeys'. + * + * For each key in 'newkeys', see if it has a match in 'keys'. + * - If not, and if the metadata says the key should be published: + * add it to 'keys', and place a dns_difftuple into 'diff' so + * the key can be added to the DNSKEY set. If the metadata says it + * should be active, set the first_sign flag. + * - If so, and if the metadata says it should be removed: + * remove it from 'keys', and place a dns_difftuple into 'diff' so + * the key can be removed from the DNSKEY set. if 'removed' is non-NULL, + * copy the key into that list; otherwise destroy it. + * - Otherwise, make sure keys has current metadata. + * + * 'hint_ttl' is the TTL to use for the DNSKEY RRset if there is no + * existing RRset, and if none of the keys to be added has a default TTL + * (in which case we would use the shortest one). If the TTL is longer + * than the time until a new key will be activated, then we have to delay + * the key's activation. + * + * 'report' points to a function for reporting status. + * + * On completion, any remaining keys in 'newkeys' are freed. + */ + +isc_result_t +dns_dnssec_syncupdate(dns_dnsseckeylist_t *keys, dns_dnsseckeylist_t *rmkeys, + dns_rdataset_t *cds, dns_rdataset_t *cdnskey, + isc_stdtime_t now, dns_ttl_t hint_ttl, dns_diff_t *diff, + isc_mem_t *mctx); +/*%< + * Update the CDS and CDNSKEY RRsets, adding and removing keys as needed. + * + * Returns: + *\li ISC_R_SUCCESS + *\li Other values indicate error + */ + +isc_result_t +dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, + dns_name_t *origin, dns_rdataclass_t zclass, + dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx, + bool expect_cds_delete, bool expect_cdnskey_delete); +/*%< + * Add or remove the CDS DELETE record and the CDNSKEY DELETE record. + * If 'expect_cds_delete' is true, the CDS DELETE record should be present. + * Otherwise, the CDS DELETE record must be removed from the RRsets (if + * present). If 'expect_cdnskey_delete' is true, the CDNSKEY DELETE record + * should be present. Otherwise, the CDNSKEY DELETE record must be removed + * from the RRsets (if present). + * + * Returns: + *\li ISC_R_SUCCESS + *\li Other values indicate error + */ + +isc_result_t +dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata, + dns_rdataset_t *keyset, dns_rdata_t *keyrdata); +/*%< + * Given a DS rdata and a DNSKEY RRset, find the DNSKEY rdata that matches + * the DS, and place it in 'keyrdata'. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + *\li Other values indicate error + */ +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dnstap.h b/lib/dns/include/dns/dnstap.h new file mode 100644 index 0000000..ed2c199 --- /dev/null +++ b/lib/dns/include/dns/dnstap.h @@ -0,0 +1,393 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * The dt (dnstap) module provides fast passive logging of DNS messages. + * Protocol Buffers. The protobuf schema for Dnstap messages is in the + * file dnstap.proto, which is compiled to dnstap.pb-c.c and dnstap.pb-c.h. + */ + +#include +#include + +struct fstrm_iothr_options; + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*% + * Dnstap message types: + * + * STUB QUERY: SQ + * STUB RESPONSE: SR + * CLIENT QUERY: CQ + * CLIENT RESPONSE: CR + * AUTH QUERY: AQ + * AUTH RESPONSE: AR + * RESOLVER QUERY: RQ + * RESOLVER RESPONSE: RR + * FORWARDER QUERY: FQ + * FORWARDER RESPONSE: FR + */ + +#define DNS_DTTYPE_SQ 0x0001 +#define DNS_DTTYPE_SR 0x0002 +#define DNS_DTTYPE_CQ 0x0004 +#define DNS_DTTYPE_CR 0x0008 +#define DNS_DTTYPE_AQ 0x0010 +#define DNS_DTTYPE_AR 0x0020 +#define DNS_DTTYPE_RQ 0x0040 +#define DNS_DTTYPE_RR 0x0080 +#define DNS_DTTYPE_FQ 0x0100 +#define DNS_DTTYPE_FR 0x0200 +#define DNS_DTTYPE_TQ 0x0400 +#define DNS_DTTYPE_TR 0x0800 +#define DNS_DTTYPE_UQ 0x1000 +#define DNS_DTTYPE_UR 0x2000 + +#define DNS_DTTYPE_QUERY \ + (DNS_DTTYPE_SQ | DNS_DTTYPE_CQ | DNS_DTTYPE_AQ | DNS_DTTYPE_RQ | \ + DNS_DTTYPE_FQ | DNS_DTTYPE_TQ | DNS_DTTYPE_UQ) +#define DNS_DTTYPE_RESPONSE \ + (DNS_DTTYPE_SR | DNS_DTTYPE_CR | DNS_DTTYPE_AR | DNS_DTTYPE_RR | \ + DNS_DTTYPE_FR | DNS_DTTYPE_TR | DNS_DTTYPE_UR) +#define DNS_DTTYPE_ALL (DNS_DTTYPE_QUERY | DNS_DTTYPE_RESPONSE) + +typedef enum { + dns_dtmode_none = 0, + dns_dtmode_file, + dns_dtmode_unix +} dns_dtmode_t; + +typedef struct dns_dthandle dns_dthandle_t; + +#ifdef HAVE_DNSTAP +struct dns_dtdata { + isc_mem_t *mctx; + + void *frame; + + bool query; + bool tcp; + dns_dtmsgtype_t type; + + isc_time_t qtime; + isc_time_t rtime; + + isc_region_t qaddr; + isc_region_t raddr; + + uint32_t qport; + uint32_t rport; + + isc_region_t msgdata; + dns_message_t *msg; + + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; +}; +#endif /* HAVE_DNSTAP */ + +isc_result_t +dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path, + struct fstrm_iothr_options **foptp, isc_task_t *reopen_task, + dns_dtenv_t **envp); +/*%< + * Create and initialize the dnstap environment. + * + * There should be a single global dnstap environment for the server; + * copies of it will be attached to each view. + * + * Notes: + * + *\li 'path' refers to a UNIX domain socket by default. It may + * optionally be prepended with "socket:" or "file:". If prepended + * with "file:", then dnstap logs are sent to a file instead of a + * socket. + * + *\li '*foptp' set the options for fstrm_iothr_init(). '*foptp' must have + * have had the number of input queues set and this should be set + * to the number of worker threads. Additionally the queue model + * should also be set. Other options may be set if desired. + * If dns_dt_create succeeds the *foptp is set to NULL. + * + *\li 'reopen_task' needs to be set to the task in the context of which + * dns_dt_reopen() will be called. This is not an optional parameter: + * using dns_dt_create() (which sets 'reopen_task' to NULL) is only + * allowed in unit tests. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'path' is a valid C string. + * + *\li 'foptp' is non NULL. + * + *\li envp != NULL && *envp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +isc_result_t +dns_dt_setupfile(dns_dtenv_t *env, uint64_t max_size, int rolls, + isc_log_rollsuffix_t suffix); +/*%< + * Sets up the dnstap logfile limits. + * + * 'max_size' is the size a log file may grow before it is rolled + * + * 'rolls' is the number of rolled files to retain. + * + * 'suffix' is the logfile suffix setting, increment or timestamp. + * + * Requires: + * + *\li 'env' is a valid dnstap environment. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_INVALIDFILE if dnstap is set to use a UNIX domain socket + */ + +isc_result_t +dns_dt_reopen(dns_dtenv_t *env, int roll); +/*%< + * Reopens files established by dns_dt_create2(). + * + * If 'roll' is non-negative and 'env->mode' is dns_dtmode_file, + * then the file is automatically rolled over before reopening. + * The value of 'roll' indicates the number of backup log files to + * keep. If 'roll' is negative, or if 'env->mode' is dns_dtmode_unix, + * then the channel is simply reopened. + * + * Note: dns_dt_reopen() uses task-exclusive mode and must be run in the + * context of env->reopen_task. + * + * Requires: + *\li 'env' is a valid dnstap environment. + */ + +isc_result_t +dns_dt_setidentity(dns_dtenv_t *env, const char *identity); +isc_result_t +dns_dt_setversion(dns_dtenv_t *env, const char *version); +/*%< + * Set the "identity" and "version" strings to be sent in dnstap messages. + * + * Requires: + * + *\li 'env' is a valid dnstap environment. + */ + +void +dns_dt_attach(dns_dtenv_t *source, dns_dtenv_t **destp); +/*%< + * Attach '*destp' to 'source', incrementing the reference counter. + * + * Requires: + * + *\li 'source' is a valid dnstap environment. + * + *\li 'destp' is not NULL and '*destp' is NULL. + * + *\li *destp is attached to source. + */ + +void +dns_dt_detach(dns_dtenv_t **envp); +/*%< + * Detach '*envp', decrementing the reference counter. + * + * Requires: + * + *\li '*envp' is a valid dnstap environment. + * + * Ensures: + * + *\li '*envp' will be destroyed when the number of references reaches zero. + * + *\li '*envp' is NULL. + */ + +isc_result_t +dns_dt_getstats(dns_dtenv_t *env, isc_stats_t **statsp); +/*%< + * Attach to the stats struct if it exists. + * + * Requires: + * + *\li 'env' is a valid dnstap environment. + * + *\li 'statsp' is non NULL and '*statsp' is NULL. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li ISC_R_NOTFOUND + */ + +void +dns_dt_send(dns_view_t *view, dns_dtmsgtype_t msgtype, isc_sockaddr_t *qaddr, + isc_sockaddr_t *dstaddr, bool tcp, isc_region_t *zone, + isc_time_t *qtime, isc_time_t *rtime, isc_buffer_t *buf); +/*%< + * Sends a dnstap message to the log, if 'msgtype' is one of the message + * types represented in 'view->dttypes'. + * + * Parameters are: 'qaddr' (query address, i.e, the address of the + * query initiator); 'raddr' (response address, i.e., the address of + * the query responder); 'tcp' (boolean indicating whether the transaction + * was over TCP); 'zone' (the authoritative zone or bailiwick, in + * uncompressed wire format), 'qtime' and 'rtime' (query and response + * times; if NULL, they are set to the current time); and 'buf' (the + * DNS message being logged, in wire format). + * + * Requires: + * + *\li 'view' is a valid view, and 'view->dtenv' is NULL or is a + * valid dnstap environment. + */ + +isc_result_t +dns_dt_parse(isc_mem_t *mctx, isc_region_t *src, dns_dtdata_t **destp); +/*%< + * Converts a raw dnstap frame in 'src' to a parsed dnstap data structure + * in '*destp'. + * + * Requires: + *\li 'src' is not NULL + * + *\li 'destp' is not NULL and '*destp' points to a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS on success + * + *\li Other errors are possible. + */ + +isc_result_t +dns_dt_datatotext(dns_dtdata_t *d, isc_buffer_t **dest); +/*%< + * Converts a parsed dnstap data structure 'd' to text, storing + * the result in the buffer 'dest'. If 'dest' points to a dynamically + * allocated buffer, then it may be reallocated as needed. + * + * (XXX: add a 'long_form' option to generate a detailed listing of + * dnstap data instead * of a one-line summary.) + * + * Requires: + *\li 'd' is not NULL + * + *\li 'dest' is not NULL and '*dest' points to a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE if buffer is not dynamic and runs out of space + *\li #ISC_R_NOMEMORY if buffer is dynamic but memory could not be allocated + * + *\li Other errors are possible. + */ + +void +dns_dtdata_free(dns_dtdata_t **dp); +/*%< + * Frees the specified dns_dtdata structure and all its members, + * and sets *dp to NULL. + */ + +isc_result_t +dns_dt_open(const char *filename, dns_dtmode_t mode, isc_mem_t *mctx, + dns_dthandle_t **handlep); +/*%< + * Opens a dnstap framestream at 'filename' and stores a pointer to the + * reader object in a dns_dthandle_t structure. + * + * The caller is responsible for allocating the handle structure. + * + * (XXX: Currently only file readers are supported, not unix-domain socket + * readers.) + * + * Requires: + * + *\li 'filename' is not NULL. + * + *\li 'handlep' is not NULL and '*handlep' is NULL. + * + *\li '*mctx' is not a valid memory context. + * + * Returns: + * + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOTIMPLEMENTED if 'mode' is not dns_dtmode_file. (XXX) + *\li #ISC_R_NOMEMORY if the fstrm library was unable to allocate a + * reader or options structure + *\li #ISC_R_FAILURE if 'filename' could not be opened. + *\li #DNS_R_BADDNSTAP if 'filename' does not contain a dnstap + * framestream. + */ + +isc_result_t +dns_dt_getframe(dns_dthandle_t *handle, uint8_t **bufp, size_t *sizep); +/*%< + * Read a dnstap frame from the framstream reader in 'handle', storing + * a pointer to it in '*bufp' and its size in '*sizep'. + * + * Requires: + * + *\li 'handle' is not NULL + *\li 'bufp' is not NULL + *\li 'sizep' is not NULL + * + * Ensures: + * \li if returning ISC_R_SUCCESS then '*bufp' is not NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOMORE at the end of the frame stream + *\li #ISC_R_FAILURE for any other failure + */ + +void +dns_dt_close(dns_dthandle_t **handlep); +/*%< + * Closes the dnstap file referenced by 'handle'. + * + * Requires: + * + *\li '*handlep' is not NULL + */ diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h new file mode 100644 index 0000000..629729b --- /dev/null +++ b/lib/dns/include/dns/ds.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include +#include + +#define DNS_DSDIGEST_SHA1 (1) +#define DNS_DSDIGEST_SHA256 (2) +#define DNS_DSDIGEST_GOST (3) +#define DNS_DSDIGEST_SHA384 (4) + +/* + * Assuming SHA-384 digest type. + */ +#define DNS_DS_BUFFERSIZE (52) + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, + dns_dsdigest_t digest_type, unsigned char *digest, + dns_rdata_ds_t *dsrdata); +/*%< + * Build a DS rdata structure from a key. + * + * Requires: + *\li key Points to a valid DNSKEY or CDNSKEY record. + *\li buffer Points to a buffer of at least + * #ISC_MAX_MD_SIZE bytes. + */ + +isc_result_t +dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key, + dns_dsdigest_t digest_type, unsigned char *buffer, + dns_rdata_t *rdata); +/*%< + * Similar to dns_ds_fromkeyrdata(), but copies the DS into a + * dns_rdata object. + * + * Requires: + *\li key Points to a valid DNSKEY or CDNSKEY record. + *\li buffer Points to a buffer of at least + * #DNS_DS_BUFFERSIZE bytes. + *\li rdata Points to an initialized dns_rdata_t. + * + * Ensures: + * \li *rdata Contains a valid DS rdata. The 'data' member refers + * to 'buffer'. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dsdigest.h b/lib/dns/include/dns/dsdigest.h new file mode 100644 index 0000000..3b3898b --- /dev/null +++ b/lib/dns/include/dns/dsdigest.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/dsdigest.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_dsdigest_fromtext(dns_dsdigest_t *dsdigestp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DS digest type value. + * The text may contain either a mnemonic digest name or a decimal + * digest number. + * + * Requires: + *\li 'dsdigestp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_RANGE numeric type is out of range + *\li DNS_R_UNKNOWN mnemonic type is unknown + */ + +isc_result_t +dns_dsdigest_totext(dns_dsdigest_t dsdigest, isc_buffer_t *target); +/*%< + * Put a textual representation of the DS digest type 'dsdigest' + * into 'target'. + * + * Requires: + *\li 'dsdigest' is a valid dsdigest. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_NOSPACE target buffer is too small + */ + +#define DNS_DSDIGEST_FORMATSIZE 20 +void +dns_dsdigest_format(dns_dsdigest_t typ, char *cp, unsigned int size); +/*%< + * Wrapper for dns_dsdigest_totext(), writing text into 'cp' + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/dyndb.h b/lib/dns/include/dns/dyndb.h new file mode 100644 index 0000000..3cb32c2 --- /dev/null +++ b/lib/dns/include/dns/dyndb.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +/*! + * \brief + * Context for initializing a dyndb module. + * + * This structure passes global server data to which a dyndb + * module will need access -- the server memory context, hash + * initializer, log context, etc. The structure doesn't persist + * beyond configuring the dyndb module. The module's register function + * should attach to all reference-counted variables and its destroy + * function should detach from them. + */ +struct dns_dyndbctx { + unsigned int magic; + const void *hashinit; + isc_mem_t *mctx; + isc_log_t *lctx; + dns_view_t *view; + dns_zonemgr_t *zmgr; + isc_task_t *task; + isc_timermgr_t *timermgr; + const bool *refvar; /* unused, but retained for API compatibility */ +}; + +#define DNS_DYNDBCTX_MAGIC ISC_MAGIC('D', 'd', 'b', 'c') +#define DNS_DYNDBCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DYNDBCTX_MAGIC) + +/* + * API version + * + * When the API changes, increment DNS_DYNDB_VERSION. If the + * change is backward-compatible (e.g., adding a new function call + * but not changing or removing an old one), increment DNS_DYNDB_AGE; + * if not, set DNS_DYNDB_AGE to 0. + */ +#ifndef DNS_DYNDB_VERSION +#define DNS_DYNDB_VERSION 1 +#define DNS_DYNDB_AGE 0 +#endif /* ifndef DNS_DYNDB_VERSION */ + +typedef isc_result_t +dns_dyndb_register_t(isc_mem_t *mctx, const char *name, const char *parameters, + const char *file, unsigned long line, + const dns_dyndbctx_t *dctx, void **instp); +/*% + * Called when registering a new driver instance. 'name' must be unique. + * 'parameters' contains the driver configuration text. 'dctx' is the + * initialization context set up in dns_dyndb_createctx(). + * + * '*instp' will be set to the driver instance handle if the function + * is successful. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Other errors are possible + */ + +typedef void +dns_dyndb_destroy_t(void **instp); +/*% + * Destroy a driver instance. Dereference any reference-counted + * variables passed in 'dctx' and 'inst' in the register function. + * + * \c *instp must be set to \c NULL by the function before it returns. + */ + +typedef int +dns_dyndb_version_t(unsigned int *flags); +/*% + * Return the API version number a dyndb module was compiled with. + * + * If the returned version number is no greater than than + * DNS_DYNDB_VERSION, and no less than DNS_DYNDB_VERSION - DNS_DYNDB_AGE, + * then the module is API-compatible with named. + * + * 'flags' is currently unused and may be NULL, but could be used in + * the future to pass back driver capabilities or other information. + */ + +isc_result_t +dns_dyndb_load(const char *libname, const char *name, const char *parameters, + const char *file, unsigned long line, isc_mem_t *mctx, + const dns_dyndbctx_t *dctx); +/*% + * Load a dyndb module. + * + * This loads a dyndb module using dlopen() or equivalent, calls its register + * function (see dns_dyndb_register_t above), and if successful, adds + * the instance handle to a list of dyndb instances so it can be cleaned + * up later. + * + * 'file' and 'line' can be used to indicate the name of the file and + * the line number from which the parameters were taken, so that logged + * error messages, if any, will display the correct locations. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Other errors are possible + */ + +void +dns_dyndb_cleanup(bool exiting); +/*% + * Shut down and destroy all running dyndb modules. + * + * 'exiting' indicates whether the server is shutting down, + * as opposed to merely being reconfigured. + */ + +isc_result_t +dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx, + dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task, + isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp); +/*% + * Create a dyndb initialization context structure, with + * pointers to structures in the server that the dyndb module will + * need to access (view, zone manager, memory context, hash initializer, + * etc). This structure is expected to last only until all dyndb + * modules have been loaded and initialized; after that it will be + * destroyed with dns_dyndb_destroyctx(). + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Other errors are possible + */ + +void +dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp); +/*% + * Destroys a dyndb initialization context structure; all + * reference-counted members are detached and the structure is freed. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/ecs.h b/lib/dns/include/dns/ecs.h new file mode 100644 index 0000000..f9d5e88 --- /dev/null +++ b/lib/dns/include/dns/ecs.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +/*% + * Maximum scope values for IPv4 and IPv6. + */ +#ifndef ECS_MAX_V4_SCOPE +#define ECS_MAX_V4_SCOPE 24 +#endif + +#ifndef ECS_MAX_V6_SCOPE +#define ECS_MAX_V6_SCOPE 56 +#endif + +/* + * Any updates to this structure should also be applied in + * contrib/modules/dlz/dlz_minmal.h. + */ +struct dns_ecs { + isc_netaddr_t addr; + uint8_t source; + uint8_t scope; +}; + +/*
/NNN/NNN */ +#define DNS_ECS_FORMATSIZE (ISC_NETADDR_FORMATSIZE + 9) + +ISC_LANG_BEGINDECLS + +void +dns_ecs_init(dns_ecs_t *ecs); +/*%< + * Initialize a DNS ECS structure. + * + * Requires: + * \li 'ecs' is not NULL and points to a valid dns_ecs structure. + */ + +bool +dns_ecs_equals(const dns_ecs_t *ecs1, const dns_ecs_t *ecs2); +/*%< + * Determine whether two ECS address prefixes are equal (except the + * scope prefix-length field). + * + * 'ecs1->source' must exactly match 'ecs2->source'; the address families + * must match; and the first 'ecs1->source' bits of the addresses must + * match. Subsequent address bits and the 'scope' values are ignored. + */ + +void +dns_ecs_format(const dns_ecs_t *ecs, char *buf, size_t size); +/*%< + * Format an ECS record as text. Result is guaranteed to be null-terminated. + * + * Requires: + * \li 'ecs' is not NULL. + * \li 'buf' is not NULL. + * \li 'size' is at least DNS_ECS_FORMATSIZE + */ +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/edns.h b/lib/dns/include/dns/edns.h new file mode 100644 index 0000000..f015ff8 --- /dev/null +++ b/lib/dns/include/dns/edns.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*% + * The maximum version on EDNS supported by this build. + */ +#define DNS_EDNS_VERSION 0 +#ifdef DRAFT_ANDREWS_EDNS1 +#undef DNS_EDNS_VERSION +/* + * Warning: this currently disables sending COOKIE requests in resolver.c + */ +#define DNS_EDNS_VERSION 1 /* draft-andrews-edns1 */ +#endif /* ifdef DRAFT_ANDREWS_EDNS1 */ diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h new file mode 100644 index 0000000..d3d08e0 --- /dev/null +++ b/lib/dns/include/dns/events.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +/*! \file dns/events.h + * \brief + * Registry of DNS event numbers. + */ + +#define DNS_EVENT_FETCHCONTROL (ISC_EVENTCLASS_DNS + 0) +#define DNS_EVENT_FETCHDONE (ISC_EVENTCLASS_DNS + 1) +#define DNS_EVENT_VIEWRESSHUTDOWN (ISC_EVENTCLASS_DNS + 2) +#define DNS_EVENT_VIEWADBSHUTDOWN (ISC_EVENTCLASS_DNS + 3) +#define DNS_EVENT_UPDATE (ISC_EVENTCLASS_DNS + 4) +#define DNS_EVENT_UPDATEDONE (ISC_EVENTCLASS_DNS + 5) +#define DNS_EVENT_DISPATCH (ISC_EVENTCLASS_DNS + 6) +#define DNS_EVENT_TCPMSG (ISC_EVENTCLASS_DNS + 7) +#define DNS_EVENT_ADBMOREADDRESSES (ISC_EVENTCLASS_DNS + 8) +#define DNS_EVENT_ADBNOMOREADDRESSES (ISC_EVENTCLASS_DNS + 9) +#define DNS_EVENT_ADBCANCELED (ISC_EVENTCLASS_DNS + 10) +#define DNS_EVENT_ADBNAMEDELETED (ISC_EVENTCLASS_DNS + 11) +#define DNS_EVENT_ADBSHUTDOWN (ISC_EVENTCLASS_DNS + 12) +#define DNS_EVENT_ADBEXPIRED (ISC_EVENTCLASS_DNS + 13) +#define DNS_EVENT_ADBCONTROL (ISC_EVENTCLASS_DNS + 14) +#define DNS_EVENT_CACHECLEAN (ISC_EVENTCLASS_DNS + 15) +#define DNS_EVENT_BYADDRDONE (ISC_EVENTCLASS_DNS + 16) +#define DNS_EVENT_ZONECONTROL (ISC_EVENTCLASS_DNS + 17) +#define DNS_EVENT_DBDESTROYED (ISC_EVENTCLASS_DNS + 18) +#define DNS_EVENT_VALIDATORDONE (ISC_EVENTCLASS_DNS + 19) +#define DNS_EVENT_REQUESTDONE (ISC_EVENTCLASS_DNS + 20) +#define DNS_EVENT_VALIDATORSTART (ISC_EVENTCLASS_DNS + 21) +#define DNS_EVENT_VIEWREQSHUTDOWN (ISC_EVENTCLASS_DNS + 22) +#define DNS_EVENT_NOTIFYSENDTOADDR (ISC_EVENTCLASS_DNS + 23) +#define DNS_EVENT_ZONE (ISC_EVENTCLASS_DNS + 24) +#define DNS_EVENT_ZONESTARTXFRIN (ISC_EVENTCLASS_DNS + 25) +#define DNS_EVENT_MASTERQUANTUM (ISC_EVENTCLASS_DNS + 26) +#define DNS_EVENT_CACHEOVERMEM (ISC_EVENTCLASS_DNS + 27) +#define DNS_EVENT_MASTERNEXTZONE (ISC_EVENTCLASS_DNS + 28) +#define DNS_EVENT_IOREADY (ISC_EVENTCLASS_DNS + 29) +#define DNS_EVENT_LOOKUPDONE (ISC_EVENTCLASS_DNS + 30) +#define DNS_EVENT_RBTDEADNODES (ISC_EVENTCLASS_DNS + 31) +#define DNS_EVENT_DISPATCHCONTROL (ISC_EVENTCLASS_DNS + 32) +#define DNS_EVENT_REQUESTCONTROL (ISC_EVENTCLASS_DNS + 33) +#define DNS_EVENT_DUMPQUANTUM (ISC_EVENTCLASS_DNS + 34) +/* #define DNS_EVENT_IMPORTRECVDONE (ISC_EVENTCLASS_DNS + 35) */ +#define DNS_EVENT_FREESTORAGE (ISC_EVENTCLASS_DNS + 36) +#define DNS_EVENT_VIEWACACHESHUTDOWN (ISC_EVENTCLASS_DNS + 37) +#define DNS_EVENT_ACACHECONTROL (ISC_EVENTCLASS_DNS + 38) +#define DNS_EVENT_ACACHECLEAN (ISC_EVENTCLASS_DNS + 39) +#define DNS_EVENT_ACACHEOVERMEM (ISC_EVENTCLASS_DNS + 40) +#define DNS_EVENT_RBTPRUNE (ISC_EVENTCLASS_DNS + 41) +#define DNS_EVENT_MANAGEKEYS (ISC_EVENTCLASS_DNS + 42) +#define DNS_EVENT_CLIENTRESDONE (ISC_EVENTCLASS_DNS + 43) +#define DNS_EVENT_CLIENTREQDONE (ISC_EVENTCLASS_DNS + 44) +#define DNS_EVENT_ADBGROWENTRIES (ISC_EVENTCLASS_DNS + 45) +#define DNS_EVENT_ADBGROWNAMES (ISC_EVENTCLASS_DNS + 46) +#define DNS_EVENT_ZONESECURESERIAL (ISC_EVENTCLASS_DNS + 47) +#define DNS_EVENT_ZONESECUREDB (ISC_EVENTCLASS_DNS + 48) +#define DNS_EVENT_ZONELOAD (ISC_EVENTCLASS_DNS + 49) +#define DNS_EVENT_KEYDONE (ISC_EVENTCLASS_DNS + 50) +#define DNS_EVENT_SETNSEC3PARAM (ISC_EVENTCLASS_DNS + 51) +#define DNS_EVENT_SETSERIAL (ISC_EVENTCLASS_DNS + 52) +#define DNS_EVENT_CATZUPDATED (ISC_EVENTCLASS_DNS + 53) +#define DNS_EVENT_CATZADDZONE (ISC_EVENTCLASS_DNS + 54) +#define DNS_EVENT_CATZMODZONE (ISC_EVENTCLASS_DNS + 55) +#define DNS_EVENT_CATZDELZONE (ISC_EVENTCLASS_DNS + 56) +#define DNS_EVENT_RPZUPDATED (ISC_EVENTCLASS_DNS + 57) +#define DNS_EVENT_STARTUPDATE (ISC_EVENTCLASS_DNS + 58) +#define DNS_EVENT_TRYSTALE (ISC_EVENTCLASS_DNS + 59) +#define DNS_EVENT_ZONEFLUSH (ISC_EVENTCLASS_DNS + 60) +#define DNS_EVENT_CHECKDSSENDTOADDR (ISC_EVENTCLASS_DNS + 61) + +#define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0) +#define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535) diff --git a/lib/dns/include/dns/fixedname.h b/lib/dns/include/dns/fixedname.h new file mode 100644 index 0000000..97155e7 --- /dev/null +++ b/lib/dns/include/dns/fixedname.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/fixedname.h + * \brief + * Fixed-size Names + * + * dns_fixedname_t is a convenience type containing a name, an offsets + * table, and a dedicated buffer big enough for the longest possible + * name. This is typically used for stack-allocated names. + * + * MP: + *\li The caller must ensure any required synchronization. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li Per dns_fixedname_t: + *\code + * sizeof(dns_name_t) + sizeof(dns_offsets_t) + + * sizeof(isc_buffer_t) + 255 bytes + structure padding + *\endcode + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +/***** +***** Imports +*****/ + +#include +#include + +#include + +/***** +***** Types +*****/ + +struct dns_fixedname { + dns_name_t name; + dns_offsets_t offsets; + isc_buffer_t buffer; + unsigned char data[DNS_NAME_MAXWIRE]; +}; + +ISC_LANG_BEGINDECLS + +void +dns_fixedname_init(dns_fixedname_t *fixed); + +void +dns_fixedname_invalidate(dns_fixedname_t *fixed); + +dns_name_t * +dns_fixedname_name(dns_fixedname_t *fixed); + +dns_name_t * +dns_fixedname_initname(dns_fixedname_t *fixed); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/forward.h b/lib/dns/include/dns/forward.h new file mode 100644 index 0000000..f43fce7 --- /dev/null +++ b/lib/dns/include/dns/forward.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/forward.h */ + +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +struct dns_forwarder { + isc_sockaddr_t addr; + ISC_LINK(dns_forwarder_t) link; +}; + +typedef ISC_LIST(struct dns_forwarder) dns_forwarderlist_t; + +struct dns_forwarders { + dns_forwarderlist_t fwdrs; + dns_fwdpolicy_t fwdpolicy; +}; + +isc_result_t +dns_fwdtable_create(isc_mem_t *mctx, dns_fwdtable_t **fwdtablep); +/*%< + * Creates a new forwarding table. + * + * Requires: + * \li mctx is a valid memory context. + * \li fwdtablep != NULL && *fwdtablep == NULL + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, const dns_name_t *name, + dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t policy); +isc_result_t +dns_fwdtable_add(dns_fwdtable_t *fwdtable, const dns_name_t *name, + isc_sockaddrlist_t *addrs, dns_fwdpolicy_t policy); +/*%< + * Adds an entry to the forwarding table. The entry associates + * a domain with a list of forwarders and a forwarding policy. The + * addrs/fwdrs list is copied if not empty, so the caller should free + * its copy. + * + * Requires: + * \li fwdtable is a valid forwarding table. + * \li name is a valid name + * \li addrs/fwdrs is a valid list of isc_sockaddr/dns_forwarder + * structures, which may be empty. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_fwdtable_delete(dns_fwdtable_t *fwdtable, const dns_name_t *name); +/*%< + * Removes an entry for 'name' from the forwarding table. If an entry + * that exactly matches 'name' does not exist, ISC_R_NOTFOUND will be returned. + * + * Requires: + * \li fwdtable is a valid forwarding table. + * \li name is a valid name + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + * \li #ISC_R_NOSPACE + */ + +isc_result_t +dns_fwdtable_find(dns_fwdtable_t *fwdtable, const dns_name_t *name, + dns_name_t *foundname, dns_forwarders_t **forwardersp); +/*%< + * Finds a domain in the forwarding table. The closest matching parent + * domain is returned. + * + * Requires: + * \li fwdtable is a valid forwarding table. + * \li name is a valid name + * \li forwardersp != NULL && *forwardersp == NULL + * \li foundname to be NULL or a valid name with buffer. + * + * Returns: + * \li #ISC_R_SUCCESS Success + * \li #DNS_R_PARTIALMATCH Superdomain found with data + * \li #ISC_R_NOTFOUND No match + * \li #ISC_R_NOSPACE Concatenating nodes to form foundname failed + */ + +void +dns_fwdtable_destroy(dns_fwdtable_t **fwdtablep); +/*%< + * Destroys a forwarding table. + * + * Requires: + * \li fwtablep != NULL && *fwtablep != NULL + * + * Ensures: + * \li all memory associated with the forwarding table is freed. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h new file mode 100644 index 0000000..0d44f6a --- /dev/null +++ b/lib/dns/include/dns/geoip.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/geoip.h + * \brief + * GeoIP/GeoIP2 data types and function prototypes. + */ + +#if defined(HAVE_GEOIP2) + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +/*** + *** Types + ***/ + +typedef enum { + dns_geoip_countrycode, + dns_geoip_countrycode3, + dns_geoip_countryname, + dns_geoip_continentcode, + dns_geoip_continent, + dns_geoip_region, + dns_geoip_regionname, + dns_geoip_country_code, + dns_geoip_country_code3, + dns_geoip_country_name, + dns_geoip_country_continentcode, + dns_geoip_country_continent, + dns_geoip_region_countrycode, + dns_geoip_region_code, + dns_geoip_region_name, + dns_geoip_city_countrycode, + dns_geoip_city_countrycode3, + dns_geoip_city_countryname, + dns_geoip_city_region, + dns_geoip_city_regionname, + dns_geoip_city_name, + dns_geoip_city_postalcode, + dns_geoip_city_metrocode, + dns_geoip_city_areacode, + dns_geoip_city_continentcode, + dns_geoip_city_continent, + dns_geoip_city_timezonecode, + dns_geoip_isp_name, + dns_geoip_org_name, + dns_geoip_as_asnum, + dns_geoip_domain_name, + dns_geoip_netspeed_id +} dns_geoip_subtype_t; + +typedef struct dns_geoip_elem { + dns_geoip_subtype_t subtype; + void *db; + union { + char as_string[256]; + int as_int; + }; +} dns_geoip_elem_t; + +struct dns_geoip_databases { + void *country; /* GeoIP2-Country or GeoLite2-Country */ + void *city; /* GeoIP2-CIty or GeoLite2-City */ + void *domain; /* GeoIP2-Domain */ + void *isp; /* GeoIP2-ISP */ + void *as; /* GeoIP2-ASN or GeoLite2-ASN */ +}; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +bool +dns_geoip_match(const isc_netaddr_t *reqaddr, + const dns_geoip_databases_t *geoip, + const dns_geoip_elem_t *elt); + +ISC_LANG_ENDDECLS + +#endif /* HAVE_GEOIP2 */ diff --git a/lib/dns/include/dns/ipkeylist.h b/lib/dns/include/dns/ipkeylist.h new file mode 100644 index 0000000..50f229c --- /dev/null +++ b/lib/dns/include/dns/ipkeylist.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include + +#include + +/*% + * A structure holding a list of addresses and keys. Used to store + * primaries for a secondary zone, created by parsing config options. + */ +struct dns_ipkeylist { + isc_sockaddr_t *addrs; + dns_name_t **keys; + dns_name_t **tlss; + dns_name_t **labels; + uint32_t count; + uint32_t allocated; +}; + +void +dns_ipkeylist_init(dns_ipkeylist_t *ipkl); +/*%< + * Reset ipkl to empty state + * + * Requires: + *\li 'ipkl' to be non NULL. + */ + +void +dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl); +/*%< + * Free `ipkl` contents using `mctx`. + * + * After this call, `ipkl` is a freshly cleared structure with all + * pointers set to `NULL` and count set to 0. + * + * Requires: + *\li 'mctx' to be a valid memory context. + *\li 'ipkl' to be non NULL. + */ + +isc_result_t +dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src, + dns_ipkeylist_t *dst); +/*%< + * Deep copy `src` into empty `dst`, allocating `dst`'s contents. + * + * Requires: + *\li 'mctx' to be a valid memory context. + *\li 'src' to be non NULL + *\li 'dst' to be non NULL and point to an empty \ref dns_ipkeylist_t + * with all pointers set to `NULL` and count set to 0. + * + * Returns: + *\li #ISC_R_SUCCESS -- success + *\li any other value -- failure + */ +isc_result_t +dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n); +/*%< + * Resize ipkl to contain n elements. Size (count) is not changed, and the + * added space is zeroed. + * + * Requires: + * \li 'mctx' to be a valid memory context. + * \li 'ipk' to be non NULL + * \li 'n' >= ipkl->count + * + * Returns: + * \li #ISC_R_SUCCESS if success + * \li #ISC_R_NOMEMORY if there's no memory, ipkeylist is left untouched + */ diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h new file mode 100644 index 0000000..5ad667b --- /dev/null +++ b/lib/dns/include/dns/iptable.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +struct dns_iptable { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + isc_radix_tree_t *radix; + ISC_LINK(dns_iptable_t) nextincache; +}; + +#define DNS_IPTABLE_MAGIC ISC_MAGIC('T', 'a', 'b', 'l') +#define DNS_IPTABLE_VALID(a) ISC_MAGIC_VALID(a, DNS_IPTABLE_MAGIC) + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_iptable_create(isc_mem_t *mctx, dns_iptable_t **target); +/* + * Create a new IP table and the underlying radix structure + */ + +isc_result_t +dns_iptable_addprefix(dns_iptable_t *tab, const isc_netaddr_t *addr, + uint16_t bitlen, bool pos); +/* + * Add an IP prefix to an existing IP table + */ + +isc_result_t +dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, bool pos); +/* + * Merge one IP table into another one. + */ + +void +dns_iptable_attach(dns_iptable_t *source, dns_iptable_t **target); + +void +dns_iptable_detach(dns_iptable_t **tabp); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h new file mode 100644 index 0000000..05f904a --- /dev/null +++ b/lib/dns/include/dns/journal.h @@ -0,0 +1,338 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/journal.h + * \brief + * Database journaling. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +/*** + *** Defines. + ***/ +#define DNS_JOURNALOPT_RESIGN 0x00000001 + +#define DNS_JOURNAL_READ 0x00000000 /* false */ +#define DNS_JOURNAL_CREATE 0x00000001 /* true */ +#define DNS_JOURNAL_WRITE 0x00000002 + +#define DNS_JOURNAL_SIZE_MAX INT32_MAX +#define DNS_JOURNAL_SIZE_MIN 4096 + +/*% Print transaction header data */ +#define DNS_JOURNAL_PRINTXHDR 0x0001 + +/*% Rewrite whole journal file instead of compacting */ +#define DNS_JOURNAL_COMPACTALL 0x0001 +#define DNS_JOURNAL_VERSION1 0x0002 + +/*** + *** Types + ***/ + +/*% + * A dns_journal_t represents an open journal file. This is an opaque type. + * + * A particular dns_journal_t object may be opened for writing, in which case + * it can be used for writing transactions to a journal file, or it can be + * opened for reading, in which case it can be used for reading transactions + * from (iterating over) a journal file. A single dns_journal_t object may + * not be used for both purposes. + */ +typedef struct dns_journal dns_journal_t; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +/**************************************************************************/ + +isc_result_t +dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, + dns_diffop_t op, dns_difftuple_t **tp); +/*!< brief + * Create a diff tuple for the current database SOA. + * XXX this probably belongs somewhere else. + */ + +/*@{*/ +#define DNS_SERIAL_GT(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) > 0) +#define DNS_SERIAL_GE(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) >= 0) +/*!< brief + * Compare SOA serial numbers. DNS_SERIAL_GT(a, b) returns true iff + * a is "greater than" b where "greater than" is as defined in RFC1982. + * DNS_SERIAL_GE(a, b) returns true iff a is "greater than or equal to" b. + */ +/*@}*/ + +/**************************************************************************/ +/* + * Journal object creation and destruction. + */ + +isc_result_t +dns_journal_open(isc_mem_t *mctx, const char *filename, unsigned int mode, + dns_journal_t **journalp); +/*%< + * Open the journal file 'filename' and create a dns_journal_t object for it. + * + * DNS_JOURNAL_CREATE open the journal for reading and writing and create + * the journal if it does not exist. + * DNS_JOURNAL_WRITE open the journal for reading and writing. + * DNS_JOURNAL_READ open the journal for reading only. + */ + +void +dns_journal_destroy(dns_journal_t **journalp); +/*%< + * Destroy a dns_journal_t, closing any open files and freeing its memory. + */ + +/**************************************************************************/ +/* + * Writing transactions to journals. + */ + +isc_result_t +dns_journal_begin_transaction(dns_journal_t *j); +/*%< + * Prepare to write a new transaction to the open journal file 'j'. + * + * Requires: + * \li 'j' is open for writing. + */ + +isc_result_t +dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff); +/*%< + * Write 'diff' to the current transaction of journal file 'j'. + * + * Requires: + * \li 'j' is open for writing and dns_journal_begin_transaction() + * has been called. + * + *\li 'diff' is a full or partial, correctly ordered IXFR + * difference sequence. + */ + +isc_result_t +dns_journal_commit(dns_journal_t *j); +/*%< + * Commit the current transaction of journal file 'j'. + * + * Requires: + * \li 'j' is open for writing and dns_journal_begin_transaction() + * has been called. + * + * \li dns_journal_writediff() has been called one or more times + * to form a complete, correctly ordered IXFR difference + * sequence. + */ + +isc_result_t +dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff); +/*% + * Write a complete transaction at once to a journal file, + * sorting it if necessary, and commit it. Equivalent to calling + * dns_diff_sort(), dns_journal_begin_transaction(), + * dns_journal_writediff(), and dns_journal_commit(). + * + * Requires: + *\li 'j' is open for writing. + * + * \li 'diff' contains exactly one SOA deletion, one SOA addition + * with a greater serial number, and possibly other changes, + * in arbitrary order. + */ + +/**************************************************************************/ +/* + * Reading transactions from journals. + */ + +bool +dns_journal_empty(dns_journal_t *j); +/*< + * Find out if a journal is empty. + */ + +bool +dns_journal_recovered(dns_journal_t *j); +/*< + * Find out if the journal could be opened using old journal format + */ + +uint32_t +dns_journal_first_serial(dns_journal_t *j); +uint32_t +dns_journal_last_serial(dns_journal_t *j); +/*%< + * Get the first and last addressable serial number in the journal. + */ + +isc_result_t +dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial, + uint32_t end_serial, size_t *xfrsizep); +/*%< + * Prepare to iterate over the transactions that will bring the database + * from SOA serial number 'begin_serial' to 'end_serial'. + * + * If 'xfrsizep' is not NULL, then on success it will be set to the + * total size of all records in the iteration (excluding headers). This + * is meant to be a rough approximation of the size of an incremental + * zone transfer, though it does not account for DNS message overhead + * or name compression.) + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_RANGE begin_serial is outside the addressable range. + *\li ISC_R_NOTFOUND begin_serial is within the range of addressable + * serial numbers covered by the journal, but + * this particular serial number does not exist. + */ + +/*@{*/ +isc_result_t +dns_journal_first_rr(dns_journal_t *j); +isc_result_t +dns_journal_next_rr(dns_journal_t *j); +/*%< + * Position the iterator at the first/next RR in a journal + * transaction sequence established using dns_journal_iter_init(). + * + * Requires: + * \li dns_journal_iter_init() has been called. + * + */ +/*@}*/ + +void +dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, uint32_t *ttl, + dns_rdata_t **rdata); +/*%< + * Get the name, ttl, and rdata of the current journal RR. + * + * Requires: + * \li The last call to dns_journal_first_rr() or dns_journal_next_rr() + * returned ISC_R_SUCCESS. + */ + +/**************************************************************************/ +/* + * Database roll-forward. + */ + +isc_result_t +dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options); +/*%< + * Roll forward (play back) the journal file "filename" into the + * database "db". This should be called when the server starts + * after a shutdown or crash. + * + * Requires: + *\li 'journal' is a valid journal + *\li 'db' is a valid database which does not have a version + * open for writing. + * + * Returns: + *\li ISC_R_NOTFOUND when current serial in not in journal. + *\li ISC_R_RANGE when current serial in not in journals range. + *\li DNS_R_UPTODATE when the database was already up to date. + *\li ISC_R_SUCCESS journal has been applied successfully to the + * database without any issues. + * + * others + */ + +isc_result_t +dns_journal_print(isc_mem_t *mctx, uint32_t flags, const char *filename, + FILE *file); +/* For debugging not general use */ + +isc_result_t +dns_db_diff(isc_mem_t *mctx, dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + const char *journal_filename); + +isc_result_t +dns_db_diffx(dns_diff_t *diff, dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + const char *journal_filename); +/*%< + * Compare the databases 'dba' and 'dbb' and generate a diff/journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. Append the journal + * entry to the journal file specified by 'journal_filename' if + * non-NULL. + */ + +isc_result_t +dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial, + uint32_t flags, uint32_t target_size); +/*%< + * Attempt to compact the journal if it is greater that 'target_size'. + * Changes from 'serial' onwards will be preserved. Changes prior than + * that may be dropped in order to get the journal below `target_size`. + * + * If 'flags' includes DNS_JOURNAL_COMPACTALL, the entire journal is copied. + * In this case, `serial` is ignored. This flag is used when upgrading or + * downgrading the format version of the journal. If 'flags' also includes + * DNS_JOURNAL_VERSION1, then the journal is copied out in the original + * format used prior to BIND 9.16.12; otherwise it is copied in the + * current format. + * + * If _COMPACTALL is not in use, and the journal file exists and is + * non-empty, then 'serial' must exist in the journal. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_RANGE serial is outside the range existing in the journal + * + * Other errors may be returned from file operations. + */ + +bool +dns_journal_get_sourceserial(dns_journal_t *j, uint32_t *sourceserial); +void +dns_journal_set_sourceserial(dns_journal_t *j, uint32_t sourceserial); +/*%< + * Get and set source serial. + * + * Returns: + * true if sourceserial has previously been set. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h new file mode 100644 index 0000000..4f560a0 --- /dev/null +++ b/lib/dns/include/dns/kasp.h @@ -0,0 +1,716 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/kasp.h + * \brief + * DNSSEC Key and Signing Policy (KASP) + * + * A "kasp" is a DNSSEC policy, that determines how a zone should be + * signed and maintained. + */ + +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/* Stores a KASP key */ +struct dns_kasp_key { + isc_mem_t *mctx; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp_key) link; + + /* Configuration */ + uint32_t lifetime; + uint8_t algorithm; + int length; + uint8_t role; +}; + +struct dns_kasp_nsec3param { + uint8_t saltlen; + uint8_t algorithm; + uint8_t iterations; + bool optout; +}; + +/* Stores a DNSSEC policy */ +struct dns_kasp { + unsigned int magic; + isc_mem_t *mctx; + char *name; + + /* Internals. */ + isc_mutex_t lock; + bool frozen; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_kasp) link; + + /* Configuration: signatures */ + uint32_t signatures_refresh; + uint32_t signatures_validity; + uint32_t signatures_validity_dnskey; + + /* Configuration: Keys */ + dns_kasp_keylist_t keys; + dns_ttl_t dnskey_ttl; + + /* Configuration: Denial of existence */ + bool nsec3; + dns_kasp_nsec3param_t nsec3param; + + /* Configuration: Timings */ + uint32_t publish_safety; + uint32_t retire_safety; + uint32_t purge_keys; + + /* Zone settings */ + dns_ttl_t zone_max_ttl; + uint32_t zone_propagation_delay; + + /* Parent settings */ + dns_ttl_t parent_ds_ttl; + uint32_t parent_propagation_delay; +}; + +#define DNS_KASP_MAGIC ISC_MAGIC('K', 'A', 'S', 'P') +#define DNS_KASP_VALID(kasp) ISC_MAGIC_VALID(kasp, DNS_KASP_MAGIC) + +/* Defaults */ +#define DNS_KASP_SIG_REFRESH "P5D" +#define DNS_KASP_SIG_VALIDITY "P14D" +#define DNS_KASP_SIG_VALIDITY_DNSKEY "P14D" +#define DNS_KASP_KEY_TTL "3600" +#define DNS_KASP_DS_TTL "86400" +#define DNS_KASP_PUBLISH_SAFETY "3600" +#define DNS_KASP_PURGE_KEYS "P90D" +#define DNS_KASP_RETIRE_SAFETY "3600" +#define DNS_KASP_ZONE_MAXTTL "86400" +#define DNS_KASP_ZONE_PROPDELAY "300" +#define DNS_KASP_PARENT_PROPDELAY "3600" + +/* Key roles */ +#define DNS_KASP_KEY_ROLE_KSK 0x01 +#define DNS_KASP_KEY_ROLE_ZSK 0x02 + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp); +/*%< + * Create a KASP. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li kaspp != NULL && *kaspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen kasp. + * + *\li 'targetp' points to a NULL dns_kasp_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + *\li While *targetp is attached, the kasp will not shut down. + */ + +void +dns_kasp_detach(dns_kasp_t **kaspp); +/*%< + * Detach KASP. + * + * Requires: + * + *\li 'kaspp' points to a valid dns_kasp_t * + * + * Ensures: + * + *\li *kaspp is NULL. + */ + +void +dns_kasp_freeze(dns_kasp_t *kasp); +/*%< + * Freeze kasp. No changes can be made to kasp configuration while frozen. + * + * Requires: + * + *\li 'kasp' is a valid, unfrozen kasp. + * + * Ensures: + * + *\li 'kasp' is frozen. + */ + +void +dns_kasp_thaw(dns_kasp_t *kasp); +/*%< + * Thaw kasp. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Ensures: + * + *\li 'kasp' is no longer frozen. + */ + +const char * +dns_kasp_getname(dns_kasp_t *kasp); +/*%< + * Get kasp name. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + * Returns: + * + *\li name of 'kasp'. + */ + +uint32_t +dns_kasp_signdelay(dns_kasp_t *kasp); +/*%< + * Get the delay that is needed to ensure that all existing RRsets have been + * re-signed with a successor key. This is the signature validity minus the + * signature refresh time (that indicates how far before signature expiry an + * RRSIG should be refreshed). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +uint32_t +dns_kasp_sigrefresh(dns_kasp_t *kasp); +/*%< + * Get signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature refresh interval. + */ + +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature refresh interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_sigvalidity(dns_kasp_t *kasp); +uint32_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp); +/*%< + * Get signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li signature validity. + */ + +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value); +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set signature validity. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp); +/*%< + * Get DNSKEY TTL. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li DNSKEY TTL. + */ + +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DNSKEY TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_purgekeys(dns_kasp_t *kasp); +/*%< + * Get purge keys interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Purge keys interval. + */ + +void +dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set purge keys interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_publishsafety(dns_kasp_t *kasp); +/*%< + * Get publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Publish safety interval. + */ + +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set publish safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_retiresafety(dns_kasp_t *kasp); +/*%< + * Get retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Retire safety interval. + */ + +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set retire safety interval. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp, bool fallback); +/*%< + * Get maximum zone TTL. If 'fallback' is true, return a default maximum TTL + * if the maximum zone TTL is set to unlimited (value 0). Fallback should be + * used if determining key rollover timings in keymgr.c + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Maximum zone TTL. + */ + +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set maximum zone TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp); +/*%< + * Get zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Zone propagation delay. + */ + +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp); +/*%< + * Get DS TTL (should match that of the parent DS record). + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Expected parent DS TTL. + */ + +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl); +/*%< + * Set DS TTL. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +uint32_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp); +/*%< + * Get parent zone propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li Parent zone propagation delay. + */ + +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value); +/*%< + * Set parent propagation delay. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + */ + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp); +/*%< + * Search for a kasp with name 'name' in 'list'. + * If found, '*kaspp' is (strongly) attached to it. + * + * Requires: + * + *\li 'kaspp' points to a NULL dns_kasp_t *. + * + * Returns: + * + *\li #ISC_R_SUCCESS A matching kasp was found. + *\li #ISC_R_NOTFOUND No matching kasp was found. + */ + +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp); +/*%< + * Get the list of kasp keys. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp); +/*%< + * Check if the keylist is empty. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + * Returns: + * + *\li true if the keylist is empty, false otherwise. + */ + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key); +/*%< + * Add a key. + * + * Requires: + * + *\li 'kasp' is a valid, thawed kasp. + *\li 'key' is not NULL. + */ + +isc_result_t +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp); +/*%< + * Create a key inside a KASP. + * + * Requires: + * + *\li 'kasp' is a valid kasp. + * + *\li keyp != NULL && *keyp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_kasp_key_destroy(dns_kasp_key_t *key); +/*%< + * Destroy a KASP key. + * + * Requires: + * + *\li key != NULL + */ + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key); +/*%< + * Get the key algorithm. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Key algorithm. + */ + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key); +/*%< + * Get the key size. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Configured key size, or default key size for key algorithm if no size + * configured. + */ + +uint32_t +dns_kasp_key_lifetime(dns_kasp_key_t *key); +/*%< + * The lifetime of this key (how long may this key be active?) + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Lifetime of key. + * + */ + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key); +/*%< + * Does this key act as a KSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_KSK set. + *\li False, otherwise. + * + */ + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key); +/*%< + * Does this key act as a ZSK? + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li True, if the key role has DNS_KASP_KEY_ROLE_ZSK set. + *\li False, otherwise. + * + */ + +bool +dns_kasp_nsec3(dns_kasp_t *kasp); +/*%< + * Return true if NSEC3 chain should be used. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + * + */ + +uint8_t +dns_kasp_nsec3iter(dns_kasp_t *kasp); +/*%< + * The number of NSEC3 iterations to use. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + *\li 'kasp->nsec3' is true. + * + */ + +uint8_t +dns_kasp_nsec3flags(dns_kasp_t *kasp); +/*%< + * The NSEC3 flags field value. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + *\li 'kasp->nsec3' is true. + * + */ + +uint8_t +dns_kasp_nsec3saltlen(dns_kasp_t *kasp); +/*%< + * The NSEC3 salt length. + * + * Requires: + * + *\li 'kasp' is a valid, frozen kasp. + *\li 'kasp->nsec3' is true. + * + */ + +void +dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3); +/*%< + * Set to use NSEC3 if 'nsec3' is 'true', otherwise policy will use NSEC. + * + * Requires: + * + *\li 'kasp' is a valid, unfrozen kasp. + * + */ + +void +dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout, + uint8_t saltlen); +/*%< + * Set the desired NSEC3 parameters. + * + * Requires: + * + *\li 'kasp' is a valid, unfrozen kasp. + *\li 'kasp->nsec3' is true. + * + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/keydata.h b/lib/dns/include/dns/keydata.h new file mode 100644 index 0000000..fba86d4 --- /dev/null +++ b/lib/dns/include/dns/keydata.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/keydata.h + * \brief + * KEYDATA utilities. + */ + +/*** + *** Imports + ***/ + +#include + +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_keydata_todnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey, + isc_mem_t *mctx); + +isc_result_t +dns_keydata_fromdnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey, + uint32_t refresh, uint32_t addhd, uint32_t removehd, + isc_mem_t *mctx); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/keyflags.h b/lib/dns/include/dns/keyflags.h new file mode 100644 index 0000000..51f6cee --- /dev/null +++ b/lib/dns/include/dns/keyflags.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/keyflags.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_keyflags_fromtext(dns_keyflags_t *flagsp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNSSEC KEY flags value. + * The text may contain either a set of flag mnemonics separated by + * vertical bars or a decimal flags value. For compatibility with + * older versions of BIND and the DNSSEC signer, octal values + * prefixed with a zero and hexadecimal values prefixed with "0x" + * are also accepted. + * + * Requires: + *\li 'flagsp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_RANGE numeric flag value is out of range + *\li DNS_R_UNKNOWN mnemonic flag is unknown + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h new file mode 100644 index 0000000..bf08fbb --- /dev/null +++ b/lib/dns/include/dns/keymgr.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/keymgr.h */ + +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys, + dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime); +/*%< + * Manage keys in 'keyring' and update timing data according to 'kasp' policy. + * Create new keys for 'origin' if necessary in 'directory'. Append all such + * keys, along with use hints gleaned from their metadata, onto 'keyring'. + * + * Update key states and store changes back to disk. Store when to run next + * in 'nexttime'. + * + * Requires: + *\li 'origin' is a valid FQDN. + *\li 'mctx' is a valid memory context. + *\li 'keyring' is not NULL. + *\li 'kasp' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li any error returned by dst_key_generate(), isc_dir_open(), + * dst_key_to_file(), or dns_dnsseckey_create(). + * + * Ensures: + *\li On error, keypool is unchanged + */ + +isc_result_t +dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, isc_stdtime_t when, + bool dspublish); +isc_result_t +dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, + isc_stdtime_t when, bool dspublish, dns_keytag_t id, + unsigned int algorithm); +/*%< + * Check DS for one key in 'keyring'. The key must have the KSK role. + * If 'dspublish' is set to true, set the DS Publish time to 'now'. + * If 'dspublish' is set to false, set the DS Removed time to 'now'. + * If a specific key 'id' is given it must match the keytag. + * If the 'algorithm' is non-zero, it must match the key's algorithm. + * The result is stored in the key state file. + * + * Requires: + *\li 'kasp' is not NULL. + *\li 'keyring' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS (No error). + *\li #DNS_R_NOKEYMATCH (No matching keys found). + *\li #DNS_R_TOOMANYKEYS (More than one matching keys found). + * + */ + +isc_result_t +dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, + isc_stdtime_t when, dns_keytag_t id, + unsigned int algorithm); +/*%< + * Rollover key with given 'id'. If the 'algorithm' is non-zero, it must + * match the key's algorithm. The changes are stored in the key state file. + * + * A rollover means adjusting the key metadata so that keymgr will start the + * actual rollover on the next run. Update the 'inactive' time and adjust + * key lifetime to match the 'when' to rollover time. + * + * The 'when' time may be in the past. In that case keymgr will roll the + * key as soon as possible. + * + * The 'when' time may be in the future. This may extend the lifetime, + * overriding the default lifetime from the policy. + * + * Requires: + *\li 'kasp' is not NULL. + *\li 'keyring' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS (No error). + *\li #DNS_R_NOKEYMATCH (No matching keys found). + *\li #DNS_R_TOOMANYKEYS (More than one matching keys found). + *\li #DNS_R_KEYNOTACTIVE (Key is not active). + * + */ + +void +dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + isc_stdtime_t now, char *out, size_t out_len); +/*%< + * Retrieve the status of given 'kasp' policy and keys in the + * 'keyring' and store the printable output in the 'out' buffer. + * + * Requires: + *\li 'kasp' is not NULL. + *\li 'keyring' is not NULL. + *\li 'out' is not NULL. + * + * Returns: + *\li Printable status in 'out'. + * + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h new file mode 100644 index 0000000..6095a85 --- /dev/null +++ b/lib/dns/include/dns/keytable.h @@ -0,0 +1,351 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * The keytable module provides services for storing and retrieving DNSSEC + * trusted keys, as well as the ability to find the deepest matching key + * for a given domain name. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +typedef void (*dns_keytable_callback_t)(const dns_name_t *name, void *fn_arg); + +isc_result_t +dns_keytable_create(isc_mem_t *mctx, dns_keytable_t **keytablep); +/*%< + * Create a keytable. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li keytablep != NULL && *keytablep == NULL + * + * Ensures: + * + *\li On success, *keytablep is a valid, empty key table. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +void +dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + *\li 'source' is a valid keytable. + * + *\li 'targetp' points to a NULL dns_keytable_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + */ + +void +dns_keytable_detach(dns_keytable_t **keytablep); +/*%< + * Detach *keytablep from its keytable. + * + * Requires: + * + *\li 'keytablep' points to a valid keytable. + * + * Ensures: + * + *\li *keytablep is NULL. + * + *\li If '*keytablep' is the last reference to the keytable, + * all resources used by the keytable will be freed + */ + +isc_result_t +dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial, + dns_name_t *name, dns_rdata_ds_t *ds, + dns_keytable_callback_t callback, void *callback_arg); +/*%< + * Add a key to 'keytable'. The keynode associated with 'name' + * is updated with the DS specified in 'ds'. + * + * The value of keynode->managed is set to 'managed', and the + * value of keynode->initial is set to 'initial'. (Note: 'initial' + * should only be used when adding managed-keys from configuration. + * This indicates the key is in "initializing" state, and has not yet + * been confirmed with a key refresh query. Once a key refresh query + * has validated, we update the keynode with initial == false.) + * + * Notes: + * + *\li If the key already exists in the table, adding it again + * has no effect and ISC_R_SUCCESS is returned. + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + *\li 'ds' is not NULL. + *\li if 'initial' is true then 'managed' must also be true. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_EXISTS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name); +/*%< + * Add a null key to 'keytable' for name 'name'. This marks the + * name as a secure domain, but doesn't supply any key data to allow the + * domain to be validated. (Used when automated trust anchor management + * has gotten broken by a zone misconfiguration; for example, when the + * active key has been revoked but the stand-by key was still in its 30-day + * waiting period for validity.) + * + * Notes: + * + *\li If a key already exists in the table, ISC_R_EXISTS is + * returned and nothing is done. + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + * + *\li keyp != NULL && *keyp is a valid dst_key_t *. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_EXISTS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_keytable_callback_t callback, void *callback_arg); +/*%< + * Delete all trust anchors from 'keytable' matching name 'keyname' + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + * + *\li 'name' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_rdata_dnskey_t *dnskey); +/*%< + * Remove the trust anchor matching the name 'keyname' and the DNSKEY + * rdata struct 'dnskey' from 'keytable'. + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + *\li 'dnskey' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_keynode_t **keynodep); +/*%< + * Search for the first instance of a trust anchor named 'name' in + * 'keytable', without regard to keyid and algorithm. + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'name' is a valid absolute name. + * + *\li keynodep != NULL && *keynodep == NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + * + *\li Any other result indicates an error. + */ + +isc_result_t +dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name, + dns_name_t *foundname); +/*%< + * Search for the deepest match of 'name' in 'keytable'. + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'name' is a valid absolute name. + * + *\li 'foundname' is a name with a dedicated buffer. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + * + *\li Any other result indicates an error. + */ + +void +dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep); +/*%< + * Detach a keynode found via dns_keytable_find(). + * + * Requires: + * + *\li *keynodep is a valid keynode returned by a call to dns_keytable_find(). + * + * Ensures: + * + *\li *keynodep == NULL + */ + +isc_result_t +dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name, + dns_name_t *foundname, bool *wantdnssecp); +/*%< + * Is 'name' at or beneath a trusted key? + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'name' is a valid absolute name. + * + *\li 'foundanme' is NULL or is a pointer to an initialized dns_name_t + * + *\li '*wantsdnssecp' is a valid bool. + * + * Ensures: + * + *\li On success, *wantsdnssecp will be true if and only if 'name' + * is at or beneath a trusted key. If 'foundname' is not NULL, then + * it will be updated to contain the name of the closest enclosing + * trust anchor. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result is an error. + */ + +isc_result_t +dns_keytable_dump(dns_keytable_t *keytable, FILE *fp); +/*%< + * Dump the keytable on fp. + */ + +isc_result_t +dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **buf); +/*%< + * Dump the keytable to buffer at 'buf' + */ + +bool +dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset); +/*%< + * Clone the DS RRset associated with 'keynode' into 'rdataset' if + * it exists. 'dns_rdataset_disassociate(rdataset)' needs to be + * called when done. + * + * Returns: + *\li true if there is a DS RRset. + *\li false if there isn't DS RRset. + * + * Requires: + *\li 'keynode' is valid. + *\li 'rdataset' is valid or NULL. + */ + +bool +dns_keynode_managed(dns_keynode_t *keynode); +/*%< + * Is this flagged as a managed key? + */ + +bool +dns_keynode_initial(dns_keynode_t *keynode); +/*%< + * Is this flagged as an initializing key? + */ + +void +dns_keynode_trust(dns_keynode_t *keynode); +/*%< + * Sets keynode->initial to false in order to mark the key as + * trusted: no longer an initializing key. + */ + +isc_result_t +dns_keytable_forall(dns_keytable_t *keytable, + void (*func)(dns_keytable_t *, dns_keynode_t *, + dns_name_t *, void *), + void *arg); +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/keyvalues.h b/lib/dns/include/dns/keyvalues.h new file mode 100644 index 0000000..2155266 --- /dev/null +++ b/lib/dns/include/dns/keyvalues.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/keyvalues.h */ + +/* + * Flags field of the KEY RR rdata + */ +#define DNS_KEYFLAG_TYPEMASK 0xC000 /*%< Mask for "type" bits */ +#define DNS_KEYTYPE_AUTHCONF 0x0000 /*%< Key usable for both */ +#define DNS_KEYTYPE_CONFONLY 0x8000 /*%< Key usable for confidentiality */ +#define DNS_KEYTYPE_AUTHONLY 0x4000 /*%< Key usable for authentication */ +#define DNS_KEYTYPE_NOKEY 0xC000 /*%< No key usable for either; no key */ +#define DNS_KEYTYPE_NOAUTH DNS_KEYTYPE_CONFONLY +#define DNS_KEYTYPE_NOCONF DNS_KEYTYPE_AUTHONLY + +#define DNS_KEYFLAG_RESERVED2 0x2000 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_EXTENDED 0x1000 /*%< key has extended flags */ +#define DNS_KEYFLAG_RESERVED4 0x0800 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_RESERVED5 0x0400 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_OWNERMASK 0x0300 /*%< these bits determine the type */ +#define DNS_KEYOWNER_USER 0x0000 /*%< key is assoc. with user */ +#define DNS_KEYOWNER_ENTITY 0x0200 /*%< key is assoc. with entity eg host */ +#define DNS_KEYOWNER_ZONE 0x0100 /*%< key is zone key */ +#define DNS_KEYOWNER_RESERVED 0x0300 /*%< reserved meaning */ +#define DNS_KEYFLAG_REVOKE 0x0080 /*%< key revoked (per rfc5011) */ +#define DNS_KEYFLAG_RESERVED9 0x0040 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_RESERVED10 0x0020 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_RESERVED11 0x0010 /*%< reserved - must be zero */ +#define DNS_KEYFLAG_SIGNATORYMASK \ + 0x000F /*%< key can sign RR's of same name \ + */ + +#define DNS_KEYFLAG_RESERVEDMASK \ + (DNS_KEYFLAG_RESERVED2 | DNS_KEYFLAG_RESERVED4 | \ + DNS_KEYFLAG_RESERVED5 | DNS_KEYFLAG_RESERVED9 | \ + DNS_KEYFLAG_RESERVED10 | DNS_KEYFLAG_RESERVED11) +#define DNS_KEYFLAG_KSK 0x0001 /*%< key signing key */ + +#define DNS_KEYFLAG_RESERVEDMASK2 0xFFFF /*%< no bits defined here */ + +/* The Algorithm field of the KEY and SIG RR's is an integer, {1..254} */ +#define DNS_KEYALG_RSAMD5 1 /*%< RSA with MD5 */ +#define DNS_KEYALG_RSA 1 /*%< Used just for tagging */ +#define DNS_KEYALG_DH 2 /*%< Diffie Hellman KEY */ +#define DNS_KEYALG_DSA 3 /*%< DSA KEY */ +#define DNS_KEYALG_NSEC3DSA 6 +#define DNS_KEYALG_DSS DNS_ALG_DSA +#define DNS_KEYALG_ECC 4 +#define DNS_KEYALG_RSASHA1 5 +#define DNS_KEYALG_NSEC3RSASHA1 7 +#define DNS_KEYALG_RSASHA256 8 +#define DNS_KEYALG_RSASHA512 10 +#define DNS_KEYALG_ECCGOST 12 +#define DNS_KEYALG_ECDSA256 13 +#define DNS_KEYALG_ECDSA384 14 +#define DNS_KEYALG_ED25519 15 +#define DNS_KEYALG_ED448 16 +#define DNS_KEYALG_INDIRECT 252 +#define DNS_KEYALG_PRIVATEDNS 253 +#define DNS_KEYALG_PRIVATEOID 254 /*%< Key begins with OID giving alg */ +#define DNS_KEYALG_MAX 255 + +/* Protocol values */ +#define DNS_KEYPROTO_RESERVED 0 +#define DNS_KEYPROTO_TLS 1 +#define DNS_KEYPROTO_EMAIL 2 +#define DNS_KEYPROTO_DNSSEC 3 +#define DNS_KEYPROTO_IPSEC 4 +#define DNS_KEYPROTO_ANY 255 + +/* Signatures */ +#define DNS_SIG_RSAMINBITS 512 /*%< Size of a mod or exp in bits */ +#define DNS_SIG_RSAMAXBITS 2552 +/* Total of binary mod and exp */ +#define DNS_SIG_RSAMAXBYTES ((DNS_SIG_RSAMAXBITS + 7 / 8) * 2 + 3) +/*%< Max length of text sig block */ +#define DNS_SIG_RSAMAXBASE64 (((DNS_SIG_RSAMAXBYTES + 2) / 3) * 4) +#define DNS_SIG_RSAMINSIZE ((DNS_SIG_RSAMINBITS + 7) / 8) +#define DNS_SIG_RSAMAXSIZE ((DNS_SIG_RSAMAXBITS + 7) / 8) + +#define DNS_SIG_ECDSA256SIZE 64 +#define DNS_SIG_ECDSA384SIZE 96 + +#define DNS_KEY_ECDSA256SIZE 64 +#define DNS_KEY_ECDSA384SIZE 96 + +#define DNS_SIG_ED25519SIZE 64 +#define DNS_SIG_ED448SIZE 114 + +#define DNS_KEY_ED25519SIZE 32 +#define DNS_KEY_ED448SIZE 57 diff --git a/lib/dns/include/dns/librpz.h b/lib/dns/include/dns/librpz.h new file mode 100644 index 0000000..8fdbe16 --- /dev/null +++ b/lib/dns/include/dns/librpz.h @@ -0,0 +1,945 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Define the interface from a DNS resolver to the Response Policy Zone + * library, librpz. + * + * This file should be included only the interface functions between the + * resolver and librpz to avoid name space pollution. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * version 1.2.12 + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +/* + * Allow either ordinary or dlopen() linking. + */ +#ifdef LIBRPZ_INTERNAL +#define LIBDEF(t, s) extern t s; +#define LIBDEF_F(f) LIBDEF(librpz_##f##_t, librpz_##f) +#else /* ifdef LIBRPZ_INTERNAL */ +#define LIBDEF(t, s) +#define LIBDEF_F(f) +#endif /* ifdef LIBRPZ_INTERNAL */ + +/* + * Response Policy Zone triggers. + * Comparisons of trigger precedences require + * LIBRPZ_TRIG_CLIENT_IP < LIBRPZ_TRIG_QNAME < LIBRPZ_TRIG_IP + * < LIBRPZ_TRIG_NSDNAME < LIBRPZ_TRIG_NSIP} + */ +typedef enum { + LIBRPZ_TRIG_BAD = 0, + LIBRPZ_TRIG_CLIENT_IP = 1, + LIBRPZ_TRIG_QNAME = 2, + LIBRPZ_TRIG_IP = 3, + LIBRPZ_TRIG_NSDNAME = 4, + LIBRPZ_TRIG_NSIP = 5 +} librpz_trig_t; +#define LIBRPZ_TRIG_SIZE 3 /* sizeof librpz_trig_t in bits */ +typedef uint8_t librpz_tbit_t; /* one bit for each of the TRIGS_NUM + * trigger types */ + +/* + * Response Policy Zone Actions or policies + */ +typedef enum { + LIBRPZ_POLICY_UNDEFINED = 0, /* an empty entry or no decision yet */ + LIBRPZ_POLICY_DELETED = 1, /* placeholder for a deleted policy */ + + LIBRPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */ + LIBRPZ_POLICY_DROP = 3, /* 'drop': do not respond */ + LIBRPZ_POLICY_TCP_ONLY = 4, /* 'tcp-only': answer UDP with TC=1 */ + LIBRPZ_POLICY_NXDOMAIN = 5, /* 'nxdomain': answer with NXDOMAIN */ + LIBRPZ_POLICY_NODATA = 6, /* 'nodata': answer with ANCOUNT=0 */ + LIBRPZ_POLICY_RECORD = 7, /* rewrite with the policy's RR */ + + /* only in client configurations to override the zone */ + LIBRPZ_POLICY_GIVEN, /* 'given': what policy record says */ + LIBRPZ_POLICY_DISABLED, /* at most log */ + LIBRPZ_POLICY_CNAME, /* answer with 'cname x' */ +} librpz_policy_t; +#define LIBRPZ_POLICY_BITS 4 + +/* + * Special policies that appear as targets of CNAMEs + * NXDOMAIN is signaled by a CNAME with a "." target. + * NODATA is signaled by a CNAME with a "*." target. + */ +#define LIBRPZ_RPZ_PREFIX "rpz-" +#define LIBRPZ_RPZ_PASSTHRU LIBRPZ_RPZ_PREFIX "passthru" +#define LIBRPZ_RPZ_DROP LIBRPZ_RPZ_PREFIX "drop" +#define LIBRPZ_RPZ_TCP_ONLY LIBRPZ_RPZ_PREFIX "tcp-only" + +typedef uint16_t librpz_dznum_t; /* dnsrpzd zone # in [0,DZNUM_MAX] */ +typedef uint8_t librpz_cznum_t; /* client zone # in [0,CZNUM_MAX] */ + +/* + * CIDR block + */ +typedef struct librpz_prefix { + union { + struct in_addr in; + struct in6_addr in6; + } addr; + uint8_t family; + uint8_t len; +} librpz_prefix_t; + +/* + * A domain + */ +typedef uint8_t librpz_dsize_t; +typedef struct librpz_domain { + librpz_dsize_t size; /* of only .d */ + uint8_t d[0]; /* variable length wire format */ +} librpz_domain_t; + +/* + * A maximal domain buffer + */ +typedef struct librpz_domain_buf { + librpz_dsize_t size; + uint8_t d[NS_MAXCDNAME]; +} librpz_domain_buf_t; + +/* + * A resource record without the owner name. + * C compilers say that sizeof(librpz_rr_t)=12 instead of 10. + */ +typedef struct { + uint16_t type; /* network byte order */ + uint16_t class; /* network byte order */ + uint32_t ttl; /* network byte order */ + uint16_t rdlength; /* network byte order */ + uint8_t rdata[0]; /* variable length */ +} librpz_rr_t; + +/* + * The database file might be mapped with different starting addresses + * by concurrent clients (resolvers), and so all pointers are offsets. + */ +typedef uint32_t librpz_idx_t; +#define LIBRPZ_IDX_NULL 0 +#define LIBRPZ_IDX_MIN 1 +#define LIBRPZ_IDX_BAD ((librpz_idx_t)-1) +/** + * Partial decoded results of a set of RPZ queries for a single DNS response + * or iteration through the mapped file. + */ +typedef int16_t librpz_result_id_t; +typedef struct librpz_result { + librpz_idx_t next_rr; + librpz_result_id_t hit_id; /* trigger ID from resolver */ + librpz_policy_t zpolicy; /* policy from zone */ + librpz_policy_t policy; /* adjusted by client configuration */ + librpz_dznum_t dznum; /* dnsrpzd zone number */ + librpz_cznum_t cznum; /* librpz client zone number */ + librpz_trig_t trig : LIBRPZ_TRIG_SIZE; + bool log : 1; /* log rewrite given librpz_log_level + * */ +} librpz_result_t; + +/** + * librpz trace or log levels. + */ +typedef enum { + LIBRPZ_LOG_FATAL = 0, /* always print fatal errors */ + LIBRPZ_LOG_ERROR = 1, /* errors have this level */ + LIBRPZ_LOG_TRACE1 = 2, /* big events such as dnsrpzd starts */ + LIBRPZ_LOG_TRACE2 = 3, /* smaller dnsrpzd zone transfers */ + LIBRPZ_LOG_TRACE3 = 4, /* librpz hits */ + LIBRPZ_LOG_TRACE4 = 5, /* librpz lookups */ + LIBRPZ_LOG_INVALID = 999, +} librpz_log_level_t; +typedef librpz_log_level_t(librpz_log_level_val_t)(librpz_log_level_t level); +LIBDEF_F(log_level_val) + +/** + * Logging function that can be supplied by the resolver. + * @param level is one of librpz_log_level_t + * @param ctx is for use by the resolver's logging system. + * NULL mean a context-free message. + */ +typedef void(librpz_log_fnc_t)(librpz_log_level_t level, void *ctx, + const char *buf); + +/** + * Point librpz logging functions to the resolver's choice. + */ +typedef void(librpz_set_log_t)(librpz_log_fnc_t *new_log, const char *prog_nm); +LIBDEF_F(set_log) + +/** + * librpz error messages are put in these buffers. + * Use a structure instead of naked char* to let the compiler check the length. + * A function defined with "foo(char buf[120])" can be called with + * "char sbuf[2]; foo(sbuf)" and suffer a buffer overrun. + */ +typedef struct { + char c[120]; +} librpz_emsg_t; + +#ifdef LIBRPZ_HAVE_ATTR +#define LIBRPZ_UNUSED __attribute__((unused)) +#define LIBRPZ_PF(f, l) __attribute__((format(printf, f, l))) +#define LIBRPZ_NORET __attribute__((__noreturn__)) +#else /* ifdef LIBRPZ_HAVE_ATTR */ +#define LIBRPZ_UNUSED +#define LIBRPZ_PF(f, l) +#define LIBRPZ_NORET +#endif /* ifdef LIBRPZ_HAVE_ATTR */ + +typedef bool(librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg); +LIBDEF_F(parse_log_opt) + +typedef void(librpz_vpemsg_t)(librpz_emsg_t *emsg, const char *p, va_list args); +LIBDEF_F(vpemsg) +typedef void(librpz_pemsg_t)(librpz_emsg_t *emsg, const char *p, ...) + LIBRPZ_PF(2, 3); +LIBDEF_F(pemsg) + +typedef void(librpz_vlog_t)(librpz_log_level_t level, void *ctx, const char *p, + va_list args); +LIBDEF_F(vlog) +typedef void(librpz_log_t)(librpz_log_level_t level, void *ctx, const char *p, + ...) LIBRPZ_PF(3, 4); +LIBDEF_F(log) + +typedef void(librpz_fatal_t)(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3); +extern void +librpz_fatal(int ex_code, const char *p, ...) LIBRPZ_PF(2, 3) LIBRPZ_NORET; + +typedef void(librpz_rpz_assert_t)(const char *file, unsigned line, + const char *p, ...) LIBRPZ_PF(3, 4); +extern void +librpz_rpz_assert(const char *file, unsigned line, const char *p, ...) + LIBRPZ_PF(3, 4) LIBRPZ_NORET; + +typedef void(librpz_rpz_vassert_t)(const char *file, uint line, const char *p, + va_list args); +extern void +librpz_rpz_vassert(const char *file, uint line, const char *p, + va_list args) LIBRPZ_NORET; + +/* + * As far as clients are concerned, all relative pointers or indexes in a + * version of the mapped file except trie node parent pointers remain valid + * forever. A client must release a version so that it can be garbage + * collected by the file system. When dnsrpzd needs to expand the file, + * it copies the old file to a new, larger file. Clients can continue + * using the old file. + * + * Versions can also appear in a single file. Old nodes and trie values + * within the file are not destroyed until all clients using the version + * that contained the old values release the version. + * + * A client is marked as using version by connecting to the daemon. It is + * marked as using all subsequent versions. A client releases all versions + * by closing the connection or a range of versions by updating is slot + * in the shared memory version table. + * + * As far as clients are concerned, there are the following possible librpz + * failures: + * - malloc() or other fatal internal librpz problems indicated by + * a failing return from a librpz function + * All operations will fail until client handle is destroyed and + * recreated with librpz_client_detach() and librpz_client_create(). + * - corrupt database detected by librpz code, corrupt database detected + * by dnsrpzd, or disconnection from the daemon. + * Current operations will fail. + * + * Clients assume that the file has already been unlinked before + * the corrupt flag is set so that they do not race with the server + * over the corruption of a single file. A client that finds the + * corrupt set knows that dnsrpzd has already crashed with + * abort() and is restarting. The client can re-connect to dnsrpzd + * and retransmit its configuration, backing off as usual if anything + * goes wrong. + * + * Searches of the database by a client do not need locks against dnsrpzd or + * other clients, but a lock is used to protect changes to the connection + * by competing threads in the client. The client provides functions + * to serialize the concurrent use of any single client handle. + * Functions that do nothing are appropriate for applications that are + * not "threaded" or that do not share client handles among threads. + * Otherwise, functions must be provided to librpz_clientcreate(). + * Something like the following works with pthreads: + * + * static void + * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); } + * + * static void + * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); } + * + * static void + * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); } + * + * + * + * At every instant, all of the data and pointers in the mapped file are valid. + * Changes to trie node or other data are always made so that it and + * all pointers in and to it remain valid for a time. Old versions are + * eventually discarded. + * + * Dnsrpzd periodically defines a new version by setting aside all changes + * made since the previous version was defined. Subsequent changes + * made (only!) by dnsrpzd will be part of the next version. + * + * To discard an old version, dnsrpzd must know that all clients have stopped + * using that version. Clients do that by using part of the mapped file + * to tell dnsrpzd the oldest version that each client is using. + * Dnsrpzd assigns each connecting client an entry in the cversions array + * in the mapped file. The client puts version numbers into that entry + * to signal to dnsrpzd which versions that can be discarded. + * Dnsrpzd is free, as far as that client is concerned, to discard all + * numerically smaller versions. A client can disclaim all versions with + * the version number VERSIONS_ALL or 0. + * + * The race between a client changing its entry and dnsrpzd discarding a + * version is resolved by allowing dnsrpzd to discard all versions + * smaller or equal to the client's version number. If dnsrpzd is in + * the midst of discarding or about to discard version N when the + * client asserts N, no harm is done. The client depends only on + * the consistency of version N+1. + * + * This version mechanism depends in part on not being exercised too frequently + * Version numbers are 32 bits long and dnsrpzd creates new versions + * at most once every 30 seconds. + */ + +/* + * Lock functions for concurrent use of a single librpz_client_t client handle. + */ +typedef void(librpz_mutex_t)(void *mutex); + +/* + * List of connections to dnsrpzd daemons. + */ +typedef struct librpz_clist librpz_clist_t; + +/* + * Client's handle on dnsrpzd. + */ +typedef struct librpz_client librpz_client_t; + +/** + * Create the list of connections to the dnsrpzd daemon. + * @param[out] emsg: error message + * @param lock: start exclusive access to the client handle + * @param unlock: end exclusive access to the client handle + * @param mutex_destroy: release the lock + * @param mutex: pointer to the lock for the client handle + * @param log_ctx: NULL or resolver's context log messages + */ +typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg, + librpz_mutex_t *lock, + librpz_mutex_t *unlock, + librpz_mutex_t *mutex_destroy, + void *mutex, void *log_ctx); +LIBDEF_F(clist_create) + +/** + * Release the list of dnsrpzd connections. + */ +typedef void(librpz_clist_detach_t)(librpz_clist_t **clistp); +LIBDEF_F(clist_detach) + +/** + * Create a librpz client handle. + * @param[out] emsg: error message + * @param clist: of dnsrpzd connections + * @param cstr: string of configuration settings separated by ';' or '\n' + * @param use_expired: true to not ignore expired zones + * @return client handle or NULL if the handle could not be created + */ +typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg, + librpz_clist_t *clist, + const char *cstr, + bool use_expired); +LIBDEF_F(client_create) + +/** + * Start (if necessary) dnsrpzd and connect to it. + * @param[out] emsg: error message + * @param client handle + * @param optional: true if it is ok if starting the daemon is not allowed + */ +typedef bool(librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client, + bool optional); +LIBDEF_F(connect) + +/** + * Start to destroy a librpz client handle. + * It will not be destroyed until the last set of RPZ queries represented + * by a librpz_rsp_t ends. + * @param client handle to be released + * @return false on error + */ +typedef void(librpz_client_detach_t)(librpz_client_t **clientp); +LIBDEF_F(client_detach) + +/** + * State for a set of RPZ queries for a single DNS response + * or for listing the database. + */ +typedef struct librpz_rsp librpz_rsp_t; + +/** + * Start a set of RPZ queries for a single DNS response. + * @param[out] emsg: error message for false return or *rspp=NULL + * @param[out] rspp created context or NULL + * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value + * @param client state + * @param have_rd: RD=1 in the DNS request + * @param have_do: DO=1 in the DNS request + * @return false on error + */ +typedef bool(librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp, + int *min_ns_dotsp, librpz_client_t *client, + bool have_rd, bool have_do); +LIBDEF_F(rsp_create) + +/** + * Finish RPZ work for a DNS response. + */ +typedef void(librpz_rsp_detach_t)(librpz_rsp_t **rspp); +LIBDEF_F(rsp_detach) + +/** + * Get the final, accumulated result of a set of RPZ queries. + * Yield LIBRPZ_POLICY_UNDEFINED if + * - there were no hits, + * - there was a dispositive hit, be we have not recursed and are required + * to recurse so that evil DNS authorities will not know we are using RPZ + * - we have a hit and have recursed, but later data such as NSIP could + * override + * @param[out] emsg + * @param[out] result describes the hit + * or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit + * @param[out] result: current policy rewrite values + * @param recursed: recursion has now been done even if it was not done + * when the hit was found + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result, + bool recursed, const librpz_rsp_t *rsp); +LIBDEF_F(rsp_result) + +/** + * Might looking for a trigger be worthwhile? + * @param trig: look for this type of trigger + * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, + * or LIBRPZ_TRIG_NSIP and the IP address is IPv6 + * @return: true if looking could be worthwhile + */ +typedef bool(librpz_have_trig_t)(librpz_trig_t trig, bool ipv6, + const librpz_rsp_t *rsp); +LIBDEF_F(have_trig) + +/** + * Might looking for NSDNAME and NSIP triggers be worthwhile? + * @return: true if looking could be worthwhile + */ +typedef bool(librpz_have_ns_trig_t)(const librpz_rsp_t *rsp); +LIBDEF_F(have_ns_trig) + +/** + * Convert the found client IP trie key to a CIDR block + * @param[out] emsg + * @param[out] prefix trigger + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg, + librpz_prefix_t *prefix, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_clientip_prefix) + +/** + * Compute the owner name of the found or result trie key, usually to log it. + * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip. + * example.com. might be a qname trigger. example.com.rpz-nsdname. could + * be an NSDNAME trigger. + * @param[out] emsg + * @param[out] owner domain + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_domain_t)(librpz_emsg_t *emsg, + librpz_domain_buf_t *owner, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_domain) + +/** + * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of + * librpz_rsp_result() or librpz_itr_node() or after a previous use of + * librpz_rsp_rr(). The RR is in uncompressed wire format including type, + * class, ttl and length in network byte order. + * @param[out] emsg + * @param[out] typep: optional host byte order record type or ns_t_invalid (0) + * @param[out] classp: class such as ns_c_in + * @param[out] ttlp: TTL + * @param[out] rrp: optional malloc() buffer containing the next RR or + * NULL after the last RR + * @param[out] result: current policy rewrite values + * @param qname: used construct a wildcard CNAME + * @param qname_size + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep, + uint16_t *classp, uint32_t *ttlp, + librpz_rr_t **rrp, librpz_result_t *result, + const uint8_t *qname, size_t qname_size, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_rr) + +/** + * Get the next RR of the LIBRPZ_POLICY_RECORD result. + * @param[out] emsg + * @param[out] ttlp: TTL + * @param[out] rrp: malloc() buffer with SOA RR without owner name + * @param[out] result: current policy rewrite values + * @param[out] origin: SOA owner name + * @param[out] origin_size + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp, + librpz_rr_t **rrp, librpz_domain_buf_t *origin, + librpz_result_t *result, librpz_rsp_t *rsp); +LIBDEF_F(rsp_soa) + +/** + * Get the SOA serial number for a policy zone to compare with a known value + * to check whether a zone transfer is complete. + */ +typedef bool(librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp, + const char *domain_nm, librpz_rsp_t *rsp); +LIBDEF_F(soa_serial) + +/** + * Save the current policy checking state. + * @param[out] emsg + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(rsp_push) +#define LIBRPZ_RSP_STACK_DEPTH 3 + +/** + * Restore the previous policy checking state. + * @param[out] emsg + * @param[out] result: NULL or restored policy rewrite values + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_pop) + +/** + * Discard the most recently save policy checking state. + * @param[out] emsg + * @param[out] result: NULL or restored policy rewrite values + * @return false on error + */ +typedef bool(librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(rsp_pop_discard) + +/** + * Disable a zone. + * @param[out] emsg + * @param znum + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg, librpz_cznum_t znum, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_forget_zone) + +/** + * Apply RPZ to an IP address. + * @param[out] emsg + * @param addr: address to check + * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4 + * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP + * @param hit_id: caller chosen + * @param recursed: recursion has been done + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_ck_ip_t)(librpz_emsg_t *emsg, const void *addr, uint family, + librpz_trig_t trig, librpz_result_id_t hit_id, + bool recursed, librpz_rsp_t *rsp); +LIBDEF_F(ck_ip) + +/** + * Apply RPZ to a wire-format domain. + * @param[out] emsg + * @param domain in wire format + * @param domain_size + * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME + * @param hit_id: caller chosen + * @param recursed: recursion has been done + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool(librpz_ck_domain_t)(librpz_emsg_t *emsg, const uint8_t *domain, + size_t domain_size, librpz_trig_t trig, + librpz_result_id_t hit_id, bool recursed, + librpz_rsp_t *rsp); +LIBDEF_F(ck_domain) + +/** + * Ask dnsrpzd to refresh a zone. + * @param[out] emsg error message + * @param librpz_domain_t domain to refresh + * @param client context + * @return false after error + */ +typedef bool(librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain, + librpz_rsp_t *rsp); +LIBDEF_F(zone_refresh) + +/** + * Get a string describing the database + * @param license: include the license + * @param cfiles: include the configuration file names + * @param listens: include the local notify IP addresses + * @param[out] emsg error message if the result is null + * @param client context + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg, bool license, bool cfiles, + bool listens, librpz_rsp_t *rsp); +LIBDEF_F(db_info) + +/** + * Start a context for listing the nodes and/or zones in the mapped file + * @param[out] emsg: error message for false return or *rspp=NULL + * @param[out] rspp: created context or NULL + * @param client context + * @return false after error + */ +typedef bool(librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp, + librpz_client_t *client); +LIBDEF_F(itr_start) + +/** + * Get mapped file memory allocation statistics. + * @param[out] emsg: error message + * @param rsp state from librpz_itr_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(mf_stats) + +/** + * Get versions currently used by clients. + * @param[out] emsg: error message + * @param[in,out] rsp: state from librpz_itr_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(vers_stats) + +/** + * Allocate a string describing the next zone or "" after the last zone. + * @param[out] emsg + * @param all_zones to list all instead of only requested zones + * @param[in,out] rsp state from librpz_rsp_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones, + librpz_rsp_t *rsp); +LIBDEF_F(itr_zone) + +/** + * Describe the next trie node while dumping the database. + * @param[out] emsg + * @param[out] result describes node + * or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node. + * @param all_zones to list all instead of only requested zones + * @param[in,out] rsp state from librpz_itr_start() + * @return: false on error + */ +typedef bool(librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result, + bool all_zones, librpz_rsp_t *rsp); +LIBDEF_F(itr_node) + +/** + * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size + */ +typedef const char *(librpz_policy2str_t)(librpz_policy_t policy, char *buf, + size_t buf_size); +#define POLICY2STR_SIZE sizeof("policy xxxxxx") +LIBDEF_F(policy2str) + +/** + * Trigger type to string. + */ +typedef const char *(librpz_trig2str_t)(librpz_trig_t trig); +LIBDEF_F(trig2str) + +/** + * Convert a number of seconds to a zone file duration string + */ +typedef const char *(librpz_secs2str_t)(time_t secs, char *buf, + size_t buf_size); +#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s") +LIBDEF_F(secs2str) + +/** + * Parse a duration with 's', 'm', 'h', 'd', and 'w' units. + */ +typedef bool(librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val, + const char *str0); +LIBDEF_F(str2secs) + +/** + * Translate selected rtypes to strings + */ +typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size); +#define RTYPE2STR_SIZE sizeof("type xxxxx") +LIBDEF_F(rtype2str) + +/** + * Local version of ns_name_ntop() for portability. + */ +typedef int(librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz); +LIBDEF_F(domain_ntop) + +/** + * Local version of ns_name_pton(). + */ +typedef int(librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz, + size_t *dstlen, bool lower); +LIBDEF_F(domain_pton2) + +typedef union socku socku_t; +typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp, + in_port_t port); +LIBDEF_F(mk_inet_su) + +typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su, + const struct in6_addr *addrp, + uint32_t scope_id, in_port_t port); +LIBDEF_F(mk_inet6_su) + +typedef bool(librpz_str2su_t)(socku_t *sup, const char *str); +LIBDEF_F(str2su) + +typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su); +LIBDEF_F(su2str) +#define SU2STR_SIZE (INET6_ADDRSTRLEN + 1 + 6 + 1) + +/** + * default path to dnsrpzd + */ +LIBDEF(const char *, librpz_dnsrpzd_path) + +#undef LIBDEF + +/* + * This is the dlopen() interface to librpz. + */ +typedef const struct { + const char *dnsrpzd_path; + const char *version; + librpz_parse_log_opt_t *parse_log_opt; + librpz_log_level_val_t *log_level_val; + librpz_set_log_t *set_log; + librpz_vpemsg_t *vpemsg; + librpz_pemsg_t *pemsg; + librpz_vlog_t *vlog; + librpz_log_t *log; + librpz_fatal_t *fatal LIBRPZ_NORET; + librpz_rpz_assert_t *rpz_assert LIBRPZ_NORET; + librpz_rpz_vassert_t *rpz_vassert LIBRPZ_NORET; + librpz_clist_create_t *clist_create; + librpz_clist_detach_t *clist_detach; + librpz_client_create_t *client_create; + librpz_connect_t *connect; + librpz_client_detach_t *client_detach; + librpz_rsp_create_t *rsp_create; + librpz_rsp_detach_t *rsp_detach; + librpz_rsp_result_t *rsp_result; + librpz_have_trig_t *have_trig; + librpz_have_ns_trig_t *have_ns_trig; + librpz_rsp_clientip_prefix_t *rsp_clientip_prefix; + librpz_rsp_domain_t *rsp_domain; + librpz_rsp_rr_t *rsp_rr; + librpz_rsp_soa_t *rsp_soa; + librpz_soa_serial_t *soa_serial; + librpz_rsp_push_t *rsp_push; + librpz_rsp_pop_t *rsp_pop; + librpz_rsp_pop_discard_t *rsp_pop_discard; + librpz_rsp_forget_zone_t *rsp_forget_zone; + librpz_ck_ip_t *ck_ip; + librpz_ck_domain_t *ck_domain; + librpz_zone_refresh_t *zone_refresh; + librpz_db_info_t *db_info; + librpz_itr_start_t *itr_start; + librpz_mf_stats_t *mf_stats; + librpz_vers_stats_t *vers_stats; + librpz_itr_zone_t *itr_zone; + librpz_itr_node_t *itr_node; + librpz_policy2str_t *policy2str; + librpz_trig2str_t *trig2str; + librpz_secs2str_t *secs2str; + librpz_str2secs_t *str2secs; + librpz_rtype2str_t *rtype2str; + librpz_domain_ntop_t *domain_ntop; + librpz_domain_pton2_t *domain_pton2; + librpz_mk_inet_su_t *mk_inet_su; + librpz_mk_inet6_su_t *mk_inet6_su; + librpz_str2su_t *str2su; + librpz_su2str_t *su2str; +} librpz_0_t; +extern librpz_0_t librpz_def_0; + +/* + * Future versions can be upward compatible by defining LIBRPZ_DEF as + * librpz_X_t. + */ +#define LIBRPZ_DEF librpz_def_0 +#define LIBRPZ_DEF_STR "librpz_def_0" + +typedef librpz_0_t librpz_t; +extern librpz_t *librpz; + +#if LIBRPZ_LIB_OPEN == 2 +#include + +/** + * link-load librpz + * @param[out] emsg: error message + * @param[in,out] dl_handle: NULL or pointer to new dlopen handle + * @param[in] path: librpz.so path + * @return address of interface structure or NULL on failure + */ +static inline librpz_t * +librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) { + void *handle; + librpz_t *new_librpz; + + emsg->c[0] = '\0'; + + /* + * Close a previously opened handle on librpz.so. + */ + if (dl_handle != NULL && *dl_handle != NULL) { + if (dlclose(*dl_handle) != 0) { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlopen(NULL): %s", dlerror()); + return (NULL); + } + *dl_handle = NULL; + } + + /* + * First try the main executable of the process in case it was + * linked to librpz. + * Do not worry if we cannot search the main executable of the process. + */ + handle = dlopen(NULL, RTLD_NOW | RTLD_LOCAL); + if (handle != NULL) { + new_librpz = dlsym(handle, LIBRPZ_DEF_STR); + if (new_librpz != NULL) { + if (dl_handle != NULL) { + *dl_handle = handle; + } + return (new_librpz); + } + if (dlclose(handle) != 0) { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlsym(NULL, " LIBRPZ_DEF_STR "): %s", + dlerror()); + return (NULL); + } + } + + if (path == NULL || path[0] == '\0') { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "librpz not linked and no dlopen() path provided"); + return (NULL); + } + + handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + snprintf(emsg->c, sizeof(librpz_emsg_t), "dlopen(%s): %s", path, + dlerror()); + return (NULL); + } + new_librpz = dlsym(handle, LIBRPZ_DEF_STR); + if (new_librpz != NULL) { + if (dl_handle != NULL) { + *dl_handle = handle; + } + return (new_librpz); + } + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlsym(%s, " LIBRPZ_DEF_STR "): %s", path, dlerror()); + dlclose(handle); + return (NULL); +} +#elif defined(LIBRPZ_LIB_OPEN) +/* + * Statically link to the librpz.so DSO on systems without dlopen() + */ +static inline librpz_t * +librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) { + (void)(path); + + if (dl_handle != NULL) { + *dl_handle = NULL; + } + +#if LIBRPZ_LIB_OPEN == 1 + emsg->c[0] = '\0'; + return (&LIBRPZ_DEF); +#else /* if LIBRPZ_LIB_OPEN == 1 */ + snprintf(emsg->c, sizeof(librpz_emsg_t), + "librpz not available via ./configure"); + return (NULL); +#endif /* LIBRPZ_LIB_OPEN */ +} +#endif /* LIBRPZ_LIB_OPEN */ diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h new file mode 100644 index 0000000..03b97ac --- /dev/null +++ b/lib/dns/include/dns/log.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file dns/log.h + */ + +#pragma once + +#include +#include + +extern isc_log_t *dns_lctx; +extern isc_logcategory_t dns_categories[]; +extern isc_logmodule_t dns_modules[]; + +#define DNS_LOGCATEGORY_NOTIFY (&dns_categories[0]) +#define DNS_LOGCATEGORY_DATABASE (&dns_categories[1]) +#define DNS_LOGCATEGORY_SECURITY (&dns_categories[2]) +/* DNS_LOGCATEGORY_CONFIG superseded by CFG_LOGCATEGORY_CONFIG */ +#define DNS_LOGCATEGORY_DNSSEC (&dns_categories[4]) +#define DNS_LOGCATEGORY_RESOLVER (&dns_categories[5]) +#define DNS_LOGCATEGORY_XFER_IN (&dns_categories[6]) +#define DNS_LOGCATEGORY_XFER_OUT (&dns_categories[7]) +#define DNS_LOGCATEGORY_DISPATCH (&dns_categories[8]) +#define DNS_LOGCATEGORY_LAME_SERVERS (&dns_categories[9]) +#define DNS_LOGCATEGORY_DELEGATION_ONLY (&dns_categories[10]) +#define DNS_LOGCATEGORY_EDNS_DISABLED (&dns_categories[11]) +#define DNS_LOGCATEGORY_RPZ (&dns_categories[12]) +#define DNS_LOGCATEGORY_RRL (&dns_categories[13]) +#define DNS_LOGCATEGORY_CNAME (&dns_categories[14]) +#define DNS_LOGCATEGORY_SPILL (&dns_categories[15]) +#define DNS_LOGCATEGORY_DNSTAP (&dns_categories[16]) +#define DNS_LOGCATEGORY_ZONELOAD (&dns_categories[17]) +#define DNS_LOGCATEGORY_NSID (&dns_categories[18]) +#define DNS_LOGCATEGORY_RPZ_PASSTHRU (&dns_categories[19]) + +/* Backwards compatibility. */ +#define DNS_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL + +#define DNS_LOGMODULE_DB (&dns_modules[0]) +#define DNS_LOGMODULE_RBTDB (&dns_modules[1]) +#define DNS_LOGMODULE_RBT (&dns_modules[2]) +#define DNS_LOGMODULE_RDATA (&dns_modules[3]) +#define DNS_LOGMODULE_MASTER (&dns_modules[4]) +#define DNS_LOGMODULE_MESSAGE (&dns_modules[5]) +#define DNS_LOGMODULE_CACHE (&dns_modules[6]) +#define DNS_LOGMODULE_CONFIG (&dns_modules[7]) +#define DNS_LOGMODULE_RESOLVER (&dns_modules[8]) +#define DNS_LOGMODULE_ZONE (&dns_modules[9]) +#define DNS_LOGMODULE_JOURNAL (&dns_modules[10]) +#define DNS_LOGMODULE_ADB (&dns_modules[11]) +#define DNS_LOGMODULE_XFER_IN (&dns_modules[12]) +#define DNS_LOGMODULE_XFER_OUT (&dns_modules[13]) +#define DNS_LOGMODULE_ACL (&dns_modules[14]) +#define DNS_LOGMODULE_VALIDATOR (&dns_modules[15]) +#define DNS_LOGMODULE_DISPATCH (&dns_modules[16]) +#define DNS_LOGMODULE_REQUEST (&dns_modules[17]) +#define DNS_LOGMODULE_MASTERDUMP (&dns_modules[18]) +#define DNS_LOGMODULE_TSIG (&dns_modules[19]) +#define DNS_LOGMODULE_TKEY (&dns_modules[20]) +#define DNS_LOGMODULE_SDB (&dns_modules[21]) +#define DNS_LOGMODULE_DIFF (&dns_modules[22]) +#define DNS_LOGMODULE_HINTS (&dns_modules[23]) +#define DNS_LOGMODULE_UNUSED1 (&dns_modules[24]) +#define DNS_LOGMODULE_DLZ (&dns_modules[25]) +#define DNS_LOGMODULE_DNSSEC (&dns_modules[26]) +#define DNS_LOGMODULE_CRYPTO (&dns_modules[27]) +#define DNS_LOGMODULE_PACKETS (&dns_modules[28]) +#define DNS_LOGMODULE_NTA (&dns_modules[29]) +#define DNS_LOGMODULE_DYNDB (&dns_modules[30]) +#define DNS_LOGMODULE_DNSTAP (&dns_modules[31]) +#define DNS_LOGMODULE_SSU (&dns_modules[32]) + +ISC_LANG_BEGINDECLS + +void +dns_log_init(isc_log_t *lctx); +/*% + * Make the libdns categories and modules available for use with the + * ISC logging library. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li dns_log_init() is called only once. + * + * Ensures: + * \li The categories and modules defined above are available for + * use by isc_log_usechannnel() and isc_log_write(). + */ + +void +dns_log_setcontext(isc_log_t *lctx); +/*% + * Make the libdns library use the provided context for logging internal + * messages. + * + * Requires: + *\li lctx is a valid logging context. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/lookup.h b/lib/dns/include/dns/lookup.h new file mode 100644 index 0000000..f89e93d --- /dev/null +++ b/lib/dns/include/dns/lookup.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/lookup.h + * \brief + * The lookup module performs simple DNS lookups. It implements + * the full resolver algorithm, both looking for local data and + * resolving external names as necessary. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li RFCs: 1034, 1035, 2181, TBS + *\li Drafts: TBS + */ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * A 'dns_lookupevent_t' is returned when a lookup completes. + * The sender field will be set to the lookup that completed. If 'result' + * is ISC_R_SUCCESS, then 'names' will contain a list of names associated + * with the address. The recipient of the event must not change the list + * and must not refer to any of the name data after the event is freed. + */ +typedef struct dns_lookupevent { + ISC_EVENT_COMMON(struct dns_lookupevent); + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_db_t *db; + dns_dbnode_t *node; +} dns_lookupevent_t; + +isc_result_t +dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type, + dns_view_t *view, unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_lookup_t **lookupp); +/*%< + * Finds the rrsets matching 'name' and 'type'. + * + * Requires: + * + *\li 'mctx' is a valid mctx. + * + *\li 'name' is a valid name. + * + *\li 'view' is a valid view which has a resolver. + * + *\li 'task' is a valid task. + * + *\li lookupp != NULL && *lookupp == NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + * + *\li Any resolver-related error (e.g. ISC_R_SHUTTINGDOWN) may also be + * returned. + */ + +void +dns_lookup_cancel(dns_lookup_t *lookup); +/*%< + * Cancel 'lookup'. + * + * Notes: + * + *\li If 'lookup' has not completed, post its LOOKUPDONE event with a + * result code of ISC_R_CANCELED. + * + * Requires: + * + *\li 'lookup' is a valid lookup. + */ + +void +dns_lookup_destroy(dns_lookup_t **lookupp); +/*%< + * Destroy 'lookup'. + * + * Requires: + * + *\li '*lookupp' is a valid lookup. + * + *\li The caller has received the LOOKUPDONE event (either because the + * lookup completed or because dns_lookup_cancel() was called). + * + * Ensures: + * + *\li *lookupp == NULL. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/master.h b/lib/dns/include/dns/master.h new file mode 100644 index 0000000..26e67a0 --- /dev/null +++ b/lib/dns/include/dns/master.h @@ -0,0 +1,259 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/master.h */ + +/*** + *** Imports + ***/ + +#include +#include + +#include + +#include + +/* + * Flags to be passed in the 'options' argument in the functions below. + */ +#define DNS_MASTER_AGETTL 0x00000001 /*%< Age the ttl based on $DATE. */ +#define DNS_MASTER_MANYERRORS \ + 0x00000002 /*%< Continue processing on errors. \ + */ +#define DNS_MASTER_NOINCLUDE 0x00000004 /*%< Disallow $INCLUDE directives. */ +#define DNS_MASTER_ZONE 0x00000008 /*%< Loading a zone master file. */ +#define DNS_MASTER_HINT 0x00000010 /*%< Loading a hint master file. */ +#define DNS_MASTER_SECONDARY 0x00000020 /*%< Secondary master file. */ +#define DNS_MASTER_CHECKNS \ + 0x00000040 /*%< \ + * Check NS records to see \ + * if they are an address \ + */ +#define DNS_MASTER_FATALNS \ + 0x00000080 /*%< \ + * Treat DNS_MASTER_CHECKNS \ + * matches as fatal \ + */ +#define DNS_MASTER_CHECKNAMES 0x00000100 +#define DNS_MASTER_CHECKNAMESFAIL 0x00000200 +#define DNS_MASTER_CHECKWILDCARD \ + 0x00000400 /* Check for internal wildcards. \ + */ +#define DNS_MASTER_CHECKMX 0x00000800 +#define DNS_MASTER_CHECKMXFAIL 0x00001000 + +#define DNS_MASTER_RESIGN 0x00002000 +#define DNS_MASTER_KEY 0x00004000 /*%< Loading a key zone master file. */ +#define DNS_MASTER_NOTTL 0x00008000 /*%< Don't require ttl. */ +#define DNS_MASTER_CHECKTTL 0x00010000 /*%< Check max-zone-ttl */ + +ISC_LANG_BEGINDECLS + +/* + * Structures that implement the "raw" format for master dump. + * These are provided for a reference purpose only; in the actual + * encoding, we directly read/write each field so that the encoded data + * is always "packed", regardless of the hardware architecture. + */ +#define DNS_RAWFORMAT_VERSION 1 + +/* + * Flags to indicate the status of the data in the raw file header + */ +#define DNS_MASTERRAW_COMPAT 0x01 +#define DNS_MASTERRAW_SOURCESERIALSET 0x02 +#define DNS_MASTERRAW_LASTXFRINSET 0x04 + +/* Common header */ +struct dns_masterrawheader { + uint32_t format; /* must be + * dns_masterformat_raw */ + uint32_t version; /* compatibility for future + * extensions */ + uint32_t dumptime; /* timestamp on creation + * (currently unused) */ + uint32_t flags; /* Flags */ + uint32_t sourceserial; /* Source serial number (used + * by inline-signing zones) */ + uint32_t lastxfrin; /* timestamp of last transfer + * (used by secondary zones) */ +}; + +/* The structure for each RRset */ +typedef struct { + uint32_t totallen; /* length of the data for this + * RRset, including the + * "header" part */ + dns_rdataclass_t rdclass; /* 16-bit class */ + dns_rdatatype_t type; /* 16-bit type */ + dns_rdatatype_t covers; /* same as type */ + dns_ttl_t ttl; /* 32-bit TTL */ + uint32_t nrdata; /* number of RRs in this set */ + /* followed by encoded owner name, and then rdata */ +} dns_masterrawrdataset_t; + +/* + * Method prototype: a callback to register each include file as + * it is encountered. + */ +typedef void (*dns_masterincludecb_t)(const char *file, void *arg); + +/*** + *** Function + ***/ + +isc_result_t +dns_master_loadfile(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, uint32_t resign, + dns_rdatacallbacks_t *callbacks, + dns_masterincludecb_t include_cb, void *include_arg, + isc_mem_t *mctx, dns_masterformat_t format, + dns_ttl_t maxttl); + +isc_result_t +dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx); + +isc_result_t +dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx); + +isc_result_t +dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx); + +isc_result_t +dns_master_loadfileinc(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, uint32_t resign, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **ctxp, dns_masterincludecb_t include_cb, + void *include_arg, isc_mem_t *mctx, + dns_masterformat_t format, uint32_t maxttl); + +isc_result_t +dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **ctxp, isc_mem_t *mctx); + +isc_result_t +dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, dns_rdatacallbacks_t *callbacks, + isc_task_t *task, dns_loaddonefunc_t done, + void *done_arg, dns_loadctx_t **ctxp, isc_mem_t *mctx); + +isc_result_t +dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **ctxp, isc_mem_t *mctx); + +/*%< + * Loads a RFC1305 master file from a file, stream, buffer, or existing + * lexer into rdatasets and then calls 'callbacks->commit' to commit the + * rdatasets. Rdata memory belongs to dns_master_load and will be + * reused / released when the callback completes. dns_load_master will + * abort if callbacks->commit returns any value other than ISC_R_SUCCESS. + * + * If 'DNS_MASTER_AGETTL' is set and the master file contains one or more + * $DATE directives, the TTLs of the data will be aged accordingly. + * + * 'callbacks->commit' is assumed to call 'callbacks->error' or + * 'callbacks->warn' to generate any error messages required. + * + * 'done' is called with 'done_arg' and a result code when the loading + * is completed or has failed. If the initial setup fails 'done' is + * not called. + * + * 'resign' the number of seconds before a RRSIG expires that it should + * be re-signed. 0 is used if not provided. + * + * Requires: + *\li 'master_file' points to a valid string. + *\li 'lexer' points to a valid lexer. + *\li 'top' points to a valid name. + *\li 'origin' points to a valid name. + *\li 'callbacks->commit' points to a valid function. + *\li 'callbacks->error' points to a valid function. + *\li 'callbacks->warn' points to a valid function. + *\li 'mctx' points to a valid memory context. + *\li 'task' and 'done' to be valid. + *\li 'lmgr' to be valid. + *\li 'ctxp != NULL && ctxp == NULL'. + * + * Returns: + *\li ISC_R_SUCCESS upon successfully loading the master file. + *\li ISC_R_SEENINCLUDE upon successfully loading the master file with + * a $INCLUDE statement. + *\li ISC_R_NOMEMORY out of memory. + *\li ISC_R_UNEXPECTEDEND expected to be able to read a input token and + * there was not one. + *\li ISC_R_UNEXPECTED + *\li DNS_R_NOOWNER failed to specify a ownername. + *\li DNS_R_NOTTL failed to specify a ttl. + *\li DNS_R_BADCLASS record class did not match zone class. + *\li DNS_R_CONTINUE load still in progress (dns_master_load*inc() only). + *\li Any dns_rdata_fromtext() error code. + *\li Any error code from callbacks->commit(). + */ + +void +dns_loadctx_detach(dns_loadctx_t **ctxp); +/*%< + * Detach from the load context. + * + * Requires: + *\li '*ctxp' to be valid. + * + * Ensures: + *\li '*ctxp == NULL' + */ + +void +dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target); +/*%< + * Attach to the load context. + * + * Requires: + *\li 'source' to be valid. + *\li 'target != NULL && *target == NULL'. + */ + +void +dns_loadctx_cancel(dns_loadctx_t *ctx); +/*%< + * Cancel loading the zone file associated with this load context. + * + * Requires: + *\li 'ctx' to be valid + */ + +void +dns_master_initrawheader(dns_masterrawheader_t *header); +/*%< + * Initializes the header for a raw master file, setting all + * values to zero. + */ +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h new file mode 100644 index 0000000..056780b --- /dev/null +++ b/lib/dns/include/dns/masterdump.h @@ -0,0 +1,359 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/masterdump.h */ + +/*** + *** Imports + ***/ + +#include + +#include + +#include + +/*** + *** Types + ***/ + +typedef struct dns_master_style dns_master_style_t; + +/*** + *** Definitions + ***/ + +/* + * Flags affecting master file formatting. Flags 0x0000FFFF + * define the formatting of the rdata part and are defined in + * rdata.h. + */ + +/*% Omit the owner name when possible. */ +#define DNS_STYLEFLAG_OMIT_OWNER 0x000010000ULL + +/*% + * Omit the TTL when possible. If DNS_STYLEFLAG_TTL is + * also set, this means no TTLs are ever printed + * because $TTL directives are generated before every + * change in the TTL. In this case, no columns need to + * be reserved for the TTL. Master files generated with + * these options will be rejected by BIND 4.x because it + * does not recognize the $TTL directive. + * + * If DNS_STYLEFLAG_TTL is not also set, the TTL will be + * omitted when it is equal to the previous TTL. + * This is correct according to RFC1035, but the + * TTLs may be silently misinterpreted by older + * versions of BIND which use the SOA MINTTL as a + * default TTL value. + */ +#define DNS_STYLEFLAG_OMIT_TTL 0x000020000ULL + +/*% Omit the class when possible. */ +#define DNS_STYLEFLAG_OMIT_CLASS 0x000040000ULL + +/*% Output $TTL directives. */ +#define DNS_STYLEFLAG_TTL 0x000080000ULL + +/*% + * Output $ORIGIN directives and print owner names relative to + * the origin when possible. + */ +#define DNS_STYLEFLAG_REL_OWNER 0x000100000ULL + +/*% Print domain names in RR data in relative form when possible. + * For this to take effect, DNS_STYLEFLAG_REL_OWNER must also be set. */ +#define DNS_STYLEFLAG_REL_DATA 0x000200000ULL + +/*% Print the trust level of each rdataset. */ +#define DNS_STYLEFLAG_TRUST 0x000400000ULL + +/*% Print negative caching entries. */ +#define DNS_STYLEFLAG_NCACHE 0x000800000ULL + +/*% Never print the TTL. */ +#define DNS_STYLEFLAG_NO_TTL 0x001000000ULL + +/*% Never print the CLASS. */ +#define DNS_STYLEFLAG_NO_CLASS 0x002000000ULL + +/*% Report re-signing time. */ +#define DNS_STYLEFLAG_RESIGN 0x004000000ULL + +/*% Don't printout the cryptographic parts of DNSSEC records. */ +#define DNS_STYLEFLAG_NOCRYPTO 0x008000000ULL + +/*% Comment out data by prepending with ";" */ +#define DNS_STYLEFLAG_COMMENTDATA 0x010000000ULL + +/*% Print TTL with human-readable units. */ +#define DNS_STYLEFLAG_TTL_UNITS 0x020000000ULL + +/*% Indent output. */ +#define DNS_STYLEFLAG_INDENT 0x040000000ULL + +/*% Output in YAML style. */ +#define DNS_STYLEFLAG_YAML 0x080000000ULL + +/*% Print ECS cache entries as comments (reserved for future use). */ +#define DNS_STYLEFLAG_ECSCACHE 0x100000000ULL + +/*% Print expired cache entries. */ +#define DNS_STYLEFLAG_EXPIRED 0x200000000ULL + +ISC_LANG_BEGINDECLS + +/*** + *** Constants + ***/ + +/*% + * The default master file style. + * + * This uses $TTL directives to avoid the need to dedicate a + * tab stop for the TTL. The class is only printed for the first + * rrset in the file and shares a tab stop with the RR type. + */ +extern const dns_master_style_t dns_master_style_default; + +/*% + * A master file style that dumps zones to a very generic format easily + * imported/checked with external tools. + */ +extern const dns_master_style_t dns_master_style_full; + +/*% + * A master file style that prints explicit TTL values on each + * record line, never using $TTL statements. The TTL has a tab + * stop of its own, but the class and type share one. + */ +extern const dns_master_style_t dns_master_style_explicitttl; + +/*% + * A master style format designed for cache files. It prints explicit TTL + * values on each record line and never uses $ORIGIN or relative names. + */ +extern const dns_master_style_t dns_master_style_cache; + +/*% + * A master style format designed for cache files. The same as above but + * this also prints expired entries. + */ +extern const dns_master_style_t dns_master_style_cache_with_expired; + +/*% + * A master style that prints name, ttl, class, type, and value on + * every line. Similar to explicitttl above, but more verbose. + * Intended for generating master files which can be easily parsed + * by perl scripts and similar applications. + */ +extern const dns_master_style_t dns_master_style_simple; + +/*% + * The style used for debugging, "dig" output, etc. + */ +extern const dns_master_style_t dns_master_style_debug; + +/*% + * Similar to dns_master_style_debug but data is prepended with ";" + */ +extern const dns_master_style_t dns_master_style_comment; + +/*% + * Similar to dns_master_style_debug but data is indented with "\t" (tab) + */ +extern const dns_master_style_t dns_master_style_indent; + +/*% + * The style used for dumping "key" zones. + */ +extern const dns_master_style_t dns_master_style_keyzone; + +/*% + * YAML-compatible output + */ +extern const dns_master_style_t dns_master_style_yaml; + +/*** + *** Functions + ***/ + +void +dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target); +/*%< + * Attach to a dump context. + * + * Require: + *\li 'source' to be valid. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_dumpctx_detach(dns_dumpctx_t **dctxp); +/*%< + * Detach from a dump context. + * + * Require: + *\li 'dctxp' to point to a valid dump context. + * + * Ensures: + *\li '*dctxp' is NULL. + */ + +void +dns_dumpctx_cancel(dns_dumpctx_t *dctx); +/*%< + * Cancel a in progress dump. + * + * Require: + *\li 'dctx' to be valid. + */ + +dns_dbversion_t * +dns_dumpctx_version(dns_dumpctx_t *dctx); +/*%< + * Return the version handle (if any) of the database being dumped. + * + * Require: + *\li 'dctx' to be valid. + */ + +dns_db_t * +dns_dumpctx_db(dns_dumpctx_t *dctx); +/*%< + * Return the database being dumped. + * + * Require: + *\li 'dctx' to be valid. + */ + +/*@{*/ +isc_result_t +dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, + const dns_master_style_t *style, FILE *f, + isc_task_t *task, dns_dumpdonefunc_t done, + void *done_arg, dns_dumpctx_t **dctxp); + +isc_result_t +dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, + dns_masterformat_t format, + dns_masterrawheader_t *header, FILE *f); +/*%< + * Dump the database 'db' to the steam 'f' in the specified format by + * 'format'. If the format is dns_masterformat_text (the RFC1035 format), + * 'style' specifies the file style (e.g., &dns_master_style_default). + * + * If 'format' is dns_masterformat_raw, then 'header' can contain + * information to be written to the file header. + * + * Temporary dynamic memory may be allocated from 'mctx'. + * + * Require: + *\li 'task' to be valid. + *\li 'done' to be non NULL. + *\li 'dctxp' to be non NULL && '*dctxp' to be NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + *\li Any database or rrset iterator error. + *\li Any dns_rdata_totext() error code. + */ +/*@}*/ + +/*@{*/ + +isc_result_t +dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, const char *filename, + isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg, + dns_dumpctx_t **dctxp, dns_masterformat_t format, + dns_masterrawheader_t *header); + +isc_result_t +dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, const char *filename, + dns_masterformat_t format, dns_masterrawheader_t *header); + +/*%< + * Dump the database 'db' to the file 'filename' in the specified format by + * 'format'. If the format is dns_masterformat_text (the RFC1035 format), + * 'style' specifies the file style (e.g., &dns_master_style_default). + * + * If 'format' is dns_masterformat_raw, then 'header' can contain + * information to be written to the file header. + * + * Temporary dynamic memory may be allocated from 'mctx'. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + *\li Any database or rrset iterator error. + *\li Any dns_rdata_totext() error code. + */ +/*@}*/ + +isc_result_t +dns_master_rdatasettotext(const dns_name_t *owner_name, + dns_rdataset_t *rdataset, + const dns_master_style_t *style, dns_indent_t *indent, + isc_buffer_t *target); +/*%< + * Convert 'rdataset' to text format, storing the result in 'target'. + * + * Notes: + *\li The rdata cursor position will be changed. + * + * Requires: + *\li 'rdataset' is a valid non-question rdataset. + * + *\li 'rdataset' is not empty. + */ + +isc_result_t +dns_master_questiontotext(const dns_name_t *owner_name, + dns_rdataset_t *rdataset, + const dns_master_style_t *style, + isc_buffer_t *target); + +isc_result_t +dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *name, + const dns_master_style_t *style, FILE *f); + +isc_result_t +dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, const dns_name_t *name, + const dns_master_style_t *style, const char *filename); + +dns_masterstyle_flags_t +dns_master_styleflags(const dns_master_style_t *style); + +isc_result_t +dns_master_stylecreate(dns_master_style_t **style, + dns_masterstyle_flags_t flags, unsigned int ttl_column, + unsigned int class_column, unsigned int type_column, + unsigned int rdata_column, unsigned int line_length, + unsigned int tab_width, unsigned int split_width, + isc_mem_t *mctx); + +void +dns_master_styledestroy(dns_master_style_t **style, isc_mem_t *mctx); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h new file mode 100644 index 0000000..b5d9a5a --- /dev/null +++ b/lib/dns/include/dns/message.h @@ -0,0 +1,1567 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +/*! \file dns/message.h + * \brief Message Handling Module + * + * How this beast works: + * + * When a dns message is received in a buffer, dns_message_parse() is called + * on the memory region. Various items are checked including the format + * of the message (if counts are right, if counts consume the entire sections, + * and if sections consume the entire message) and known pseudo-RRs in the + * additional data section are analyzed and removed. + * + * TSIG checking is also done at this layer, and any DNSSEC transaction + * signatures should also be checked here. + * + * Notes on using the gettemp*() and puttemp*() functions: + * + * These functions return items (names, rdatasets, etc) allocated from some + * internal state of the dns_message_t. + * + * Names and rdatasets must be put back into the dns_message_t in + * one of two ways. Assume a name was allocated via + * dns_message_gettempname(): + * + *\li (1) insert it into a section, using dns_message_addname(). + * + *\li (2) return it to the message using dns_message_puttempname(). + * + * The same applies to rdatasets. + * + * On the other hand, offsets, rdatalists and rdatas allocated using + * dns_message_gettemp*() will always be freed automatically + * when the message is reset or destroyed; calling dns_message_puttemp*() + * on rdatalists and rdatas is optional and serves only to enable the item + * to be reused multiple times during the lifetime of the message; offsets + * cannot be reused. + * + * Buffers allocated using isc_buffer_allocate() can be automatically freed + * as well by giving the buffer to the message using dns_message_takebuffer(). + * Doing this will cause the buffer to be freed using isc_buffer_free() + * when the section lists are cleared, such as in a reset or in a destroy. + * Since the buffer itself exists until the message is destroyed, this sort + * of code can be written: + * + * \code + * buffer = isc_buffer_allocate(mctx, 512); + * name = NULL; + * result = dns_message_gettempname(message, &name); + * result = dns_name_fromtext(name, &source, dns_rootname, 0, buffer); + * dns_message_takebuffer(message, &buffer); + * \endcode + * + * + * TODO: + * + * XXX Needed: ways to set and retrieve EDNS information, add rdata to a + * section, move rdata from one section to another, remove rdata, etc. + */ + +#define DNS_MESSAGEFLAG_QR 0x8000U +#define DNS_MESSAGEFLAG_AA 0x0400U +#define DNS_MESSAGEFLAG_TC 0x0200U +#define DNS_MESSAGEFLAG_RD 0x0100U +#define DNS_MESSAGEFLAG_RA 0x0080U +#define DNS_MESSAGEFLAG_AD 0x0020U +#define DNS_MESSAGEFLAG_CD 0x0010U + +/*%< EDNS0 extended message flags */ +#define DNS_MESSAGEEXTFLAG_DO 0x8000U + +/*%< EDNS0 extended OPT codes */ +#define DNS_OPT_LLQ 1 /*%< LLQ opt code */ +#define DNS_OPT_NSID 3 /*%< NSID opt code */ +#define DNS_OPT_CLIENT_SUBNET 8 /*%< client subnet opt code */ +#define DNS_OPT_EXPIRE 9 /*%< EXPIRE opt code */ +#define DNS_OPT_COOKIE 10 /*%< COOKIE opt code */ +#define DNS_OPT_TCP_KEEPALIVE 11 /*%< TCP keepalive opt code */ +#define DNS_OPT_PAD 12 /*%< PAD opt code */ +#define DNS_OPT_KEY_TAG 14 /*%< Key tag opt code */ +#define DNS_OPT_EDE 15 /*%< Extended DNS Error opt code */ +#define DNS_OPT_CLIENT_TAG 16 /*%< Client tag opt code */ +#define DNS_OPT_SERVER_TAG 17 /*%< Server tag opt code */ + +/*%< Experimental options [65001...65534] as per RFC6891 */ + +/*%< + * The maximum number of EDNS options we allow to set. Reserve space for the + * options we know about. Extended DNS Errors may occur multiple times, but we + * will set only one per message (for now). + */ +#define DNS_EDNSOPTIONS 8 + +/*%< EDNS0 extended DNS errors */ +#define DNS_EDE_OTHER 0 /*%< Other Error */ +#define DNS_EDE_DNSKEYALG 1 /*%< Unsupported DNSKEY Algorithm */ +#define DNS_EDE_DSDIGESTTYPE 2 /*%< Unsupported DS Digest Type */ +#define DNS_EDE_STALEANSWER 3 /*%< Stale Answer */ +#define DNS_EDE_FORGEDANSWER 4 /*%< Forged Answer */ +#define DNS_EDE_DNSSECINDETERMINATE 5 /*%< DNSSEC Indeterminate */ +#define DNS_EDE_DNSSECBOGUS 6 /*%< DNSSEC Bogus */ +#define DNS_EDE_SIGNATUREEXPIRED 7 /*%< Signature Expired */ +#define DNS_EDE_SIGNATURENOTYETVALID 8 /*%< Signature Not Yet Valid */ +#define DNS_EDE_DNSKEYMISSING 9 /*%< DNSKEY Missing */ +#define DNS_EDE_RRSIGSMISSING 10 /*%< RRSIGs Missing */ +#define DNS_EDE_NOZONEKEYBITSET 11 /*%< No Zone Key Bit Set */ +#define DNS_EDE_NSECMISSING 12 /*%< NSEC Missing */ +#define DNS_EDE_CACHEDERROR 13 /*%< Cached Error */ +#define DNS_EDE_NOTREADY 14 /*%< Not Ready */ +#define DNS_EDE_BLOCKED 15 /*%< Blocked */ +#define DNS_EDE_CENSORED 16 /*%< Censored */ +#define DNS_EDE_FILTERED 17 /*%< Filtered */ +#define DNS_EDE_PROHIBITED 18 /*%< Prohibited */ +#define DNS_EDE_STALENXANSWER 19 /*%< Stale NXDomain Answer */ +#define DNS_EDE_NOTAUTH 20 /*%< Not Authoritative */ +#define DNS_EDE_NOTSUPPORTED 21 /*%< Not Supported */ +#define DNS_EDE_NOREACHABLEAUTH 22 /*%< No Reachable Authority */ +#define DNS_EDE_NETWORKERROR 23 /*%< Network Error */ +#define DNS_EDE_INVALIDDATA 24 /*%< Invalid Data */ + +/* + * From RFC 8914: + * Because long EXTRA-TEXT fields may trigger truncation (which is undesirable + * given the supplemental nature of EDE), implementers and operators creating + * EDE options SHOULD avoid lengthy EXTRA-TEXT contents. + * + * Following this advice we limit the EXTRA-TEXT length to 64 characters. + */ +#define DNS_EDE_EXTRATEXT_LEN 64 + +#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD) +#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO) + +#define DNS_MESSAGE_HEADERLEN 12 /*%< 6 uint16_t's */ + +#define DNS_MESSAGE_MAGIC ISC_MAGIC('M', 'S', 'G', '@') +#define DNS_MESSAGE_VALID(msg) ISC_MAGIC_VALID(msg, DNS_MESSAGE_MAGIC) + +/* + * Ordering here matters. DNS_SECTION_ANY must be the lowest and negative, + * and DNS_SECTION_MAX must be one greater than the last used section. + */ +typedef int dns_section_t; +#define DNS_SECTION_ANY (-1) +#define DNS_SECTION_QUESTION 0 +#define DNS_SECTION_ANSWER 1 +#define DNS_SECTION_AUTHORITY 2 +#define DNS_SECTION_ADDITIONAL 3 +#define DNS_SECTION_MAX 4 + +typedef int dns_pseudosection_t; +#define DNS_PSEUDOSECTION_ANY (-1) +#define DNS_PSEUDOSECTION_OPT 0 +#define DNS_PSEUDOSECTION_TSIG 1 +#define DNS_PSEUDOSECTION_SIG0 2 +#define DNS_PSEUDOSECTION_MAX 3 + +typedef int dns_messagetextflag_t; +#define DNS_MESSAGETEXTFLAG_NOCOMMENTS 0x0001 +#define DNS_MESSAGETEXTFLAG_NOHEADERS 0x0002 +#define DNS_MESSAGETEXTFLAG_ONESOA 0x0004 +#define DNS_MESSAGETEXTFLAG_OMITSOA 0x0008 + +/* + * Dynamic update names for these sections. + */ +#define DNS_SECTION_ZONE DNS_SECTION_QUESTION +#define DNS_SECTION_PREREQUISITE DNS_SECTION_ANSWER +#define DNS_SECTION_UPDATE DNS_SECTION_AUTHORITY + +/* + * These tell the message library how the created dns_message_t will be used. + */ +#define DNS_MESSAGE_INTENTUNKNOWN 0 /*%< internal use only */ +#define DNS_MESSAGE_INTENTPARSE 1 /*%< parsing messages */ +#define DNS_MESSAGE_INTENTRENDER 2 /*%< rendering */ + +/* + * Control behavior of parsing + */ +#define DNS_MESSAGEPARSE_PRESERVEORDER 0x0001 /*%< preserve rdata order */ +#define DNS_MESSAGEPARSE_BESTEFFORT \ + 0x0002 /*%< return a message if a \ + * recoverable parse error \ + * occurs */ +#define DNS_MESSAGEPARSE_CLONEBUFFER \ + 0x0004 /*%< save a copy of the \ + * source buffer */ +#define DNS_MESSAGEPARSE_IGNORETRUNCATION \ + 0x0008 /*%< truncation errors are \ + * not fatal. */ + +/* + * Control behavior of rendering + */ +#define DNS_MESSAGERENDER_ORDERED 0x0001 /*%< don't change order */ +#define DNS_MESSAGERENDER_PARTIAL 0x0002 /*%< allow a partial rdataset */ +#define DNS_MESSAGERENDER_OMITDNSSEC 0x0004 /*%< omit DNSSEC records */ +#define DNS_MESSAGERENDER_PREFER_A \ + 0x0008 /*%< prefer A records in \ + * additional section. */ +#define DNS_MESSAGERENDER_PREFER_AAAA \ + 0x0010 /*%< prefer AAAA records in \ + * additional section. */ +/* Obsolete: DNS_MESSAGERENDER_FILTER_AAAA 0x0020 */ + +typedef struct dns_msgblock dns_msgblock_t; + +struct dns_sortlist_arg { + dns_aclenv_t *env; + dns_acl_t *acl; + const dns_aclelement_t *element; +}; + +typedef struct dns_minttl { + bool is_set; + dns_ttl_t ttl; +} dns_minttl_t; + +struct dns_message { + /* public from here down */ + unsigned int magic; + isc_refcount_t refcount; + + dns_messageid_t id; + unsigned int flags; + dns_rcode_t rcode; + dns_opcode_t opcode; + dns_rdataclass_t rdclass; + + /* 4 real, 1 pseudo */ + unsigned int counts[DNS_SECTION_MAX]; + + /* private from here down */ + dns_namelist_t sections[DNS_SECTION_MAX]; + dns_name_t *cursors[DNS_SECTION_MAX]; + dns_rdataset_t *opt; + dns_rdataset_t *sig0; + dns_rdataset_t *tsig; + + int state; + unsigned int from_to_wire : 2; + unsigned int header_ok : 1; + unsigned int question_ok : 1; + unsigned int tcp_continuation : 1; + unsigned int verified_sig : 1; + unsigned int verify_attempted : 1; + unsigned int free_query : 1; + unsigned int free_saved : 1; + unsigned int cc_ok : 1; + unsigned int cc_bad : 1; + unsigned int cc_echoed : 1; + unsigned int tkey : 1; + unsigned int rdclass_set : 1; + unsigned int fuzzing : 1; + + unsigned int opt_reserved; + unsigned int sig_reserved; + unsigned int reserved; /* reserved space (render) */ + + uint16_t padding; + unsigned int padding_off; + + isc_buffer_t *buffer; + dns_compress_t *cctx; + + isc_mem_t *mctx; + isc_mempool_t *namepool; + isc_mempool_t *rdspool; + + isc_bufferlist_t scratchpad; + isc_bufferlist_t cleanup; + + ISC_LIST(dns_msgblock_t) rdatas; + ISC_LIST(dns_msgblock_t) rdatalists; + ISC_LIST(dns_msgblock_t) offsets; + + ISC_LIST(dns_rdata_t) freerdata; + ISC_LIST(dns_rdatalist_t) freerdatalist; + + dns_rcode_t tsigstatus; + dns_rcode_t querytsigstatus; + dns_name_t *tsigname; /* Owner name of TSIG, if any + * */ + dns_rdataset_t *querytsig; + dns_tsigkey_t *tsigkey; + dst_context_t *tsigctx; + int sigstart; + int timeadjust; + + dns_name_t *sig0name; /* Owner name of SIG0, if any + * */ + dst_key_t *sig0key; + dns_rcode_t sig0status; + isc_region_t query; + isc_region_t saved; + + /* + * Time to be used when fuzzing. + */ + isc_stdtime_t fuzztime; + + dns_rdatasetorderfunc_t order; + dns_sortlist_arg_t order_arg; + + dns_indent_t indent; + + dns_minttl_t minttl[DNS_SECTION_MAX]; +}; + +struct dns_ednsopt { + uint16_t code; + uint16_t length; + unsigned char *value; +}; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +void +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp); + +/*%< + * Create msg structure. + * + * This function will allocate some internal blocks of memory that are + * expected to be needed for parsing or rendering nearly any type of message. + * + * Requires: + *\li 'mctx' be a valid memory context. + * + *\li 'msgp' be non-null and '*msg' be NULL. + * + *\li 'intent' must be one of DNS_MESSAGE_INTENTPARSE or + * #DNS_MESSAGE_INTENTRENDER. + * + * Ensures: + *\li The data in "*msg" is set to indicate an unused and empty msg + * structure. + * + * Returns: + *\li #ISC_R_NOMEMORY -- out of memory + *\li #ISC_R_SUCCESS -- success + */ + +void +dns_message_reset(dns_message_t *msg, unsigned int intent); +/*%< + * Reset a message structure to default state. All internal lists are freed + * or reset to a default state as well. This is simply a more efficient + * way to call dns_message_detach() (assuming last reference is hold), + * followed by dns_message_create(), since it avoid many memory allocations. + * + * If any data loanouts (buffers, names, rdatas, etc) were requested, + * the caller must no longer use them after this call. + * + * The intended next use of the message will be 'intent'. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'intent' is DNS_MESSAGE_INTENTPARSE or DNS_MESSAGE_INTENTRENDER + */ + +void +dns_message_attach(dns_message_t *source, dns_message_t **target); +/*%< + * Attach to message 'source'. + * + * Requires: + *\li 'source' to be a valid message. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_message_detach(dns_message_t **messagep); +/*%< + * Detach *messagep from its message. + * list. + * + * Requires: + *\li '*messagep' to be a valid message. + */ + +isc_result_t +dns_message_sectiontotext(dns_message_t *msg, dns_section_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target); + +isc_result_t +dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, + isc_buffer_t *target); +/*%< + * Convert section 'section' or 'pseudosection' of message 'msg' to + * a cleartext representation + * + * Notes: + * \li See dns_message_totext for meanings of flags. + * + * Requires: + * + *\li 'msg' is a valid message. + * + *\li 'style' is a valid master dump style. + * + *\li 'target' is a valid buffer. + * + *\li 'section' is a valid section label. + * + * Ensures: + * + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + *\li #ISC_R_NOMORE + * + *\li Note: On error return, *target may be partially filled with data. + */ + +isc_result_t +dns_message_headertotext(dns_message_t *msg, const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target); +/*%< + * Convert the header section of message 'msg' to a cleartext + * representation. This is called from dns_message_totext(). + * + * Notes on flags: + *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is set, header lines will be + * suppressed and this function is a no-op. + * + * Requires: + * + *\li 'msg' is a valid message. + * + *\li 'target' is a valid buffer. + * + * Ensures: + * + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + *\li #ISC_R_NOMORE + * + *\li Note: On error return, *target may be partially filled with data. + */ + +isc_result_t +dns_message_totext(dns_message_t *msg, const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target); +/*%< + * Convert all sections of message 'msg' to a cleartext representation + * + * Notes on flags: + *\li If #DNS_MESSAGETEXTFLAG_NOCOMMENTS is cleared, lines beginning with + * ";;" will be emitted indicating section name. + *\li If #DNS_MESSAGETEXTFLAG_NOHEADERS is cleared, header lines will be + * emitted. + *\li If #DNS_MESSAGETEXTFLAG_ONESOA is set then only print the first + * SOA record in the answer section. + *\li If *#DNS_MESSAGETEXTFLAG_OMITSOA is set don't print any SOA records + * in the answer section. + * + * The SOA flags are useful for suppressing the display of the second + * SOA record in an AXFR by setting #DNS_MESSAGETEXTFLAG_ONESOA on the + * first message in an AXFR stream and #DNS_MESSAGETEXTFLAG_OMITSOA on + * subsequent messages. + * + * Requires: + * + *\li 'msg' is a valid message. + * + *\li 'style' is a valid master dump style. + * + *\li 'target' is a valid buffer. + * + * Ensures: + * + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + *\li #ISC_R_NOMORE + * + *\li Note: On error return, *target may be partially filled with data. + */ + +isc_result_t +dns_message_parse(dns_message_t *msg, isc_buffer_t *source, + unsigned int options); +/*%< + * Parse raw wire data in 'source' as a DNS message. + * + * OPT records are detected and stored in the pseudo-section "opt". + * TSIGs are detected and stored in the pseudo-section "tsig". + * + * If #DNS_MESSAGEPARSE_PRESERVEORDER is set, or if the opcode of the message + * is UPDATE, a separate dns_name_t object will be created for each RR in the + * message. Each such dns_name_t will have a single rdataset containing the + * single RR, and the order of the RRs in the message is preserved. + * Otherwise, only one dns_name_t object will be created for each unique + * owner name in the section, and each such dns_name_t will have a list + * of rdatasets. To access the names and their data, use + * dns_message_firstname() and dns_message_nextname(). + * + * If #DNS_MESSAGEPARSE_BESTEFFORT is set, errors in message content will + * not be considered FORMERRs. If the entire message can be parsed, it + * will be returned and DNS_R_RECOVERABLE will be returned. + * + * If #DNS_MESSAGEPARSE_IGNORETRUNCATION is set then return as many complete + * RR's as possible, DNS_R_RECOVERABLE will be returned. + * + * OPT and TSIG records are always handled specially, regardless of the + * 'preserve_order' setting. + * + * Requires: + *\li "msg" be valid. + * + *\li "buffer" be a wire format buffer. + * + * Ensures: + *\li The buffer's data format is correct. + * + *\li The buffer's contents verify as correct regarding header bits, buffer + * and rdata sizes, etc. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well + *\li #ISC_R_NOMEMORY -- no memory + *\li #DNS_R_RECOVERABLE -- the message parsed properly, but contained + * errors. + *\li Many other errors possible XXXMLG + */ + +isc_result_t +dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx, + isc_buffer_t *buffer); +/*%< + * Begin rendering on a message. Only one call can be made to this function + * per message. + * + * The compression context is "owned" by the message library until + * dns_message_renderend() is called. It must be invalidated by the caller. + * + * The buffer is "owned" by the message library until dns_message_renderend() + * is called. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'cctx' be valid. + * + *\li 'buffer' is a valid buffer. + * + * Side Effects: + * + *\li The buffer is cleared before it is used. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well + *\li #ISC_R_NOSPACE -- output buffer is too small + */ + +isc_result_t +dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer); +/*%< + * Reset the buffer. This can be used after growing the old buffer + * on a ISC_R_NOSPACE return from most of the render functions. + * + * On successful completion, the old buffer is no longer used by the + * library. The new buffer is owned by the library until + * dns_message_renderend() is called. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li dns_message_renderbegin() was called. + * + *\li buffer != NULL. + * + * Returns: + *\li #ISC_R_NOSPACE -- new buffer is too small + *\li #ISC_R_SUCCESS -- all is well. + */ + +isc_result_t +dns_message_renderreserve(dns_message_t *msg, unsigned int space); +/*%< + * XXXMLG should use size_t rather than unsigned int once the buffer + * API is cleaned up + * + * Reserve "space" bytes in the given buffer. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li dns_message_renderbegin() was called. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOSPACE -- not enough free space in the buffer. + */ + +void +dns_message_renderrelease(dns_message_t *msg, unsigned int space); +/*%< + * XXXMLG should use size_t rather than unsigned int once the buffer + * API is cleaned up + * + * Release "space" bytes in the given buffer that was previously reserved. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'space' is less than or equal to the total amount of space reserved + * via prior calls to dns_message_renderreserve(). + * + *\li dns_message_renderbegin() was called. + */ + +isc_result_t +dns_message_rendersection(dns_message_t *msg, dns_section_t section, + unsigned int options); +/*%< + * Render all names, rdatalists, etc from the given section at the + * specified priority or higher. + * + * Requires: + *\li 'msg' be valid. + * + *\li 'section' be a valid section. + * + *\li dns_message_renderbegin() was called. + * + * Returns: + *\li #ISC_R_SUCCESS -- all records were written, and there are + * no more records for this section. + *\li #ISC_R_NOSPACE -- Not enough room in the buffer to write + * all records requested. + *\li #DNS_R_MOREDATA -- All requested records written, and there + * are records remaining for this section. + */ + +void +dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target); +/*%< + * Render the message header. This is implicitly called by + * dns_message_renderend(). + * + * Requires: + * + *\li 'msg' be a valid message. + * + *\li dns_message_renderbegin() was called. + * + *\li 'target' is a valid buffer with enough space to hold a message header + */ + +isc_result_t +dns_message_renderend(dns_message_t *msg); +/*%< + * Finish rendering to the buffer. Note that more data can be in the + * 'msg' structure. Destroying the structure will free this, or in a multi- + * part EDNS1 message this data can be rendered to another buffer later. + * + * Requires: + * + *\li 'msg' be a valid message. + * + *\li dns_message_renderbegin() was called. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + */ + +void +dns_message_renderreset(dns_message_t *msg); +/*%< + * Reset the message so that it may be rendered again. + * + * Notes: + * + *\li If dns_message_renderbegin() has been called, dns_message_renderend() + * must be called before calling this function. + * + * Requires: + * + *\li 'msg' be a valid message with rendering intent. + */ + +isc_result_t +dns_message_firstname(dns_message_t *msg, dns_section_t section); +/*%< + * Set internal per-section name pointer to the beginning of the section. + * + * The functions dns_message_firstname() and dns_message_nextname() may + * be used for iterating over the owner names in a section. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'section' be a valid section. + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + *\li #ISC_R_NOMORE -- No names on given section. + */ + +isc_result_t +dns_message_nextname(dns_message_t *msg, dns_section_t section); +/*%< + * Sets the internal per-section name pointer to point to the next name + * in that section. + * + * Requires: + * + * \li 'msg' be valid. + * + *\li 'section' be a valid section. + * + *\li dns_message_firstname() must have been called on this section, + * and the result was ISC_R_SUCCESS. + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + *\li #ISC_R_NOMORE -- No more names in given section. + */ + +void +dns_message_currentname(dns_message_t *msg, dns_section_t section, + dns_name_t **name); +/*%< + * Sets 'name' to point to the name where the per-section internal name + * pointer is currently set. + * + * This function returns the name in the database, so any data associated + * with it (via the name's "list" member) contains the actual rdatasets. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'name' be non-NULL, and *name be NULL. + * + *\li 'section' be a valid section. + * + *\li dns_message_firstname() must have been called on this section, + * and the result of it and any dns_message_nextname() calls was + * #ISC_R_SUCCESS. + */ + +isc_result_t +dns_message_findname(dns_message_t *msg, dns_section_t section, + const dns_name_t *target, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_name_t **foundname, + dns_rdataset_t **rdataset); +/*%< + * Search for a name in the specified section. If it is found, *name is + * set to point to the name, and *rdataset is set to point to the found + * rdataset (if type is specified as other than dns_rdatatype_any). + * + * Requires: + *\li 'msg' be valid. + * + *\li 'section' be a valid section. + * + *\li If a pointer to the name is desired, 'foundname' should be non-NULL. + * If it is non-NULL, '*foundname' MUST be NULL. + * + *\li If a type other than dns_datatype_any is searched for, 'rdataset' + * may be non-NULL, '*rdataset' be NULL, and will point at the found + * rdataset. If the type is dns_datatype_any, 'rdataset' must be NULL. + * + *\li 'target' be a valid name. + * + *\li 'type' be a valid type. + * + *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type. + * Otherwise it should be 0. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #DNS_R_NXDOMAIN -- name does not exist in that section. + *\li #DNS_R_NXRRSET -- The name does exist, but the desired + * type does not. + */ + +isc_result_t +dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_rdataset_t **rdataset); +/*%< + * Search the name for the specified type. If it is found, *rdataset is + * filled in with a pointer to that rdataset. + * + * Requires: + *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL. + * + *\li 'type' be a valid type, and NOT dns_rdatatype_any. + * + *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type. + * Otherwise it should be 0. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOTFOUND -- the desired type does not exist. + */ + +isc_result_t +dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t type, dns_rdatatype_t covers, + dns_rdataset_t **rdataset); +/*%< + * Search the name for the specified rdclass and type. If it is found, + * *rdataset is filled in with a pointer to that rdataset. + * + * Requires: + *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL. + * + *\li 'type' be a valid type, and NOT dns_rdatatype_any. + * + *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type. + * Otherwise it should be 0. + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOTFOUND -- the desired type does not exist. + */ + +void +dns_message_movename(dns_message_t *msg, dns_name_t *name, + dns_section_t fromsection, dns_section_t tosection); +/*%< + * Move a name from one section to another. + * + * Requires: + * + *\li 'msg' be valid. + * + *\li 'name' must be a name already in 'fromsection'. + * + *\li 'fromsection' must be a valid section. + * + *\li 'tosection' must be a valid section. + */ + +void +dns_message_addname(dns_message_t *msg, dns_name_t *name, + dns_section_t section); +/*%< + * Adds the name to the given section. + * + * It is the caller's responsibility to enforce any unique name requirements + * in a section. + * + * Requires: + * + *\li 'msg' be valid, and be a renderable message. + * + *\li 'name' be a valid absolute name. + * + *\li 'section' be a named section. + */ + +void +dns_message_removename(dns_message_t *msg, dns_name_t *name, + dns_section_t section); +/*%< + * Remove a existing name from a given section. + * + * It is the caller's responsibility to ensure the name is part of the + * given section. + * + * Requires: + * + *\li 'msg' be valid, and be a renderable message. + * + *\li 'name' be a valid absolute name. + * + *\li 'section' be a named section. + */ + +/* + * LOANOUT FUNCTIONS + * + * Each of these functions loan a particular type of data to the caller. + * The storage for these will vanish when the message is destroyed or + * reset, and must NOT be used after these operations. + */ + +isc_result_t +dns_message_gettempname(dns_message_t *msg, dns_name_t **item); +/*%< + * Return a name that can be used for any temporary purpose, including + * inserting into the message's linked lists. The name must be returned + * to the message code using dns_message_puttempname() or inserted into + * one of the message's sections before the message is destroyed. + * + * The name will be associated with a dns_fixedname object, and will + * be initialized. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item == NULL + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + */ + +isc_result_t +dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item); +/*%< + * Return a rdata that can be used for any temporary purpose, including + * inserting into the message's linked lists. The rdata will be freed + * when the message is destroyed or reset. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item == NULL + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + */ + +isc_result_t +dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item); +/*%< + * Return a rdataset that can be used for any temporary purpose, including + * inserting into the message's linked lists. The name must be returned + * to the message code using dns_message_puttempname() or inserted into + * one of the message's sections before the message is destroyed. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item == NULL + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + */ + +isc_result_t +dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item); +/*%< + * Return a rdatalist that can be used for any temporary purpose, including + * inserting into the message's linked lists. The rdatalist will be + * destroyed when the message is destroyed or reset. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item == NULL + * + * Returns: + *\li #ISC_R_SUCCESS -- All is well. + */ + +void +dns_message_puttempname(dns_message_t *msg, dns_name_t **item); +/*%< + * Return a borrowed name to the message's name free list. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item point to a name returned by + * dns_message_gettempname() + * + * Ensures: + *\li *item == NULL + */ + +void +dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item); +/*%< + * Return a borrowed rdata to the message's rdata free list. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item point to a rdata returned by + * dns_message_gettemprdata() + * + * Ensures: + *\li *item == NULL + */ + +void +dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item); +/*%< + * Return a borrowed rdataset to the message's rdataset free list. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item point to a rdataset returned by + * dns_message_gettemprdataset() + * + * Ensures: + *\li *item == NULL + */ + +void +dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item); +/*%< + * Return a borrowed rdatalist to the message's rdatalist free list. + * + * Requires: + *\li msg be a valid message + * + *\li item != NULL && *item point to a rdatalist returned by + * dns_message_gettemprdatalist() + * + * Ensures: + *\li *item == NULL + */ + +isc_result_t +dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp, + unsigned int *flagsp); +/*%< + * Assume the remaining region of "source" is a DNS message. Peek into + * it and fill in "*idp" with the message id, and "*flagsp" with the flags. + * + * Requires: + * + *\li source != NULL + * + * Ensures: + * + *\li if (idp != NULL) *idp == message id. + * + *\li if (flagsp != NULL) *flagsp == message flags. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + * + *\li #ISC_R_UNEXPECTEDEND -- buffer doesn't contain enough for a header. + */ + +isc_result_t +dns_message_reply(dns_message_t *msg, bool want_question_section); +/*%< + * Start formatting a reply to the query in 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message with parsing intent, and contains a query. + * + * Ensures: + * + *\li The message will have a rendering intent. If 'want_question_section' + * is true, the message opcode is query or notify, and the question + * section is present and properly formatted, then the question section + * will be included in the reply. All other sections will be cleared. + * The QR flag will be set, the RD flag will be preserved, and all other + * flags will be cleared. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + * + *\li #DNS_R_FORMERR -- the header or question section of the + * message is invalid, replying is impossible. + * If DNS_R_FORMERR is returned when + * want_question_section is false, then + * it's the header section that's bad; + * otherwise either of the header or question + * sections may be bad. + */ + +dns_rdataset_t * +dns_message_getopt(dns_message_t *msg); +/*%< + * Get the OPT record for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message. + * + * Returns: + * + *\li The OPT rdataset of 'msg', or NULL if there isn't one. + */ + +isc_result_t +dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt); +/*%< + * Set the OPT record for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message with rendering intent + * and no sections have been rendered. + * + *\li 'opt' is a valid OPT record. + * + * Ensures: + * + *\li The OPT record has either been freed or ownership of it has + * been transferred to the message. + * + *\li If ISC_R_SUCCESS was returned, the OPT record will be rendered + * when dns_message_renderend() is called. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + * + *\li #ISC_R_NOSPACE -- there is no space for the OPT record. + */ + +dns_rdataset_t * +dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner); +/*%< + * Get the TSIG record and owner for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message. + *\li 'owner' is NULL or *owner is NULL. + * + * Returns: + * + *\li The TSIG rdataset of 'msg', or NULL if there isn't one. + * + * Ensures: + * + * \li If 'owner' is not NULL, it will point to the owner name. + */ + +isc_result_t +dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key); +/*%< + * Set the tsig key for 'msg'. This is only necessary for when rendering a + * query or parsing a response. The key (if non-NULL) is attached to, and + * will be detached when the message is destroyed. + * + * Requires: + * + *\li 'msg' is a valid message with rendering intent, + * dns_message_renderbegin() has been called, and no sections have been + * rendered. + *\li 'key' is a valid tsig key or NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + * + *\li #ISC_R_NOSPACE -- there is no space for the TSIG record. + */ + +dns_tsigkey_t * +dns_message_gettsigkey(dns_message_t *msg); +/*%< + * Gets the tsig key for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message + */ + +isc_result_t +dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig); +/*%< + * Indicates that 'querytsig' is the TSIG from the signed query for which + * 'msg' is the response. This is also used for chained TSIGs in TCP + * responses. + * + * Requires: + * + *\li 'querytsig' is a valid buffer as returned by dns_message_getquerytsig() + * or NULL + * + *\li 'msg' is a valid message + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx, + isc_buffer_t **querytsig); +/*%< + * Gets the tsig from the TSIG from the signed query 'msg'. This is also used + * for chained TSIGs in TCP responses. Unlike dns_message_gettsig, this makes + * a copy of the data, so can be used if the message is destroyed. + * + * Requires: + * + *\li 'msg' is a valid signed message + *\li 'mctx' is a valid memory context + *\li querytsig != NULL && *querytsig == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + * Ensures: + *\li 'tsig' points to NULL or an allocated buffer which must be freed + * by the caller. + */ + +dns_rdataset_t * +dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner); +/*%< + * Get the SIG(0) record and owner for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message. + *\li 'owner' is NULL or *owner is NULL. + * + * Returns: + * + *\li The SIG(0) rdataset of 'msg', or NULL if there isn't one. + * + * Ensures: + * + * \li If 'owner' is not NULL, it will point to the owner name. + */ + +isc_result_t +dns_message_setsig0key(dns_message_t *msg, dst_key_t *key); +/*%< + * Set the SIG(0) key for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message with rendering intent, + * dns_message_renderbegin() has been called, and no sections have been + * rendered. + *\li 'key' is a valid sig key or NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS -- all is well. + * + *\li #ISC_R_NOSPACE -- there is no space for the SIG(0) record. + */ + +dst_key_t * +dns_message_getsig0key(dns_message_t *msg); +/*%< + * Gets the SIG(0) key for 'msg'. + * + * Requires: + * + *\li 'msg' is a valid message + */ + +void +dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer); +/*%< + * Give the *buffer to the message code to clean up when it is no + * longer needed. This is usually when the message is reset or + * destroyed. + * + * Requires: + * + *\li msg be a valid message. + * + *\li buffer != NULL && *buffer is a valid isc_buffer_t, which was + * dynamically allocated via isc_buffer_allocate(). + */ + +isc_result_t +dns_message_signer(dns_message_t *msg, dns_name_t *signer); +/*%< + * If this message was signed, return the identity of the signer. + * Unless ISC_R_NOTFOUND is returned, signer will reflect the name of the + * key that signed the message. + * + * Requires: + * + *\li msg is a valid parsed message. + *\li signer is a valid name + * + * Returns: + * + *\li #ISC_R_SUCCESS - the message was signed, and *signer + * contains the signing identity + * + *\li #ISC_R_NOTFOUND - no TSIG or SIG(0) record is present in the + * message + * + *\li #DNS_R_TSIGVERIFYFAILURE - the message was signed by a TSIG, but + * the signature failed to verify + * + *\li #DNS_R_TSIGERRORSET - the message was signed by a TSIG and + * verified, but the query was rejected by + * the server + * + *\li #DNS_R_NOIDENTITY - the message was signed by a TSIG and + * verified, but the key has no identity since + * it was generated by an unsigned TKEY process + * + *\li #DNS_R_SIGINVALID - the message was signed by a SIG(0), but + * the signature failed to verify + * + *\li #DNS_R_NOTVERIFIEDYET - the message was signed by a TSIG or SIG(0), + * but the signature has not been verified yet + */ + +isc_result_t +dns_message_checksig(dns_message_t *msg, dns_view_t *view); +/*%< + * If this message was signed, verify the signature. + * + * Requires: + * + *\li msg is a valid parsed message. + *\li view is a valid view or NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS - the message was unsigned, or the message + * was signed correctly. + * + *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen + *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected + *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify + */ + +isc_result_t +dns_message_rechecksig(dns_message_t *msg, dns_view_t *view); +/*%< + * Reset the signature state and then if the message was signed, + * verify the message. + * + * Requires: + * + *\li msg is a valid parsed message. + *\li view is a valid view or NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS - the message was unsigned, or the message + * was signed correctly. + * + *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected, but not seen + *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected + *\li #DNS_R_TSIGVERIFYFAILURE - The TSIG failed to verify + */ + +void +dns_message_resetsig(dns_message_t *msg); +/*%< + * Reset the signature state. + * + * Requires: + *\li 'msg' is a valid parsed message. + */ + +isc_region_t * +dns_message_getrawmessage(dns_message_t *msg); +/*%< + * Retrieve the raw message in compressed wire format. The message must + * have been successfully parsed for it to have been saved. + * + * Requires: + *\li msg is a valid parsed message. + * + * Returns: + *\li NULL if there is no saved message. + * a pointer to a region which refers the dns message. + */ + +void +dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order, + dns_aclenv_t *env, dns_acl_t *acl, + const dns_aclelement_t *element); +/*%< + * Define the order in which RR sets get rendered by + * dns_message_rendersection() to be the ascending order + * defined by the integer value returned by 'order' when + * given each RR and a ns_sortlist_arg_t constructed from 'env', + * 'acl', and 'element' as arguments. + * + * If 'order' is NULL, a default order is used. + * + * Requires: + *\li msg be a valid message. + *\li If 'env' is NULL, 'order' must be NULL. + *\li If 'env' is not NULL, 'order' must not be NULL and at least one of + * 'acl' and 'element' must also not be NULL. + */ + +void +dns_message_settimeadjust(dns_message_t *msg, int timeadjust); +/*%< + * Adjust the time used to sign/verify a message by timeadjust. + * Currently only TSIG. + * + * Requires: + *\li msg be a valid message. + */ + +int +dns_message_gettimeadjust(dns_message_t *msg); +/*%< + * Return the current time adjustment. + * + * Requires: + *\li msg be a valid message. + */ + +void +dns_message_logpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx); + +void +dns_message_logfmtpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx); +/*%< + * Log 'message' at the specified logging parameters. + * + * For dns_message_logpacket and dns_message_logfmtpacket expect the + * 'description' to end in a newline. + * + * For dns_message_logpacket2 and dns_message_logfmtpacket2 + * 'description' will be emitted at the start of the message followed + * by the formatted address and a newline. + * + * Requires: + * \li message be a valid. + * \li description to be non NULL. + * \li address to be non NULL. + * \li category to be valid. + * \li module to be valid. + * \li style to be valid. + * \li mctx to be a valid. + */ + +isc_result_t +dns_message_buildopt(dns_message_t *msg, dns_rdataset_t **opt, + unsigned int version, uint16_t udpsize, unsigned int flags, + dns_ednsopt_t *ednsopts, size_t count); +/*%< + * Built a opt record. + * + * Requires: + * \li msg be a valid message. + * \li opt to be a non NULL and *opt to be NULL. + * + * Returns: + * \li ISC_R_SUCCESS on success. + * \li ISC_R_NOMEMORY + * \li ISC_R_NOSPACE + * \li other. + */ + +void +dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass); +/*%< + * Set the expected class of records in the response. + * + * Requires: + * \li msg be a valid message with parsing intent. + */ + +void +dns_message_setpadding(dns_message_t *msg, uint16_t padding); +/*%< + * Set the padding block size in the response. + * 0 means no padding (default). + * + * Requires: + * \li msg be a valid message. + */ + +void +dns_message_clonebuffer(dns_message_t *msg); +/*%< + * Clone the query or saved buffers if they where not cloned + * when parsing. + * + * Requires: + * \li msg be a valid message. + */ + +isc_result_t +dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid, + dns_ttl_t *pttl); +/*%< + * Get the smallest TTL from the 'sectionid' section of a rendered + * message. + * + * Requires: + * \li msg be a valid rendered message; + * \li 'pttl != NULL'. + */ + +isc_result_t +dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl); +/*%< + * Get the smalled TTL from the Answer section of 'msg', or if empty, try + * the MIN(SOA TTL, SOA MINIMUM) value from an SOA record in the Authority + * section. If neither of these are set, return ISC_R_NOTFOUND. + * + * Requires: + * \li msg be a valid rendered message; + * \li 'pttl != NULL'. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h new file mode 100644 index 0000000..a758c4d --- /dev/null +++ b/lib/dns/include/dns/name.h @@ -0,0 +1,1381 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/name.h + * \brief + * Provides facilities for manipulating DNS names and labels, including + * conversions to and from wire format and text format. + * + * Given the large number of names possible in a nameserver, and because + * names occur in rdata, it was important to come up with a very efficient + * way of storing name data, but at the same time allow names to be + * manipulated. The decision was to store names in uncompressed wire format, + * and not to make them fully abstracted objects; i.e. certain parts of the + * server know names are stored that way. This saves a lot of memory, and + * makes adding names to messages easy. Having much of the server know + * the representation would be perilous, and we certainly don't want each + * user of names to be manipulating such a low-level structure. This is + * where the Names and Labels module comes in. The module allows name or + * label handles to be created and attached to uncompressed wire format + * regions. All name operations and conversions are done through these + * handles. + * + * MP: + *\li Clients of this module must impose any required synchronization. + * + * Reliability: + *\li This module deals with low-level byte streams. Errors in any of + * the functions are likely to crash the server or corrupt memory. + * + * Resources: + *\li None. + * + * Security: + * + *\li *** WARNING *** + * + *\li dns_name_fromwire() deals with raw network data. An error in + * this routine could result in the failure or hijacking of the server. + * + * Standards: + *\li RFC1035 + *\li Draft EDNS0 (0) + *\li Draft Binary Labels (2) + * + */ + +/*** + *** Imports + ***/ + +#include +#include +#include + +#include +#include +#include /* Required for storage size of dns_label_t. */ + +#include + +ISC_LANG_BEGINDECLS + +/***** +***** Labels +***** +***** A 'label' is basically a region. It contains one DNS wire format +***** label of type 00 (ordinary). +*****/ + +/***** +***** Names +***** +***** A 'name' is a handle to a binary region. It contains a sequence of one +***** or more DNS wire format labels of type 00 (ordinary). +***** Note that all names are not required to end with the root label, +***** as they are in the actual DNS wire protocol. +*****/ + +/*** + *** Types + ***/ + +/*% + * Clients are strongly discouraged from using this type directly, with + * the exception of the 'link' and 'list' fields which may be used directly + * for whatever purpose the client desires. + */ +struct dns_name { + unsigned int magic; + unsigned char *ndata; + unsigned int length; + unsigned int labels; + unsigned int attributes; + unsigned char *offsets; + isc_buffer_t *buffer; + ISC_LINK(dns_name_t) link; + ISC_LIST(dns_rdataset_t) list; +}; + +#define DNS_NAME_MAGIC ISC_MAGIC('D', 'N', 'S', 'n') + +#define DNS_NAMEATTR_ABSOLUTE 0x00000001 +#define DNS_NAMEATTR_READONLY 0x00000002 +#define DNS_NAMEATTR_DYNAMIC 0x00000004 +#define DNS_NAMEATTR_DYNOFFSETS 0x00000008 +#define DNS_NAMEATTR_NOCOMPRESS 0x00000010 +/* + * Attributes below 0x0100 reserved for name.c usage. + */ +#define DNS_NAMEATTR_CACHE 0x00000100 /*%< Used by resolver. */ +#define DNS_NAMEATTR_ANSWER 0x00000200 /*%< Used by resolver. */ +#define DNS_NAMEATTR_NCACHE 0x00000400 /*%< Used by resolver. */ +#define DNS_NAMEATTR_CHAINING 0x00000800 /*%< Used by resolver. */ +#define DNS_NAMEATTR_CHASE 0x00001000 /*%< Used by resolver. */ +#define DNS_NAMEATTR_WILDCARD 0x00002000 /*%< Used by server. */ +#define DNS_NAMEATTR_PREREQUISITE 0x00004000 /*%< Used by client. */ +#define DNS_NAMEATTR_UPDATE 0x00008000 /*%< Used by client. */ +#define DNS_NAMEATTR_HASUPDATEREC 0x00010000 /*%< Used by client. */ + +/* + * Various flags. + */ +#define DNS_NAME_DOWNCASE 0x0001 +#define DNS_NAME_CHECKNAMES 0x0002 /*%< Used by rdata. */ +#define DNS_NAME_CHECKNAMESFAIL 0x0004 /*%< Used by rdata. */ +#define DNS_NAME_CHECKREVERSE 0x0008 /*%< Used by rdata. */ +#define DNS_NAME_CHECKMX 0x0010 /*%< Used by rdata. */ +#define DNS_NAME_CHECKMXFAIL 0x0020 /*%< Used by rdata. */ + +extern const dns_name_t *dns_rootname; +extern const dns_name_t *dns_wildcardname; + +/*%< + * DNS_NAME_INITNONABSOLUTE and DNS_NAME_INITABSOLUTE are macros for + * initializing dns_name_t structures. + * + * Note[1]: 'length' is set to (sizeof(A) - 1) in DNS_NAME_INITNONABSOLUTE + * and sizeof(A) in DNS_NAME_INITABSOLUTE to allow C strings to be used + * to initialize 'ndata'. + * + * Note[2]: The final value of offsets for DNS_NAME_INITABSOLUTE should + * match (sizeof(A) - 1) which is the offset of the root label. + * + * Typical usage: + * unsigned char data[] = "\005value"; + * unsigned char offsets[] = { 0 }; + * dns_name_t value = DNS_NAME_INITNONABSOLUTE(data, offsets); + * + * unsigned char data[] = "\005value"; + * unsigned char offsets[] = { 0, 6 }; + * dns_name_t value = DNS_NAME_INITABSOLUTE(data, offsets); + */ +#define DNS_NAME_INITNONABSOLUTE(A, B) \ + { \ + DNS_NAME_MAGIC, A, (sizeof(A) - 1), sizeof(B), \ + DNS_NAMEATTR_READONLY, B, NULL, \ + { (void *)-1, (void *)-1 }, { \ + NULL, NULL \ + } \ + } + +#define DNS_NAME_INITABSOLUTE(A, B) \ + { \ + DNS_NAME_MAGIC, A, sizeof(A), sizeof(B), \ + DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, B, \ + NULL, { (void *)-1, (void *)-1 }, { \ + NULL, NULL \ + } \ + } + +#define DNS_NAME_INITEMPTY \ + { \ + DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \ + { (void *)-1, (void *)-1 }, { \ + NULL, NULL \ + } \ + } + +/*% + * Standard size of a wire format name + */ +#define DNS_NAME_MAXWIRE 255 + +/* + * Text output filter procedure. + * 'target' is the buffer to be converted. The region to be converted + * is from 'buffer'->base + 'used_org' to the end of the used region. + */ +typedef isc_result_t(dns_name_totextfilter_t)(isc_buffer_t *target, + unsigned int used_org); + +/*** + *** Initialization + ***/ + +void +dns_name_init(dns_name_t *name, unsigned char *offsets); +/*%< + * Initialize 'name'. + * + * Notes: + * \li 'offsets' is never required to be non-NULL, but specifying a + * dns_offsets_t for 'offsets' will improve the performance of most + * name operations if the name is used more than once. + * + * Requires: + * \li 'name' is not NULL and points to a struct dns_name. + * + * \li offsets == NULL or offsets is a dns_offsets_t. + * + * Ensures: + * \li 'name' is a valid name. + * \li dns_name_countlabels(name) == 0 + * \li dns_name_isabsolute(name) == false + */ + +void +dns_name_reset(dns_name_t *name); +/*%< + * Reinitialize 'name'. + * + * Notes: + * \li This function distinguishes itself from dns_name_init() in two + * key ways: + * + * \li + If any buffer is associated with 'name' (via dns_name_setbuffer() + * or by being part of a dns_fixedname_t) the link to the buffer + * is retained but the buffer itself is cleared. + * + * \li + Of the attributes associated with 'name', all are retained except + * DNS_NAMEATTR_ABSOLUTE. + * + * Requires: + * \li 'name' is a valid name. + * + * Ensures: + * \li 'name' is a valid name. + * \li dns_name_countlabels(name) == 0 + * \li dns_name_isabsolute(name) == false + */ + +void +dns_name_invalidate(dns_name_t *name); +/*%< + * Make 'name' invalid. + * + * Requires: + * \li 'name' is a valid name. + * + * Ensures: + * \li If assertion checking is enabled, future attempts to use 'name' + * without initializing it will cause an assertion failure. + * + * \li If the name had a dedicated buffer, that association is ended. + */ + +bool +dns_name_isvalid(const dns_name_t *name); +/*%< + * Check whether 'name' points to a valid dns_name + */ + +/*** + *** Dedicated Buffers + ***/ + +void +dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer); +/*%< + * Dedicate a buffer for use with 'name'. + * + * Notes: + * \li Specification of a target buffer in dns_name_fromwire(), + * dns_name_fromtext(), and dns_name_concatenate() is optional if + * 'name' has a dedicated buffer. + * + * \li The caller must not write to buffer until the name has been + * invalidated or is otherwise known not to be in use. + * + * \li If buffer is NULL and the name previously had a dedicated buffer, + * than that buffer is no longer dedicated to use with this name. + * The caller is responsible for ensuring that the storage used by + * the name remains valid. + * + * Requires: + * \li 'name' is a valid name. + * + * \li 'buffer' is a valid binary buffer and 'name' doesn't have a + * dedicated buffer already, or 'buffer' is NULL. + */ + +bool +dns_name_hasbuffer(const dns_name_t *name); +/*%< + * Does 'name' have a dedicated buffer? + * + * Requires: + * \li 'name' is a valid name. + * + * Returns: + * \li true 'name' has a dedicated buffer. + * \li false 'name' does not have a dedicated buffer. + */ + +/*** + *** Properties + ***/ + +bool +dns_name_isabsolute(const dns_name_t *name); +/*%< + * Does 'name' end in the root label? + * + * Requires: + * \li 'name' is a valid name + * + * Returns: + * \li TRUE The last label in 'name' is the root label. + * \li FALSE The last label in 'name' is not the root label. + */ + +bool +dns_name_iswildcard(const dns_name_t *name); +/*%< + * Is 'name' a wildcard name? + * + * Requires: + * \li 'name' is a valid name + * + * \li dns_name_countlabels(name) > 0 + * + * Returns: + * \li TRUE The least significant label of 'name' is '*'. + * \li FALSE The least significant label of 'name' is not '*'. + */ + +unsigned int +dns_name_hash(const dns_name_t *name, bool case_sensitive); +/*%< + * Provide a hash value for 'name'. + * + * Note: if 'case_sensitive' is false, then names which differ only in + * case will have the same hash value. + * + * Requires: + * \li 'name' is a valid name + * + * Returns: + * \li A hash value + */ + +unsigned int +dns_name_fullhash(const dns_name_t *name, bool case_sensitive); +/*%< + * Provide a hash value for 'name'. Unlike dns_name_hash(), this function + * always takes into account of the entire name to calculate the hash value. + * + * Note: if 'case_sensitive' is false, then names which differ only in + * case will have the same hash value. + * + * Requires: + *\li 'name' is a valid name + * + * Returns: + *\li A hash value + */ + +/* + *** Comparisons + ***/ + +dns_namereln_t +dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2, + int *orderp, unsigned int *nlabelsp); +/*%< + * Determine the relative ordering under the DNSSEC order relation of + * 'name1' and 'name2', and also determine the hierarchical + * relationship of the names. + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + * + * Requires: + *\li 'name1' is a valid name + * + *\li dns_name_countlabels(name1) > 0 + * + *\li 'name2' is a valid name + * + *\li dns_name_countlabels(name2) > 0 + * + *\li orderp and nlabelsp are valid pointers. + * + *\li Either name1 is absolute and name2 is absolute, or neither is. + * + * Ensures: + * + *\li *orderp is < 0 if name1 < name2, 0 if name1 = name2, > 0 if + * name1 > name2. + * + *\li *nlabelsp is the number of common significant labels. + * + * Returns: + *\li dns_namereln_none There's no hierarchical relationship + * between name1 and name2. + *\li dns_namereln_contains name1 properly contains name2; i.e. + * name2 is a proper subdomain of name1. + *\li dns_namereln_subdomain name1 is a proper subdomain of name2. + *\li dns_namereln_equal name1 and name2 are equal. + *\li dns_namereln_commonancestor name1 and name2 share a common + * ancestor. + */ + +int +dns_name_compare(const dns_name_t *name1, const dns_name_t *name2); +/*%< + * Determine the relative ordering under the DNSSEC order relation of + * 'name1' and 'name2'. + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + * + * Requires: + * \li 'name1' is a valid name + * + * \li 'name2' is a valid name + * + * \li Either name1 is absolute and name2 is absolute, or neither is. + * + * Returns: + * \li < 0 'name1' is less than 'name2' + * \li 0 'name1' is equal to 'name2' + * \li > 0 'name1' is greater than 'name2' + */ + +bool +dns_name_equal(const dns_name_t *name1, const dns_name_t *name2); +/*%< + * Are 'name1' and 'name2' equal? + * + * Notes: + * \li Because it only needs to test for equality, dns_name_equal() can be + * significantly faster than dns_name_fullcompare() or dns_name_compare(). + * + * \li Offsets tables are not used in the comparison. + * + * \li It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + * + * Requires: + * \li 'name1' is a valid name + * + * \li 'name2' is a valid name + * + * \li Either name1 is absolute and name2 is absolute, or neither is. + * + * Returns: + * \li true 'name1' and 'name2' are equal + * \li false 'name1' and 'name2' are not equal + */ + +bool +dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2); +/*%< + * Case sensitive version of dns_name_equal(). + */ + +int +dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2); +/*%< + * Compare two names as if they are part of rdata in DNSSEC canonical + * form. + * + * Requires: + * \li 'name1' is a valid absolute name + * + * \li dns_name_countlabels(name1) > 0 + * + * \li 'name2' is a valid absolute name + * + * \li dns_name_countlabels(name2) > 0 + * + * Returns: + * \li < 0 'name1' is less than 'name2' + * \li 0 'name1' is equal to 'name2' + * \li > 0 'name1' is greater than 'name2' + */ + +bool +dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2); +/*%< + * Is 'name1' a subdomain of 'name2'? + * + * Notes: + * \li name1 is a subdomain of name2 if name1 is contained in name2, or + * name1 equals name2. + * + * \li It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + * + * Requires: + * \li 'name1' is a valid name + * + * \li 'name2' is a valid name + * + * \li Either name1 is absolute and name2 is absolute, or neither is. + * + * Returns: + * \li TRUE 'name1' is a subdomain of 'name2' + * \li FALSE 'name1' is not a subdomain of 'name2' + */ + +bool +dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname); +/*%< + * Does 'name' match the wildcard specified in 'wname'? + * + * Notes: + * \li name matches the wildcard specified in wname if all labels + * following the wildcard in wname are identical to the same number + * of labels at the end of name. + * + * \li It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + * + * Requires: + * \li 'name' is a valid name + * + * \li dns_name_countlabels(name) > 0 + * + * \li 'wname' is a valid name + * + * \li dns_name_countlabels(wname) > 0 + * + * \li dns_name_iswildcard(wname) is true + * + * \li Either name is absolute and wname is absolute, or neither is. + * + * Returns: + * \li TRUE 'name' matches the wildcard specified in 'wname' + * \li FALSE 'name' does not match the wildcard specified in 'wname' + */ + +/*** + *** Labels + ***/ + +unsigned int +dns_name_countlabels(const dns_name_t *name); +/*%< + * How many labels does 'name' have? + * + * Notes: + * \li In this case, as in other places, a 'label' is an ordinary label. + * + * Requires: + * \li 'name' is a valid name + * + * Ensures: + * \li The result is <= 128. + * + * Returns: + * \li The number of labels in 'name'. + */ + +void +dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label); +/*%< + * Make 'label' refer to the 'n'th least significant label of 'name'. + * + * Notes: + * \li Numbering starts at 0. + * + * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the + * root label. + * + * \li 'label' refers to the same memory as 'name', so 'name' must not + * be changed while 'label' is still in use. + * + * Requires: + * \li n < dns_name_countlabels(name) + */ + +void +dns_name_getlabelsequence(const dns_name_t *source, unsigned int first, + unsigned int n, dns_name_t *target); +/*%< + * Make 'target' refer to the 'n' labels including and following 'first' + * in 'source'. + * + * Notes: + * \li Numbering starts at 0. + * + * \li Given "rc.vix.com.", the label 0 is "rc", and label 3 is the + * root label. + * + * \li 'target' refers to the same memory as 'source', so 'source' + * must not be changed while 'target' is still in use. + * + * Requires: + * \li 'source' and 'target' are valid names. + * + * \li first < dns_name_countlabels(name) + * + * \li first + n <= dns_name_countlabels(name) + */ + +void +dns_name_clone(const dns_name_t *source, dns_name_t *target); +/*%< + * Make 'target' refer to the same name as 'source'. + * + * Notes: + * + * \li 'target' refers to the same memory as 'source', so 'source' + * must not be changed or freed while 'target' is still in use. + * + * \li This call is functionally equivalent to: + * + * \code + * dns_name_getlabelsequence(source, 0, + * dns_name_countlabels(source), + * target); + * \endcode + * + * but is more efficient. Also, dns_name_clone() works even if 'source' + * is empty. + * + * Requires: + * + * \li 'source' is a valid name. + * + * \li 'target' is a valid name that is not read-only. + */ + +/*** + *** Conversions + ***/ + +void +dns_name_fromregion(dns_name_t *name, const isc_region_t *r); +/*%< + * Make 'name' refer to region 'r'. + * + * Note: + * \li If the conversion encounters a root label before the end of the + * region the conversion stops and the length is set to the length + * so far converted. A maximum of 255 bytes is converted. + * + * Requires: + * \li The data in 'r' is a sequence of one or more type 00 or type 01000001 + * labels. + */ + +void +dns_name_toregion(const dns_name_t *name, isc_region_t *r); +/*%< + * Make 'r' refer to 'name'. + * + * Requires: + * + * \li 'name' is a valid name. + * + * \li 'r' is a valid region. + */ + +isc_result_t +dns_name_fromwire(dns_name_t *name, isc_buffer_t *source, + dns_decompress_t *dctx, unsigned int options, + isc_buffer_t *target); +/*%< + * Copy the possibly-compressed name at source (active region) into target, + * decompressing it. + * + * Notes: + * \li Decompression policy is controlled by 'dctx'. + * + * Security: + * + * \li *** WARNING *** + * + * \li This routine will often be used when 'source' contains raw network + * data. A programming error in this routine could result in a denial + * of service, or in the hijacking of the server. + * + * Requires: + * + * \li 'name' is a valid name. + * + * \li 'source' is a valid buffer and the first byte of the active + * region should be the first byte of a DNS wire format domain name. + * + * \li 'target' is a valid buffer or 'target' is NULL and 'name' has + * a dedicated buffer. + * + * \li 'dctx' is a valid decompression context. + * + * \li DNS_NAME_DOWNCASE is not set. + * + * Ensures: + * + * If result is success: + * \li If 'target' is not NULL, 'name' is attached to it. + * + * \li The current location in source is advanced, and the used space + * in target is updated. + * + * Result: + * \li Success + * \li Bad Form: Label Length + * \li Bad Form: Unknown Label Type + * \li Bad Form: Name Length + * \li Bad Form: Compression type not allowed + * \li Bad Form: Bad compression pointer + * \li Bad Form: Input too short + * \li Resource Limit: Not enough space in buffer + */ + +isc_result_t +dns_name_towire(const dns_name_t *name, dns_compress_t *cctx, + isc_buffer_t *target); +isc_result_t +dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx, + isc_buffer_t *target, uint16_t *comp_offsetp); +/*%< + * Convert 'name' into wire format, compressing it as specified by the + * compression context 'cctx', and storing the result in 'target'. + * + * Notes: + * \li If the compression context allows global compression, then the + * global compression table may be updated. + * + * Requires: + * \li 'name' is a valid name + * + * \li dns_name_countlabels(name) > 0 + * + * \li dns_name_isabsolute(name) == TRUE + * + * \li target is a valid buffer. + * + * \li Any offsets specified in a global compression table are valid + * for buffer. + * + * Ensures: + * + * If the result is success: + * + * \li The used space in target is updated. + * + * Returns: + * \li Success + * \li Resource Limit: Not enough space in buffer + */ + +isc_result_t +dns_name_fromtext(dns_name_t *name, isc_buffer_t *source, + const dns_name_t *origin, unsigned int options, + isc_buffer_t *target); +/*%< + * Convert the textual representation of a DNS name at source + * into uncompressed wire form stored in target. + * + * Notes: + * \li Relative domain names will have 'origin' appended to them + * unless 'origin' is NULL, in which case relative domain names + * will remain relative. + * + * \li If DNS_NAME_DOWNCASE is set in 'options', any uppercase letters + * in 'source' will be downcased when they are copied into 'target'. + * + * Requires: + * + * \li 'name' is a valid name. + * + * \li 'source' is a valid buffer. + * + * \li 'target' is a valid buffer or 'target' is NULL and 'name' has + * a dedicated buffer. + * + * Ensures: + * + * If result is success: + * \li If 'target' is not NULL, 'name' is attached to it. + * + * \li Uppercase letters are downcased in the copy iff + * DNS_NAME_DOWNCASE is set in 'options'. + * + * \li The current location in source is advanced, and the used space + * in target is updated. + * + * Result: + *\li #ISC_R_SUCCESS + *\li #DNS_R_EMPTYLABEL + *\li #DNS_R_LABELTOOLONG + *\li #DNS_R_BADESCAPE + *\li #DNS_R_BADDOTTEDQUAD + *\li #ISC_R_NOSPACE + *\li #ISC_R_UNEXPECTEDEND + */ + +#define DNS_NAME_OMITFINALDOT 0x01U +#define DNS_NAME_MASTERFILE 0x02U /* escape $ and @ */ + +isc_result_t +dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target); + +isc_result_t +dns_name_totext(const dns_name_t *name, bool omit_final_dot, + isc_buffer_t *target); + +isc_result_t +dns_name_totext2(const dns_name_t *name, unsigned int options, + isc_buffer_t *target); +/*%< + * Convert 'name' into text format, storing the result in 'target'. + * + * Notes: + *\li If 'omit_final_dot' is true, then the final '.' in absolute + * names other than the root name will be omitted. + * + *\li If DNS_NAME_OMITFINALDOT is set in options, then the final '.' + * in absolute names other than the root name will be omitted. + * + *\li If DNS_NAME_MASTERFILE is set in options, '$' and '@' will also + * be escaped. + * + *\li If dns_name_countlabels == 0, the name will be "@", representing the + * current origin as described by RFC1035. + * + *\li The name is not NUL terminated. + * + * Requires: + * + *\li 'name' is a valid name + * + *\li 'target' is a valid buffer. + * + *\li if dns_name_isabsolute == FALSE, then omit_final_dot == FALSE + * + * Ensures: + * + *\li If the result is success: + * the used space in target is updated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + */ + +#define DNS_NAME_MAXTEXT 1023 +/*%< + * The maximum length of the text representation of a domain + * name as generated by dns_name_totext(). This does not + * include space for a terminating NULL. + * + * This definition is conservative - the actual maximum + * is 1004, derived as follows: + * + * A backslash-decimal escaped character takes 4 bytes. + * A wire-encoded name can be up to 255 bytes and each + * label is one length byte + at most 63 bytes of data. + * Maximizing the label lengths gives us a name of + * three 63-octet labels, one 61-octet label, and the + * root label: + * + * 1 + 63 + 1 + 63 + 1 + 63 + 1 + 61 + 1 = 255 + * + * When printed, this is (3 * 63 + 61) * 4 + * bytes for the escaped label data + 4 bytes for the + * dot terminating each label = 1004 bytes total. + */ + +isc_result_t +dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot, + isc_buffer_t *target); +/*%< + * Convert 'name' into an alternate text format appropriate for filenames, + * storing the result in 'target'. The name data is downcased, guaranteeing + * that the filename does not depend on the case of the converted name. + * + * Notes: + *\li If 'omit_final_dot' is true, then the final '.' in absolute + * names other than the root name will be omitted. + * + *\li The name is not NUL terminated. + * + * Requires: + * + *\li 'name' is a valid absolute name + * + *\li 'target' is a valid buffer. + * + * Ensures: + * + *\li If the result is success: + * the used space in target is updated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + */ + +isc_result_t +dns_name_downcase(const dns_name_t *source, dns_name_t *name, + isc_buffer_t *target); +/*%< + * Downcase 'source'. + * + * Requires: + * + *\li 'source' and 'name' are valid names. + * + *\li If source == name, then + * 'source' must not be read-only + * + *\li Otherwise, + * 'target' is a valid buffer or 'target' is NULL and + * 'name' has a dedicated buffer. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + * + * Note: if source == name, then the result will always be ISC_R_SUCCESS. + */ + +isc_result_t +dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix, + dns_name_t *name, isc_buffer_t *target); +/*%< + * Concatenate 'prefix' and 'suffix'. + * + * Requires: + * + *\li 'prefix' is a valid name or NULL. + * + *\li 'suffix' is a valid name or NULL. + * + *\li 'name' is a valid name or NULL. + * + *\li 'target' is a valid buffer or 'target' is NULL and 'name' has + * a dedicated buffer. + * + *\li If 'prefix' is absolute, 'suffix' must be NULL or the empty name. + * + * Ensures: + * + *\li On success, + * If 'target' is not NULL and 'name' is not NULL, then 'name' + * is attached to it. + * The used space in target is updated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + *\li #DNS_R_NAMETOOLONG + */ + +void +dns_name_split(const dns_name_t *name, unsigned int suffixlabels, + dns_name_t *prefix, dns_name_t *suffix); +/*%< + * + * Split 'name' into two pieces on a label boundary. + * + * Notes: + * \li 'name' is split such that 'suffix' holds the most significant + * 'suffixlabels' labels. All other labels are stored in 'prefix'. + * + *\li Copying name data is avoided as much as possible, so 'prefix' + * and 'suffix' will end up pointing at the data for 'name'. + * + *\li It is legitimate to pass a 'prefix' or 'suffix' that has + * its name data stored someplace other than the dedicated buffer. + * This is useful to avoid name copying in the calling function. + * + *\li It is also legitimate to pass a 'prefix' or 'suffix' that is + * the same dns_name_t as 'name'. + * + * Requires: + *\li 'name' is a valid name. + * + *\li 'suffixlabels' cannot exceed the number of labels in 'name'. + * + * \li 'prefix' is a valid name or NULL, and cannot be read-only. + * + *\li 'suffix' is a valid name or NULL, and cannot be read-only. + * + * Ensures: + * + *\li On success: + * If 'prefix' is not NULL it will contain the least significant + * labels. + * If 'suffix' is not NULL it will contain the most significant + * labels. dns_name_countlabels(suffix) will be equal to + * suffixlabels. + * + *\li On failure: + * Either 'prefix' or 'suffix' is invalidated (depending + * on which one the problem was encountered with). + * + * Returns: + *\li #ISC_R_SUCCESS No worries. (This function should always success). + */ + +void +dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target); +/*%< + * Make 'target' a dynamically allocated copy of 'source'. + * + * Requires: + * + *\li 'source' is a valid non-empty name. + * + *\li 'target' is a valid name that is not read-only. + * + *\li 'mctx' is a valid memory context. + */ + +isc_result_t +dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx, + dns_name_t *target); +/*%< + * Make 'target' a read-only dynamically allocated copy of 'source'. + * 'target' will also have a dynamically allocated offsets table. + * + * Requires: + * + *\li 'source' is a valid non-empty name. + * + *\li 'target' is a valid name that is not read-only. + * + *\li 'target' has no offsets table. + * + *\li 'mctx' is a valid memory context. + */ + +void +dns_name_free(dns_name_t *name, isc_mem_t *mctx); +/*%< + * Free 'name'. + * + * Requires: + * + *\li 'name' is a valid name created previously in 'mctx' by dns_name_dup(). + * + *\li 'mctx' is a valid memory context. + * + * Ensures: + * + *\li All dynamic resources used by 'name' are freed and the name is + * invalidated. + */ + +isc_result_t +dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg); +/*%< + * Send 'name' in DNSSEC canonical form to 'digest'. + * + * Requires: + * + *\li 'name' is a valid name. + * + *\li 'digest' is a valid dns_digestfunc_t. + * + * Ensures: + * + *\li If successful, the DNSSEC canonical form of 'name' will have been + * sent to 'digest'. + * + *\li If digest() returns something other than ISC_R_SUCCESS, that result + * will be returned as the result of dns_name_digest(). + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Many other results are possible if not successful. + * + */ + +bool +dns_name_dynamic(const dns_name_t *name); +/*%< + * Returns whether there is dynamic memory associated with this name. + * + * Requires: + * + *\li 'name' is a valid name. + * + * Returns: + * + *\li 'true' if the name is dynamic otherwise 'false'. + */ + +isc_result_t +dns_name_print(const dns_name_t *name, FILE *stream); +/*%< + * Print 'name' on 'stream'. + * + * Requires: + * + *\li 'name' is a valid name. + * + *\li 'stream' is a valid stream. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_name_totext() can return. + */ + +void +dns_name_format(const dns_name_t *name, char *cp, unsigned int size); +/*%< + * Format 'name' as text appropriate for use in log messages. + * + * Store the formatted name at 'cp', writing no more than + * 'size' bytes. The resulting string is guaranteed to be + * null terminated. + * + * The formatted name will have a terminating dot only if it is + * the root. + * + * This function cannot fail, instead any errors are indicated + * in the returned text. + * + * Requires: + * + *\li 'name' is a valid name. + * + *\li 'cp' points a valid character array of size 'size'. + * + *\li 'size' > 0. + * + */ + +isc_result_t +dns_name_tostring(const dns_name_t *source, char **target, isc_mem_t *mctx); +/*%< + * Convert 'name' to string format, allocating sufficient memory to + * hold it (free with isc_mem_free()). + * + * Differs from dns_name_format in that it allocates its own memory. + * + * Requires: + * + *\li 'name' is a valid name. + *\li 'target' is not NULL. + *\li '*target' is NULL. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + * + *\li Any error that dns_name_totext() can return. + */ + +isc_result_t +dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options, + isc_mem_t *mctx); +isc_result_t +dns_name_fromstring2(dns_name_t *target, const char *src, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx); +/*%< + * Convert a string to a name and place it in target, allocating memory + * as necessary. 'options' has the same semantics as that of + * dns_name_fromtext(). + * + * If 'target' has a buffer then the name will be copied into it rather than + * memory being allocated. + * + * Requires: + * + * \li 'target' is a valid name that is not read-only. + * \li 'src' is not NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_name_fromtext() can return. + * + *\li Any error that dns_name_dup() can return. + */ + +isc_result_t +dns_name_settotextfilter(dns_name_totextfilter_t *proc); +/*%< + * Set / clear a thread specific function 'proc' to be called at the + * end of dns_name_totext(). + * + * Note: Under Windows you need to call "dns_name_settotextfilter(NULL);" + * prior to exiting the thread otherwise memory will be leaked. + * For other platforms, which are pthreads based, this is still a good + * idea but not required. + * + * Returns + *\li #ISC_R_SUCCESS + *\li #ISC_R_UNEXPECTED + */ + +#define DNS_NAME_FORMATSIZE (DNS_NAME_MAXTEXT + 1) +/*%< + * Suggested size of buffer passed to dns_name_format(). + * Includes space for the terminating NULL. + */ + +void +dns_name_copy(const dns_name_t *source, dns_name_t *dest); +/*%< + * Copies the name in 'source' into 'dest'. The name data is copied to + * the dedicated buffer for 'dest'. (If copying to a name that doesn't + * have a dedicated buffer, use dns_name_setbuffer() first.) + * + * Requires: + * \li 'source' is a valid name. + * + * \li 'dest' is an initialized name with a dedicated buffer. + */ + +bool +dns_name_ishostname(const dns_name_t *name, bool wildcard); +/*%< + * Return if 'name' is a valid hostname. RFC 952 / RFC 1123. + * If 'wildcard' is true then allow the first label of name to + * be a wildcard. + * The root is also accepted. + * + * Requires: + * 'name' to be valid. + */ + +bool +dns_name_ismailbox(const dns_name_t *name); +/*%< + * Return if 'name' is a valid mailbox. RFC 821. + * + * Requires: + * \li 'name' to be valid. + */ + +bool +dns_name_internalwildcard(const dns_name_t *name); +/*%< + * Return if 'name' contains a internal wildcard name. + * + * Requires: + * \li 'name' to be valid. + */ + +bool +dns_name_isdnssd(const dns_name_t *owner); +/*%< + * Determine if the 'owner' is a DNS-SD prefix. + */ + +bool +dns_name_isrfc1918(const dns_name_t *owner); +/*%< + * Determine if the 'name' is in the RFC 1918 reverse namespace. + */ + +bool +dns_name_isula(const dns_name_t *owner); +/*%< + * Determine if the 'name' is in the ULA reverse namespace. + */ + +bool +dns_name_istat(const dns_name_t *name); +/* + * Determine if 'name' is a potential 'trust-anchor-telemetry' name. + */ + +ISC_LANG_ENDDECLS + +/* + *** High Performance Macros + ***/ + +/* + * WARNING: Use of these macros by applications may require recompilation + * of the application in some situations where calling the function + * would not. + * + * WARNING: No assertion checking is done for these macros. + */ + +#define DNS_NAME_INIT(n, o) \ + do { \ + dns_name_t *_n = (n); \ + /* memset(_n, 0, sizeof(*_n)); */ \ + _n->magic = DNS_NAME_MAGIC; \ + _n->ndata = NULL; \ + _n->length = 0; \ + _n->labels = 0; \ + _n->attributes = 0; \ + _n->offsets = (o); \ + _n->buffer = NULL; \ + ISC_LINK_INIT(_n, link); \ + ISC_LIST_INIT(_n->list); \ + } while (0) + +#define DNS_NAME_RESET(n) \ + do { \ + (n)->ndata = NULL; \ + (n)->length = 0; \ + (n)->labels = 0; \ + (n)->attributes &= ~DNS_NAMEATTR_ABSOLUTE; \ + if ((n)->buffer != NULL) \ + isc_buffer_clear((n)->buffer); \ + } while (0) + +#define DNS_NAME_SETBUFFER(n, b) (n)->buffer = (b) + +#define DNS_NAME_ISABSOLUTE(n) \ + (((n)->attributes & DNS_NAMEATTR_ABSOLUTE) != 0 ? true : false) + +#define DNS_NAME_COUNTLABELS(n) ((n)->labels) + +#define DNS_NAME_TOREGION(n, r) \ + do { \ + (r)->base = (n)->ndata; \ + (r)->length = (n)->length; \ + } while (0) + +#define DNS_NAME_SPLIT(n, l, p, s) \ + do { \ + dns_name_t *_n = (n); \ + dns_name_t *_p = (p); \ + dns_name_t *_s = (s); \ + unsigned int _l = (l); \ + if (_p != NULL) \ + dns_name_getlabelsequence(_n, 0, _n->labels - _l, _p); \ + if (_s != NULL) \ + dns_name_getlabelsequence(_n, _n->labels - _l, _l, \ + _s); \ + } while (0) + +#ifdef DNS_NAME_USEINLINE + +#define dns_name_init(n, o) DNS_NAME_INIT(n, o) +#define dns_name_reset(n) DNS_NAME_RESET(n) +#define dns_name_setbuffer(n, b) DNS_NAME_SETBUFFER(n, b) +#define dns_name_countlabels(n) DNS_NAME_COUNTLABELS(n) +#define dns_name_isabsolute(n) DNS_NAME_ISABSOLUTE(n) +#define dns_name_toregion(n, r) DNS_NAME_TOREGION(n, r) +#define dns_name_split(n, l, p, s) DNS_NAME_SPLIT(n, l, p, s) + +#endif /* DNS_NAME_USEINLINE */ diff --git a/lib/dns/include/dns/ncache.h b/lib/dns/include/dns/ncache.h new file mode 100644 index 0000000..ce62136 --- /dev/null +++ b/lib/dns/include/dns/ncache.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/ncache.h + *\brief + * DNS Ncache + * + * XXX TBS XXX + * + * MP: + *\li The caller must ensure any required synchronization. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li RFC2308 + */ + +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * _OMITDNSSEC: + * Omit DNSSEC records when rendering. + */ +#define DNS_NCACHETOWIRE_OMITDNSSEC 0x0001 + +isc_result_t +dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, dns_rdataset_t *addedrdataset); +isc_result_t +dns_ncache_addoptout(dns_message_t *message, dns_db_t *cache, + dns_dbnode_t *node, dns_rdatatype_t covers, + isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl, + bool optout, dns_rdataset_t *addedrdataset); +/*%< + * Convert the authority data from 'message' into a negative cache + * rdataset, and store it in 'cache' at 'node' with a TTL limited to + * 'maxttl'. + * + * \li dns_ncache_add produces a negative cache entry with a trust of no + * more than answer + * \li dns_ncache_addoptout produces a negative cache entry which will have + * a trust of secure if all the records that make up the entry are secure. + * + * The 'covers' argument is the RR type whose nonexistence we are caching, + * or dns_rdatatype_any when caching a NXDOMAIN response. + * + * 'optout' indicates a DNS_RDATASETATTR_OPTOUT should be set. + * + * Note: + *\li If 'addedrdataset' is not NULL, then it will be attached to the added + * rdataset. See dns_db_addrdataset() for more details. + * + * Requires: + *\li 'message' is a valid message with a properly formatting negative cache + * authority section. + * + *\li The requirements of dns_db_addrdataset() apply to 'cache', 'node', + * 'now', and 'addedrdataset'. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + * + *\li Any result code of dns_db_addrdataset() is a possible result code + * of dns_ncache_add(). + */ + +isc_result_t +dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx, + isc_buffer_t *target, unsigned int options, + unsigned int *countp); +/*%< + * Convert the negative caching rdataset 'rdataset' to wire format, + * compressing names as specified in 'cctx', and storing the result in + * 'target'. If 'omit_dnssec' is set, DNSSEC records will not + * be added to 'target'. + * + * Notes: + *\li The number of RRs added to target will be added to *countp. + * + * Requires: + *\li 'rdataset' is a valid negative caching rdataset. + * + *\li 'rdataset' is not empty. + * + *\li 'countp' is a valid pointer. + * + * Ensures: + *\li On a return of ISC_R_SUCCESS, 'target' contains a wire format + * for the data contained in 'rdataset'. Any error return leaves + * the buffer unchanged. + * + *\li *countp has been incremented by the number of RRs added to + * target. + * + * Returns: + *\li #ISC_R_SUCCESS - all ok + *\li #ISC_R_NOSPACE - 'target' doesn't have enough room + * + *\li Any error returned by dns_rdata_towire(), dns_rdataset_next(), + * dns_name_towire(). + */ + +isc_result_t +dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, + dns_rdatatype_t type, dns_rdataset_t *rdataset); +/*%< + * Search the negative caching rdataset for an rdataset with the + * specified name and type. + * + * Requires: + *\li 'ncacherdataset' is a valid negative caching rdataset. + * + *\li 'ncacherdataset' is not empty. + * + *\li 'name' is a valid name. + * + *\li 'type' is not SIG, or a meta-RR type. + * + *\li 'rdataset' is a valid disassociated rdataset. + * + * Ensures: + *\li On a return of ISC_R_SUCCESS, 'rdataset' is bound to the found + * rdataset. + * + * Returns: + *\li #ISC_R_SUCCESS - the rdataset was found. + *\li #ISC_R_NOTFOUND - the rdataset was not found. + * + */ + +isc_result_t +dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, + dns_rdatatype_t covers, dns_rdataset_t *rdataset); +/*%< + * Similar to dns_ncache_getrdataset() but get the rrsig that matches. + */ + +void +dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found, + dns_rdataset_t *rdataset); + +/*%< + * Extract the current rdataset and name from a ncache entry. + * + * Requires: + * \li 'ncacherdataset' to be valid and to be a negative cache entry + * \li 'found' to be valid. + * \li 'rdataset' to be unassociated. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h new file mode 100644 index 0000000..e68ea35 --- /dev/null +++ b/lib/dns/include/dns/nsec.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/nsec.h */ + +#include + +#include + +#include +#include +#include + +#define DNS_NSEC_BUFFERSIZE (DNS_NAME_MAXWIRE + 8192 + 512) + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *target, unsigned char *buffer, + dns_rdata_t *rdata); +/*%< + * Build the rdata of a NSEC record. + * + * Requires: + *\li buffer Points to a temporary buffer of at least + * DNS_NSEC_BUFFERSIZE bytes. + *\li rdata Points to an initialized dns_rdata_t. + * + * Ensures: + * \li *rdata Contains a valid NSEC rdata. The 'data' member refers + * to 'buffer'. + */ + +isc_result_t +dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *target, dns_ttl_t ttl); +/*%< + * Build a NSEC record and add it to a database. + */ + +bool +dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type); +/*%< + * Determine if a type is marked as present in an NSEC record. + * + * Requires: + *\li 'nsec' points to a valid rdataset of type NSEC + */ + +isc_result_t +dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff, + bool *answer); +/* + * Report whether the DNSKEY RRset has a NSEC only algorithm. Unknown + * algorithms are assumed to support NSEC3. If DNSKEY is not found, + * *answer is set to false, and ISC_R_NOTFOUND is returned. + * If 'diff' is provided, check if the NSEC only DNSKEY will be deleted. + * If so, and there are no other NSEC only DNSKEYs that will stay in 'db', + * consider the DNSKEY RRset to have no NSEC only DNSKEYs. + * + * Requires: + * 'answer' to be non NULL. + */ + +unsigned int +dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw, + unsigned int max_type); +/*%< + * Convert a raw bitmap into a compressed windowed bit map. 'map' and 'raw' + * may overlap. + * + * Returns the length of the compressed windowed bit map. + */ + +void +dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit); +/*%< + * Set type bit in raw 'array' to 'bit'. + */ + +bool +dns_nsec_isset(const unsigned char *array, unsigned int type); +/*%< + * Test if the corresponding 'type' bit is set in 'array'. + */ + +isc_result_t +dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + const dns_name_t *nsecname, dns_rdataset_t *nsecset, + bool *exists, bool *data, dns_name_t *wild, + dns_nseclog_t log, void *arg); +/*% + * Return ISC_R_SUCCESS if we can determine that the name doesn't exist + * or we can determine whether there is data or not at the name. + * If the name does not exist return the wildcard name. + * + * Return DNS_R_DNAME when the NSEC indicates that name is covered by + * a DNAME. 'wild' is not set in this case. + * + * Return ISC_R_IGNORE when the NSEC is not the appropriate one. + */ + +bool +dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset); +/* + * Return true if all the NSEC records in rdataset have both + * NSEC and RRSIG present. + * + * Requires: + * \li rdataset to be a NSEC rdataset. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h new file mode 100644 index 0000000..e4da790 --- /dev/null +++ b/lib/dns/include/dns/nsec3.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DNS_NSEC3_SALTSIZE 255 +#define DNS_NSEC3_MAXITERATIONS 150U + +/* + * hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max) + * hash length = 1, hash = 255 (max), bitmap = 8192 + 512 (max) + */ +#define DNS_NSEC3_BUFFERSIZE (6 + 255 + 255 + 8192 + 512) +/* + * hash = 1, flags = 1, iterations = 2, salt length = 1, salt = 255 (max) + */ +#define DNS_NSEC3PARAM_BUFFERSIZE (5 + 255) + +/* + * Test "unknown" algorithm. Is mapped to dns_hash_sha1. + */ +#define DNS_NSEC3_UNKNOWNALG ((dns_hash_t)245U) + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + unsigned int hashalg, unsigned int optin, + unsigned int iterations, const unsigned char *salt, + size_t salt_length, const unsigned char *nexthash, + size_t hash_length, unsigned char *buffer, + dns_rdata_t *rdata); +/*%< + * Build the rdata of a NSEC3 record for the data at 'node'. + * Note: 'node' is not the node where the NSEC3 record will be stored. + * + * Requires: + * buffer Points to a temporary buffer of at least + * DNS_NSEC_BUFFERSIZE bytes. + * rdata Points to an initialized dns_rdata_t. + * + * Ensures: + * *rdata Contains a valid NSEC3 rdata. The 'data' member refers + * to 'buffer'. + */ + +bool +dns_nsec3_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type); +/*%< + * Determine if a type is marked as present in an NSEC3 record. + * + * Requires: + * 'nsec' points to a valid rdataset of type NSEC3 + */ + +isc_result_t +dns_nsec3_generate_salt(unsigned char *salt, size_t saltlen); +/*%< + * Generate a salt with the given salt length. + */ + +isc_result_t +dns_nsec3_hashname(dns_fixedname_t *result, + unsigned char rethash[NSEC3_MAX_HASH_LENGTH], + size_t *hash_length, const dns_name_t *name, + const dns_name_t *origin, dns_hash_t hashalg, + unsigned int iterations, const unsigned char *salt, + size_t saltlength); +/*%< + * Make a hashed domain name from an unhashed one. If rethash is not NULL + * the raw hash is stored there. + */ + +unsigned int +dns_nsec3_hashlength(dns_hash_t hash); +/*%< + * Return the length of the hash produced by the specified algorithm + * or zero when unknown. + */ + +bool +dns_nsec3_supportedhash(dns_hash_t hash); +/*%< + * Return whether we support this hash algorithm or not. + */ + +isc_result_t +dns_nsec3_addnsec3(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_ttl_t nsecttl, + bool unsecure, dns_diff_t *diff); + +isc_result_t +dns_nsec3_addnsec3s(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, + dns_diff_t *diff); + +isc_result_t +dns_nsec3_addnsec3sx(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, + dns_rdatatype_t private, dns_diff_t *diff); +/*%< + * Add NSEC3 records for 'name', recording the change in 'diff'. + * Adjust previous NSEC3 records, if any, to reflect the addition. + * The existing NSEC3 records are removed. + * + * dns_nsec3_addnsec3() will only add records to the chain identified by + * 'nsec3param'. + * + * 'unsecure' should be set to reflect if this is a potentially + * unsecure delegation (no DS record). + * + * dns_nsec3_addnsec3s() will examine the NSEC3PARAM RRset to determine which + * chains to be updated. NSEC3PARAM records with the DNS_NSEC3FLAG_CREATE + * will be preferentially chosen over NSEC3PARAM records without + * DNS_NSEC3FLAG_CREATE set. NSEC3PARAM records with DNS_NSEC3FLAG_REMOVE + * set will be ignored by dns_nsec3_addnsec3s(). If DNS_NSEC3FLAG_CREATE + * is set then the new NSEC3 will have OPTOUT set to match the that in the + * NSEC3PARAM record otherwise OPTOUT will be inherited from the previous + * record in the chain. + * + * dns_nsec3_addnsec3sx() is similar to dns_nsec3_addnsec3s() but 'private' + * specifies the type of the private rdataset to be checked in addition to + * the nsec3param rdataset at the zone apex. + * + * Requires: + * 'db' to be valid. + * 'version' to be valid or NULL. + * 'name' to be valid. + * 'nsec3param' to be valid. + * 'diff' to be valid. + */ + +isc_result_t +dns_nsec3_delnsec3(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff); + +isc_result_t +dns_nsec3_delnsec3s(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_diff_t *diff); + +isc_result_t +dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_rdatatype_t private, + dns_diff_t *diff); +/*%< + * Remove NSEC3 records for 'name', recording the change in 'diff'. + * Adjust previous NSEC3 records, if any, to reflect the removal. + * + * dns_nsec3_delnsec3() performs the above for the chain identified by + * 'nsec3param'. + * + * dns_nsec3_delnsec3s() examines the NSEC3PARAM RRset in a similar manner + * to dns_nsec3_addnsec3s(). Unlike dns_nsec3_addnsec3s() updated NSEC3 + * records have the OPTOUT flag preserved. + * + * dns_nsec3_delnsec3sx() is similar to dns_nsec3_delnsec3s() but 'private' + * specifies the type of the private rdataset to be checked in addition to + * the nsec3param rdataset at the zone apex. + * + * Requires: + * 'db' to be valid. + * 'version' to be valid or NULL. + * 'name' to be valid. + * 'nsec3param' to be valid. + * 'diff' to be valid. + */ + +isc_result_t +dns_nsec3_active(dns_db_t *db, dns_dbversion_t *version, bool complete, + bool *answer); + +isc_result_t +dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete, + dns_rdatatype_t private, bool *answer); +/*%< + * Check if there are any complete/to be built NSEC3 chains. + * If 'complete' is true only complete chains will be recognized. + * + * dns_nsec3_activex() is similar to dns_nsec3_active() but 'private' + * specifies the type of the private rdataset to be checked in addition to + * the nsec3param rdataset at the zone apex. + * + * Requires: + * 'db' to be valid. + * 'version' to be valid or NULL. + * 'answer' to be non NULL. + */ + +unsigned int +dns_nsec3_maxiterations(void); +/*%< + * Return the maximum permissible number of NSEC3 iterations. + */ + +bool +dns_nsec3param_fromprivate(dns_rdata_t *src, dns_rdata_t *target, + unsigned char *buf, size_t buflen); +/*%< + * Convert a private rdata to a nsec3param rdata. + * + * Return true if 'src' could be successfully converted. + * + * 'buf' should be at least DNS_NSEC3PARAM_BUFFERSIZE in size. + */ + +void +dns_nsec3param_toprivate(dns_rdata_t *src, dns_rdata_t *target, + dns_rdatatype_t privatetype, unsigned char *buf, + size_t buflen); +/*%< + * Convert a nsec3param rdata to a private rdata. + * + * 'buf' should be at least src->length + 1 in size. + */ + +isc_result_t +dns_nsec3param_salttotext(dns_rdata_nsec3param_t *nsec3param, char *dst, + size_t dstlen); +/*%< + * Convert the salt of given NSEC3PARAM RDATA into hex-encoded, NULL-terminated + * text stored at "dst". + * + * Requires: + * + *\li "dst" to have enough space (as indicated by "dstlen") to hold the + * resulting text and its NULL-terminating byte. + */ + +isc_result_t +dns_nsec3param_deletechains(dns_db_t *db, dns_dbversion_t *ver, + dns_zone_t *zone, bool nonsec, dns_diff_t *diff); + +/*%< + * Mark NSEC3PARAM for deletion. + */ + +isc_result_t +dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + const dns_name_t *nsec3name, dns_rdataset_t *nsec3set, + dns_name_t *zonename, bool *exists, bool *data, + bool *optout, bool *unknown, bool *setclosest, + bool *setnearest, dns_name_t *closest, + dns_name_t *nearest, dns_nseclog_t logit, void *arg); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h new file mode 100644 index 0000000..eb3f0dc --- /dev/null +++ b/lib/dns/include/dns/nta.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * The NTA module provides services for storing and retrieving negative + * trust anchors, and determine whether a given domain is subject to + * DNSSEC validation. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +struct dns_ntatable { + /* Unlocked. */ + unsigned int magic; + dns_view_t *view; + isc_rwlock_t rwlock; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + isc_task_t *task; + /* Protected by atomics */ + isc_refcount_t references; + /* Locked by rwlock. */ + dns_rbt_t *table; + bool shuttingdown; +}; + +#define NTATABLE_MAGIC ISC_MAGIC('N', 'T', 'A', 't') +#define VALID_NTATABLE(nt) ISC_MAGIC_VALID(nt, NTATABLE_MAGIC) + +isc_result_t +dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep); +/*%< + * Create an NTA table in view 'view'. + * + * Requires: + * + *\li 'view' is a valid view. + * + *\li 'tmgr' is a valid timer manager. + * + *\li ntatablep != NULL && *ntatablep == NULL + * + * Ensures: + * + *\li On success, *ntatablep is a valid, empty NTA table. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li Any other result indicates failure. + */ + +void +dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + *\li 'source' is a valid ntatable. + * + *\li 'targetp' points to a NULL dns_ntatable_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + */ + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep); +/*%< + * Detach *ntatablep from its ntatable. + * + * Requires: + * + *\li 'ntatablep' points to a valid ntatable. + * + * Ensures: + * + *\li *ntatablep is NULL. + * + *\li If '*ntatablep' is the last reference to the ntatable, + * all resources used by the ntatable will be freed + */ + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force, + isc_stdtime_t now, uint32_t lifetime); +/*%< + * Add a negative trust anchor to 'ntatable' for name 'name', + * which will expire at time 'now' + 'lifetime'. If 'force' is true, + * then the NTA will persist for the entire specified lifetime. + * If it is false, then the name will be queried periodically and + * validation will be attempted to see whether it's still bogus; + * if validation is successful, the NTA will be allowed to expire + * early and validation below the NTA will resume. + * + * Notes: + * + *\li If an NTA already exists in the table, its expiry time + * is updated. + * + * Requires: + * + *\li 'ntatable' points to a valid ntatable. + * + *\li 'name' points to a valid name. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *keyname); +/*%< + * Delete node(s) from 'ntatable' matching name 'keyname' + * + * Requires: + * + *\li 'ntatable' points to a valid ntatable. + * + *\li 'name' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +bool +dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, + const dns_name_t *name, const dns_name_t *anchor); +/*%< + * Return true if 'name' is below a non-expired negative trust + * anchor which in turn is at or below 'anchor'. + * + * If 'ntatable' has not been initialized, return false. + * + * Requires: + * + *\li 'ntatable' is NULL or is a valid ntatable. + * + *\li 'name' is a valid absolute name. + */ + +isc_result_t +dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view, + isc_buffer_t **buf); +/*%< + * Dump the NTA table to buffer at 'buf', with view names + * + * Requires: + * \li "ntatable" is a valid table. + * + * \li "*buf" is a valid buffer. + */ + +isc_result_t +dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp); +/*%< + * Save the NTA table to the file opened as 'fp', for later loading. + */ + +void +dns_ntatable_shutdown(dns_ntatable_t *ntatable); +/*%< + * Cancel future checks to see if NTAs can be removed. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/opcode.h b/lib/dns/include/dns/opcode.h new file mode 100644 index 0000000..6b8f6f8 --- /dev/null +++ b/lib/dns/include/dns/opcode.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/opcode.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target); +/*%< + * Put a textual representation of error 'opcode' into 'target'. + * + * Requires: + *\li 'opcode' is a valid opcode. + * + *\li 'target' is a valid text buffer. + * + * Ensures: + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/order.h b/lib/dns/include/dns/order.h new file mode 100644 index 0000000..4b0a25f --- /dev/null +++ b/lib/dns/include/dns/order.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/order.h */ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_order_create(isc_mem_t *mctx, dns_order_t **orderp); +/*%< + * Create a order object. + * + * Requires: + * \li 'orderp' to be non NULL and '*orderp == NULL'. + *\li 'mctx' to be valid. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + */ + +isc_result_t +dns_order_add(dns_order_t *order, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass, + unsigned int mode); +/*%< + * Add a entry to the end of the order list. + * + * Requires: + * \li 'order' to be valid. + *\li 'name' to be valid. + *\li 'mode' to be one of #DNS_RDATASETATTR_RANDOMIZE, + * #DNS_RDATASETATTR_FIXEDORDER or zero (#DNS_RDATASETATTR_CYCLIC). + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +unsigned int +dns_order_find(dns_order_t *order, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass); +/*%< + * Find the first matching entry on the list. + * + * Requires: + *\li 'order' to be valid. + *\li 'name' to be valid. + * + * Returns the mode set by dns_order_add() or zero. + */ + +void +dns_order_attach(dns_order_t *source, dns_order_t **target); +/*%< + * Attach to the 'source' object. + * + * Requires: + * \li 'source' to be valid. + *\li 'target' to be non NULL and '*target == NULL'. + */ + +void +dns_order_detach(dns_order_t **orderp); +/*%< + * Detach from the object. Clean up if last this was the last + * reference. + * + * Requires: + *\li '*orderp' to be valid. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/peer.h b/lib/dns/include/dns/peer.h new file mode 100644 index 0000000..101e6f2 --- /dev/null +++ b/lib/dns/include/dns/peer.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/peer.h + * \brief + * Data structures for peers (e.g. a 'server' config file statement) + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include + +#include + +#define DNS_PEERLIST_MAGIC ISC_MAGIC('s', 'e', 'R', 'L') +#define DNS_PEER_MAGIC ISC_MAGIC('S', 'E', 'r', 'v') + +#define DNS_PEERLIST_VALID(ptr) ISC_MAGIC_VALID(ptr, DNS_PEERLIST_MAGIC) +#define DNS_PEER_VALID(ptr) ISC_MAGIC_VALID(ptr, DNS_PEER_MAGIC) + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list); + +void +dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target); + +void +dns_peerlist_detach(dns_peerlist_t **list); + +/* + * After return caller still holds a reference to peer. + */ +void +dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer); + +/* + * Ditto. */ +isc_result_t +dns_peerlist_peerbyaddr(dns_peerlist_t *peers, const isc_netaddr_t *addr, + dns_peer_t **retval); + +/* + * What he said. + */ +isc_result_t +dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval); + +isc_result_t +dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *ipaddr, dns_peer_t **peer); + +isc_result_t +dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *ipaddr, + unsigned int prefixlen, dns_peer_t **peer); + +void +dns_peer_attach(dns_peer_t *source, dns_peer_t **target); + +void +dns_peer_detach(dns_peer_t **list); + +isc_result_t +dns_peer_setbogus(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getbogus(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setrequestixfr(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setprovideixfr(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setrequestnsid(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setsendcookie(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getsendcookie(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setrequestexpire(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setsupportedns(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getforcetcp(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_setforcetcp(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getsupportedns(dns_peer_t *peer, bool *retval); + +isc_result_t +dns_peer_settransfers(dns_peer_t *peer, uint32_t newval); + +isc_result_t +dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval); + +isc_result_t +dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval); + +isc_result_t +dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval); + +isc_result_t +dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval); + +isc_result_t +dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval); + +isc_result_t +dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval); + +isc_result_t +dns_peer_settransfersource(dns_peer_t *peer, + const isc_sockaddr_t *transfer_source); + +isc_result_t +dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source); + +isc_result_t +dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize); + +isc_result_t +dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize); + +isc_result_t +dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp); + +isc_result_t +dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp); + +isc_result_t +dns_peer_setpadding(dns_peer_t *peer, uint16_t padding); + +isc_result_t +dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding); + +isc_result_t +dns_peer_setnotifysource(dns_peer_t *peer, const isc_sockaddr_t *notify_source); + +isc_result_t +dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source); + +isc_result_t +dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source); + +isc_result_t +dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source); + +isc_result_t +dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion); + +isc_result_t +dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion); +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/private.h b/lib/dns/include/dns/private.h new file mode 100644 index 0000000..675510c --- /dev/null +++ b/lib/dns/include/dns/private.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#include +#include + +#include +#include + +#pragma once + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_private_chains(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatatype_t privatetype, bool *build_nsec, + bool *build_nsec3); +/*%< + * Examine the NSEC, NSEC3PARAM and privatetype RRsets at the apex of the + * database to determine which of NSEC or NSEC3 chains we are currently + * maintaining. In normal operations only one of NSEC or NSEC3 is being + * maintained but when we are transitiong between NSEC and NSEC3 we need + * to update both sets of chains. If 'privatetype' is zero then the + * privatetype RRset will not be examined. + * + * Requires: + * \li 'db' is valid. + * \li 'version' is valid or NULL. + * \li 'build_nsec' is a pointer to a bool or NULL. + * \li 'build_nsec3' is a pointer to a bool or NULL. + * + * Returns: + * \li ISC_R_SUCCESS, 'build_nsec' and 'build_nsec3' will be valid. + * \li other on error + */ + +isc_result_t +dns_private_totext(dns_rdata_t *privaterdata, isc_buffer_t *buffer); +/*%< + * Convert a private-type RR 'privaterdata' to human-readable form, + * and place the result in 'buffer'. The text should indicate + * which action the private-type record specifies and whether the + * action has been completed. + * + * Requires: + * \li 'privaterdata' is a valid rdata containing at least five bytes + * \li 'buffer' is a valid buffer + * + * Returns: + * \li ISC_R_SUCCESS + * \li other on error + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h new file mode 100644 index 0000000..3b62e12 --- /dev/null +++ b/lib/dns/include/dns/rbt.h @@ -0,0 +1,995 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rbt.h */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*@{*/ +/*% + * Option values for dns_rbt_findnode() and dns_rbt_findname(). + * These are used to form a bitmask. + */ +#define DNS_RBTFIND_NOOPTIONS 0x00 +#define DNS_RBTFIND_EMPTYDATA 0x01 +#define DNS_RBTFIND_NOEXACT 0x02 +#define DNS_RBTFIND_NOPREDECESSOR 0x04 +/*@}*/ + +#define DNS_RBT_USEMAGIC 1 + +#define DNS_RBT_LOCKLENGTH (sizeof(((dns_rbtnode_t *)0)->locknum) * 8) + +#define DNS_RBTNODE_MAGIC ISC_MAGIC('R', 'B', 'N', 'O') +#if DNS_RBT_USEMAGIC +#define DNS_RBTNODE_VALID(n) ISC_MAGIC_VALID(n, DNS_RBTNODE_MAGIC) +#else /* if DNS_RBT_USEMAGIC */ +#define DNS_RBTNODE_VALID(n) true +#endif /* if DNS_RBT_USEMAGIC */ + +/*% + * This is the structure that is used for each node in the red/black + * tree of trees. NOTE WELL: the implementation manages this as a variable + * length structure, with the actual wire-format name and other data + * appended to this structure. Allocating a contiguous block of memory for + * multiple dns_rbtnode structures will not work. + */ +typedef struct dns_rbtnode dns_rbtnode_t; +enum { + DNS_RBT_NSEC_NORMAL = 0, /* in main tree */ + DNS_RBT_NSEC_HAS_NSEC = 1, /* also has node in nsec tree */ + DNS_RBT_NSEC_NSEC = 2, /* in nsec tree */ + DNS_RBT_NSEC_NSEC3 = 3 /* in nsec3 tree */ +}; +struct dns_rbtnode { +#if DNS_RBT_USEMAGIC + unsigned int magic; +#endif /* if DNS_RBT_USEMAGIC */ + /*@{*/ + /*! + * The following bitfields add up to a total bitwidth of 32. + * The range of values necessary for each item is indicated, + * but in the case of "attributes" the field is wider to accommodate + * possible future expansion. + * + * In each case below the "range" indicated is what's _necessary_ for + * the bitfield to hold, not what it actually _can_ hold. + * + * Note: Tree lock must be held before modifying these + * bit-fields. + * + * Note: The two "unsigned int :0;" unnamed bitfields on either + * side of the bitfields below are scaffolding that border the + * set of bitfields which are accessed after acquiring the tree + * lock. Please don't insert any other bitfield members between + * the unnamed bitfields unless they should also be accessed + * after acquiring the tree lock. + */ + unsigned int : 0; /* start of bitfields c/o tree lock */ + unsigned int is_root : 1; /*%< range is 0..1 */ + unsigned int color : 1; /*%< range is 0..1 */ + unsigned int find_callback : 1; /*%< range is 0..1 */ + unsigned int attributes : 3; /*%< range is 0..2 */ + unsigned int nsec : 2; /*%< range is 0..3 */ + unsigned int namelen : 8; /*%< range is 1..255 */ + unsigned int offsetlen : 8; /*%< range is 1..128 */ + unsigned int oldnamelen : 8; /*%< range is 1..255 */ + /*@}*/ + + /* node needs to be cleaned from rpz */ + unsigned int rpz : 1; + unsigned int : 0; /* end of bitfields c/o tree lock */ + + /*% + * These are needed for hashing. The 'uppernode' points to the + * node's superdomain node in the parent subtree, so that it can + * be reached from a child that was found by a hash lookup. + */ + unsigned int hashval; + dns_rbtnode_t *uppernode; + dns_rbtnode_t *hashnext; + + dns_rbtnode_t *parent; + dns_rbtnode_t *left; + dns_rbtnode_t *right; + dns_rbtnode_t *down; + + /*% + * Used for LRU cache. This linked list is used to mark nodes which + * have no data any longer, but we cannot unlink at that exact moment + * because we did not or could not obtain a write lock on the tree. + */ + ISC_LINK(dns_rbtnode_t) deadlink; + + /*@{*/ + /*! + * These values are used in the RBT DB implementation. The appropriate + * node lock must be held before accessing them. + * + * Note: The two "unsigned int :0;" unnamed bitfields on either + * side of the bitfields below are scaffolding that border the + * set of bitfields which are accessed after acquiring the node + * lock. Please don't insert any other bitfield members between + * the unnamed bitfields unless they should also be accessed + * after acquiring the node lock. + * + * NOTE: Do not merge these fields into bitfields above, as + * they'll all be put in the same qword that could be accessed + * without the node lock as it shares the qword with other + * members. Leave these members here so that they occupy a + * separate region of memory. + */ + void *data; + uint8_t : 0; /* start of bitfields c/o node lock */ + uint8_t dirty : 1; + uint8_t wild : 1; + uint8_t : 0; /* end of bitfields c/o node lock */ + uint16_t locknum; /* note that this is not in the bitfield */ + isc_refcount_t references; + /*@}*/ +}; + +typedef isc_result_t (*dns_rbtfindcallback_t)(dns_rbtnode_t *node, + dns_name_t *name, + void *callback_arg); + +typedef isc_result_t (*dns_rbtdatawriter_t)(FILE *file, unsigned char *data, + void *arg, uint64_t *crc); + +typedef isc_result_t (*dns_rbtdatafixer_t)(dns_rbtnode_t *rbtnode, void *base, + size_t offset, void *arg, + uint64_t *crc); + +typedef void (*dns_rbtdeleter_t)(void *, void *); + +/***** +***** Chain Info +*****/ + +/*! + * A chain is used to keep track of the sequence of nodes to reach any given + * node from the root of the tree. Originally nodes did not have parent + * pointers in them (for memory usage reasons) so there was no way to find + * the path back to the root from any given node. Now that nodes have parent + * pointers, chains might be going away in a future release, though the + * movement functionality would remain. + * + * Chains may be used to iterate over a tree of trees. After setting up the + * chain's structure using dns_rbtnodechain_init(), it needs to be initialized + * to point to the lexically first or lexically last node in the tree of trees + * using dns_rbtnodechain_first() or dns_rbtnodechain_last(), respectively. + * Calling dns_rbtnodechain_next() or dns_rbtnodechain_prev() then moves the + * chain over to the next or previous node, respectively. + * + * In any event, parent information, whether via parent pointers or chains, is + * necessary information for iterating through the tree or for basic internal + * tree maintenance issues (ie, the rotations that are done to rebalance the + * tree when a node is added). The obvious implication of this is that for a + * chain to remain valid, the tree has to be locked down against writes for the + * duration of the useful life of the chain, because additions or removals can + * change the path from the root to the node the chain has targeted. + * + * The dns_rbtnodechain_ functions _first, _last, _prev and _next all take + * dns_name_t parameters for the name and the origin, which can be NULL. If + * non-NULL, 'name' will end up pointing to the name data and offsets that are + * stored at the node (and thus it will be read-only), so it should be a + * regular dns_name_t that has been initialized with dns_name_init. When + * 'origin' is non-NULL, it will get the name of the origin stored in it, so it + * needs to have its own buffer space and offsets, which is most easily + * accomplished with a dns_fixedname_t. It is _not_ necessary to reinitialize + * either 'name' or 'origin' between calls to the chain functions. + * + * NOTE WELL: even though the name data at the root of the tree of trees will + * be absolute (typically just "."), it will will be made into a relative name + * with an origin of "." -- an empty name when the node is ".". This is + * because a common on operation on 'name' and 'origin' is to use + * dns_name_concatenate() on them to generate the complete name. An empty name + * can be detected when dns_name_countlabels == 0, and is printed by + * dns_name_totext()/dns_name_format() as "@", consistent with RFC1035's + * definition of "@" as the current origin. + * + * dns_rbtnodechain_current is similar to the _first, _last, _prev and _next + * functions but additionally can provide the node to which the chain points. + */ + +/*% + * The number of level blocks to allocate at a time. Currently the maximum + * number of levels is allocated directly in the structure, but future + * revisions of this code might have a static initial block with dynamic + * growth. Allocating space for 256 levels when the tree is almost never that + * deep is wasteful, but it's not clear that it matters, since the waste is + * only 2MB for 1000 concurrently active chains on a system with 64-bit + * pointers. + */ +#define DNS_RBT_LEVELBLOCK 254 + +typedef struct dns_rbtnodechain { + unsigned int magic; + /*% + * The terminal node of the chain. It is not in levels[]. + * This is ostensibly private ... but in a pinch it could be + * used tell that the chain points nowhere without needing to + * call dns_rbtnodechain_current(). + */ + dns_rbtnode_t *end; + /*% + * The maximum number of labels in a name is 128; bitstrings mean + * a conceptually very large number (which I have not bothered to + * compute) of logical levels because splitting can potentially occur + * at each bit. However, DNSSEC restricts the number of "logical" + * labels in a name to 255, meaning only 254 pointers are needed + * in the worst case. + */ + dns_rbtnode_t *levels[DNS_RBT_LEVELBLOCK]; + /*% + * level_count indicates how deep the chain points into the + * tree of trees, and is the index into the levels[] array. + * Thus, levels[level_count - 1] is the last level node stored. + * A chain that points to the top level of the tree of trees has + * a level_count of 0, the first level has a level_count of 1, and + * so on. + */ + unsigned int level_count; + /*% + * level_matches tells how many levels matched above the node + * returned by dns_rbt_findnode(). A match (partial or exact) found + * in the first level thus results in level_matches being set to 1. + * This is used by the rbtdb to set the start point for a recursive + * search of superdomains until the RR it is looking for is found. + */ + unsigned int level_matches; +} dns_rbtnodechain_t; + +/***** +***** Public interfaces. +*****/ +isc_result_t +dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg, + dns_rbt_t **rbtp); +/*%< + * Initialize a red-black tree of trees. + * + * Notes: + *\li The deleter argument, if non-null, points to a function that is + * responsible for cleaning up any memory associated with the data + * pointer of a node when the node is deleted. It is passed the + * deleted node's data pointer as its first argument and deleter_arg + * as its second argument. + * + * Requires: + * \li mctx is a pointer to a valid memory context. + *\li rbtp != NULL && *rbtp == NULL + *\li arg == NULL iff deleter == NULL + * + * Ensures: + *\li If result is ISC_R_SUCCESS: + * *rbtp points to a valid red-black tree manager + * + *\li If result is failure: + * *rbtp does not point to a valid red-black tree manager. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of Memory + */ + +isc_result_t +dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data); +/*%< + * Add 'name' to the tree of trees, associated with 'data'. + * + * Notes: + *\li 'data' is never required to be non-NULL, but specifying it + * when the name is added is faster than searching for 'name' + * again and then setting the data pointer. The lack of a data pointer + * for a node also has other ramifications regarding whether + * dns_rbt_findname considers a node to exist, or dns_rbt_deletename + * joins nodes. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li dns_name_isabsolute(name) == TRUE + * + * Ensures: + *\li 'name' is not altered in any way. + * + *\li Any external references to nodes in the tree are unaffected by + * node splits that are necessary to insert the new name. + * + *\li If result is #ISC_R_SUCCESS: + * 'name' is findable in the red/black tree of trees in O(log N). + * The data pointer of the node for 'name' is set to 'data'. + * + *\li If result is #ISC_R_EXISTS or #ISC_R_NOSPACE: + * The tree of trees is unaltered. + * + *\li If result is #ISC_R_NOMEMORY: + * No guarantees. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_EXISTS The name already exists with associated data. + *\li #ISC_R_NOSPACE The name had more logical labels than are allowed. + *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory + */ + +isc_result_t +dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep); + +/*%< + * Just like dns_rbt_addname, but returns the address of the node. + * + * Requires: + *\li rbt is a valid rbt structure. + *\li dns_name_isabsolute(name) == TRUE + *\li nodep != NULL && *nodep == NULL + * + * Ensures: + *\li 'name' is not altered in any way. + * + *\li Any external references to nodes in the tree are unaffected by + * node splits that are necessary to insert the new name. + * + *\li If result is ISC_R_SUCCESS: + * 'name' is findable in the red/black tree of trees in O(log N). + * *nodep is the node that was added for 'name'. + * + *\li If result is ISC_R_EXISTS: + * The tree of trees is unaltered. + * *nodep is the existing node for 'name'. + * + *\li If result is ISC_R_NOMEMORY: + * No guarantees. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_EXISTS The name already exists, possibly without data. + *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory + */ + +isc_result_t +dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options, + dns_name_t *foundname, void **data); +/*%< + * Get the data pointer associated with 'name'. + * + * Notes: + *\li When #DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is + * returned (also subject to #DNS_RBTFIND_EMPTYDATA), even when there is + * an exact match in the tree. + * + *\li A node that has no data is considered not to exist for this function, + * unless the #DNS_RBTFIND_EMPTYDATA option is set. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li dns_name_isabsolute(name) == TRUE + *\li data != NULL && *data == NULL + * + * Ensures: + *\li 'name' and the tree are not altered in any way. + * + *\li If result is ISC_R_SUCCESS: + * *data is the data associated with 'name'. + * + *\li If result is DNS_R_PARTIALMATCH: + * *data is the data associated with the deepest superdomain + * of 'name' which has data. + * + *\li If result is ISC_R_NOTFOUND: + * Neither the name nor a superdomain was found with data. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #DNS_R_PARTIALMATCH Superdomain found with data + *\li #ISC_R_NOTFOUND No match + *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed + */ + +isc_result_t +dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname, + dns_rbtnode_t **node, dns_rbtnodechain_t *chain, + unsigned int options, dns_rbtfindcallback_t callback, + void *callback_arg); +/*%< + * Find the node for 'name'. + * + * Notes: + *\li A node that has no data is considered not to exist for this function, + * unless the DNS_RBTFIND_EMPTYDATA option is set. This applies to both + * exact matches and partial matches. + * + *\li If the chain parameter is non-NULL, then the path through the tree + * to the DNSSEC predecessor of the searched for name is maintained, + * unless the DNS_RBTFIND_NOPREDECESSOR or DNS_RBTFIND_NOEXACT option + * is used. (For more details on those options, see below.) + * + *\li If there is no predecessor, then the chain will point to nowhere, as + * indicated by chain->end being NULL or dns_rbtnodechain_current + * returning ISC_R_NOTFOUND. Note that in a normal Internet DNS RBT + * there will always be a predecessor for all names except the root + * name, because '.' will exist and '.' is the predecessor of + * everything. But you can certainly construct a trivial tree and a + * search for it that has no predecessor. + * + *\li Within the chain structure, the 'levels' member of the structure holds + * the root node of each level except the first. + * + *\li The 'level_count' of the chain indicates how deep the chain to the + * predecessor name is, as an index into the 'levels[]' array. It does + * not count name elements, per se, but only levels of the tree of trees, + * the distinction arising because multiple labels from a name can be + * stored on only one level. It is also does not include the level + * that has the node, since that level is not stored in levels[]. + * + *\li The chain's 'level_matches' is not directly related to the predecessor. + * It is the number of levels above the level of the found 'node', + * regardless of whether it was a partial match or exact match. When + * the node is found in the top level tree, or no node is found at all, + * level_matches is 0. + * + *\li When DNS_RBTFIND_NOEXACT is set, the closest matching superdomain is + * returned (also subject to DNS_RBTFIND_EMPTYDATA), even when + * there is an exact match in the tree. In this case, the chain + * will not point to the DNSSEC predecessor, but will instead point + * to the exact match, if there was any. Thus the preceding paragraphs + * should have "exact match" substituted for "predecessor" to describe + * how the various elements of the chain are set. This was done to + * ensure that the chain's state was sane, and to prevent problems that + * occurred when running the predecessor location code under conditions + * it was not designed for. It is not clear *where* the chain should + * point when DNS_RBTFIND_NOEXACT is set, so if you end up using a chain + * with this option because you want a particular node, let us know + * where you want the chain pointed, so this can be made more firm. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li dns_name_isabsolute(name) == TRUE. + *\li node != NULL && *node == NULL. + *\li #DNS_RBTFIND_NOEXACT and DNS_RBTFIND_NOPREDECESSOR are mutually + * exclusive. + * + * Ensures: + *\li 'name' and the tree are not altered in any way. + * + *\li If result is ISC_R_SUCCESS: + *\verbatim + * *node is the terminal node for 'name'. + * + * 'foundname' and 'name' represent the same name (though not + * the same memory). + * + * 'chain' points to the DNSSEC predecessor, if any, of 'name'. + * + * chain->level_matches and chain->level_count are equal. + *\endverbatim + * + * If result is DNS_R_PARTIALMATCH: + *\verbatim + * *node is the data associated with the deepest superdomain + * of 'name' which has data. + * + * 'foundname' is the name of deepest superdomain (which has + * data, unless the DNS_RBTFIND_EMPTYDATA option is set). + * + * 'chain' points to the DNSSEC predecessor, if any, of 'name'. + *\endverbatim + * + *\li If result is ISC_R_NOTFOUND: + *\verbatim + * Neither the name nor a superdomain was found. *node is NULL. + * + * 'chain' points to the DNSSEC predecessor, if any, of 'name'. + * + * chain->level_matches is 0. + *\endverbatim + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #DNS_R_PARTIALMATCH Superdomain found with data + *\li #ISC_R_NOTFOUND No match, or superdomain with no data + *\li #ISC_R_NOSPACE Concatenating nodes to form foundname failed + */ + +isc_result_t +dns_rbt_deletename(dns_rbt_t *rbt, const dns_name_t *name, bool recurse); +/*%< + * Delete 'name' from the tree of trees. + * + * Notes: + *\li When 'name' is removed, if recurse is true then all of its + * subnames are removed too. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li dns_name_isabsolute(name) == TRUE + * + * Ensures: + *\li 'name' is not altered in any way. + * + *\li Does NOT ensure that any external references to nodes in the tree + * are unaffected by node joins. + * + *\li If result is ISC_R_SUCCESS: + * 'name' does not appear in the tree with data; however, + * the node for the name might still exist which can be + * found with dns_rbt_findnode (but not dns_rbt_findname). + * + *\li If result is ISC_R_NOTFOUND: + * 'name' does not appear in the tree with data, because + * it did not appear in the tree before the function was called. + * + *\li If result is something else: + * See result codes for dns_rbt_findnode (if it fails, the + * node is not deleted) or dns_rbt_deletenode (if it fails, + * the node is deleted, but the tree is not optimized when + * it could have been). + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOTFOUND No match + *\li something_else Any return code from dns_rbt_findnode except + * DNS_R_PARTIALMATCH (which causes ISC_R_NOTFOUND + * to be returned instead), and any code from + * dns_rbt_deletenode. + */ + +isc_result_t +dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse); +/*%< + * Delete 'node' from the tree of trees. + * + * Notes: + *\li When 'node' is removed, if recurse is true then all nodes + * in levels down from it are removed too. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li node != NULL. + * + * Ensures: + *\li Does NOT ensure that any external references to nodes in the tree + * are unaffected by node joins. + * + *\li If result is ISC_R_SUCCESS: + * 'node' does not appear in the tree with data; however, + * the node might still exist if it serves as a pointer to + * a lower tree level as long as 'recurse' was false, hence + * the node could can be found with dns_rbt_findnode when + * that function's empty_data_ok parameter is true. + * + *\li If result is ISC_R_NOMEMORY or ISC_R_NOSPACE: + * The node was deleted, but the tree structure was not + * optimized. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory when joining nodes. + *\li #ISC_R_NOSPACE dns_name_concatenate failed when joining nodes. + */ + +void +dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name); +/*%< + * Convert the sequence of labels stored at 'node' into a 'name'. + * + * Notes: + *\li This function does not return the full name, from the root, but + * just the labels at the indicated node. + * + *\li The name data pointed to by 'name' is the information stored + * in the node, not a copy. Altering the data at this pointer + * will likely cause grief. + * + * Requires: + * \li name->offsets == NULL + * + * Ensures: + * \li 'name' is DNS_NAMEATTR_READONLY. + * + * \li 'name' will point directly to the labels stored after the + * dns_rbtnode_t struct. + * + * \li 'name' will have offsets that also point to the information stored + * as part of the node. + */ + +isc_result_t +dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name); +/*%< + * Like dns_rbt_namefromnode, but returns the full name from the root. + * + * Notes: + * \li Unlike dns_rbt_namefromnode, the name will not point directly + * to node data. Rather, dns_name_concatenate will be used to copy + * the name data from each node into the 'name' argument. + * + * Requires: + * \li name != NULL + * \li name has a dedicated buffer. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOSPACE (possible via dns_name_concatenate) + * \li DNS_R_NAMETOOLONG (possible via dns_name_concatenate) + */ + +char * +dns_rbt_formatnodename(dns_rbtnode_t *node, char *printname, unsigned int size); +/*%< + * Format the full name of a node for printing, using dns_name_format(). + * + * Notes: + * \li 'size' is the length of the printname buffer. This should be + * DNS_NAME_FORMATSIZE or larger. + * + * Requires: + * \li node and printname are not NULL. + * + * Returns: + * \li The 'printname' pointer. + */ + +unsigned int +dns_rbt_nodecount(dns_rbt_t *rbt); +/*%< + * Obtain the number of nodes in the tree of trees. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +size_t +dns_rbt_hashsize(dns_rbt_t *rbt); +/*%< + * Obtain the current number of buckets in the 'rbt' hash table. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +void +dns_rbt_destroy(dns_rbt_t **rbtp); +isc_result_t +dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum); +/*%< + * Stop working with a red-black tree of trees. + * If 'quantum' is zero then the entire tree will be destroyed. + * If 'quantum' is non zero then up to 'quantum' nodes will be destroyed + * allowing the rbt to be incrementally destroyed by repeated calls to + * dns_rbt_destroy2(). Once dns_rbt_destroy2() has been called no other + * operations than dns_rbt_destroy()/dns_rbt_destroy2() should be + * performed on the tree of trees. + * + * Requires: + * \li *rbt is a valid rbt manager. + * + * Ensures on ISC_R_SUCCESS: + * \li All space allocated by the RBT library has been returned. + * + * \li *rbt is invalidated as an rbt manager. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_QUOTA if 'quantum' nodes have been destroyed. + */ + +void +dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *), + FILE *f); +/*%< + * Print an ASCII representation of the internal structure of the red-black + * tree of trees to the passed stream. + * + * data_printer is a callback function that is called to print the data + * in a node. It should print it to the passed FILE stream. + * + * Notes: + * \li The name stored at each node, along with the node's color, is printed. + * Then the down pointer, left and right pointers are displayed + * recursively in turn. NULL down pointers are silently omitted; + * NULL left and right pointers are printed. + */ + +void +dns_rbt_printdot(dns_rbt_t *rbt, bool show_pointers, FILE *f); +/*%< + * Print a GraphViz dot representation of the internal structure of the + * red-black tree of trees to the passed stream. + * + * If show_pointers is TRUE, pointers are also included in the generated + * graph. + * + * Notes: + * \li The name stored at each node, along with the node's color is displayed. + * Then the down pointer, left and right pointers are displayed + * recursively in turn. NULL left, right and down pointers are + * silently omitted. + */ + +void +dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f); +/*%< + * Print out various information about a node + * + * Requires: + *\li 'n' is a valid pointer. + * + *\li 'f' points to a valid open FILE structure that allows writing. + */ + +size_t +dns__rbt_getheight(dns_rbt_t *rbt); +/*%< + * Return the maximum height of sub-root nodes found in the red-black + * forest. + * + * The height of a node is defined as the number of nodes in the longest + * path from the node to a leaf. For each subtree in the forest, this + * function determines the height of its root node. Then it returns the + * maximum such height in the forest. + * + * Note: This function exists for testing purposes. Non-test code must + * not use it. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +bool +dns__rbt_checkproperties(dns_rbt_t *rbt); +/*%< + * Check red-black properties of the forest. + * + * Note: This function exists for testing purposes. Non-test code must + * not use it. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +size_t +dns__rbtnode_getdistance(dns_rbtnode_t *node); +/*%< + * Return the distance (in nodes) from the node to its upper node of its + * subtree. The root node has a distance of 1. A child of the root node + * has a distance of 2. + */ + +/***** +***** Chain Functions +*****/ + +void +dns_rbtnodechain_init(dns_rbtnodechain_t *chain); +/*%< + * Initialize 'chain'. + * + * Requires: + *\li 'chain' is a valid pointer. + * + * Ensures: + *\li 'chain' is suitable for use. + */ + +void +dns_rbtnodechain_reset(dns_rbtnodechain_t *chain); +/*%< + * Free any dynamic storage associated with 'chain', and then reinitialize + * 'chain'. + * + * Requires: + *\li 'chain' is a valid pointer. + * + * Ensures: + *\li 'chain' is suitable for use, and uses no dynamic storage. + */ + +void +dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain); +/*%< + * Free any dynamic storage associated with 'chain', and then invalidates it. + * + * Notes: + *\li Future calls to any dns_rbtnodechain_ function will need to call + * dns_rbtnodechain_init on the chain first (except, of course, + * dns_rbtnodechain_init itself). + * + * Requires: + *\li 'chain' is a valid chain. + * + * Ensures: + *\li 'chain' is no longer suitable for use, and uses no dynamic storage. + */ + +isc_result_t +dns_rbtnodechain_current(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin, dns_rbtnode_t **node); +/*%< + * Provide the name, origin and node to which the chain is currently pointed. + * + * Notes: + *\li The tree need not have be locked against additions for the chain + * to remain valid, however there are no guarantees if any deletion + * has been made since the chain was established. + * + * Requires: + *\li 'chain' is a valid chain. + * + * Ensures: + *\li 'node', if non-NULL, is the node to which the chain was pointed + * by dns_rbt_findnode, dns_rbtnodechain_first or dns_rbtnodechain_last. + * If none were called for the chain since it was initialized or reset, + * or if the was no predecessor to the name searched for with + * dns_rbt_findnode, then '*node' is NULL and ISC_R_NOTFOUND is returned. + * + *\li 'name', if non-NULL, is the name stored at the terminal level of + * the chain. This is typically a single label, like the "www" of + * "www.isc.org", but need not be so. At the root of the tree of trees, + * if the node is "." then 'name' is ".", otherwise it is relative to ".". + * (Minimalist and atypical case: if the tree has just the name + * "isc.org." then the root node's stored name is "isc.org." but 'name' + * will be "isc.org".) + * + *\li 'origin', if non-NULL, is the sequence of labels in the levels + * above the terminal level, such as "isc.org." in the above example. + * 'origin' is always "." for the root node. + * + * + * Returns: + *\li #ISC_R_SUCCESS name, origin & node were successfully set. + *\li #ISC_R_NOTFOUND The chain does not point to any node. + *\li <something_else> Any error return from dns_name_concatenate. + */ + +isc_result_t +dns_rbtnodechain_first(dns_rbtnodechain_t *chain, dns_rbt_t *rbt, + dns_name_t *name, dns_name_t *origin); +/*%< + * Set the chain to the lexically first node in the tree of trees. + * + * Notes: + *\li By the definition of ordering for DNS names, the root of the tree of + * trees is the very first node, since everything else in the megatree + * uses it as a common suffix. + * + * Requires: + *\li 'chain' is a valid chain. + *\li 'rbt' is a valid rbt manager. + * + * Ensures: + *\li The chain points to the very first node of the tree. + * + *\li 'name' and 'origin', if non-NULL, are set as described for + * dns_rbtnodechain_current. Thus 'origin' will always be ".". + * + * Returns: + *\li #DNS_R_NEWORIGIN The name & origin were successfully set. + *\li <something_else> Any error result from dns_rbtnodechain_current. + */ + +isc_result_t +dns_rbtnodechain_last(dns_rbtnodechain_t *chain, dns_rbt_t *rbt, + dns_name_t *name, dns_name_t *origin); +/*%< + * Set the chain to the lexically last node in the tree of trees. + * + * Requires: + *\li 'chain' is a valid chain. + *\li 'rbt' is a valid rbt manager. + * + * Ensures: + *\li The chain points to the very last node of the tree. + * + *\li 'name' and 'origin', if non-NULL, are set as described for + * dns_rbtnodechain_current. + * + * Returns: + *\li #DNS_R_NEWORIGIN The name & origin were successfully set. + *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory building chain. + *\li <something_else> Any error result from dns_name_concatenate. + */ + +isc_result_t +dns_rbtnodechain_prev(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin); +/*%< + * Adjusts chain to point the DNSSEC predecessor of the name to which it + * is currently pointed. + * + * Requires: + *\li 'chain' is a valid chain. + *\li 'chain' has been pointed somewhere in the tree with dns_rbt_findnode, + * dns_rbtnodechain_first or dns_rbtnodechain_last -- and remember that + * dns_rbt_findnode is not guaranteed to point the chain somewhere, + * since there may have been no predecessor to the searched for name. + * + * Ensures: + *\li The chain is pointed to the predecessor of its current target. + * + *\li 'name' and 'origin', if non-NULL, are set as described for + * dns_rbtnodechain_current. + * + *\li 'origin' is only if a new origin was found. + * + * Returns: + *\li #ISC_R_SUCCESS The predecessor was found and 'name' was set. + *\li #DNS_R_NEWORIGIN The predecessor was found with a + * different origin and 'name' and 'origin' were set. \li #ISC_R_NOMORE There + * was no predecessor. \li <something_else> Any error result from + * dns_rbtnodechain_current. + */ + +isc_result_t +dns_rbtnodechain_next(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin); +/*%< + * Adjusts chain to point the DNSSEC successor of the name to which it + * is currently pointed. + * + * Requires: + *\li 'chain' is a valid chain. + *\li 'chain' has been pointed somewhere in the tree with dns_rbt_findnode, + * dns_rbtnodechain_first or dns_rbtnodechain_last -- and remember that + * dns_rbt_findnode is not guaranteed to point the chain somewhere, + * since there may have been no predecessor to the searched for name. + * + * Ensures: + *\li The chain is pointed to the successor of its current target. + * + *\li 'name' and 'origin', if non-NULL, are set as described for + * dns_rbtnodechain_current. + * + *\li 'origin' is only if a new origin was found. + * + * Returns: + *\li #ISC_R_SUCCESS The successor was found and 'name' was set. + *\li #DNS_R_NEWORIGIN The successor was found with a different + * origin and 'name' and 'origin' were set. + *\li #ISC_R_NOMORE There was no successor. + *\li <something_else> Any error result from dns_name_concatenate. + */ + +isc_result_t +dns_rbtnodechain_down(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin); +/*%< + * Descend down if possible. + */ + +isc_result_t +dns_rbtnodechain_nextflat(dns_rbtnodechain_t *chain, dns_name_t *name); +/*%< + * Find the next node at the current depth in DNSSEC order. + */ + +unsigned int +dns__rbtnode_namelen(dns_rbtnode_t *node); +/*%< + * Returns the length of the full name of the node. Used only internally + * and in unit tests. + */ +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rcode.h b/lib/dns/include/dns/rcode.h new file mode 100644 index 0000000..2c496ee --- /dev/null +++ b/lib/dns/include/dns/rcode.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rcode.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNS error value. + * + * Requires: + *\li 'rcodep' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #DNS_R_UNKNOWN type is unknown + */ + +isc_result_t +dns_rcode_totext(dns_rcode_t rcode, isc_buffer_t *target); +/*%< + * Put a textual representation of error 'rcode' into 'target'. + * + * Requires: + *\li 'rcode' is a valid rcode. + * + *\li 'target' is a valid text buffer. + * + * Ensures: + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +isc_result_t +dns_tsigrcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a TSIG/TKEY error value. + * + * Requires: + *\li 'rcodep' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #DNS_R_UNKNOWN type is unknown + */ + +isc_result_t +dns_tsigrcode_totext(dns_rcode_t rcode, isc_buffer_t *target); +/*%< + * Put a textual representation of TSIG/TKEY error 'rcode' into 'target'. + * + * Requires: + *\li 'rcode' is a valid TSIG/TKEY error code. + * + *\li 'target' is a valid text buffer. + * + * Ensures: + *\li If the result is success: + * The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +isc_result_t +dns_hashalg_fromtext(unsigned char *hashalg, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a has algorithm value. + * + * Requires: + *\li 'hashalg' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #DNS_R_UNKNOWN type is unknown + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdata.h b/lib/dns/include/dns/rdata.h new file mode 100644 index 0000000..257a22c --- /dev/null +++ b/lib/dns/include/dns/rdata.h @@ -0,0 +1,809 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/rdata.h + * \brief + * Provides facilities for manipulating DNS rdata, including conversions to + * and from wire format and text format. + * + * Given the large amount of rdata possible in a nameserver, it was important + * to come up with a very efficient way of storing rdata, but at the same + * time allow it to be manipulated. + * + * The decision was to store rdata in uncompressed wire format, + * and not to make it a fully abstracted object; i.e. certain parts of the + * server know rdata is stored that way. This saves a lot of memory, and + * makes adding rdata to messages easy. Having much of the server know + * the representation would be perilous, and we certainly don't want each + * user of rdata to be manipulating such a low-level structure. This is + * where the rdata module comes in. The module allows rdata handles to be + * created and attached to uncompressed wire format regions. All rdata + * operations and conversions are done through these handles. + * + * Implementation Notes: + * + *\li The routines in this module are expected to be synthesized by the + * build process from a set of source files, one per rdata type. For + * portability, it's probably best that the building be done by a C + * program. Adding a new rdata type will be a simple matter of adding + * a file to a directory and rebuilding the server. *All* knowledge of + * the format of a particular rdata type is in this file. + * + * MP: + *\li Clients of this module must impose any required synchronization. + * + * Reliability: + *\li This module deals with low-level byte streams. Errors in any of + * the functions are likely to crash the server or corrupt memory. + * + *\li Rdata is typed, and the caller must know what type of rdata it has. + * A caller that gets this wrong could crash the server. + * + *\li The fromstruct() and tostruct() routines use a void * pointer to + * represent the structure. The caller must ensure that it passes a + * pointer to the appropriate type, or the server could crash or memory + * could be corrupted. + * + * Resources: + *\li None. + * + * Security: + * + *\li *** WARNING *** + * dns_rdata_fromwire() deals with raw network data. An error in + * this routine could result in the failure or hijacking of the server. + * + * Standards: + *\li RFC1035 + *\li Draft EDNS0 (0) + *\li Draft EDNS1 (0) + *\li Draft Binary Labels (2) + *\li Draft Local Compression (1) + *\li Various RFCs for particular types; these will be documented in the + * sources files of the types. + * + */ + +/*** + *** Imports + ***/ + +#include + +#include + +#include +#include +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% + ***** An 'rdata' is a handle to a binary region. The handle has an RR + ***** class and type, and the data in the binary region is in the format + ***** of the given class and type. + *****/ +/*% + * Clients are strongly discouraged from using this type directly, with + * the exception of the 'link' field which may be used directly for whatever + * purpose the client desires. + */ +struct dns_rdata { + unsigned char *data; + unsigned int length; + dns_rdataclass_t rdclass; + dns_rdatatype_t type; + unsigned int flags; + ISC_LINK(dns_rdata_t) link; +}; + +#define DNS_RDATA_INIT \ + { \ + NULL, 0, 0, 0, 0, { (void *)(-1), (void *)(-1) } \ + } + +#define DNS_RDATA_CHECKINITIALIZED +#ifdef DNS_RDATA_CHECKINITIALIZED +#define DNS_RDATA_INITIALIZED(rdata) \ + ((rdata)->data == NULL && (rdata)->length == 0 && \ + (rdata)->rdclass == 0 && (rdata)->type == 0 && (rdata)->flags == 0 && \ + !ISC_LINK_LINKED((rdata), link)) +#else /* ifdef DNS_RDATA_CHECKINITIALIZED */ +#ifdef ISC_LIST_CHECKINIT +#define DNS_RDATA_INITIALIZED(rdata) (!ISC_LINK_LINKED((rdata), link)) +#else /* ifdef ISC_LIST_CHECKINIT */ +#define DNS_RDATA_INITIALIZED(rdata) true +#endif /* ifdef ISC_LIST_CHECKINIT */ +#endif /* ifdef DNS_RDATA_CHECKINITIALIZED */ + +#define DNS_RDATA_UPDATE 0x0001 /*%< update pseudo record. */ +#define DNS_RDATA_OFFLINE 0x0002 /*%< RRSIG has a offline key. */ + +#define DNS_RDATA_VALIDFLAGS(rdata) \ + (((rdata)->flags & ~(DNS_RDATA_UPDATE | DNS_RDATA_OFFLINE)) == 0) + +/* + * The maximum length of a RDATA that can be sent on the wire. + * Max packet size (65535) less header (12), less name (1), type (2), + * class (2), ttl(4), length (2). + * + * None of the defined types that support name compression can exceed + * this and all new types are to be sent uncompressed. + */ + +#define DNS_RDATA_MAXLENGTH 65512U + +/* + * Flags affecting rdata formatting style. Flags 0xFFFF0000 + * are used by masterfile-level formatting and defined elsewhere. + * See additional comments at dns_rdata_tofmttext(). + */ + +/*% Split the rdata into multiple lines to try to keep it + * within the "width". */ +#define DNS_STYLEFLAG_MULTILINE 0x00000001ULL + +/*% Output explanatory comments. */ +#define DNS_STYLEFLAG_COMMENT 0x00000002ULL +#define DNS_STYLEFLAG_RRCOMMENT 0x00000004ULL + +/*% Output KEYDATA in human readable format. */ +#define DNS_STYLEFLAG_KEYDATA 0x00000008ULL + +/*% Output textual RR type and RDATA in RFC 3597 unknown format */ +#define DNS_STYLEFLAG_UNKNOWNFORMAT 0x00000010ULL + +/*% Print AAAA record fully expanded */ +#define DNS_STYLEFLAG_EXPANDAAAA 0x00000020ULL + +#define DNS_RDATA_DOWNCASE DNS_NAME_DOWNCASE +#define DNS_RDATA_CHECKNAMES DNS_NAME_CHECKNAMES +#define DNS_RDATA_CHECKNAMESFAIL DNS_NAME_CHECKNAMESFAIL +#define DNS_RDATA_CHECKREVERSE DNS_NAME_CHECKREVERSE +#define DNS_RDATA_CHECKMX DNS_NAME_CHECKMX +#define DNS_RDATA_CHECKMXFAIL DNS_NAME_CHECKMXFAIL +#define DNS_RDATA_UNKNOWNESCAPE 0x80000000 + +/*** + *** Initialization + ***/ + +void +dns_rdata_init(dns_rdata_t *rdata); +/*%< + * Make 'rdata' empty. + * + * Requires: + * 'rdata' is a valid rdata (i.e. not NULL, points to a struct dns_rdata) + */ + +void +dns_rdata_reset(dns_rdata_t *rdata); +/*%< + * Make 'rdata' empty. + * + * Requires: + *\li 'rdata' is a previously initialized rdata and is not linked. + */ + +void +dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target); +/*%< + * Clone 'target' from 'src'. + * + * Requires: + *\li 'src' to be initialized. + *\li 'target' to be initialized. + */ + +/*** + *** Comparisons + ***/ + +int +dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2); +/*%< + * Determine the relative ordering under the DNSSEC order relation of + * 'rdata1' and 'rdata2'. + * + * Requires: + * + *\li 'rdata1' is a valid, non-empty rdata + * + *\li 'rdata2' is a valid, non-empty rdata + * + * Returns: + *\li < 0 'rdata1' is less than 'rdata2' + *\li 0 'rdata1' is equal to 'rdata2' + *\li > 0 'rdata1' is greater than 'rdata2' + */ + +int +dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2); +/*%< + * dns_rdata_casecompare() is similar to dns_rdata_compare() but also + * compares domain names case insensitively in known rdata types that + * are treated as opaque data by dns_rdata_compare(). + * + * Requires: + * + *\li 'rdata1' is a valid, non-empty rdata + * + *\li 'rdata2' is a valid, non-empty rdata + * + * Returns: + *\li < 0 'rdata1' is less than 'rdata2' + *\li 0 'rdata1' is equal to 'rdata2' + *\li > 0 'rdata1' is greater than 'rdata2' + */ + +/*** + *** Conversions + ***/ + +void +dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_region_t *r); +/*%< + * Make 'rdata' refer to region 'r'. + * + * Requires: + * + *\li The data in 'r' is properly formatted for whatever type it is. + */ + +void +dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r); +/*%< + * Make 'r' refer to 'rdata'. + */ + +isc_result_t +dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_buffer_t *source, + dns_decompress_t *dctx, unsigned int options, + isc_buffer_t *target); +/*%< + * Copy the possibly-compressed rdata at source into the target region. + * + * Notes: + *\li Name decompression policy is controlled by 'dctx'. + * + * 'options' + *\li DNS_RDATA_DOWNCASE downcase domain names when they are copied + * into target. + * + * Requires: + * + *\li 'rdclass' and 'type' are valid. + * + *\li 'source' is a valid buffer, and the active region of 'source' + * references the rdata to be processed. + * + *\li 'target' is a valid buffer. + * + *\li 'dctx' is a valid decompression context. + * + * Ensures, + * if result is success: + * \li If 'rdata' is not NULL, it is attached to the target. + * \li The conditions dns_name_fromwire() ensures for names hold + * for all names in the rdata. + * \li The current location in source is advanced, and the used space + * in target is updated. + * + * Result: + *\li Success + *\li Any non-success status from dns_name_fromwire() + *\li Various 'Bad Form' class failures depending on class and type + *\li Bad Form: Input too short + *\li Resource Limit: Not enough space + */ + +isc_result_t +dns_rdata_towire(dns_rdata_t *rdata, dns_compress_t *cctx, + isc_buffer_t *target); +/*%< + * Convert 'rdata' into wire format, compressing it as specified by the + * compression context 'cctx', and storing the result in 'target'. + * + * Notes: + *\li If the compression context allows global compression, then the + * global compression table may be updated. + * + * Requires: + *\li 'rdata' is a valid, non-empty rdata + * + *\li target is a valid buffer + * + *\li Any offsets specified in a global compression table are valid + * for target. + * + * Ensures, + * if the result is success: + * \li The used space in target is updated. + * + * Returns: + *\li Success + *\li Any non-success status from dns_name_towire() + *\li Resource Limit: Not enough space + */ + +isc_result_t +dns_rdata_fromtext(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_lex_t *lexer, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx, isc_buffer_t *target, + dns_rdatacallbacks_t *callbacks); +/*%< + * Convert the textual representation of a DNS rdata into uncompressed wire + * form stored in the target region. Tokens constituting the text of the rdata + * are taken from 'lexer'. + * + * Notes: + *\li Relative domain names in the rdata will have 'origin' appended to them. + * A NULL origin implies "origin == dns_rootname". + * + * + * 'options' + *\li DNS_RDATA_DOWNCASE downcase domain names when they are copied + * into target. + *\li DNS_RDATA_CHECKNAMES perform checknames checks. + *\li DNS_RDATA_CHECKNAMESFAIL fail if the checknames check fail. If + * not set a warning will be issued. + *\li DNS_RDATA_CHECKREVERSE this should set if the owner name ends + * in IP6.ARPA, IP6.INT or IN-ADDR.ARPA. + * + * Requires: + * + *\li 'rdclass' and 'type' are valid. + * + *\li 'lexer' is a valid isc_lex_t. + * + *\li 'mctx' is a valid isc_mem_t. + * + *\li 'target' is a valid region. + * + *\li 'origin' if non NULL it must be absolute. + * + *\li 'callbacks' to be NULL or callbacks->warn and callbacks->error be + * initialized. + * + * Ensures, + * if result is success: + *\li If 'rdata' is not NULL, it is attached to the target. + * + *\li The conditions dns_name_fromtext() ensures for names hold + * for all names in the rdata. + * + *\li The used space in target is updated. + * + * Result: + *\li Success + *\li Translated result codes from isc_lex_gettoken + *\li Various 'Bad Form' class failures depending on class and type + *\li Bad Form: Input too short + *\li Resource Limit: Not enough space + *\li Resource Limit: Not enough memory + */ + +isc_result_t +dns_rdata_totext(dns_rdata_t *rdata, const dns_name_t *origin, + isc_buffer_t *target); +/*%< + * Convert 'rdata' into text format, storing the result in 'target'. + * The text will consist of a single line, with fields separated by + * single spaces. + * + * Notes: + *\li If 'origin' is not NULL, then any names in the rdata that are + * subdomains of 'origin' will be made relative it. + * + *\li XXX Do we *really* want to support 'origin'? I'm inclined towards "no" + * at the moment. + * + * Requires: + * + *\li 'rdata' is a valid, non-empty rdata + * + *\li 'origin' is NULL, or is a valid name + * + *\li 'target' is a valid text buffer + * + * Ensures, + * if the result is success: + * + * \li The used space in target is updated. + * + * Returns: + *\li Success + *\li Any non-success status from dns_name_totext() + *\li Resource Limit: Not enough space + */ + +isc_result_t +dns_rdata_tofmttext(dns_rdata_t *rdata, const dns_name_t *origin, + dns_masterstyle_flags_t flags, unsigned int width, + unsigned int split_width, const char *linebreak, + isc_buffer_t *target); +/*%< + * Like dns_rdata_totext, but do formatted output suitable for + * database dumps. This is intended for use by dns_db_dump(); + * library users are discouraged from calling it directly. + * + * If (flags & #DNS_STYLEFLAG_MULTILINE) != 0, attempt to stay + * within 'width' by breaking the text into multiple lines. + * The string 'linebreak' is inserted between lines, and parentheses + * are added when necessary. Because RRs contain unbreakable elements + * such as domain names whose length is variable, unpredictable, and + * potentially large, there is no guarantee that the lines will + * not exceed 'width' anyway. + * + * If (flags & #DNS_STYLEFLAG_MULTILINE) == 0, the rdata is always + * printed as a single line, and no parentheses are used. + * The 'width' and 'linebreak' arguments are ignored. + * + * If (flags & #DNS_STYLEFLAG_COMMENT) != 0, output explanatory + * comments next to things like the SOA timer fields. Some + * comments (e.g., the SOA ones) are only printed when multiline + * output is selected. + * + * base64 rdata text (e.g., DNSKEY records) will be split into chunks + * of 'split_width' characters. If split_width == 0, the text will + * not be split at all. If split_width == UINT_MAX (0xffffffff), then + * it is undefined and falls back to the default value of 'width' + */ + +isc_result_t +dns_rdata_fromstruct(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, void *source, isc_buffer_t *target); +/*%< + * Convert the C structure representation of an rdata into uncompressed wire + * format in 'target'. + * + * XXX Should we have a 'size' parameter as a sanity check on target? + * + * Requires: + * + *\li 'rdclass' and 'type' are valid. + * + *\li 'source' points to a valid C struct for the class and type. + * + *\li 'target' is a valid buffer. + * + *\li All structure pointers to memory blocks should be NULL if their + * corresponding length values are zero. + * + * Ensures, + * if result is success: + * \li If 'rdata' is not NULL, it is attached to the target. + * + * \li The used space in 'target' is updated. + * + * Result: + *\li Success + *\li Various 'Bad Form' class failures depending on class and type + *\li Resource Limit: Not enough space + */ + +isc_result_t +dns_rdata_tostruct(const dns_rdata_t *rdata, void *target, isc_mem_t *mctx); +/*%< + * Convert an rdata into its C structure representation. + * + * If 'mctx' is NULL then 'rdata' must persist while 'target' is being used. + * + * If 'mctx' is non NULL then memory will be allocated if required. + * + * Requires: + * + *\li 'rdata' is a valid, non-empty, non-pseudo rdata. + * + *\li 'target' to point to a valid pointer for the type and class. + * + * Result: + *\li Success + *\li Resource Limit: Not enough memory + */ + +void +dns_rdata_freestruct(void *source); +/*%< + * Free dynamic memory attached to 'source' (if any). + * + * Requires: + * + *\li 'source' to point to the structure previously filled in by + * dns_rdata_tostruct(). + */ + +bool +dns_rdatatype_ismeta(dns_rdatatype_t type); +/*%< + * Return true iff the rdata type 'type' is a meta-type + * like ANY or AXFR. + */ + +bool +dns_rdatatype_issingleton(dns_rdatatype_t type); +/*%< + * Return true iff the rdata type 'type' is a singleton type, + * like CNAME or SOA. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdataclass_ismeta(dns_rdataclass_t rdclass); +/*%< + * Return true iff the rdata class 'rdclass' is a meta-class + * like ANY or NONE. + */ + +bool +dns_rdatatype_isdnssec(dns_rdatatype_t type); +/*%< + * Return true iff 'type' is one of the DNSSEC + * rdata types that may exist alongside a CNAME record. + * + * Requires: + * \li 'type' is a valid rdata type. + */ + +bool +dns_rdatatype_iszonecutauth(dns_rdatatype_t type); +/*%< + * Return true iff rdata of type 'type' is considered authoritative + * data (not glue) in the NSEC chain when it occurs in the parent zone + * at a zone cut. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdatatype_isknown(dns_rdatatype_t type); +/*%< + * Return true iff the rdata type 'type' is known. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +isc_result_t +dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner, + dns_additionaldatafunc_t add, void *arg); +/*%< + * Call 'add' for each name and type from 'rdata' which is subject to + * additional section processing. + * + * Requires: + * + *\li 'rdata' is a valid, non-empty rdata. + * + *\li 'add' is a valid dns_additionalfunc_t. + * + * Ensures: + * + *\li If successful, then add() will have been called for each name + * and type subject to additional section processing. + * + *\li If add() returns something other than #ISC_R_SUCCESS, that result + * will be returned as the result of dns_rdata_additionaldata(). + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Many other results are possible if not successful. + */ + +isc_result_t +dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg); +/*%< + * Send 'rdata' in DNSSEC canonical form to 'digest'. + * + * Note: + *\li 'digest' may be called more than once by dns_rdata_digest(). The + * concatenation of all the regions, in the order they were given + * to 'digest', will be the DNSSEC canonical form of 'rdata'. + * + * Requires: + * + *\li 'rdata' is a valid, non-empty rdata. + * + *\li 'digest' is a valid dns_digestfunc_t. + * + * Ensures: + * + *\li If successful, then all of the rdata's data has been sent, in + * DNSSEC canonical form, to 'digest'. + * + *\li If digest() returns something other than ISC_R_SUCCESS, that result + * will be returned as the result of dns_rdata_digest(). + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Many other results are possible if not successful. + */ + +bool +dns_rdatatype_questiononly(dns_rdatatype_t type); +/*%< + * Return true iff rdata of type 'type' can only appear in the question + * section of a properly formatted message. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdatatype_notquestion(dns_rdatatype_t type); +/*%< + * Return true iff rdata of type 'type' can not appear in the question + * section of a properly formatted message. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdatatype_atparent(dns_rdatatype_t type); +/*%< + * Return true iff rdata of type 'type' should appear at the parent of + * a zone cut. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdatatype_atcname(dns_rdatatype_t type); +/*%< + * Return true iff rdata of type 'type' can appear beside a cname. + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +bool +dns_rdatatype_followadditional(dns_rdatatype_t type); +/*%< + * Return true if adding a record of type 'type' to the ADDITIONAL section + * of a message can itself trigger the addition of still more data to the + * additional section. + * + * (For example: adding SRV to the ADDITIONAL section may trigger + * the addition of address records associated with that SRV.) + * + * Requires: + * \li 'type' is a valid rdata type. + * + */ + +unsigned int +dns_rdatatype_attributes(dns_rdatatype_t rdtype); +/*%< + * Return attributes for the given type. + * + * Requires: + *\li 'rdtype' are known. + * + * Returns: + *\li a bitmask consisting of the following flags. + */ + +/*% only one may exist for a name */ +#define DNS_RDATATYPEATTR_SINGLETON 0x00000001U +/*% requires no other data be present */ +#define DNS_RDATATYPEATTR_EXCLUSIVE 0x00000002U +/*% Is a meta type */ +#define DNS_RDATATYPEATTR_META 0x00000004U +/*% Is a DNSSEC type, like RRSIG or NSEC */ +#define DNS_RDATATYPEATTR_DNSSEC 0x00000008U +/*% Is a zone cut authority type */ +#define DNS_RDATATYPEATTR_ZONECUTAUTH 0x00000010U +/*% Is reserved (unusable) */ +#define DNS_RDATATYPEATTR_RESERVED 0x00000020U +/*% Is an unknown type */ +#define DNS_RDATATYPEATTR_UNKNOWN 0x00000040U +/*% Is META, and can only be in a question section */ +#define DNS_RDATATYPEATTR_QUESTIONONLY 0x00000080U +/*% Is META, and can NOT be in a question section */ +#define DNS_RDATATYPEATTR_NOTQUESTION 0x00000100U +/*% Is present at zone cuts in the parent, not the child */ +#define DNS_RDATATYPEATTR_ATPARENT 0x00000200U +/*% Can exist along side a CNAME */ +#define DNS_RDATATYPEATTR_ATCNAME 0x00000400U +/*% Follow additional */ +#define DNS_RDATATYPEATTR_FOLLOWADDITIONAL 0x00000800U + +dns_rdatatype_t +dns_rdata_covers(dns_rdata_t *rdata); +/*%< + * Return the rdatatype that this type covers. + * + * Requires: + *\li 'rdata' is a valid, non-empty rdata. + * + *\li 'rdata' is a type that covers other rdata types. + * + * Returns: + *\li The type covered. + */ + +bool +dns_rdata_checkowner(const dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t type, bool wildcard); +/* + * Returns whether this is a valid ownername for this . + * If wildcard is true allow the first label to be a wildcard if + * appropriate. + * + * Requires: + * 'name' is a valid name. + */ + +bool +dns_rdata_checknames(dns_rdata_t *rdata, const dns_name_t *owner, + dns_name_t *bad); +/* + * Returns whether 'rdata' contains valid domain names. The checks are + * sensitive to the owner name. + * + * If 'bad' is non-NULL and a domain name fails the check the + * the offending name will be return in 'bad' by cloning from + * the 'rdata' contents. + * + * Requires: + * 'rdata' to be valid. + * 'owner' to be valid. + * 'bad' to be NULL or valid. + */ + +void +dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type); + +void +dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type); + +void +dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type); + +void +dns_rdata_makedelete(dns_rdata_t *rdata); + +const char * +dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdataclass.h b/lib/dns/include/dns/rdataclass.h new file mode 100644 index 0000000..466c4a3 --- /dev/null +++ b/lib/dns/include/dns/rdataclass.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rdataclass.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rdataclass_fromtext(dns_rdataclass_t *classp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNS class. + * + * Requires: + *\li 'classp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #DNS_R_UNKNOWN class is unknown + */ + +isc_result_t +dns_rdataclass_totext(dns_rdataclass_t rdclass, isc_buffer_t *target); +/*%< + * Put a textual representation of class 'rdclass' into 'target'. + * + * Requires: + *\li 'rdclass' is a valid class. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +isc_result_t +dns_rdataclass_tounknowntext(dns_rdataclass_t rdclass, isc_buffer_t *target); +/*%< + * Put textual RFC3597 CLASSXXXX representation of class 'rdclass' into + * 'target'. + * + * Requires: + *\li 'rdclass' is a valid class. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +void +dns_rdataclass_format(dns_rdataclass_t rdclass, char *array, unsigned int size); +/*%< + * Format a human-readable representation of the class 'rdclass' + * into the character array 'array', which is of size 'size'. + * The resulting string is guaranteed to be null-terminated. + */ + +#define DNS_RDATACLASS_FORMATSIZE sizeof("CLASS65535") +/*%< + * Minimum size of array to pass to dns_rdataclass_format(). + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdatalist.h b/lib/dns/include/dns/rdatalist.h new file mode 100644 index 0000000..3608cfe --- /dev/null +++ b/lib/dns/include/dns/rdatalist.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/rdatalist.h + * \brief + * A DNS rdatalist is a list of rdata of a common type and class. + * + * MP: + *\li Clients of this module must impose any required synchronization. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +#include + +#include + +/*% + * Clients may use this type directly. + */ +struct dns_rdatalist { + dns_rdataclass_t rdclass; + dns_rdatatype_t type; + dns_rdatatype_t covers; + dns_ttl_t ttl; + ISC_LIST(dns_rdata_t) rdata; + ISC_LINK(dns_rdatalist_t) link; + /*%< + * Case vector. If the bit is set then the corresponding + * character in the owner name needs to be AND'd with 0x20, + * rendering that character upper case. + */ + unsigned char upper[32]; +}; + +ISC_LANG_BEGINDECLS + +void +dns_rdatalist_init(dns_rdatalist_t *rdatalist); +/*%< + * Initialize rdatalist. + * + * Ensures: + *\li All fields of rdatalist have been initialized to their default + * values. + */ + +isc_result_t +dns_rdatalist_tordataset(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset); +/*%< + * Make 'rdataset' refer to the rdata in 'rdatalist'. + * + * Note: + *\li The caller must ensure that 'rdatalist' remains valid and unchanged + * while 'rdataset' is associated with it. + * + * Requires: + * + *\li 'rdatalist' is a valid rdatalist. + * + *\li 'rdataset' is a valid rdataset that is not currently associated with + * any rdata. + * + * Ensures, + * on success, + * + *\li 'rdataset' is associated with the rdata in rdatalist. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_result_t +dns_rdatalist_fromrdataset(dns_rdataset_t *rdataset, + dns_rdatalist_t **rdatalist); +/*%< + * Point 'rdatalist' to the rdatalist in 'rdataset'. + * + * Requires: + * + *\li 'rdatalist' is a pointer to a NULL dns_rdatalist_t pointer. + * + *\li 'rdataset' is a valid rdataset associated with an rdatalist. + * + * Ensures, + * on success, + * + *\li 'rdatalist' is pointed to the rdatalist in rdataset. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h new file mode 100644 index 0000000..566ea44 --- /dev/null +++ b/lib/dns/include/dns/rdataset.h @@ -0,0 +1,621 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/rdataset.h + * \brief + * A DNS rdataset is a handle that can be associated with a collection of + * rdata all having a common owner name, class, and type. + * + * The dns_rdataset_t type is like a "virtual class". To actually use + * rdatasets, an implementation of the method suite (e.g. "slabbed rdata") is + * required. + * + * XXX <more> XXX + * + * MP: + *\li Clients of this module must impose any required synchronization. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +#include +#include + +#include +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +typedef enum { + dns_rdatasetadditional_fromauth, + dns_rdatasetadditional_fromcache, + dns_rdatasetadditional_fromglue +} dns_rdatasetadditional_t; + +typedef struct dns_rdatasetmethods { + void (*disassociate)(dns_rdataset_t *rdataset); + isc_result_t (*first)(dns_rdataset_t *rdataset); + isc_result_t (*next)(dns_rdataset_t *rdataset); + void (*current)(dns_rdataset_t *rdataset, dns_rdata_t *rdata); + void (*clone)(dns_rdataset_t *source, dns_rdataset_t *target); + unsigned int (*count)(dns_rdataset_t *rdataset); + isc_result_t (*addnoqname)(dns_rdataset_t *rdataset, + const dns_name_t *name); + isc_result_t (*getnoqname)(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); + isc_result_t (*addclosest)(dns_rdataset_t *rdataset, + const dns_name_t *name); + isc_result_t (*getclosest)(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); + void (*settrust)(dns_rdataset_t *rdataset, dns_trust_t trust); + void (*expire)(dns_rdataset_t *rdataset); + void (*clearprefetch)(dns_rdataset_t *rdataset); + void (*setownercase)(dns_rdataset_t *rdataset, const dns_name_t *name); + void (*getownercase)(const dns_rdataset_t *rdataset, dns_name_t *name); + isc_result_t (*addglue)(dns_rdataset_t *rdataset, + dns_dbversion_t *version, dns_message_t *msg); +} dns_rdatasetmethods_t; + +#define DNS_RDATASET_MAGIC ISC_MAGIC('D', 'N', 'S', 'R') +#define DNS_RDATASET_VALID(set) ISC_MAGIC_VALID(set, DNS_RDATASET_MAGIC) + +/*% + * Direct use of this structure by clients is strongly discouraged, except + * for the 'link' field which may be used however the client wishes. The + * 'private', 'current', and 'index' fields MUST NOT be changed by clients. + * rdataset implementations may change any of the fields. + */ +struct dns_rdataset { + unsigned int magic; + dns_rdatasetmethods_t *methods; + ISC_LINK(dns_rdataset_t) link; + + /* + * XXX do we need these, or should they be retrieved by methods? + * Leaning towards the latter, since they are not frequently required + * once you have the rdataset. + */ + dns_rdataclass_t rdclass; + dns_rdatatype_t type; + dns_ttl_t ttl; + + dns_trust_t trust; + dns_rdatatype_t covers; + + /* + * attributes + */ + unsigned int attributes; + + /*% + * the counter provides the starting point in the "cyclic" order. + * The value UINT32_MAX has a special meaning of "picking up a + * random value." in order to take care of databases that do not + * increment the counter. + */ + uint32_t count; + + /* + * This RRSIG RRset should be re-generated around this time. + * Only valid if DNS_RDATASETATTR_RESIGN is set in attributes. + */ + isc_stdtime_t resign; + + /*@{*/ + /*% + * These are for use by the rdataset implementation, and MUST NOT + * be changed by clients. + */ + void *private1; + void *private2; + void *private3; + unsigned int privateuint4; + void *private5; + const void *private6; + const void *private7; + /*@}*/ +}; + +#define DNS_RDATASET_COUNT_UNDEFINED UINT32_MAX + +#define DNS_RDATASET_INIT \ + { \ + .magic = DNS_RDATASET_MAGIC, .link = ISC_LINK_INITIALIZER, \ + .count = DNS_RDATASET_COUNT_UNDEFINED \ + } + +/*! + * \def DNS_RDATASETATTR_RENDERED + * Used by message.c to indicate that the rdataset was rendered. + * + * \def DNS_RDATASETATTR_TTLADJUSTED + * Used by message.c to indicate that the rdataset's rdata had differing + * TTL values, and the rdataset->ttl holds the smallest. + * + * \def DNS_RDATASETATTR_LOADORDER + * Output the RRset in load order. + * + * \def DNS_RDATASETATTR_STALE_ADDED + * Set on rdatasets that were added during a stale-answer-client-timeout + * lookup. In other words, the RRset was added during a lookup of stale + * data and does not necessarily mean that the rdataset itself is stale. + */ + +#define DNS_RDATASETATTR_NONE 0x00000000 /*%< No ordering. */ +#define DNS_RDATASETATTR_QUESTION 0x00000001 +#define DNS_RDATASETATTR_RENDERED 0x00000002 /*%< Used by message.c */ +#define DNS_RDATASETATTR_ANSWERED 0x00000004 /*%< Used by server. */ +#define DNS_RDATASETATTR_CACHE 0x00000008 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_ANSWER 0x00000010 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_ANSWERSIG 0x00000020 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_EXTERNAL 0x00000040 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_NCACHE 0x00000080 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_CHAINING 0x00000100 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_TTLADJUSTED 0x00000200 /*%< Used by message.c */ +#define DNS_RDATASETATTR_FIXEDORDER 0x00000400 /*%< Fixed ordering. */ +#define DNS_RDATASETATTR_RANDOMIZE 0x00000800 /*%< Random ordering. */ +#define DNS_RDATASETATTR_CHASE 0x00001000 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_NXDOMAIN 0x00002000 +#define DNS_RDATASETATTR_NOQNAME 0x00004000 +#define DNS_RDATASETATTR_CHECKNAMES 0x00008000 /*%< Used by resolver. */ +#define DNS_RDATASETATTR_REQUIRED 0x00010000 +#define DNS_RDATASETATTR_REQUIREDGLUE DNS_RDATASETATTR_REQUIRED +#define DNS_RDATASETATTR_LOADORDER 0x00020000 +#define DNS_RDATASETATTR_RESIGN 0x00040000 +#define DNS_RDATASETATTR_CLOSEST 0x00080000 +#define DNS_RDATASETATTR_OPTOUT 0x00100000 /*%< OPTOUT proof */ +#define DNS_RDATASETATTR_NEGATIVE 0x00200000 +#define DNS_RDATASETATTR_PREFETCH 0x00400000 +#define DNS_RDATASETATTR_CYCLIC 0x00800000 /*%< Cyclic ordering. */ +#define DNS_RDATASETATTR_STALE 0x01000000 +#define DNS_RDATASETATTR_ANCIENT 0x02000000 +#define DNS_RDATASETATTR_STALE_WINDOW 0x04000000 +#define DNS_RDATASETATTR_STALE_ADDED 0x08000000 + +/*% + * _OMITDNSSEC: + * Omit DNSSEC records when rendering ncache records. + */ +#define DNS_RDATASETTOWIRE_OMITDNSSEC 0x0001 + +void +dns_rdataset_init(dns_rdataset_t *rdataset); +/*%< + * Make 'rdataset' a valid, disassociated rdataset. + * + * Requires: + *\li 'rdataset' is not NULL. + * + * Ensures: + *\li 'rdataset' is a valid, disassociated rdataset. + */ + +void +dns_rdataset_invalidate(dns_rdataset_t *rdataset); +/*%< + * Invalidate 'rdataset'. + * + * Requires: + *\li 'rdataset' is a valid, disassociated rdataset. + * + * Ensures: + *\li If assertion checking is enabled, future attempts to use 'rdataset' + * without initializing it will cause an assertion failure. + */ + +void +dns_rdataset_disassociate(dns_rdataset_t *rdataset); +/*%< + * Disassociate 'rdataset' from its rdata, allowing it to be reused. + * + * Notes: + *\li The client must ensure it has no references to rdata in the rdataset + * before disassociating. + * + * Requires: + *\li 'rdataset' is a valid, associated rdataset. + * + * Ensures: + *\li 'rdataset' is a valid, disassociated rdataset. + */ + +bool +dns_rdataset_isassociated(dns_rdataset_t *rdataset); +/*%< + * Is 'rdataset' associated? + * + * Requires: + *\li 'rdataset' is a valid rdataset. + * + * Returns: + *\li #true 'rdataset' is associated. + *\li #false 'rdataset' is not associated. + */ + +void +dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass, + dns_rdatatype_t type); +/*%< + * Make 'rdataset' a valid, associated, question rdataset, with a + * question class of 'rdclass' and type 'type'. + * + * Notes: + *\li Question rdatasets have a class and type, but no rdata. + * + * Requires: + *\li 'rdataset' is a valid, disassociated rdataset. + * + * Ensures: + *\li 'rdataset' is a valid, associated, question rdataset. + */ + +void +dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target); +/*%< + * Make 'target' refer to the same rdataset as 'source'. + * + * Requires: + *\li 'source' is a valid, associated rdataset. + * + *\li 'target' is a valid, dissociated rdataset. + * + * Ensures: + *\li 'target' references the same rdataset as 'source'. + */ + +unsigned int +dns_rdataset_count(dns_rdataset_t *rdataset); +/*%< + * Return the number of records in 'rdataset'. + * + * Requires: + *\li 'rdataset' is a valid, associated rdataset. + * + * Returns: + *\li The number of records in 'rdataset'. + */ + +isc_result_t +dns_rdataset_first(dns_rdataset_t *rdataset); +/*%< + * Move the rdata cursor to the first rdata in the rdataset (if any). + * + * Requires: + *\li 'rdataset' is a valid, associated rdataset. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no rdata in the set. + */ + +isc_result_t +dns_rdataset_next(dns_rdataset_t *rdataset); +/*%< + * Move the rdata cursor to the next rdata in the rdataset (if any). + * + * Requires: + *\li 'rdataset' is a valid, associated rdataset. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no more rdata in the set. + */ + +void +dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata); +/*%< + * Make 'rdata' refer to the current rdata. + * + * Notes: + * + *\li The data returned in 'rdata' is valid for the life of the + * rdataset; in particular, subsequent changes in the cursor position + * do not invalidate 'rdata'. + * + * Requires: + *\li 'rdataset' is a valid, associated rdataset. + * + *\li The rdata cursor of 'rdataset' is at a valid location (i.e. the + * result of last call to a cursor movement command was ISC_R_SUCCESS). + * + * Ensures: + *\li 'rdata' refers to the rdata at the rdata cursor location of + *\li 'rdataset'. + */ + +isc_result_t +dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + bool omit_final_dot, bool question, isc_buffer_t *target); +/*%< + * Convert 'rdataset' to text format, storing the result in 'target'. + * + * Notes: + *\li The rdata cursor position will be changed. + * + *\li The 'question' flag should normally be #false. If it is + * #true, the TTL and rdata fields are not printed. This is + * for use when printing an rdata representing a question section. + * + *\li This interface is deprecated; use dns_master_rdatasettottext() + * and/or dns_master_questiontotext() instead. + * + * Requires: + *\li 'rdataset' is a valid rdataset. + * + *\li 'rdataset' is not empty. + */ + +isc_result_t +dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_compress_t *cctx, isc_buffer_t *target, + unsigned int options, unsigned int *countp); +/*%< + * Convert 'rdataset' to wire format, compressing names as specified + * in 'cctx', and storing the result in 'target'. + * + * Notes: + *\li The rdata cursor position will be changed. + * + *\li The number of RRs added to target will be added to *countp. + * + * Requires: + *\li 'rdataset' is a valid rdataset. + * + *\li 'rdataset' is not empty. + * + *\li 'countp' is a valid pointer. + * + * Ensures: + *\li On a return of ISC_R_SUCCESS, 'target' contains a wire format + * for the data contained in 'rdataset'. Any error return leaves + * the buffer unchanged. + * + *\li *countp has been incremented by the number of RRs added to + * target. + * + * Returns: + *\li #ISC_R_SUCCESS - all ok + *\li #ISC_R_NOSPACE - 'target' doesn't have enough room + * + *\li Any error returned by dns_rdata_towire(), dns_rdataset_next(), + * dns_name_towire(). + */ + +isc_result_t +dns_rdataset_towiresorted(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, dns_compress_t *cctx, + isc_buffer_t *target, dns_rdatasetorderfunc_t order, + const void *order_arg, unsigned int options, + unsigned int *countp); +/*%< + * Like dns_rdataset_towire(), but sorting the rdatasets according to + * the integer value returned by 'order' when called with the rdataset + * and 'order_arg' as arguments. + * + * Requires: + *\li All the requirements of dns_rdataset_towire(), and + * that order_arg is NULL if and only if order is NULL. + */ + +isc_result_t +dns_rdataset_towirepartial(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, dns_compress_t *cctx, + isc_buffer_t *target, dns_rdatasetorderfunc_t order, + const void *order_arg, unsigned int options, + unsigned int *countp, void **state); +/*%< + * Like dns_rdataset_towiresorted() except that a partial rdataset + * may be written. + * + * Requires: + *\li All the requirements of dns_rdataset_towiresorted(). + * If 'state' is non NULL then the current position in the + * rdataset will be remembered if the rdataset in not + * completely written and should be passed on on subsequent + * calls (NOT CURRENTLY IMPLEMENTED). + * + * Returns: + *\li #ISC_R_SUCCESS if all of the records were written. + *\li #ISC_R_NOSPACE if unable to fit in all of the records. *countp + * will be updated to reflect the number of records + * written. + */ + +isc_result_t +dns_rdataset_additionaldata(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, + dns_additionaldatafunc_t add, void *arg); +/*%< + * For each rdata in rdataset, call 'add' for each name and type in the + * rdata which is subject to additional section processing. + * + * Requires: + * + *\li 'rdataset' is a valid, non-question rdataset. + * + *\li 'add' is a valid dns_additionaldatafunc_t + * + * Ensures: + * + *\li If successful, dns_rdata_additionaldata() will have been called for + * each rdata in 'rdataset'. + * + *\li If a call to dns_rdata_additionaldata() is not successful, the + * result returned will be the result of dns_rdataset_additionaldata(). + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_rdata_additionaldata() can return. + */ + +isc_result_t +dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); +/*%< + * Return the noqname proof for this record. + * + * Requires: + *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set. + *\li 'name' to be valid. + *\li 'neg' and 'negsig' to be valid and not associated. + */ + +isc_result_t +dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name); +/*%< + * Associate a noqname proof with this record. + * Sets #DNS_RDATASETATTR_NOQNAME if successful. + * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and + * the 'nsec'/'nsec3' and 'rrsig(nsec)'/'rrsig(nsec3)' ttl. + * + * Requires: + *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set. + *\li 'name' to be valid and have NSEC or NSEC3 and associated RRSIG + * rdatasets. + */ + +isc_result_t +dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *nsec, dns_rdataset_t *nsecsig); +/*%< + * Return the closest encloser for this record. + * + * Requires: + *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set. + *\li 'name' to be valid. + *\li 'nsec' and 'nsecsig' to be valid and not associated. + */ + +isc_result_t +dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name); +/*%< + * Associate a closest encloset proof with this record. + * Sets #DNS_RDATASETATTR_CLOSEST if successful. + * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and + * the 'nsec' and 'rrsig(nsec)' ttl. + * + * Requires: + *\li 'rdataset' to be valid and #DNS_RDATASETATTR_CLOSEST to be set. + *\li 'name' to be valid and have NSEC3 and RRSIG(NSEC3) rdatasets. + */ + +void +dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust); +/*%< + * Set the trust of the 'rdataset' to trust in any in the backing database. + * The local trust level of 'rdataset' is also set. + */ + +void +dns_rdataset_expire(dns_rdataset_t *rdataset); +/*%< + * Mark the rdataset to be expired in the backing database. + */ + +void +dns_rdataset_clearprefetch(dns_rdataset_t *rdataset); +/*%< + * Clear the PREFETCH attribute for the given rdataset in the + * underlying database. + * + * In the cache database, this signals that the rdataset is not + * eligible to be prefetched when the TTL is close to expiring. + * It has no function in other databases. + */ + +void +dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name); +/*%< + * Store the casing of 'name', the owner name of 'rdataset', into + * a bitfield so that the name can be capitalized the same when when + * the rdataset is used later. This sets the CASESET attribute. + */ + +void +dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name); +/*%< + * If the CASESET attribute is set, retrieve the case bitfield that was + * previously stored by dns_rdataset_getownername(), and capitalize 'name' + * according to it. If CASESET is not set, do nothing. + */ + +isc_result_t +dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version, + dns_message_t *msg); +/*%< + * Add glue records for rdataset to the additional section of message in + * 'msg'. 'rdataset' must be of type NS. + * + * In case a successful result is not returned, the caller should try to + * add glue directly to the message by iterating for additional data. + * + * Requires: + * \li 'rdataset' is a valid NS rdataset. + * \li 'version' is the DB version. + * \li 'msg' is the DNS message to which the glue should be added. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTIMPLEMENTED + *\li #ISC_R_FAILURE + *\li Any error that dns_rdata_additionaldata() can return. + */ + +void +dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_rdata_rrsig_t *rrsig, isc_stdtime_t now, + bool acceptexpired); +/*%< + * Trim the ttl of 'rdataset' and 'sigrdataset' so that they will expire + * at or before 'rrsig->expiretime'. If 'acceptexpired' is true and the + * signature has expired or will expire in the next 120 seconds, limit + * the ttl to be no more than 120 seconds. + * + * The ttl is further limited by the original ttl as stored in 'rrsig' + * and the original ttl values of 'rdataset' and 'sigrdataset'. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + * \li 'sigrdataset' is a valid rdataset. + * \li 'rrsig' is non NULL. + */ + +const char * +dns_trust_totext(dns_trust_t trust); +/*%< + * Display trust in textual form. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdatasetiter.h b/lib/dns/include/dns/rdatasetiter.h new file mode 100644 index 0000000..8611d99 --- /dev/null +++ b/lib/dns/include/dns/rdatasetiter.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/rdatasetiter.h + * \brief + * The DNS Rdataset Iterator interface allows iteration of all of the + * rdatasets at a node. + * + * The dns_rdatasetiter_t type is like a "virtual class". To actually use + * it, an implementation of the class is required. This implementation is + * supplied by the database. + * + * It is the client's responsibility to call dns_rdataset_disassociate() + * on all rdatasets returned. + * + * XXX more XXX + * + * MP: + *\li The iterator itself is not locked. The caller must ensure + * synchronization. + * + *\li The iterator methods ensure appropriate database locking. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +/***** +***** Imports +*****/ + +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/***** +***** Types +*****/ + +typedef struct dns_rdatasetitermethods { + void (*destroy)(dns_rdatasetiter_t **iteratorp); + isc_result_t (*first)(dns_rdatasetiter_t *iterator); + isc_result_t (*next)(dns_rdatasetiter_t *iterator); + void (*current)(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset); +} dns_rdatasetitermethods_t; + +#define DNS_RDATASETITER_MAGIC ISC_MAGIC('D', 'N', 'S', 'i') +#define DNS_RDATASETITER_VALID(i) ISC_MAGIC_VALID(i, DNS_RDATASETITER_MAGIC) + +/*% + * This structure is actually just the common prefix of a DNS db + * implementation's version of a dns_rdatasetiter_t. + * \brief + * Direct use of this structure by clients is forbidden. DB implementations + * may change the structure. 'magic' must be #DNS_RDATASETITER_MAGIC for + * any of the dns_rdatasetiter routines to work. DB implementations must + * maintain all DB rdataset iterator invariants. + */ +struct dns_rdatasetiter { + /* Unlocked. */ + unsigned int magic; + dns_rdatasetitermethods_t *methods; + dns_db_t *db; + dns_dbnode_t *node; + dns_dbversion_t *version; + isc_stdtime_t now; + unsigned int options; +}; + +void +dns_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp); +/*%< + * Destroy '*iteratorp'. + * + * Requires: + * + *\li '*iteratorp' is a valid iterator. + * + * Ensures: + * + *\li All resources used by the iterator are freed. + * + *\li *iteratorp == NULL. + */ + +isc_result_t +dns_rdatasetiter_first(dns_rdatasetiter_t *iterator); +/*%< + * Move the rdataset cursor to the first rdataset at the node (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMORE There are no rdatasets at the node. + * + *\li Other results are possible, depending on the DB implementation. + */ + +isc_result_t +dns_rdatasetiter_next(dns_rdatasetiter_t *iterator); +/*%< + * Move the rdataset cursor to the next rdataset at the node (if any). + * + * Requires: + *\li 'iterator' is a valid iterator. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMORE There are no more rdatasets at the + * node. + * + *\li Other results are possible, depending on the DB implementation. + */ + +void +dns_rdatasetiter_current(dns_rdatasetiter_t *iterator, + dns_rdataset_t *rdataset); +/*%< + * Return the current rdataset. + * + * Requires: + *\li 'iterator' is a valid iterator. + * + *\li 'rdataset' is a valid, disassociated rdataset. + * + *\li The rdataset cursor of 'iterator' is at a valid location (i.e. the + * result of last call to a cursor movement command was #ISC_R_SUCCESS). + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h new file mode 100644 index 0000000..7364b8d --- /dev/null +++ b/lib/dns/include/dns/rdataslab.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rdataslab.h + * \brief + * Implements storage of rdatasets into slabs of memory. + * + * MP: + *\li Clients of this module must impose any required synchronization. + * + * Reliability: + *\li This module deals with low-level byte streams. Errors in any of + * the functions are likely to crash the server or corrupt memory. + * + *\li If the caller passes invalid memory references, these functions are + * likely to crash the server or corrupt memory. + * + * Resources: + *\li None. + * + * Security: + *\li None. + * + * Standards: + *\li None. + */ + +/*** + *** Imports + ***/ + +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +#define DNS_RDATASLAB_FORCE 0x1 +#define DNS_RDATASLAB_EXACT 0x2 + +#define DNS_RDATASLAB_OFFLINE 0x01 /* RRSIG is for offline DNSKEY */ +#define DNS_RDATASLAB_WARNMASK \ + 0x0E /*%< RRSIG(DNSKEY) expired \ + * warnings number mask. */ +#define DNS_RDATASLAB_WARNSHIFT \ + 1 /*%< How many bits to shift to find \ + * remaining expired warning number. */ + +/*** + *** Functions + ***/ + +isc_result_t +dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, + isc_region_t *region, unsigned int reservelen); +/*%< + * Slabify a rdataset. The slab area will be allocated and returned + * in 'region'. + * + * Requires: + *\li 'rdataset' is valid. + * + * Ensures: + *\li 'region' will have base pointing to the start of allocated memory, + * with the slabified region beginning at region->base + reservelen. + * region->length contains the total length allocated. + * + * Returns: + *\li ISC_R_SUCCESS - successful completion + *\li ISC_R_NOMEMORY - no memory. + *\li XXX others + */ + +unsigned int +dns_rdataslab_size(unsigned char *slab, unsigned int reservelen); +/*%< + * Return the total size of an rdataslab. + * + * Requires: + *\li 'slab' points to a slab. + * + * Returns: + *\li The number of bytes in the slab, including the reservelen. + */ + +unsigned int +dns_rdataslab_rdatasize(unsigned char *slab, unsigned int reservelen); +/*%< + * Return the size of the rdata in an rdataslab. + * + * Requires: + *\li 'slab' points to a slab. + */ + +unsigned int +dns_rdataslab_count(unsigned char *slab, unsigned int reservelen); +/*%< + * Return the number of records in the rdataslab + * + * Requires: + *\li 'slab' points to a slab. + * + * Returns: + *\li The number of records in the slab. + */ + +isc_result_t +dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, + unsigned int reservelen, isc_mem_t *mctx, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int flags, unsigned char **tslabp); +/*%< + * Merge 'oslab' and 'nslab'. + */ + +isc_result_t +dns_rdataslab_subtract(unsigned char *mslab, unsigned char *sslab, + unsigned int reservelen, isc_mem_t *mctx, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int flags, unsigned char **tslabp); +/*%< + * Subtract 'sslab' from 'mslab'. If 'exact' is true then all elements + * of 'sslab' must exist in 'mslab'. + * + * XXX + * valid flags are DNS_RDATASLAB_EXACT + */ + +bool +dns_rdataslab_equal(unsigned char *slab1, unsigned char *slab2, + unsigned int reservelen); +/*%< + * Compare two rdataslabs for equality. This does _not_ do a full + * DNSSEC comparison. + * + * Requires: + *\li 'slab1' and 'slab2' point to slabs. + * + * Returns: + *\li true if the slabs are equal, false otherwise. + */ +bool +dns_rdataslab_equalx(unsigned char *slab1, unsigned char *slab2, + unsigned int reservelen, dns_rdataclass_t rdclass, + dns_rdatatype_t type); +/*%< + * Compare two rdataslabs for DNSSEC equality. + * + * Requires: + *\li 'slab1' and 'slab2' point to slabs. + * + * Returns: + *\li true if the slabs are equal, #false otherwise. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdatatype.h b/lib/dns/include/dns/rdatatype.h new file mode 100644 index 0000000..9590752 --- /dev/null +++ b/lib/dns/include/dns/rdatatype.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rdatatype.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rdatatype_fromtext(dns_rdatatype_t *typep, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNS rdata type. + * + * Requires: + *\li 'typep' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li DNS_R_UNKNOWN type is unknown + */ + +isc_result_t +dns_rdatatype_totext(dns_rdatatype_t type, isc_buffer_t *target); +/*%< + * Put a textual representation of type 'type' into 'target'. + * + * Requires: + *\li 'type' is a valid type. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +isc_result_t +dns_rdatatype_tounknowntext(dns_rdatatype_t type, isc_buffer_t *target); +/*%< + * Put textual RFC3597 TYPEXXXX representation of type 'type' into + * 'target'. + * + * Requires: + *\li 'type' is a valid type. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li #ISC_R_SUCCESS on success + *\li #ISC_R_NOSPACE target buffer is too small + */ + +void +dns_rdatatype_format(dns_rdatatype_t rdtype, char *array, unsigned int size); +/*%< + * Format a human-readable representation of the type 'rdtype' + * into the character array 'array', which is of size 'size'. + * The resulting string is guaranteed to be null-terminated. + */ + +#define DNS_RDATATYPE_FORMATSIZE sizeof("NSEC3PARAM") + +/*%< + * Minimum size of array to pass to dns_rdatatype_format(). + * May need to be adjusted if a new RR type with a very long + * name is defined. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/request.h b/lib/dns/include/dns/request.h new file mode 100644 index 0000000..d00574f --- /dev/null +++ b/lib/dns/include/dns/request.h @@ -0,0 +1,321 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/request.h + * + * \brief + * The request module provides simple request/response services useful for + * sending SOA queries, DNS Notify messages, and dynamic update requests. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + */ + +#include + +#include +#include + +#include + +#define DNS_REQUESTOPT_TCP 0x00000001U +#define DNS_REQUESTOPT_CASE 0x00000002U +#define DNS_REQUESTOPT_FIXEDID 0x00000004U + +typedef struct dns_requestevent { + ISC_EVENT_COMMON(struct dns_requestevent); + isc_result_t result; + dns_request_t *request; +} dns_requestevent_t; + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_requestmgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, + dns_requestmgr_t **requestmgrp); +/*%< + * Create a request manager. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'socketmgr' is a valid socket manager. + * + *\li 'taskmgr' is a valid task manager. + * + *\li 'dispatchv4' is a valid dispatcher with an IPv4 UDP socket, or is NULL. + * + *\li 'dispatchv6' is a valid dispatcher with an IPv6 UDP socket, or is NULL. + * + *\li requestmgrp != NULL && *requestmgrp == NULL + * + * Ensures: + * + *\li On success, *requestmgrp is a valid request manager. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +void +dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task, + isc_event_t **eventp); +/*%< + * Send '*eventp' to 'task' when 'requestmgr' has completed shutdown. + * + * Notes: + * + *\li It is not safe to detach the last reference to 'requestmgr' until + * shutdown is complete. + * + * Requires: + * + *\li 'requestmgr' is a valid request manager. + * + *\li 'task' is a valid task. + * + *\li *eventp is a valid event. + * + * Ensures: + * + *\li *eventp == NULL. + */ + +void +dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr); +/*%< + * Start the shutdown process for 'requestmgr'. + * + * Notes: + * + *\li This call has no effect if the request manager is already shutting + * down. + * + * Requires: + * + *\li 'requestmgr' is a valid requestmgr. + */ + +void +dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp); +/*%< + * Attach to the request manager. dns_requestmgr_shutdown() must not + * have been called on 'source' prior to calling dns_requestmgr_attach(). + * + * Requires: + * + *\li 'source' is a valid requestmgr. + * + *\li 'targetp' to be non NULL and '*targetp' to be NULL. + */ + +void +dns_requestmgr_detach(dns_requestmgr_t **requestmgrp); +/*%< + * Detach from the given requestmgr. If this is the final detach + * requestmgr will be destroyed. dns_requestmgr_shutdown() must + * be called before the final detach. + * + * Requires: + * + *\li '*requestmgrp' is a valid requestmgr. + * + * Ensures: + *\li '*requestmgrp' is NULL. + */ + +isc_result_t +dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, unsigned int options, + dns_tsigkey_t *key, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp); +/*%< + * Create and send a request. + * + * Notes: + * + *\li 'message' will be rendered and sent to 'address'. If the + * #DNS_REQUESTOPT_TCP option is set, TCP will be used, + * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP + * (vs. connected) will be shared too. The request + * will timeout after 'timeout' seconds. UDP requests will be resent + * at 'udptimeout' intervals if non-zero or 'udpretries' is non-zero. + * + *\li If the #DNS_REQUESTOPT_CASE option is set, use case sensitive + * compression. + * + *\li When the request completes, successfully, due to a timeout, or + * because it was canceled, a completion event will be sent to 'task'. + * + * Requires: + * + *\li 'message' is a valid DNS message. + * + *\li 'dstaddr' is a valid sockaddr. + * + *\li 'srcaddr' is a valid sockaddr or NULL. + * + *\li 'srcaddr' and 'dstaddr' are the same protocol family. + * + *\li 'timeout' > 0 + * + *\li 'task' is a valid task. + * + *\li requestp != NULL && *requestp == NULL + */ + +isc_result_t +dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, unsigned int options, + unsigned int timeout, unsigned int udptimeout, + unsigned int udpretries, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp); +/*!< + * \brief Create and send a request. + * + * Notes: + * + *\li 'msgbuf' will be sent to 'destaddr' after setting the id. If the + * #DNS_REQUESTOPT_TCP option is set, TCP will be used, + * #DNS_REQUESTOPT_SHARE option is set too, connecting TCP + * (vs. connected) will be shared too. The request + * will timeout after 'timeout' seconds. UDP requests will be resent + * at 'udptimeout' intervals if non-zero or if 'udpretries' is not zero. + * + *\li When the request completes, successfully, due to a timeout, or + * because it was canceled, a completion event will be sent to 'task'. + * + * Requires: + * + *\li 'msgbuf' is a valid DNS message in compressed wire format. + * + *\li 'destaddr' is a valid sockaddr. + * + *\li 'srcaddr' is a valid sockaddr or NULL. + * + *\li 'srcaddr' and 'dstaddr' are the same protocol family. + * + *\li 'timeout' > 0 + * + *\li 'task' is a valid task. + * + *\li requestp != NULL && *requestp == NULL + */ + +void +dns_request_cancel(dns_request_t *request); +/*%< + * Cancel 'request'. + * + * Requires: + * + *\li 'request' is a valid request. + * + * Ensures: + * + *\li If the completion event for 'request' has not yet been sent, it + * will be sent, and the result code will be ISC_R_CANCELED. + */ + +isc_result_t +dns_request_getresponse(dns_request_t *request, dns_message_t *message, + unsigned int options); +/*%< + * Get the response to 'request' by filling in 'message'. + * + * 'options' is passed to dns_message_parse(). See dns_message_parse() + * for more details. + * + * Requires: + * + *\li 'request' is a valid request for which the caller has received the + * completion event. + * + *\li The result code of the completion event was #ISC_R_SUCCESS. + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any result that dns_message_parse() can return. + */ +isc_buffer_t * +dns_request_getanswer(dns_request_t *request); +/* + * Get the response to 'request' as a buffer. + * + * Requires: + * + *\li 'request' is a valid request for which the caller has received the + * completion event. + * + * Returns: + * + *\li a pointer to the answer buffer. + */ + +bool +dns_request_usedtcp(dns_request_t *request); +/*%< + * Return whether this query used TCP or not. Setting #DNS_REQUESTOPT_TCP + * in the call to dns_request_create() will cause the function to return + * #true, otherwise the result is based on the query message size. + * + * Requires: + *\li 'request' is a valid request. + * + * Returns: + *\li true if TCP was used. + *\li false if UDP was used. + */ + +void +dns_request_destroy(dns_request_t **requestp); +/*%< + * Destroy 'request'. + * + * Requires: + * + *\li 'request' is a valid request for which the caller has received the + * completion event. + * + * Ensures: + * + *\li *requestp == NULL + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h new file mode 100644 index 0000000..4de314c --- /dev/null +++ b/lib/dns/include/dns/resolver.h @@ -0,0 +1,709 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/resolver.h + * + * \brief + * This is the BIND 9 resolver, the module responsible for resolving DNS + * requests by iteratively querying authoritative servers and following + * referrals. This is a "full resolver", not to be confused with + * the stub resolvers most people associate with the word "resolver". + * The full resolver is part of the caching name server or resolver + * daemon the stub resolver talks to. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li RFCs: 1034, 1035, 2181, TBS + *\li Drafts: TBS + */ + +#include +#include + +#include +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*% + * A dns_fetchevent_t is sent when a 'fetch' completes. Any of 'db', + * 'node', 'rdataset', and 'sigrdataset' may be bound. It is the + * receiver's responsibility to detach before freeing the event. + * \brief + * 'rdataset', 'sigrdataset', 'client' and 'id' are the values that were + * supplied when dns_resolver_createfetch() was called. They are returned + * to the caller so that they may be freed. + */ +typedef struct dns_fetchevent { + ISC_EVENT_COMMON(struct dns_fetchevent); + dns_fetch_t *fetch; + isc_result_t result; + dns_rdatatype_t qtype; + dns_db_t *db; + dns_dbnode_t *node; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_fixedname_t fname; + dns_name_t *foundname; + const isc_sockaddr_t *client; + dns_messageid_t id; + isc_result_t vresult; +} dns_fetchevent_t; + +/*% + * The two quota types (fetches-per-zone and fetches-per-server) + */ +typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t; + +/* + * Options that modify how a 'fetch' is done. + */ +enum { + DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */ + DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */ + DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */ + DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */ + DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */ + DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */ + DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */ + DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */ + DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */ + DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */ + DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */ + DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */ + DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset + * from the child zone when a + * delegation is returned in + * response to a NS query. */ + DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers + * that return errors on + * non-empty terminals. */ + DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when + * doing qname minimization + * on ip6.arpa. */ + DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if + * possible. */ + DNS_FETCHOPT_TRYSTALE_ONTIMEOUT = 1 << 16, + + /*% EDNS version bits: */ + DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23, + DNS_FETCHOPT_EDNSVERSIONSHIFT = 24, + DNS_FETCHOPT_EDNSVERSIONMASK = 0xff000000, +}; + +/* + * Upper bounds of class of query RTT (ms). Corresponds to + * dns_resstatscounter_queryrttX statistics counters. + */ +#define DNS_RESOLVER_QRYRTTCLASS0 10 +#define DNS_RESOLVER_QRYRTTCLASS0STR "10" +#define DNS_RESOLVER_QRYRTTCLASS1 100 +#define DNS_RESOLVER_QRYRTTCLASS1STR "100" +#define DNS_RESOLVER_QRYRTTCLASS2 500 +#define DNS_RESOLVER_QRYRTTCLASS2STR "500" +#define DNS_RESOLVER_QRYRTTCLASS3 800 +#define DNS_RESOLVER_QRYRTTCLASS3STR "800" +#define DNS_RESOLVER_QRYRTTCLASS4 1600 +#define DNS_RESOLVER_QRYRTTCLASS4STR "1600" + +/* + * XXXRTH Should this API be made semi-private? (I.e. + * _dns_resolver_create()). + */ + +#define DNS_RESOLVER_CHECKNAMES 0x01 +#define DNS_RESOLVER_CHECKNAMESFAIL 0x02 + +#define DNS_QMIN_MAXLABELS 7 +#define DNS_QMIN_MAX_NO_DELEGATION 3 +#define DNS_MAX_LABELS 127 + +isc_result_t +dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm, + isc_timermgr_t *timermgr, unsigned int options, + dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4, + dns_dispatch_t *dispatchv6, dns_resolver_t **resp); + +/*%< + * Create a resolver. + * + * Notes: + * + *\li Generally, applications should not create a resolver directly, but + * should instead call dns_view_createresolver(). + * + * Requires: + * + *\li 'view' is a valid view. + * + *\li 'taskmgr' is a valid task manager. + * + *\li 'ntasks' > 0. + * + *\li 'nm' is a valid network manager. + * + *\li 'timermgr' is a valid timer manager. + * + *\li 'dispatchv4' is a dispatch with an IPv4 UDP socket, or is NULL. + * If not NULL, 'ndisp' clones of it will be created by the resolver. + * + *\li 'dispatchv6' is a dispatch with an IPv6 UDP socket, or is NULL. + * If not NULL, 'ndisp' clones of it will be created by the resolver. + * + *\li resp != NULL && *resp == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +void +dns_resolver_freeze(dns_resolver_t *res); +/*%< + * Freeze resolver. + * + * Notes: + * + *\li Certain configuration changes cannot be made after the resolver + * is frozen. Fetches cannot be created until the resolver is frozen. + * + * Requires: + * + *\li 'res' is a valid resolver. + * + * Ensures: + * + *\li 'res' is frozen. + */ + +void +dns_resolver_prime(dns_resolver_t *res); +/*%< + * Prime resolver. + * + * Notes: + * + *\li Resolvers which have a forwarding policy other than dns_fwdpolicy_only + * need to be primed with the root nameservers, otherwise the root + * nameserver hints data may be used indefinitely. This function requests + * that the resolver start a priming fetch, if it isn't already priming. + * + * Requires: + * + *\li 'res' is a valid, frozen resolver. + */ + +void +dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task, + isc_event_t **eventp); +/*%< + * Send '*eventp' to 'task' when 'res' has completed shutdown. + * + * Notes: + * + *\li It is not safe to detach the last reference to 'res' until + * shutdown is complete. + * + * Requires: + * + *\li 'res' is a valid resolver. + * + *\li 'task' is a valid task. + * + *\li *eventp is a valid event. + * + * Ensures: + * + *\li *eventp == NULL. + */ + +void +dns_resolver_shutdown(dns_resolver_t *res); +/*%< + * Start the shutdown process for 'res'. + * + * Notes: + * + *\li This call has no effect if the resolver is already shutting down. + * + * Requires: + * + *\li 'res' is a valid resolver. + */ + +void +dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp); + +void +dns_resolver_detach(dns_resolver_t **resp); + +isc_result_t +dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain, + dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + const isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, unsigned int depth, + isc_counter_t *qc, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp); +/*%< + * Recurse to answer a question. + * + * Notes: + * + *\li This call starts a query for 'name', type 'type'. + * + *\li The 'domain' is a parent domain of 'name' for which + * a set of name servers 'nameservers' is known. If no + * such name server information is available, set + * 'domain' and 'nameservers' to NULL. + * + *\li 'forwarders' is unimplemented, and subject to change when + * we figure out how selective forwarding will work. + * + *\li When the fetch completes (successfully or otherwise), a + * #DNS_EVENT_FETCHDONE event with action 'action' and arg 'arg' will be + * posted to 'task'. + * + *\li The values of 'rdataset' and 'sigrdataset' will be returned in + * the FETCHDONE event. + * + *\li 'client' and 'id' are used for duplicate query detection. '*client' + * must remain stable until after 'action' has been called or + * dns_resolver_cancelfetch() is called. + * + * Requires: + * + *\li 'res' is a valid resolver that has been frozen. + * + *\li 'name' is a valid name. + * + *\li 'type' is not a meta type other than ANY. + * + *\li 'domain' is a valid name or NULL. + * + *\li 'nameservers' is a valid NS rdataset (whose owner name is 'domain') + * iff. 'domain' is not NULL. + * + *\li 'forwarders' is NULL. + * + *\li 'client' is a valid sockaddr or NULL. + * + *\li 'options' contains valid options. + * + *\li 'rdataset' is a valid, disassociated rdataset. + * + *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset. + * + *\li fetchp != NULL && *fetchp == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS Success + *\li #DNS_R_DUPLICATE + *\li #DNS_R_DROP + * + *\li Many other values are possible, all of which indicate failure. + */ + +void +dns_resolver_cancelfetch(dns_fetch_t *fetch); +/*%< + * Cancel 'fetch'. + * + * Notes: + * + *\li If 'fetch' has not completed, post its FETCHDONE event with a + * result code of #ISC_R_CANCELED. + * + * Requires: + * + *\li 'fetch' is a valid fetch. + */ + +void +dns_resolver_destroyfetch(dns_fetch_t **fetchp); +/*%< + * Destroy 'fetch'. + * + * Requires: + * + *\li '*fetchp' is a valid fetch. + * + *\li The caller has received the FETCHDONE event (either because the + * fetch completed or because dns_resolver_cancelfetch() was called). + * + * Ensures: + * + *\li *fetchp == NULL. + */ + +void +dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, bool duplicateok); +/*%< + * Dump a log message on internal state at the completion of given 'fetch'. + * 'lctx', 'category', 'module', and 'level' are used to write the log message. + * By default, only one log message is written even if the corresponding fetch + * context serves multiple clients; if 'duplicateok' is true the suppression + * is disabled and the message can be written every time this function is + * called. + * + * Requires: + * + *\li 'fetch' is a valid fetch, and has completed. + */ + +dns_dispatchmgr_t * +dns_resolver_dispatchmgr(dns_resolver_t *resolver); + +dns_dispatch_t * +dns_resolver_dispatchv4(dns_resolver_t *resolver); + +dns_dispatch_t * +dns_resolver_dispatchv6(dns_resolver_t *resolver); + +isc_taskmgr_t * +dns_resolver_taskmgr(dns_resolver_t *resolver); + +uint32_t +dns_resolver_getlamettl(dns_resolver_t *resolver); +/*%< + * Get the resolver's lame-ttl. zero => no lame processing. + * + * Requires: + *\li 'resolver' to be valid. + */ + +void +dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl); +/*%< + * Set the resolver's lame-ttl. zero => no lame processing. + * + * Requires: + *\li 'resolver' to be valid. + */ + +void +dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt, + const dns_name_t *name, in_port_t port); +/*%< + * Add alternate addresses to be tried in the event that the nameservers + * for a zone are not available in the address families supported by the + * operating system. + * + * Require: + * \li only one of 'name' or 'alt' to be valid. + */ + +void +dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize); +/*%< + * Set the EDNS UDP buffer size advertised by the server. + */ + +uint16_t +dns_resolver_getudpsize(dns_resolver_t *resolver); +/*%< + * Get the current EDNS UDP buffer size. + */ + +void +dns_resolver_reset_algorithms(dns_resolver_t *resolver); +/*%< + * Clear the disabled DNSSEC algorithms. + */ + +void +dns_resolver_reset_ds_digests(dns_resolver_t *resolver); +/*%< + * Clear the disabled DS digest types. + */ + +isc_result_t +dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int alg); +/*%< + * Mark the given DNSSEC algorithm as disabled and below 'name'. + * Valid algorithms are less than 256. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_RANGE + *\li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int digest_type); +/*%< + * Mark the given DS digest type as disabled and below 'name'. + * Valid types are less than 256. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_RANGE + *\li #ISC_R_NOMEMORY + */ + +bool +dns_resolver_algorithm_supported(dns_resolver_t *resolver, + const dns_name_t *name, unsigned int alg); +/*%< + * Check if the given algorithm is supported by this resolver. + * This checks whether the algorithm has been disabled via + * dns_resolver_disable_algorithm(), then checks the underlying + * crypto libraries if it was not specifically disabled. + */ + +bool +dns_resolver_ds_digest_supported(dns_resolver_t *resolver, + const dns_name_t *name, + unsigned int digest_type); +/*%< + * Check if the given digest type is supported by this resolver. + * This checks whether the digest type has been disabled via + * dns_resolver_disable_ds_digest(), then checks the underlying + * crypto libraries if it was not specifically disabled. + */ + +void +dns_resolver_resetmustbesecure(dns_resolver_t *resolver); + +isc_result_t +dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name, + bool value); + +bool +dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name); + +void +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout); +/*%< + * Set the length of time the resolver will work on a query, in milliseconds. + * + * 'timeout' was originally defined in seconds, and later redefined to be in + * milliseconds. Values less than or equal to 300 are treated as seconds. + * + * If timeout is 0, the default timeout will be applied. + * + * Requires: + * \li resolver to be valid. + */ + +unsigned int +dns_resolver_gettimeout(dns_resolver_t *resolver); +/*%< + * Get the current length of time the resolver will work on a query, + * in milliseconds. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min, + uint32_t max); +void +dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients); + +void +dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur, + uint32_t *min, uint32_t *max); + +bool +dns_resolver_getzeronosoattl(dns_resolver_t *resolver); + +void +dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state); + +unsigned int +dns_resolver_getretryinterval(dns_resolver_t *resolver); + +void +dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval); +/*%< + * Sets the amount of time, in milliseconds, that is waited for a reply + * to a server before another server is tried. Interacts with the + * value of dns_resolver_getnonbackofftries() by trying that number of times + * at this interval, before doing exponential backoff and doubling the interval + * on each subsequent try, to a maximum of 10 seconds. Defaults to 800 ms; + * silently capped at 2000 ms. + * + * Requires: + * \li resolver to be valid. + * \li interval > 0. + */ + +unsigned int +dns_resolver_getnonbackofftries(dns_resolver_t *resolver); + +void +dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries); +/*%< + * Sets the number of failures of getting a reply from remote servers for + * a query before backing off by doubling the retry interval for each + * subsequent request sent. Defaults to 3. + * + * Requires: + * \li resolver to be valid. + * \li tries > 0. + */ + +unsigned int +dns_resolver_getoptions(dns_resolver_t *resolver); +/*%< + * Get the resolver options. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *expire); +/*%< + * Add a entry to the bad cache for that will expire at 'expire'. + * + * Requires: + * \li resolver to be valid. + * \li name to be valid. + */ + +bool +dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *now); +/*%< + * Check to see if there is a unexpired entry in the bad cache for + * . + * + * Requires: + * \li resolver to be valid. + * \li name to be valid. + */ + +void +dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name); +/*%< + * Flush the bad cache of all entries at 'name' if 'name' is non NULL. + * Flush the entire bad cache if 'name' is NULL. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name); +/*%< + * Flush the bad cache of all entries at or below 'name'. + * + * Requires: + * \li resolver to be valid. + * \li name != NULL + */ + +void +dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp); +/*% + * Print out the contents of the bad cache to 'fp'. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth); +unsigned int +dns_resolver_getmaxdepth(dns_resolver_t *resolver); +/*% + * Get and set how many NS indirections will be followed when looking for + * nameserver addresses. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries); +unsigned int +dns_resolver_getmaxqueries(dns_resolver_t *resolver); +/*% + * Get and set how many iterative queries will be allowed before + * terminating a recursive query. + * + * Requires: + * \li resolver to be valid. + */ + +void +dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which, + isc_result_t resp); +isc_result_t +dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which); +/*% + * Get and set the result code that will be used when quotas + * are exceeded. If 'which' is set to quotatype "zone", then the + * result specified in 'resp' will be used when the fetches-per-zone + * quota is exceeded by a fetch. If 'which' is set to quotatype "server", + * then the result specified in 'resp' will be used when the + * fetches-per-server quota has been exceeded for all the + * authoritative servers for a zone. Valid choices are + * DNS_R_DROP or DNS_R_SERVFAIL. + * + * Requires: + * \li 'resolver' to be valid. + * \li 'which' to be dns_quotatype_zone or dns_quotatype_server + * \li 'resp' to be DNS_R_DROP or DNS_R_SERVFAIL. + */ + +void +dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format, + FILE *fp); + +#ifdef ENABLE_AFL +/*% + * Enable fuzzing of resolver, changes behaviour and eliminates retries + */ +void +dns_resolver_setfuzzing(void); +#endif /* ifdef ENABLE_AFL */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h new file mode 100644 index 0000000..6ab7e51 --- /dev/null +++ b/lib/dns/include/dns/result.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/result.h */ + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +dns_rcode_t +dns_result_torcode(isc_result_t result); + +isc_result_t +dns_result_fromrcode(dns_rcode_t rcode); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rootns.h b/lib/dns/include/dns/rootns.h new file mode 100644 index 0000000..d7740f7 --- /dev/null +++ b/lib/dns/include/dns/rootns.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/rootns.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rootns_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + const char *filename, dns_db_t **target); + +void +dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db); +/* + * Reports differences between hints and the real roots. + * + * Requires view, hints and (cache) db to be valid. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h new file mode 100644 index 0000000..364ad92 --- /dev/null +++ b/lib/dns/include/dns/rpz.h @@ -0,0 +1,441 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* + * Define this for reference count tracing in the unit + */ +#undef DNS_RPZ_TRACE + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +ISC_LANG_BEGINDECLS + +#define DNS_RPZ_PREFIX "rpz-" +/* + * Sub-zones of various trigger types. + */ +#define DNS_RPZ_CLIENT_IP_ZONE DNS_RPZ_PREFIX "client-ip" +#define DNS_RPZ_IP_ZONE DNS_RPZ_PREFIX "ip" +#define DNS_RPZ_NSIP_ZONE DNS_RPZ_PREFIX "nsip" +#define DNS_RPZ_NSDNAME_ZONE DNS_RPZ_PREFIX "nsdname" +/* + * Special policies. + */ +#define DNS_RPZ_PASSTHRU_NAME DNS_RPZ_PREFIX "passthru" +#define DNS_RPZ_DROP_NAME DNS_RPZ_PREFIX "drop" +#define DNS_RPZ_TCP_ONLY_NAME DNS_RPZ_PREFIX "tcp-only" + +typedef uint8_t dns_rpz_prefix_t; + +typedef enum { + DNS_RPZ_TYPE_BAD, + DNS_RPZ_TYPE_CLIENT_IP, + DNS_RPZ_TYPE_QNAME, + DNS_RPZ_TYPE_IP, + DNS_RPZ_TYPE_NSDNAME, + DNS_RPZ_TYPE_NSIP +} dns_rpz_type_t; + +/* + * Require DNS_RPZ_POLICY_PASSTHRU < DNS_RPZ_POLICY_DROP + * < DNS_RPZ_POLICY_TCP_ONLY DNS_RPZ_POLICY_NXDOMAIN < DNS_RPZ_POLICY_NODATA + * < DNS_RPZ_POLICY_CNAME to choose among competing policies. + */ +typedef enum { + DNS_RPZ_POLICY_GIVEN = 0, /* 'given': what policy record says */ + DNS_RPZ_POLICY_DISABLED = 1, /* log what would have happened */ + DNS_RPZ_POLICY_PASSTHRU = 2, /* 'passthru': do not rewrite */ + DNS_RPZ_POLICY_DROP = 3, /* 'drop': do not respond */ + DNS_RPZ_POLICY_TCP_ONLY = 4, /* 'tcp-only': answer UDP with TC=1 */ + DNS_RPZ_POLICY_NXDOMAIN = 5, /* 'nxdomain': answer with NXDOMAIN */ + DNS_RPZ_POLICY_NODATA = 6, /* 'nodata': answer with ANCOUNT=0 */ + DNS_RPZ_POLICY_CNAME = 7, /* 'cname x': answer with x's rrsets */ + DNS_RPZ_POLICY_DNS64, /* Apply DN64 to the A rewrite */ + DNS_RPZ_POLICY_RECORD, + DNS_RPZ_POLICY_WILDCNAME, + DNS_RPZ_POLICY_MISS, + DNS_RPZ_POLICY_ERROR +} dns_rpz_policy_t; + +typedef uint8_t dns_rpz_num_t; + +#define DNS_RPZ_MAX_ZONES 64 +/* + * Type dns_rpz_zbits_t must be an unsigned int wide enough to contain + * at least DNS_RPZ_MAX_ZONES bits. + */ +typedef uint64_t dns_rpz_zbits_t; + +#define DNS_RPZ_ALL_ZBITS ((dns_rpz_zbits_t)-1) + +#define DNS_RPZ_INVALID_NUM DNS_RPZ_MAX_ZONES + +#define DNS_RPZ_ZBIT(n) (((dns_rpz_zbits_t)1) << (dns_rpz_num_t)(n)) + +/* + * Mask of the specified and higher numbered policy zones + * Avoid hassles with (1<<33) or (1<<65) + */ +#define DNS_RPZ_ZMASK(n) \ + ((dns_rpz_zbits_t)((((n) >= DNS_RPZ_MAX_ZONES - 1) \ + ? 0 \ + : (1ULL << ((n) + 1))) - \ + 1)) + +/* + * The trigger counter type. + */ +typedef size_t dns_rpz_trigger_counter_t; + +/* + * The number of triggers of each type in a response policy zone. + */ +typedef struct dns_rpz_triggers dns_rpz_triggers_t; +struct dns_rpz_triggers { + dns_rpz_trigger_counter_t client_ipv4; + dns_rpz_trigger_counter_t client_ipv6; + dns_rpz_trigger_counter_t qname; + dns_rpz_trigger_counter_t ipv4; + dns_rpz_trigger_counter_t ipv6; + dns_rpz_trigger_counter_t nsdname; + dns_rpz_trigger_counter_t nsipv4; + dns_rpz_trigger_counter_t nsipv6; +}; + +/* + * A single response policy zone. + */ +typedef struct dns_rpz_zone dns_rpz_zone_t; +typedef struct dns_rpz_zones dns_rpz_zones_t; + +struct dns_rpz_zone { + unsigned int magic; + + dns_rpz_num_t num; /* ordinal in list of policy zones */ + dns_name_t origin; /* Policy zone name */ + dns_name_t client_ip; /* DNS_RPZ_CLIENT_IP_ZONE.origin. */ + dns_name_t ip; /* DNS_RPZ_IP_ZONE.origin. */ + dns_name_t nsdname; /* DNS_RPZ_NSDNAME_ZONE.origin */ + dns_name_t nsip; /* DNS_RPZ_NSIP_ZONE.origin. */ + dns_name_t passthru; /* DNS_RPZ_PASSTHRU_NAME. */ + dns_name_t drop; /* DNS_RPZ_DROP_NAME. */ + dns_name_t tcp_only; /* DNS_RPZ_TCP_ONLY_NAME. */ + dns_name_t cname; /* override value for ..._CNAME */ + dns_ttl_t max_policy_ttl; + dns_rpz_policy_t policy; /* DNS_RPZ_POLICY_GIVEN or override */ + + uint32_t min_update_interval; /* minimal interval between updates */ + isc_ht_t *nodes; /* entries in zone */ + dns_rpz_zones_t *rpzs; /* owner */ + isc_time_t lastupdated; /* last time the zone was processed */ + bool updatepending; /* there is an update pending */ + bool updaterunning; /* there is an update running */ + isc_result_t updateresult; /* result from the offloaded work */ + dns_db_t *db; /* zones database */ + dns_dbversion_t *dbversion; /* version we will be updating to */ + dns_db_t *updb; /* zones database we're working on */ + dns_dbversion_t *updbversion; /* version we're working on */ + bool addsoa; /* add soa to the additional section */ + isc_timer_t *updatetimer; + isc_event_t updateevent; +}; + +/* + * Radix tree node for response policy IP addresses + */ +typedef struct dns_rpz_cidr_node dns_rpz_cidr_node_t; + +/* + * Bitfields indicating which policy zones have policies of + * which type. + */ +typedef struct dns_rpz_have dns_rpz_have_t; +struct dns_rpz_have { + dns_rpz_zbits_t client_ipv4; + dns_rpz_zbits_t client_ipv6; + dns_rpz_zbits_t client_ip; + dns_rpz_zbits_t qname; + dns_rpz_zbits_t ipv4; + dns_rpz_zbits_t ipv6; + dns_rpz_zbits_t ip; + dns_rpz_zbits_t nsdname; + dns_rpz_zbits_t nsipv4; + dns_rpz_zbits_t nsipv6; + dns_rpz_zbits_t nsip; + dns_rpz_zbits_t qname_skip_recurse; +}; + +/* + * Policy options + */ +typedef struct dns_rpz_popt dns_rpz_popt_t; +struct dns_rpz_popt { + dns_rpz_zbits_t no_rd_ok; + dns_rpz_zbits_t no_log; + dns_rpz_zbits_t nsip_on; + dns_rpz_zbits_t nsdname_on; + bool dnsrps_enabled; + bool break_dnssec; + bool qname_wait_recurse; + bool nsip_wait_recurse; + bool nsdname_wait_recurse; + unsigned int min_ns_labels; + dns_rpz_num_t num_zones; +}; + +/* + * Response policy zones known to a view. + */ +struct dns_rpz_zones { + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + isc_task_t *updater; + + dns_rpz_popt_t p; + dns_rpz_zone_t *zones[DNS_RPZ_MAX_ZONES]; + dns_rpz_triggers_t triggers[DNS_RPZ_MAX_ZONES]; + + /* + * RPZ policy version number. + * It is initially 0 and it increases whenever the server is + * reconfigured with new zones or policy. + */ + int rpz_ver; + + dns_rpz_zbits_t defined; + + /* + * The set of records for a policy zone are in one of these states: + * never loaded load_begun=0 have=0 + * during initial loading load_begun=1 have=0 + * and rbtdb->rpzsp == rbtdb->load_rpzsp + * after good load load_begun=1 have!=0 + * after failed initial load load_begun=1 have=0 + * and rbtdb->load_rpzsp == NULL + * reloading after failure load_begun=1 have=0 + * reloading after success + * main rpzs load_begun=1 have!=0 + * load rpzs load_begun=1 have=0 + */ + dns_rpz_zbits_t load_begun; + dns_rpz_have_t have; + + /* + * total_triggers maintains the total number of triggers in all + * policy zones in the view. It is only used to print summary + * statistics after a zone load of how the trigger counts + * changed. + */ + dns_rpz_triggers_t total_triggers; + + /* + * One lock for short term read-only search that guarantees the + * consistency of the pointers. + * A second lock for maintenance that guarantees no other thread + * is adding or deleting nodes. + */ + isc_rwlock_t search_lock; + isc_mutex_t maint_lock; + + bool shuttingdown; + + dns_rpz_cidr_node_t *cidr; + dns_rbt_t *rbt; + + /* + * DNSRPZ librpz configuration string and handle on librpz connection + */ + char *rps_cstr; + size_t rps_cstr_size; + struct librpz_client *rps_client; +}; + +/* + * context for finding the best policy + */ +typedef struct { + unsigned int state; +#define DNS_RPZ_REWRITTEN 0x0001 +#define DNS_RPZ_DONE_CLIENT_IP 0x0002 /* client IP address checked */ +#define DNS_RPZ_DONE_QNAME 0x0004 /* qname checked */ +#define DNS_RPZ_DONE_QNAME_IP 0x0008 /* IP addresses of qname checked */ +#define DNS_RPZ_DONE_NSDNAME 0x0010 /* NS name missed; checking addresses */ +#define DNS_RPZ_DONE_IPv4 0x0020 +#define DNS_RPZ_RECURSING 0x0040 +#define DNS_RPZ_ACTIVE 0x0080 + /* + * Best match so far. + */ + struct { + dns_rpz_type_t type; + dns_rpz_zone_t *rpz; + dns_rpz_prefix_t prefix; + dns_rpz_policy_t policy; + dns_ttl_t ttl; + isc_result_t result; + dns_zone_t *zone; + dns_db_t *db; + dns_dbversion_t *version; + dns_dbnode_t *node; + dns_rdataset_t *rdataset; + } m; + /* + * State for chasing IP addresses and NS names including recursion. + */ + struct { + unsigned int label; + dns_db_t *db; + dns_rdataset_t *ns_rdataset; + dns_rdatatype_t r_type; + isc_result_t r_result; + dns_rdataset_t *r_rdataset; + } r; + + /* + * State of real query while recursing for NSIP or NSDNAME. + */ + struct { + isc_result_t result; + bool is_zone; + bool authoritative; + dns_zone_t *zone; + dns_db_t *db; + dns_dbnode_t *node; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_rdatatype_t qtype; + } q; + + /* + * A copy of the 'have' and 'p' structures and the RPZ + * policy version as of the beginning of RPZ processing, + * used to avoid problems when policy is updated while + * RPZ recursion is ongoing. + */ + dns_rpz_have_t have; + dns_rpz_popt_t popt; + int rpz_ver; + + /* + * Shim db between BIND and DNRPS librpz. + */ + dns_db_t *rpsdb; + + /* + * p_name: current policy owner name + * r_name: recursing for this name to possible policy triggers + * f_name: saved found name from before recursion + */ + dns_name_t *p_name; + dns_name_t *r_name; + dns_name_t *fname; + dns_fixedname_t _p_namef; + dns_fixedname_t _r_namef; + dns_fixedname_t _fnamef; +} dns_rpz_st_t; + +#define DNS_RPZ_TTL_DEFAULT 5 +#define DNS_RPZ_MAX_TTL_DEFAULT DNS_RPZ_TTL_DEFAULT +#define DNS_RPZ_MINUPDATEINTERVAL_DEFAULT 60 + +/* + * So various response policy zone messages can be turned up or down. + */ +#define DNS_RPZ_ERROR_LEVEL ISC_LOG_WARNING +#define DNS_RPZ_INFO_LEVEL ISC_LOG_INFO +#define DNS_RPZ_DEBUG_LEVEL1 ISC_LOG_DEBUG(1) +#define DNS_RPZ_DEBUG_LEVEL2 ISC_LOG_DEBUG(2) +#define DNS_RPZ_DEBUG_LEVEL3 ISC_LOG_DEBUG(3) +#define DNS_RPZ_DEBUG_QUIET (DNS_RPZ_DEBUG_LEVEL3 + 1) + +const char * +dns_rpz_type2str(dns_rpz_type_t type); + +dns_rpz_policy_t +dns_rpz_str2policy(const char *str); + +const char * +dns_rpz_policy2str(dns_rpz_policy_t policy); + +dns_rpz_policy_t +dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, + dns_name_t *selfname); + +isc_result_t +dns_rpz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, char *rps_cstr, + size_t rps_cstr_size, dns_rpz_zones_t **rpzsp); + +isc_result_t +dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp); + +isc_result_t +dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg); + +void +dns_rpz_zones_shutdown(dns_rpz_zones_t *rpzs); + +#ifdef DNS_RPZ_TRACE +/* Compatibility macros */ +#define dns_rpz_detach_rpzs(rpzsp) \ + dns_rpz_zones__detach(rpzsp, __func__, __FILE__, __LINE__) +#define dns_rpz_attach_rpzs(rpzs, rpzsp) \ + dns_rpz_zones__attach(rpzs, rpzsp, __func__, __FILE__, __LINE__) +#define dns_rpz_ref_rpzs(ptr) \ + dns_rpz_zones__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_rpz_unref_rpzs(ptr) \ + dns_rpz_zones__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_rpz_shutdown_rpzs(rpzs) \ + dns_rpz_zones_shutdown(rpzs, __func__, __FILE__, __LINE__) + +ISC_REFCOUNT_TRACE_DECL(dns_rpz_zones); +#else +/* Compatibility macros */ +#define dns_rpz_detach_rpzs(rpzsp) dns_rpz_zones_detach(rpzsp) +#define dns_rpz_attach_rpzs(rpzs, rpzsp) dns_rpz_zones_attach(rpzs, rpzsp) +#define dns_rpz_shutdown_rpzs(rpzsp) dns_rpz_zones_shutdown(rpzsp) +#define dns_rpz_ref_rpzs(ptr) dns_rpz_zones_ref(ptr) +#define dns_rpz_unref_rpzs(ptr) dns_rpz_zones_unref(ptr) + +ISC_REFCOUNT_DECL(dns_rpz_zones); +#endif + +dns_rpz_num_t +dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, + dns_name_t *ip_name, dns_rpz_prefix_t *prefixp); + +dns_rpz_zbits_t +dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_name_t *trig_name); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rriterator.h b/lib/dns/include/dns/rriterator.h new file mode 100644 index 0000000..a1cd9b9 --- /dev/null +++ b/lib/dns/include/dns/rriterator.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/rriterator.h + * \brief + * Functions for "walking" a zone database, visiting each RR or RRset in turn. + */ + +/***** +***** Imports +*****/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +/***** +***** Types +*****/ + +/*% + * A dns_rriterator_t is an iterator that iterates over an entire database, + * returning one RR at a time, in some arbitrary order. + */ + +typedef struct dns_rriterator { + unsigned int magic; + isc_result_t result; + dns_db_t *db; + dns_dbiterator_t *dbit; + dns_dbversion_t *ver; + isc_stdtime_t now; + dns_dbnode_t *node; + dns_fixedname_t fixedname; + dns_rdatasetiter_t *rdatasetit; + dns_rdataset_t rdataset; + dns_rdata_t rdata; +} dns_rriterator_t; + +#define RRITERATOR_MAGIC ISC_MAGIC('R', 'R', 'I', 't') +#define VALID_RRITERATOR(m) ISC_MAGIC_VALID(m, RRITERATOR_MAGIC) + +isc_result_t +dns_rriterator_init(dns_rriterator_t *it, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now); +/*% + * Initialize an rriterator; sets the cursor to the origin node + * of the database. + * + * Requires: + * + * \li 'db' is a valid database. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_rriterator_first(dns_rriterator_t *it); +/*%< + * Move the rriterator cursor to the first rdata in the database. + * + * Requires: + *\li 'it' is a valid, initialized rriterator + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE There are no rdata in the set. + */ + +isc_result_t +dns_rriterator_nextrrset(dns_rriterator_t *it); +/*%< + * Move the rriterator cursor to the next rrset in the database, + * skipping over any remaining records that have the same rdatatype + * as the current one. + * + * Requires: + *\li 'it' is a valid, initialized rriterator + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE No more rrsets in the database + */ + +isc_result_t +dns_rriterator_next(dns_rriterator_t *it); +/*%< + * Move the rriterator cursor to the next rrset in the database, + * skipping over any remaining records that have the same rdatatype + * as the current one. + * + * Requires: + *\li 'it' is a valid, initialized rriterator + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE No more records in the database + */ + +void +dns_rriterator_current(dns_rriterator_t *it, dns_name_t **name, uint32_t *ttl, + dns_rdataset_t **rdataset, dns_rdata_t **rdata); +/*%< + * Make '*name' refer to the current name. If 'rdataset' is not NULL, + * make '*rdataset' refer to the current * rdataset. If '*rdata' is not + * NULL, make '*rdata' refer to the current record. + * + * Requires: + *\li '*name' is a valid name object + *\li 'rdataset' is NULL or '*rdataset' is NULL + *\li 'rdata' is NULL or '*rdata' is NULL + * + * Ensures: + *\li 'rdata' refers to the rdata at the rdata cursor location of + *\li 'rdataset'. + */ + +void +dns_rriterator_pause(dns_rriterator_t *it); +/*%< + * Pause rriterator. Frees any locks held by the database iterator. + * Callers should use this routine any time they are not going to + * execute another rriterator method in the immediate future. + * + * Requires: + *\li 'it' is a valid iterator. + * + * Ensures: + *\li Any database locks being held for efficiency of iterator access are + * released. + */ + +void +dns_rriterator_destroy(dns_rriterator_t *it); +/*%< + * Shut down and free resources in rriterator 'it'. + * + * Requires: + * + *\li 'it' is a valid iterator. + * + * Ensures: + * + *\li All resources used by the rriterator are freed. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h new file mode 100644 index 0000000..bdffed9 --- /dev/null +++ b/lib/dns/include/dns/rrl.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* + * Rate limit DNS responses. + */ + +#include +#include + +#include + +#include +#include +#include + +ISC_LANG_BEGINDECLS + +/* + * Memory allocation or other failures. + */ +#define DNS_RRL_LOG_FAIL ISC_LOG_WARNING +/* + * dropped or slipped responses. + */ +#define DNS_RRL_LOG_DROP ISC_LOG_INFO +/* + * Major events in dropping or slipping. + */ +#define DNS_RRL_LOG_DEBUG1 ISC_LOG_DEBUG(3) +/* + * Limit computations. + */ +#define DNS_RRL_LOG_DEBUG2 ISC_LOG_DEBUG(4) +/* + * Even less interesting. + */ +#define DNS_RRL_LOG_DEBUG3 ISC_LOG_DEBUG(9) + +#define DNS_RRL_LOG_ERR_LEN 64 +#define DNS_RRL_LOG_BUF_LEN \ + (sizeof("would continue limiting") + DNS_RRL_LOG_ERR_LEN + \ + sizeof(" responses to ") + ISC_NETADDR_FORMATSIZE + \ + sizeof("/128 for IN ") + DNS_RDATATYPE_FORMATSIZE + \ + DNS_NAME_FORMATSIZE) + +typedef struct dns_rrl_hash dns_rrl_hash_t; + +/* + * Response types. + */ +typedef enum { + DNS_RRL_RTYPE_FREE = 0, + DNS_RRL_RTYPE_QUERY, + DNS_RRL_RTYPE_REFERRAL, + DNS_RRL_RTYPE_NODATA, + DNS_RRL_RTYPE_NXDOMAIN, + DNS_RRL_RTYPE_ERROR, + DNS_RRL_RTYPE_ALL, + DNS_RRL_RTYPE_TCP, +} dns_rrl_rtype_t; + +/* + * A rate limit bucket key. + * This should be small to limit the total size of the database. + * The hash of the qname should be wide enough to make the probability + * of collisions among requests from a single IP address block less than 50%. + * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged + * by attacker) to collide with legitimate qnames from the target with + * probability at most 1%. + */ +#define DNS_RRL_MAX_PREFIX 64 +typedef union dns_rrl_key dns_rrl_key_t; +struct dns__rrl_key { + uint32_t ip[DNS_RRL_MAX_PREFIX / 32]; + uint32_t qname_hash; + dns_rdatatype_t qtype; + uint8_t qclass; + unsigned int rtype : 4; /* dns_rrl_rtype_t */ + unsigned int ipv6 : 1; +}; +union dns_rrl_key { + struct dns__rrl_key s; + uint16_t w[sizeof(struct dns__rrl_key) / sizeof(uint16_t)]; +}; + +/* + * A rate-limit entry. + * This should be small to limit the total size of the table of entries. + */ +typedef struct dns_rrl_entry dns_rrl_entry_t; +typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t; +struct dns_rrl_entry { + ISC_LINK(dns_rrl_entry_t) lru; + ISC_LINK(dns_rrl_entry_t) hlink; + dns_rrl_key_t key; +#define DNS_RRL_RESPONSE_BITS 24 + signed int responses : DNS_RRL_RESPONSE_BITS; +#define DNS_RRL_QNAMES_BITS 8 + unsigned int log_qname : DNS_RRL_QNAMES_BITS; + +#define DNS_RRL_TS_GEN_BITS 2 + unsigned int ts_gen : DNS_RRL_TS_GEN_BITS; + unsigned int ts_valid : 1; +#define DNS_RRL_HASH_GEN_BITS 1 + unsigned int hash_gen : DNS_RRL_HASH_GEN_BITS; + unsigned int logged : 1; +#define DNS_RRL_LOG_BITS 11 + unsigned int log_secs : DNS_RRL_LOG_BITS; + +#define DNS_RRL_TS_BITS 12 + unsigned int ts : DNS_RRL_TS_BITS; + +#define DNS_RRL_MAX_SLIP 10 + unsigned int slip_cnt : 4; +}; + +#define DNS_RRL_MAX_TIME_TRAVEL 5 +#define DNS_RRL_FOREVER (1 << DNS_RRL_TS_BITS) +#define DNS_RRL_MAX_TS (DNS_RRL_FOREVER - 1) + +#define DNS_RRL_MAX_RESPONSES ((1 << (DNS_RRL_RESPONSE_BITS - 1)) - 1) +#define DNS_RRL_MAX_WINDOW 3600 +#if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS +#error "DNS_RRL_MAX_WINDOW is too large" +#endif /* if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS */ +#define DNS_RRL_MAX_RATE 1000 +#if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW) +#error "DNS_RRL_MAX_rate is too large" +#endif /* if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW) \ + */ + +#if (1 << DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER +#error DNS_RRL_LOG_BITS is too big +#endif /* if (1 << DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER */ +#define DNS_RRL_MAX_LOG_SECS 1800 +#if DNS_RRL_MAX_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) +#error "DNS_RRL_MAX_LOG_SECS is too large" +#endif /* if DNS_RRL_MAX_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) */ +#define DNS_RRL_STOP_LOG_SECS 60 +#if DNS_RRL_STOP_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) +#error "DNS_RRL_STOP_LOG_SECS is too large" +#endif /* if DNS_RRL_STOP_LOG_SECS >= (1 << DNS_RRL_LOG_BITS) */ + +/* + * A hash table of rate-limit entries. + */ +struct dns_rrl_hash { + isc_stdtime_t check_time; + unsigned int gen : DNS_RRL_HASH_GEN_BITS; + int length; + dns_rrl_bin_t bins[1]; +}; + +/* + * A block of rate-limit entries. + */ +typedef struct dns_rrl_block dns_rrl_block_t; +struct dns_rrl_block { + ISC_LINK(dns_rrl_block_t) link; + int size; + dns_rrl_entry_t entries[1]; +}; + +/* + * A rate limited qname buffer. + */ +typedef struct dns_rrl_qname_buf dns_rrl_qname_buf_t; +struct dns_rrl_qname_buf { + ISC_LINK(dns_rrl_qname_buf_t) link; + const dns_rrl_entry_t *e; + unsigned int index; + dns_fixedname_t qname; +}; + +typedef struct dns_rrl_rate dns_rrl_rate_t; +struct dns_rrl_rate { + int r; + int scaled; + const char *str; +}; + +/* + * Per-view query rate limit parameters and a pointer to database. + */ +typedef struct dns_rrl dns_rrl_t; +struct dns_rrl { + isc_mutex_t lock; + isc_mem_t *mctx; + + bool log_only; + dns_rrl_rate_t responses_per_second; + dns_rrl_rate_t referrals_per_second; + dns_rrl_rate_t nodata_per_second; + dns_rrl_rate_t nxdomains_per_second; + dns_rrl_rate_t errors_per_second; + dns_rrl_rate_t all_per_second; + dns_rrl_rate_t slip; + int window; + double qps_scale; + int max_entries; + + dns_acl_t *exempt; + + int num_entries; + + int qps_responses; + isc_stdtime_t qps_time; + double qps; + + unsigned int probes; + unsigned int searches; + + ISC_LIST(dns_rrl_block_t) blocks; + ISC_LIST(dns_rrl_entry_t) lru; + + dns_rrl_hash_t *hash; + dns_rrl_hash_t *old_hash; + unsigned int hash_gen; + + unsigned int ts_gen; +#define DNS_RRL_TS_BASES (1 << DNS_RRL_TS_GEN_BITS) + isc_stdtime_t ts_bases[DNS_RRL_TS_BASES]; + + int ipv4_prefixlen; + uint32_t ipv4_mask; + int ipv6_prefixlen; + uint32_t ipv6_mask[4]; + + isc_stdtime_t log_stops_time; + dns_rrl_entry_t *last_logged; + int num_logged; + int num_qnames; + ISC_LIST(dns_rrl_qname_buf_t) qname_free; +#define DNS_RRL_QNAMES (1 << DNS_RRL_QNAMES_BITS) + dns_rrl_qname_buf_t *qnames[DNS_RRL_QNAMES]; +}; + +typedef enum { + DNS_RRL_RESULT_OK, + DNS_RRL_RESULT_DROP, + DNS_RRL_RESULT_SLIP, +} dns_rrl_result_t; + +dns_rrl_result_t +dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr, + bool is_tcp, dns_rdataclass_t rdclass, dns_rdatatype_t qtype, + const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now, + bool wouldlog, char *log_buf, unsigned int log_buf_len); + +void +dns_rrl_view_destroy(dns_view_t *view); + +isc_result_t +dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/sdb.h b/lib/dns/include/dns/sdb.h new file mode 100644 index 0000000..54b2223 --- /dev/null +++ b/lib/dns/include/dns/sdb.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/sdb.h + * \brief + * Simple database API. + */ + +/*** + *** Imports + ***/ + +#include + +#include + +#include +#include + +/*** + *** Types + ***/ + +/*% + * A simple database. This is an opaque type. + */ +typedef struct dns_sdb dns_sdb_t; + +/*% + * A simple database lookup in progress. This is an opaque type. + */ +typedef struct dns_sdblookup dns_sdblookup_t; + +/*% + * A simple database traversal in progress. This is an opaque type. + */ +typedef struct dns_sdballnodes dns_sdballnodes_t; + +typedef isc_result_t (*dns_sdblookupfunc_t)(const char *zone, const char *name, + void *dbdata, + dns_sdblookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +typedef isc_result_t (*dns_sdblookup2func_t)(const dns_name_t *zone, + const dns_name_t *name, + void *dbdata, + dns_sdblookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +typedef isc_result_t (*dns_sdbauthorityfunc_t)(const char *zone, void *dbdata, + dns_sdblookup_t *); + +typedef isc_result_t (*dns_sdballnodesfunc_t)(const char *zone, void *dbdata, + dns_sdballnodes_t *allnodes); + +typedef isc_result_t (*dns_sdbcreatefunc_t)(const char *zone, int argc, + char **argv, void *driverdata, + void **dbdata); + +typedef void (*dns_sdbdestroyfunc_t)(const char *zone, void *driverdata, + void **dbdata); + +typedef struct dns_sdbmethods { + dns_sdblookupfunc_t lookup; + dns_sdbauthorityfunc_t authority; + dns_sdballnodesfunc_t allnodes; + dns_sdbcreatefunc_t create; + dns_sdbdestroyfunc_t destroy; + dns_sdblookup2func_t lookup2; +} dns_sdbmethods_t; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +#define DNS_SDBFLAG_RELATIVEOWNER 0x00000001U +#define DNS_SDBFLAG_RELATIVERDATA 0x00000002U +#define DNS_SDBFLAG_THREADSAFE 0x00000004U +#define DNS_SDBFLAG_DNS64 0x00000008U + +isc_result_t +dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods, + void *driverdata, unsigned int flags, isc_mem_t *mctx, + dns_sdbimplementation_t **sdbimp); +/*%< + * Register a simple database driver for the database type 'drivername', + * implemented by the functions in '*methods'. + * + * sdbimp must point to a NULL dns_sdbimplementation_t pointer. That is, + * sdbimp != NULL && *sdbimp == NULL. It will be assigned a value that + * will later be used to identify the driver when deregistering it. + * + * The name server will perform lookups in the database by calling the + * function 'lookup', passing it a printable zone name 'zone', a printable + * domain name 'name', and a copy of the argument 'dbdata' that + * was potentially returned by the create function. The 'dns_sdblookup_t' + * argument to 'lookup' and 'authority' is an opaque pointer to be passed to + * ns_sdb_putrr(). + * + * The lookup function returns the lookup results to the name server + * by calling ns_sdb_putrr() once for each record found. On success, + * the return value of the lookup function should be ISC_R_SUCCESS. + * If the domain name 'name' does not exist, the lookup function should + * ISC_R_NOTFOUND. Any other return value is treated as an error. + * + * Lookups at the zone apex will cause the server to also call the + * function 'authority' (if non-NULL), which must provide an SOA record + * and NS records for the zone by calling ns_sdb_putrr() once for each of + * these records. The 'authority' function may be NULL if invoking + * the 'lookup' function on the zone apex will return SOA and NS records. + * + * The allnodes function, if non-NULL, fills in an opaque structure to be + * used by a database iterator. This allows the zone to be transferred. + * This may use a considerable amount of memory for large zones, and the + * zone transfer may not be fully RFC1035 compliant if the zone is + * frequently changed. + * + * The create function will be called for each zone configured + * into the name server using this database type. It can be used + * to create a "database object" containing zone specific data, + * which can make use of the database arguments specified in the + * name server configuration. + * + * The destroy function will be called to free the database object + * when its zone is destroyed. + * + * The create and destroy functions may be NULL. + * + * If flags includes DNS_SDBFLAG_RELATIVEOWNER, the lookup and authority + * functions will be called with relative names rather than absolute names. + * The string "@" represents the zone apex in this case. + * + * If flags includes DNS_SDBFLAG_RELATIVERDATA, the rdata strings may + * include relative names. Otherwise, all names in the rdata string must + * be absolute. Be aware that if relative names are allowed, any + * absolute names must contain a trailing dot. + * + * If flags includes DNS_SDBFLAG_THREADSAFE, the driver must be able to + * handle multiple lookups in parallel. Otherwise, calls into the driver + * are serialized. + */ + +void +dns_sdb_unregister(dns_sdbimplementation_t **sdbimp); +/*%< + * Removes the simple database driver from the list of registered database + * types. There must be no active databases of this type when this function + * is called. + */ + +/*% See dns_sdb_putradata() */ +isc_result_t +dns_sdb_putrr(dns_sdblookup_t *lookup, const char *type, dns_ttl_t ttl, + const char *data); +isc_result_t +dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t type, dns_ttl_t ttl, + const unsigned char *rdata, unsigned int rdlen); +/*%< + * Add a single resource record to the lookup structure to be + * returned in the query response. dns_sdb_putrr() takes the + * resource record in master file text format as a null-terminated + * string, and dns_sdb_putrdata() takes the raw RDATA in + * uncompressed wire format. + */ + +/*% See dns_sdb_putnamerdata() */ +isc_result_t +dns_sdb_putnamedrr(dns_sdballnodes_t *allnodes, const char *name, + const char *type, dns_ttl_t ttl, const char *data); +isc_result_t +dns_sdb_putnamedrdata(dns_sdballnodes_t *allnodes, const char *name, + dns_rdatatype_t type, dns_ttl_t ttl, const void *rdata, + unsigned int rdlen); +/*%< + * Add a single resource record to the allnodes structure to be + * included in a zone transfer response, in text or wire + * format as above. + */ + +isc_result_t +dns_sdb_putsoa(dns_sdblookup_t *lookup, const char *mname, const char *rname, + uint32_t serial); +/*%< + * This function may optionally be called from the 'authority' callback + * to simplify construction of the SOA record for 'zone'. It will + * provide a SOA listing 'mname' as as the primary server and 'rname' as + * the responsible person mailbox. It is the responsibility of the + * driver to increment the serial number between responses if necessary. + * All other SOA fields will have reasonable default values. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/sdlz.h b/lib/dns/include/dns/sdlz.h new file mode 100644 index 0000000..b0388b9 --- /dev/null +++ b/lib/dns/include/dns/sdlz.h @@ -0,0 +1,356 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file dns/sdlz.h */ + +#pragma once + +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +#define DNS_SDLZFLAG_THREADSAFE 0x00000001U +#define DNS_SDLZFLAG_RELATIVEOWNER 0x00000002U +#define DNS_SDLZFLAG_RELATIVERDATA 0x00000004U + +/* A simple DLZ database. */ +typedef struct dns_sdlz_db dns_sdlz_db_t; + +/* A simple DLZ database lookup in progress. */ +typedef struct dns_sdlzlookup dns_sdlzlookup_t; + +/* A simple DLZ database traversal in progress. */ +typedef struct dns_sdlzallnodes dns_sdlzallnodes_t; + +typedef isc_result_t (*dns_sdlzallnodesfunc_t)(const char *zone, + void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes); +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply an all nodes method. This method is called when the DNS + * server is performing a zone transfer query, after the allow zone + * transfer method has been called. This method is only called if the + * allow zone transfer method returned ISC_R_SUCCESS. This method and + * the allow zone transfer method are both required for zone transfers + * to be supported. If the driver generates data dynamically (instead + * of searching in a database for it) it should not implement this + * function as a zone transfer would be meaningless. A SDLZ driver + * does not have to implement an all nodes method. + */ + +typedef isc_result_t (*dns_sdlzallowzonexfr_t)(void *driverarg, void *dbdata, + const char *name, + const char *client); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply an allow zone transfer method. This method is called when + * the DNS server is performing a zone transfer query, before the all + * nodes method can be called. This method and the all node method + * are both required for zone transfers to be supported. If the + * driver generates data dynamically (instead of searching in a + * database for it) it should not implement this function as a zone + * transfer would be meaningless. A SDLZ driver does not have to + * implement an allow zone transfer method. + * + * This method should return ISC_R_SUCCESS if the zone is supported by + * the database and a zone transfer is allowed for the specified + * client. If the zone is supported by the database, but zone + * transfers are not allowed for the specified client this method + * should return ISC_R_NOPERM.. Lastly the method should return + * ISC_R_NOTFOUND if the zone is not supported by the database. If an + * error occurs it should return a result code indicating the type of + * error. + */ + +typedef isc_result_t (*dns_sdlzauthorityfunc_t)(const char *zone, + void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply an authority method. This method is called when the DNS + * server is performing a query, after both the find zone and lookup + * methods have been called. This method is required if the lookup + * function does not supply authority information for the dns + * record. A SDLZ driver does not have to implement an authority + * method. + */ + +typedef isc_result_t (*dns_sdlzcreate_t)(const char *dlzname, unsigned int argc, + char *argv[], void *driverarg, + void **dbdata); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a create method. This method is called when the DNS server + * is starting up and creating drivers for use later. A SDLZ driver + * does not have to implement a create method. + */ + +typedef void (*dns_sdlzdestroy_t)(void *driverarg, void *dbdata); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a destroy method. This method is called when the DNS server + * is shutting down and no longer needs the driver. A SDLZ driver does + * not have to implement a destroy method. + */ + +typedef isc_result_t (*dns_sdlzfindzone_t)(void *driverarg, void *dbdata, + const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +/*%< + * Method prototype. Drivers implementing the SDLZ interface MUST + * supply a find zone method. This method is called when the DNS + * server is performing a query to to determine if 'name' is a + * supported dns zone. The find zone method will be called with the + * longest possible name first, and continue to be called with + * successively shorter domain names, until any of the following + * occur: + * + * \li 1) the function returns (ISC_R_SUCCESS) indicating a zone name + * match. + * + * \li 2) a problem occurs, and the functions returns anything other than + * (ISC_R_NOTFOUND) + * + * \li 3) we run out of domain name labels. I.E. we have tried the + * shortest domain name + * + * \li 4) the number of labels in the domain name is less than min_labels + * for dns_dlzfindzone + * + * The driver's find zone method should return ISC_R_SUCCESS if the + * zone is supported by the database. Otherwise it should return + * ISC_R_NOTFOUND, if the zone is not supported. If an error occurs + * it should return a result code indicating the type of error. + */ + +typedef isc_result_t (*dns_sdlzlookupfunc_t)(const char *zone, const char *name, + void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface MUST + * supply a lookup method. This method is called when the + * DNS server is performing a query, after the find zone and before any + * other methods have been called. This function returns DNS record + * information using the dns_sdlz_putrr and dns_sdlz_putsoa functions. + * If this function supplies authority information for the DNS record + * the authority method is not required. If it does not, the + * authority function is required. + * + * The 'methods' and 'clientinfo' args allow an SDLZ driver to retrieve + * information about the querying client (such as source IP address) + * from the caller. + */ + +typedef isc_result_t (*dns_sdlznewversion_t)(const char *zone, void *driverarg, + void *dbdata, void **versionp); +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a newversion method. This method is called to start a + * write transaction on a zone and should only be implemented by + * writeable backends. + * When implemented, the driver should create a new transaction, and + * fill *versionp with a pointer to the transaction state. The + * closeversion function will be called to close the transaction. + */ + +typedef void (*dns_sdlzcloseversion_t)(const char *zone, bool commit, + void *driverarg, void *dbdata, + void **versionp); +/*%< + * Method prototype. Drivers implementing the SDLZ interface must + * supply a closeversion method if they supply a newversion method. + * When implemented, the driver should close the given transaction, + * committing changes if 'commit' is true. If 'commit' is not true + * then all changes should be discarded and the database rolled back. + * If the call is successful then *versionp should be set to NULL + */ + +typedef isc_result_t (*dns_sdlzconfigure_t)(dns_view_t *view, + dns_dlzdb_t *dlzdb, void *driverarg, + void *dbdata); +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a configure method. When supplied, it will be called + * immediately after the create method to give the driver a chance + * to configure writeable zones + */ + +typedef bool (*dns_sdlzssumatch_t)(const char *signer, const char *name, + const char *tcpaddr, const char *type, + const char *key, uint32_t keydatalen, + unsigned char *keydata, void *driverarg, + void *dbdata); + +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a ssumatch method. If supplied, then ssumatch will be + * called to authorize any zone updates. The driver should return + * true to allow the update, and false to deny it. For a DLZ + * controlled zone, this is the only access control on updates. + */ + +typedef isc_result_t (*dns_sdlzmodrdataset_t)(const char *name, + const char *rdatastr, + void *driverarg, void *dbdata, + void *version); +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply addrdataset and subtractrdataset methods. If supplied, then these + * will be called when rdatasets are added/subtracted during + * updates. The version parameter comes from a call to the sdlz + * newversion() method from the driver. The rdataset parameter is a + * linearise string representation of the rdataset change. The format + * is the same as used by dig when displaying records. The fields are + * tab delimited. + */ + +typedef isc_result_t (*dns_sdlzdelrdataset_t)(const char *name, + const char *type, void *driverarg, + void *dbdata, void *version); +/*%< + * Method prototype. Drivers implementing the SDLZ interface may + * supply a delrdataset method. If supplied, then this + * function will be called when rdatasets are deleted during + * updates. The call should remove all rdatasets of the given type for + * the specified name. + */ + +typedef struct dns_sdlzmethods { + dns_sdlzcreate_t create; + dns_sdlzdestroy_t destroy; + dns_sdlzfindzone_t findzone; + dns_sdlzlookupfunc_t lookup; + dns_sdlzauthorityfunc_t authority; + dns_sdlzallnodesfunc_t allnodes; + dns_sdlzallowzonexfr_t allowzonexfr; + dns_sdlznewversion_t newversion; + dns_sdlzcloseversion_t closeversion; + dns_sdlzconfigure_t configure; + dns_sdlzssumatch_t ssumatch; + dns_sdlzmodrdataset_t addrdataset; + dns_sdlzmodrdataset_t subtractrdataset; + dns_sdlzdelrdataset_t delrdataset; +} dns_sdlzmethods_t; + +isc_result_t +dns_sdlzregister(const char *drivername, const dns_sdlzmethods_t *methods, + void *driverarg, unsigned int flags, isc_mem_t *mctx, + dns_sdlzimplementation_t **sdlzimp); +/*%< + * Register a dynamically loadable zones (dlz) driver for the database + * type 'drivername', implemented by the functions in '*methods'. + * + * sdlzimp must point to a NULL dns_sdlzimplementation_t pointer. + * That is, sdlzimp != NULL && *sdlzimp == NULL. It will be assigned + * a value that will later be used to identify the driver when + * deregistering it. + */ + +void +dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp); + +/*%< + * Removes the sdlz driver from the list of registered sdlz drivers. + * There must be no active sdlz drivers of this type when this + * function is called. + */ + +typedef isc_result_t +dns_sdlz_putnamedrr_t(dns_sdlzallnodes_t *allnodes, const char *name, + const char *type, dns_ttl_t ttl, const char *data); +dns_sdlz_putnamedrr_t dns_sdlz_putnamedrr; + +/*%< + * Add a single resource record to the allnodes structure to be later + * parsed into a zone transfer response. + */ + +typedef isc_result_t +dns_sdlz_putrr_t(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl, + const char *data); +dns_sdlz_putrr_t dns_sdlz_putrr; +/*%< + * Add a single resource record to the lookup structure to be later + * parsed into a query response. + */ + +typedef isc_result_t + dns_sdlz_putsoa_t(dns_sdlzlookup_t *lookup, const char *mname, + const char *rname, uint32_t serial); +dns_sdlz_putsoa_t dns_sdlz_putsoa; +/*%< + * This function may optionally be called from the 'authority' + * callback to simplify construction of the SOA record for 'zone'. It + * will provide a SOA listing 'mname' as as the primary server and + * 'rname' as the responsible person mailbox. It is the + * responsibility of the driver to increment the serial number between + * responses if necessary. All other SOA fields will have reasonable + * default values. + */ + +typedef isc_result_t +dns_sdlz_setdb_t(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass, + const dns_name_t *name, dns_db_t **dbp); +dns_sdlz_setdb_t dns_sdlz_setdb; +/*%< + * Create the database pointers for a writeable SDLZ zone + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/secalg.h b/lib/dns/include/dns/secalg.h new file mode 100644 index 0000000..09b3d2b --- /dev/null +++ b/lib/dns/include/dns/secalg.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/secalg.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_secalg_fromtext(dns_secalg_t *secalgp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNSSEC security algorithm value. + * The text may contain either a mnemonic algorithm name or a decimal algorithm + * number. + * + * Requires: + *\li 'secalgp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_RANGE numeric type is out of range + *\li DNS_R_UNKNOWN mnemonic type is unknown + */ + +isc_result_t +dns_secalg_totext(dns_secalg_t secalg, isc_buffer_t *target); +/*%< + * Put a textual representation of the DNSSEC security algorithm 'secalg' + * into 'target'. + * + * Requires: + *\li 'secalg' is a valid secalg. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_NOSPACE target buffer is too small + */ + +#define DNS_SECALG_FORMATSIZE 20 +void +dns_secalg_format(dns_secalg_t alg, char *cp, unsigned int size); +/*%< + * Wrapper for dns_secalg_totext(), writing text into 'cp' + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/secproto.h b/lib/dns/include/dns/secproto.h new file mode 100644 index 0000000..0dc80a0 --- /dev/null +++ b/lib/dns/include/dns/secproto.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/secproto.h */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_secproto_fromtext(dns_secproto_t *secprotop, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DNSSEC security protocol value. + * The text may contain either a mnemonic protocol name or a decimal protocol + * number. + * + * Requires: + *\li 'secprotop' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_RANGE numeric type is out of range + *\li DNS_R_UNKNOWN mnemonic type is unknown + */ + +isc_result_t +dns_secproto_totext(dns_secproto_t secproto, isc_buffer_t *target); +/*%< + * Put a textual representation of the DNSSEC security protocol 'secproto' + * into 'target'. + * + * Requires: + *\li 'secproto' is a valid secproto. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + * \li The used space in 'target' is updated. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_NOSPACE target buffer is too small + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/soa.h b/lib/dns/include/dns/soa.h new file mode 100644 index 0000000..4f8aa07 --- /dev/null +++ b/lib/dns/include/dns/soa.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/soa.h + * \brief + * SOA utilities. + */ + +/*** + *** Imports + ***/ + +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +#define DNS_SOA_BUFFERSIZE ((2 * DNS_NAME_MAXWIRE) + (4 * 5)) + +isc_result_t +dns_soa_buildrdata(const dns_name_t *origin, const dns_name_t *contact, + dns_rdataclass_t rdclass, uint32_t serial, uint32_t refresh, + uint32_t retry, uint32_t expire, uint32_t minimum, + unsigned char *buffer, dns_rdata_t *rdata); +/*%< + * Build the rdata of an SOA record. + * + * Requires: + *\li buffer Points to a temporary buffer of at least + * DNS_SOA_BUFFERSIZE bytes. + *\li rdata Points to an initialized dns_rdata_t. + * + * Ensures: + * \li *rdata Contains a valid SOA rdata. The 'data' member + * refers to 'buffer'. + */ + +uint32_t +dns_soa_getserial(dns_rdata_t *rdata); +uint32_t +dns_soa_getrefresh(dns_rdata_t *rdata); +uint32_t +dns_soa_getretry(dns_rdata_t *rdata); +uint32_t +dns_soa_getexpire(dns_rdata_t *rdata); +uint32_t +dns_soa_getminimum(dns_rdata_t *rdata); +/* + * Extract an integer field from the rdata of a SOA record. + * + * Requires: + * rdata refers to the rdata of a well-formed SOA record. + */ + +void +dns_soa_setserial(uint32_t val, dns_rdata_t *rdata); +void +dns_soa_setrefresh(uint32_t val, dns_rdata_t *rdata); +void +dns_soa_setretry(uint32_t val, dns_rdata_t *rdata); +void +dns_soa_setexpire(uint32_t val, dns_rdata_t *rdata); +void +dns_soa_setminimum(uint32_t val, dns_rdata_t *rdata); +/* + * Change an integer field of a SOA record by modifying the + * rdata in-place. + * + * Requires: + * rdata refers to the rdata of a well-formed SOA record. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h new file mode 100644 index 0000000..ead0097 --- /dev/null +++ b/lib/dns/include/dns/ssu.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/ssu.h */ + +#include + +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +typedef enum { + dns_ssumatchtype_name = 0, + dns_ssumatchtype_subdomain = 1, + dns_ssumatchtype_wildcard = 2, + dns_ssumatchtype_self = 3, + dns_ssumatchtype_selfsub = 4, + dns_ssumatchtype_selfwild = 5, + dns_ssumatchtype_selfkrb5 = 6, + dns_ssumatchtype_selfms = 7, + dns_ssumatchtype_subdomainms = 8, + dns_ssumatchtype_subdomainkrb5 = 9, + dns_ssumatchtype_tcpself = 10, + dns_ssumatchtype_6to4self = 11, + dns_ssumatchtype_external = 12, + dns_ssumatchtype_local = 13, + dns_ssumatchtype_selfsubms = 14, + dns_ssumatchtype_selfsubkrb5 = 15, + dns_ssumatchtype_subdomainselfkrb5rhs = 16, + dns_ssumatchtype_subdomainselfmsrhs = 17, + dns_ssumatchtype_max = 17, /* max value */ + + dns_ssumatchtype_dlz = 18 /* intentionally higher than _max */ +} dns_ssumatchtype_t; + +typedef struct dns_ssuruletype { + dns_rdatatype_t type; /* type allowed */ + unsigned int max; /* maximum number of records allowed. */ +} dns_ssuruletype_t; + +void +dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table); +/*%< + * Creates a table that will be used to store simple-secure-update rules. + * Note: all locking must be provided by the client. + * + * Requires: + *\li 'mctx' is a valid memory context + *\li 'table' is not NULL, and '*table' is NULL + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + */ + +void +dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, + dns_dlzdb_t *dlzdatabase); +/*%< + * Create an SSU table that contains a dlzdatabase pointer, and a + * single rule with matchtype dns_ssumatchtype_dlz. This type of SSU + * table is used by writeable DLZ drivers to offload authorization for + * updates to the driver. + */ + +void +dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + *\li 'source' is a valid SSU table + *\li 'targetp' points to a NULL dns_ssutable_t *. + * + * Ensures: + *\li *targetp is attached to source. + */ + +void +dns_ssutable_detach(dns_ssutable_t **tablep); +/*%< + * Detach '*tablep' from its simple-secure-update rule table. + * + * Requires: + *\li 'tablep' points to a valid dns_ssutable_t + * + * Ensures: + *\li *tablep is NULL + *\li If '*tablep' is the last reference to the SSU table, all + * resources used by the table will be freed. + */ + +void +dns_ssutable_addrule(dns_ssutable_t *table, bool grant, + const dns_name_t *identity, dns_ssumatchtype_t matchtype, + const dns_name_t *name, unsigned int ntypes, + dns_ssuruletype_t *types); +/*%< + * Adds a new rule to a simple-secure-update rule table. The rule + * either grants or denies update privileges of an identity (or set of + * identities) to modify a name (or set of names) or certain types present + * at that name. + * + * Notes: + *\li If 'matchtype' is of SELF type, this rule only matches if the + * name to be updated matches the signing identity. + * + *\li If 'ntypes' is 0, this rule applies to all types except + * NS, SOA, RRSIG, and NSEC. + * + *\li If 'types' includes ANY, this rule applies to all types + * except NSEC. + * + * Requires: + *\li 'table' is a valid SSU table + *\li 'identity' is a valid absolute name + *\li 'matchtype' must be one of the defined constants. + *\li 'name' is a valid absolute name + *\li If 'ntypes' > 0, 'types' must not be NULL + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOMEMORY + */ + +bool +dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *addr, + bool tcp, dns_aclenv_t *env, dns_rdatatype_t type, + const dns_name_t *target, const dst_key_t *key, + const dns_ssurule_t **rulep); +/*%< + * Checks that the attempted update of (name, type) is allowed according + * to the rules specified in the simple-secure-update rule table. If + * no rules are matched, access is denied. + * + * Notes: + * In dns_ssutable_checkrules(), 'addr' should only be + * set if the request received via TCP. This provides a + * weak assurance that the request was not spoofed. + * 'addr' is to to validate dns_ssumatchtype_tcpself + * and dns_ssumatchtype_6to4self rules. + * + * In dns_ssutable_checkrules2(), 'addr' can also be passed for + * UDP requests and TCP is specified via the 'tcp' parameter. + * In addition to dns_ssumatchtype_tcpself and + * tcp_ssumatchtype_6to4self rules, the address + * also be used to check dns_ssumatchtype_local rules. + * If 'addr' is set then 'env' must also be set so that + * requests from non-localhost addresses can be rejected. + * + * For dns_ssumatchtype_tcpself the addresses are mapped to + * the standard reverse names under IN-ADDR.ARPA and IP6.ARPA. + * RFC 1035, Section 3.5, "IN-ADDR.ARPA domain" and RFC 3596, + * Section 2.5, "IP6.ARPA Domain". + * + * For dns_ssumatchtype_6to4self, IPv4 address are converted + * to a 6to4 prefix (48 bits) per the rules in RFC 3056. Only + * the top 48 bits of the IPv6 address are mapped to the reverse + * name. This is independent of whether the most significant 16 + * bits match 2002::/16, assigned for 6to4 prefixes, or not. + * + * Requires: + *\li 'table' is a valid SSU table + *\li 'signer' is NULL or a valid absolute name + *\li 'addr' is NULL or a valid network address. + *\li 'aclenv' is NULL or a valid ACL environment. + *\li 'name' is a valid absolute name + *\li if 'addr' is not NULL, 'env' is not NULL. + */ + +/*% Accessor functions to extract rule components */ +bool +dns_ssurule_isgrant(const dns_ssurule_t *rule); + +/*% Accessor functions to extract rule components */ +dns_name_t * +dns_ssurule_identity(const dns_ssurule_t *rule); + +/*% Accessor functions to extract rule components */ +unsigned int +dns_ssurule_matchtype(const dns_ssurule_t *rule); + +/*% Accessor functions to extract rule components */ +dns_name_t * +dns_ssurule_name(const dns_ssurule_t *rule); + +/*% Accessor functions to extract rule components */ +unsigned int +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types); + +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type); +/*%< + * Returns the maximum number of records configured for type `type`. + * If no maximum has been configured for `type` but one has been + * configured for ANY, return that value instead. Otherwise, return + * zero, which implies "unlimited". + */ + +isc_result_t +dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule); +/*%< + * Initiates a rule iterator. There is no need to maintain any state. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE + */ + +isc_result_t +dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule); +/*%< + * Returns the next rule in the table. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE + */ + +bool +dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *tcpaddr, + dns_rdatatype_t type, const dst_key_t *key, + isc_mem_t *mctx); +/*%< + * Check a policy rule via an external application + */ + +isc_result_t +dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype); +/*%< + * Set 'mtype' from 'str' + * + * Requires: + *\li 'str' is not NULL. + *\li 'mtype' is not NULL, + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTFOUND + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/stats.h b/lib/dns/include/dns/stats.h new file mode 100644 index 0000000..683f870 --- /dev/null +++ b/lib/dns/include/dns/stats.h @@ -0,0 +1,825 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/stats.h */ + +#include + +#include + +/*% + * Statistics counters. Used as isc_statscounter_t values. + */ +enum { + /*% + * Resolver statistics counters. + */ + dns_resstatscounter_queryv4 = 0, + dns_resstatscounter_queryv6 = 1, + dns_resstatscounter_responsev4 = 2, + dns_resstatscounter_responsev6 = 3, + dns_resstatscounter_nxdomain = 4, + dns_resstatscounter_servfail = 5, + dns_resstatscounter_formerr = 6, + dns_resstatscounter_othererror = 7, + dns_resstatscounter_edns0fail = 8, + dns_resstatscounter_mismatch = 9, + dns_resstatscounter_truncated = 10, + dns_resstatscounter_lame = 11, + dns_resstatscounter_retry = 12, + dns_resstatscounter_gluefetchv4 = 13, + dns_resstatscounter_gluefetchv6 = 14, + dns_resstatscounter_gluefetchv4fail = 15, + dns_resstatscounter_gluefetchv6fail = 16, + dns_resstatscounter_val = 17, + dns_resstatscounter_valsuccess = 18, + dns_resstatscounter_valnegsuccess = 19, + dns_resstatscounter_valfail = 20, + dns_resstatscounter_dispabort = 21, + dns_resstatscounter_dispsockfail = 22, + dns_resstatscounter_querytimeout = 23, + dns_resstatscounter_queryrtt0 = 24, + dns_resstatscounter_queryrtt1 = 25, + dns_resstatscounter_queryrtt2 = 26, + dns_resstatscounter_queryrtt3 = 27, + dns_resstatscounter_queryrtt4 = 28, + dns_resstatscounter_queryrtt5 = 29, + dns_resstatscounter_nfetch = 30, + dns_resstatscounter_disprequdp = 31, + dns_resstatscounter_dispreqtcp = 32, + dns_resstatscounter_buckets = 33, + dns_resstatscounter_refused = 34, + dns_resstatscounter_cookienew = 35, + dns_resstatscounter_cookieout = 36, + dns_resstatscounter_cookiein = 37, + dns_resstatscounter_cookieok = 38, + dns_resstatscounter_badvers = 39, + dns_resstatscounter_badcookie = 40, + dns_resstatscounter_zonequota = 41, + dns_resstatscounter_serverquota = 42, + dns_resstatscounter_clientquota = 43, + dns_resstatscounter_nextitem = 44, + dns_resstatscounter_priming = 45, + dns_resstatscounter_max = 46, + + /* + * DNSSEC stats. + */ + dns_dnssecstats_asis = 0, + dns_dnssecstats_downcase = 1, + dns_dnssecstats_wildcard = 2, + dns_dnssecstats_fail = 3, + + dns_dnssecstats_max = 4, + + /*% + * Zone statistics counters. + */ + dns_zonestatscounter_notifyoutv4 = 0, + dns_zonestatscounter_notifyoutv6 = 1, + dns_zonestatscounter_notifyinv4 = 2, + dns_zonestatscounter_notifyinv6 = 3, + dns_zonestatscounter_notifyrej = 4, + dns_zonestatscounter_soaoutv4 = 5, + dns_zonestatscounter_soaoutv6 = 6, + dns_zonestatscounter_axfrreqv4 = 7, + dns_zonestatscounter_axfrreqv6 = 8, + dns_zonestatscounter_ixfrreqv4 = 9, + dns_zonestatscounter_ixfrreqv6 = 10, + dns_zonestatscounter_xfrsuccess = 11, + dns_zonestatscounter_xfrfail = 12, + + dns_zonestatscounter_max = 13, + + /* + * Adb statistics values. + */ + dns_adbstats_nentries = 0, + dns_adbstats_entriescnt = 1, + dns_adbstats_nnames = 2, + dns_adbstats_namescnt = 3, + + dns_adbstats_max = 4, + + /* + * Cache statistics values. + */ + dns_cachestatscounter_hits = 1, + dns_cachestatscounter_misses = 2, + dns_cachestatscounter_queryhits = 3, + dns_cachestatscounter_querymisses = 4, + dns_cachestatscounter_deletelru = 5, + dns_cachestatscounter_deletettl = 6, + dns_cachestatscounter_coveringnsec = 7, + + dns_cachestatscounter_max = 8, + + /*% + * Query statistics counters (obsolete). + */ + dns_statscounter_success = 0, /*%< Successful lookup */ + dns_statscounter_referral = 1, /*%< Referral result */ + dns_statscounter_nxrrset = 2, /*%< NXRRSET result */ + dns_statscounter_nxdomain = 3, /*%< NXDOMAIN result */ + dns_statscounter_recursion = 4, /*%< Recursion was used */ + dns_statscounter_failure = 5, /*%< Some other failure */ + dns_statscounter_duplicate = 6, /*%< Duplicate query */ + dns_statscounter_dropped = 7, /*%< Duplicate query (dropped) */ + + /*% + * DNSTAP statistics counters. + */ + dns_dnstapcounter_success = 0, + dns_dnstapcounter_drop = 1, + dns_dnstapcounter_max = 2, + + /* + * Glue cache statistics counters. + */ + dns_gluecachestatscounter_hits_present = 0, + dns_gluecachestatscounter_hits_absent = 1, + dns_gluecachestatscounter_inserts_present = 2, + dns_gluecachestatscounter_inserts_absent = 3, + + dns_gluecachestatscounter_max = 4, +}; + +/*% + * Traffic size statistics counters. Used as isc_statscounter_t values. + */ +enum { + dns_sizecounter_in_0 = 0, + dns_sizecounter_in_16 = 1, + dns_sizecounter_in_32 = 2, + dns_sizecounter_in_48 = 3, + dns_sizecounter_in_64 = 4, + dns_sizecounter_in_80 = 5, + dns_sizecounter_in_96 = 6, + dns_sizecounter_in_112 = 7, + dns_sizecounter_in_128 = 8, + dns_sizecounter_in_144 = 9, + dns_sizecounter_in_160 = 10, + dns_sizecounter_in_176 = 11, + dns_sizecounter_in_192 = 12, + dns_sizecounter_in_208 = 13, + dns_sizecounter_in_224 = 14, + dns_sizecounter_in_240 = 15, + dns_sizecounter_in_256 = 16, + dns_sizecounter_in_272 = 17, + dns_sizecounter_in_288 = 18, + + dns_sizecounter_in_max = 19, +}; + +enum { + dns_sizecounter_out_0 = 0, + dns_sizecounter_out_16 = 1, + dns_sizecounter_out_32 = 2, + dns_sizecounter_out_48 = 3, + dns_sizecounter_out_64 = 4, + dns_sizecounter_out_80 = 5, + dns_sizecounter_out_96 = 6, + dns_sizecounter_out_112 = 7, + dns_sizecounter_out_128 = 8, + dns_sizecounter_out_144 = 9, + dns_sizecounter_out_160 = 10, + dns_sizecounter_out_176 = 11, + dns_sizecounter_out_192 = 12, + dns_sizecounter_out_208 = 13, + dns_sizecounter_out_224 = 14, + dns_sizecounter_out_240 = 15, + dns_sizecounter_out_256 = 16, + dns_sizecounter_out_272 = 17, + dns_sizecounter_out_288 = 18, + dns_sizecounter_out_304 = 19, + dns_sizecounter_out_320 = 20, + dns_sizecounter_out_336 = 21, + dns_sizecounter_out_352 = 22, + dns_sizecounter_out_368 = 23, + dns_sizecounter_out_384 = 24, + dns_sizecounter_out_400 = 25, + dns_sizecounter_out_416 = 26, + dns_sizecounter_out_432 = 27, + dns_sizecounter_out_448 = 28, + dns_sizecounter_out_464 = 29, + dns_sizecounter_out_480 = 30, + dns_sizecounter_out_496 = 31, + dns_sizecounter_out_512 = 32, + dns_sizecounter_out_528 = 33, + dns_sizecounter_out_544 = 34, + dns_sizecounter_out_560 = 35, + dns_sizecounter_out_576 = 36, + dns_sizecounter_out_592 = 37, + dns_sizecounter_out_608 = 38, + dns_sizecounter_out_624 = 39, + dns_sizecounter_out_640 = 40, + dns_sizecounter_out_656 = 41, + dns_sizecounter_out_672 = 42, + dns_sizecounter_out_688 = 43, + dns_sizecounter_out_704 = 44, + dns_sizecounter_out_720 = 45, + dns_sizecounter_out_736 = 46, + dns_sizecounter_out_752 = 47, + dns_sizecounter_out_768 = 48, + dns_sizecounter_out_784 = 49, + dns_sizecounter_out_800 = 50, + dns_sizecounter_out_816 = 51, + dns_sizecounter_out_832 = 52, + dns_sizecounter_out_848 = 53, + dns_sizecounter_out_864 = 54, + dns_sizecounter_out_880 = 55, + dns_sizecounter_out_896 = 56, + dns_sizecounter_out_912 = 57, + dns_sizecounter_out_928 = 58, + dns_sizecounter_out_944 = 59, + dns_sizecounter_out_960 = 60, + dns_sizecounter_out_976 = 61, + dns_sizecounter_out_992 = 62, + dns_sizecounter_out_1008 = 63, + dns_sizecounter_out_1024 = 64, + dns_sizecounter_out_1040 = 65, + dns_sizecounter_out_1056 = 66, + dns_sizecounter_out_1072 = 67, + dns_sizecounter_out_1088 = 68, + dns_sizecounter_out_1104 = 69, + dns_sizecounter_out_1120 = 70, + dns_sizecounter_out_1136 = 71, + dns_sizecounter_out_1152 = 72, + dns_sizecounter_out_1168 = 73, + dns_sizecounter_out_1184 = 74, + dns_sizecounter_out_1200 = 75, + dns_sizecounter_out_1216 = 76, + dns_sizecounter_out_1232 = 77, + dns_sizecounter_out_1248 = 78, + dns_sizecounter_out_1264 = 79, + dns_sizecounter_out_1280 = 80, + dns_sizecounter_out_1296 = 81, + dns_sizecounter_out_1312 = 82, + dns_sizecounter_out_1328 = 83, + dns_sizecounter_out_1344 = 84, + dns_sizecounter_out_1360 = 85, + dns_sizecounter_out_1376 = 86, + dns_sizecounter_out_1392 = 87, + dns_sizecounter_out_1408 = 88, + dns_sizecounter_out_1424 = 89, + dns_sizecounter_out_1440 = 90, + dns_sizecounter_out_1456 = 91, + dns_sizecounter_out_1472 = 92, + dns_sizecounter_out_1488 = 93, + dns_sizecounter_out_1504 = 94, + dns_sizecounter_out_1520 = 95, + dns_sizecounter_out_1536 = 96, + dns_sizecounter_out_1552 = 97, + dns_sizecounter_out_1568 = 98, + dns_sizecounter_out_1584 = 99, + dns_sizecounter_out_1600 = 100, + dns_sizecounter_out_1616 = 101, + dns_sizecounter_out_1632 = 102, + dns_sizecounter_out_1648 = 103, + dns_sizecounter_out_1664 = 104, + dns_sizecounter_out_1680 = 105, + dns_sizecounter_out_1696 = 106, + dns_sizecounter_out_1712 = 107, + dns_sizecounter_out_1728 = 108, + dns_sizecounter_out_1744 = 109, + dns_sizecounter_out_1760 = 110, + dns_sizecounter_out_1776 = 111, + dns_sizecounter_out_1792 = 112, + dns_sizecounter_out_1808 = 113, + dns_sizecounter_out_1824 = 114, + dns_sizecounter_out_1840 = 115, + dns_sizecounter_out_1856 = 116, + dns_sizecounter_out_1872 = 117, + dns_sizecounter_out_1888 = 118, + dns_sizecounter_out_1904 = 119, + dns_sizecounter_out_1920 = 120, + dns_sizecounter_out_1936 = 121, + dns_sizecounter_out_1952 = 122, + dns_sizecounter_out_1968 = 123, + dns_sizecounter_out_1984 = 124, + dns_sizecounter_out_2000 = 125, + dns_sizecounter_out_2016 = 126, + dns_sizecounter_out_2032 = 127, + dns_sizecounter_out_2048 = 128, + dns_sizecounter_out_2064 = 129, + dns_sizecounter_out_2080 = 130, + dns_sizecounter_out_2096 = 131, + dns_sizecounter_out_2112 = 132, + dns_sizecounter_out_2128 = 133, + dns_sizecounter_out_2144 = 134, + dns_sizecounter_out_2160 = 135, + dns_sizecounter_out_2176 = 136, + dns_sizecounter_out_2192 = 137, + dns_sizecounter_out_2208 = 138, + dns_sizecounter_out_2224 = 139, + dns_sizecounter_out_2240 = 140, + dns_sizecounter_out_2256 = 141, + dns_sizecounter_out_2272 = 142, + dns_sizecounter_out_2288 = 143, + dns_sizecounter_out_2304 = 144, + dns_sizecounter_out_2320 = 145, + dns_sizecounter_out_2336 = 146, + dns_sizecounter_out_2352 = 147, + dns_sizecounter_out_2368 = 148, + dns_sizecounter_out_2384 = 149, + dns_sizecounter_out_2400 = 150, + dns_sizecounter_out_2416 = 151, + dns_sizecounter_out_2432 = 152, + dns_sizecounter_out_2448 = 153, + dns_sizecounter_out_2464 = 154, + dns_sizecounter_out_2480 = 155, + dns_sizecounter_out_2496 = 156, + dns_sizecounter_out_2512 = 157, + dns_sizecounter_out_2528 = 158, + dns_sizecounter_out_2544 = 159, + dns_sizecounter_out_2560 = 160, + dns_sizecounter_out_2576 = 161, + dns_sizecounter_out_2592 = 162, + dns_sizecounter_out_2608 = 163, + dns_sizecounter_out_2624 = 164, + dns_sizecounter_out_2640 = 165, + dns_sizecounter_out_2656 = 166, + dns_sizecounter_out_2672 = 167, + dns_sizecounter_out_2688 = 168, + dns_sizecounter_out_2704 = 169, + dns_sizecounter_out_2720 = 170, + dns_sizecounter_out_2736 = 171, + dns_sizecounter_out_2752 = 172, + dns_sizecounter_out_2768 = 173, + dns_sizecounter_out_2784 = 174, + dns_sizecounter_out_2800 = 175, + dns_sizecounter_out_2816 = 176, + dns_sizecounter_out_2832 = 177, + dns_sizecounter_out_2848 = 178, + dns_sizecounter_out_2864 = 179, + dns_sizecounter_out_2880 = 180, + dns_sizecounter_out_2896 = 181, + dns_sizecounter_out_2912 = 182, + dns_sizecounter_out_2928 = 183, + dns_sizecounter_out_2944 = 184, + dns_sizecounter_out_2960 = 185, + dns_sizecounter_out_2976 = 186, + dns_sizecounter_out_2992 = 187, + dns_sizecounter_out_3008 = 188, + dns_sizecounter_out_3024 = 189, + dns_sizecounter_out_3040 = 190, + dns_sizecounter_out_3056 = 191, + dns_sizecounter_out_3072 = 192, + dns_sizecounter_out_3088 = 193, + dns_sizecounter_out_3104 = 194, + dns_sizecounter_out_3120 = 195, + dns_sizecounter_out_3136 = 196, + dns_sizecounter_out_3152 = 197, + dns_sizecounter_out_3168 = 198, + dns_sizecounter_out_3184 = 199, + dns_sizecounter_out_3200 = 200, + dns_sizecounter_out_3216 = 201, + dns_sizecounter_out_3232 = 202, + dns_sizecounter_out_3248 = 203, + dns_sizecounter_out_3264 = 204, + dns_sizecounter_out_3280 = 205, + dns_sizecounter_out_3296 = 206, + dns_sizecounter_out_3312 = 207, + dns_sizecounter_out_3328 = 208, + dns_sizecounter_out_3344 = 209, + dns_sizecounter_out_3360 = 210, + dns_sizecounter_out_3376 = 211, + dns_sizecounter_out_3392 = 212, + dns_sizecounter_out_3408 = 213, + dns_sizecounter_out_3424 = 214, + dns_sizecounter_out_3440 = 215, + dns_sizecounter_out_3456 = 216, + dns_sizecounter_out_3472 = 217, + dns_sizecounter_out_3488 = 218, + dns_sizecounter_out_3504 = 219, + dns_sizecounter_out_3520 = 220, + dns_sizecounter_out_3536 = 221, + dns_sizecounter_out_3552 = 222, + dns_sizecounter_out_3568 = 223, + dns_sizecounter_out_3584 = 224, + dns_sizecounter_out_3600 = 225, + dns_sizecounter_out_3616 = 226, + dns_sizecounter_out_3632 = 227, + dns_sizecounter_out_3648 = 228, + dns_sizecounter_out_3664 = 229, + dns_sizecounter_out_3680 = 230, + dns_sizecounter_out_3696 = 231, + dns_sizecounter_out_3712 = 232, + dns_sizecounter_out_3728 = 233, + dns_sizecounter_out_3744 = 234, + dns_sizecounter_out_3760 = 235, + dns_sizecounter_out_3776 = 236, + dns_sizecounter_out_3792 = 237, + dns_sizecounter_out_3808 = 238, + dns_sizecounter_out_3824 = 239, + dns_sizecounter_out_3840 = 240, + dns_sizecounter_out_3856 = 241, + dns_sizecounter_out_3872 = 242, + dns_sizecounter_out_3888 = 243, + dns_sizecounter_out_3904 = 244, + dns_sizecounter_out_3920 = 245, + dns_sizecounter_out_3936 = 246, + dns_sizecounter_out_3952 = 247, + dns_sizecounter_out_3968 = 248, + dns_sizecounter_out_3984 = 249, + dns_sizecounter_out_4000 = 250, + dns_sizecounter_out_4016 = 251, + dns_sizecounter_out_4032 = 252, + dns_sizecounter_out_4048 = 253, + dns_sizecounter_out_4064 = 254, + dns_sizecounter_out_4080 = 255, + dns_sizecounter_out_4096 = 256, + + dns_sizecounter_out_max = 257 +}; + +#define DNS_STATS_NCOUNTERS 8 + +#if 0 +/*%< + * Flag(s) for dns_xxxstats_dump(). DNS_STATSDUMP_VERBOSE is obsolete. + * ISC_STATSDUMP_VERBOSE should be used instead. These two values are + * intentionally defined to be the same value to ensure binary compatibility. + */ +#define DNS_STATSDUMP_VERBOSE 0x00000001 /*%< dump 0-value counters */ +#endif /* if 0 */ + +/*%< + * (Obsoleted) + */ +extern const char *dns_statscounter_names[]; + +/*% + * Attributes for statistics counters of RRset and Rdatatype types. + * + * _OTHERTYPE + * The rdata type is not explicitly supported and the corresponding counter + * is counted for other such types, too. When this attribute is set, + * the base type is of no use. + * + * _NXRRSET + * RRset type counters only. Indicates the RRset is non existent. + * + * _NXDOMAIN + * RRset type counters only. Indicates a non existent name. When this + * attribute is set, the base type is of no use. + * + * _STALE + * RRset type counters only. This indicates a record that is stale + * but may still be served. + * + * _ANCIENT + * RRset type counters only. This indicates a record that is marked for + * removal. + */ +#define DNS_RDATASTATSTYPE_ATTR_OTHERTYPE 0x0001 +#define DNS_RDATASTATSTYPE_ATTR_NXRRSET 0x0002 +#define DNS_RDATASTATSTYPE_ATTR_NXDOMAIN 0x0004 +#define DNS_RDATASTATSTYPE_ATTR_STALE 0x0008 +#define DNS_RDATASTATSTYPE_ATTR_ANCIENT 0x0010 + +/*%< + * Conversion macros among dns_rdatatype_t, attributes and isc_statscounter_t. + */ +#define DNS_RDATASTATSTYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF)) +#define DNS_RDATASTATSTYPE_ATTR(type) ((type) >> 16) +#define DNS_RDATASTATSTYPE_VALUE(b, a) (((a) << 16) | (b)) + +/*% + * Types of DNSSEC sign statistics operations. + */ +typedef enum { + dns_dnssecsignstats_sign = 1, + dns_dnssecsignstats_refresh = 2 +} dnssecsignstats_type_t; + +/*%< + * Types of dump callbacks. + */ +typedef void (*dns_generalstats_dumper_t)(isc_statscounter_t, uint64_t, void *); +typedef void (*dns_rdatatypestats_dumper_t)(dns_rdatastatstype_t, uint64_t, + void *); +typedef void (*dns_dnssecsignstats_dumper_t)(dns_keytag_t, uint64_t, void *); +typedef void (*dns_opcodestats_dumper_t)(dns_opcode_t, uint64_t, void *); +typedef void (*dns_rcodestats_dumper_t)(dns_rcode_t, uint64_t, void *); + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_generalstats_create(isc_mem_t *mctx, dns_stats_t **statsp, int ncounters); +/*%< + * Create a statistics counter structure of general type. It counts a general + * set of counters indexed by an ID between 0 and ncounters -1. + * This function is obsolete. A more general function, isc_stats_create(), + * should be used. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +isc_result_t +dns_rdatatypestats_create(isc_mem_t *mctx, dns_stats_t **statsp); +/*%< + * Create a statistics counter structure per rdatatype. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +isc_result_t +dns_rdatasetstats_create(isc_mem_t *mctx, dns_stats_t **statsp); +/*%< + * Create a statistics counter structure per RRset. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +isc_result_t +dns_opcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp); +/*%< + * Create a statistics counter structure per opcode. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +isc_result_t +dns_rcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp); +/*%< + * Create a statistics counter structure per assigned rcode. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +isc_result_t +dns_dnssecsignstats_create(isc_mem_t *mctx, dns_stats_t **statsp); +/*%< + * Create a statistics counter structure per assigned DNSKEY id. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +void +dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp); +/*%< + * Attach to a statistics set. + * + * Requires: + *\li 'stats' is a valid dns_stats_t. + * + *\li 'statsp' != NULL && '*statsp' == NULL + */ + +void +dns_stats_detach(dns_stats_t **statsp); +/*%< + * Detaches from the statistics set. + * + * Requires: + *\li 'statsp' != NULL and '*statsp' is a valid dns_stats_t. + */ + +void +dns_generalstats_increment(dns_stats_t *stats, isc_statscounter_t counter); +/*%< + * Increment the counter-th counter of stats. This function is obsolete. + * A more general function, isc_stats_increment(), should be used. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + * + *\li counter is less than the maximum available ID for the stats specified + * on creation. + */ + +void +dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type); +/*%< + * Increment the statistics counter for 'type'. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_rdatatypestats_create(). + */ + +void +dns_rdatasetstats_increment(dns_stats_t *stats, dns_rdatastatstype_t rrsettype); +/*%< + * Increment the statistics counter for 'rrsettype'. + * + * Note: if 'rrsettype' has the _STALE attribute set the corresponding + * non-stale counter will be decremented. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create(). + */ + +void +dns_rdatasetstats_decrement(dns_stats_t *stats, dns_rdatastatstype_t rrsettype); +/*%< + * Decrement the statistics counter for 'rrsettype'. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_rdatasetstats_create(). + */ + +void +dns_opcodestats_increment(dns_stats_t *stats, dns_opcode_t code); +/*%< + * Increment the statistics counter for 'code'. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_opcodestats_create(). + */ + +void +dns_rcodestats_increment(dns_stats_t *stats, dns_opcode_t code); +/*%< + * Increment the statistics counter for 'code'. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_rcodestats_create(). + */ + +void +dns_dnssecsignstats_increment(dns_stats_t *stats, dns_keytag_t id, uint8_t alg, + dnssecsignstats_type_t operation); +/*%< + * Increment the statistics counter for the DNSKEY 'id' with algorithm 'alg'. + * The 'operation' determines what counter is incremented. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create(). + */ + +void +dns_dnssecsignstats_clear(dns_stats_t *stats, dns_keytag_t id, uint8_t alg); +/*%< + * Clear the statistics counter for the DNSKEY 'id' with algorithm 'alg'. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_dnssecsignstats_create(). + */ + +void +dns_generalstats_dump(dns_stats_t *stats, dns_generalstats_dumper_t dump_fn, + void *arg, unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with its current value and the given argument + * arg. By default counters that have a value of 0 is skipped; if options has + * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped. + * + * This function is obsolete. A more general function, isc_stats_dump(), + * should be used. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +void +dns_rdatatypestats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn, + void *arg, unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with the corresponding type in the form of + * dns_rdatastatstype_t, the current counter value and the given argument + * arg. By default counters that have a value of 0 is skipped; if options has + * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +void +dns_rdatasetstats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn, + void *arg, unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with the corresponding type in the form of + * dns_rdatastatstype_t, the current counter value and the given argument + * arg. By default counters that have a value of 0 is skipped; if options has + * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +void +dns_dnssecsignstats_dump(dns_stats_t *stats, dnssecsignstats_type_t operation, + dns_dnssecsignstats_dumper_t dump_fn, void *arg, + unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with the corresponding type in the form of + * dns_rdatastatstype_t, the current counter value and the given argument + * arg. By default counters that have a value of 0 is skipped; if options has + * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +void +dns_opcodestats_dump(dns_stats_t *stats, dns_opcodestats_dumper_t dump_fn, + void *arg, unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with the corresponding opcode, the current + * counter value and the given argument arg. By default counters that have a + * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even + * such counters are dumped. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +void +dns_rcodestats_dump(dns_stats_t *stats, dns_rcodestats_dumper_t dump_fn, + void *arg, unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with the corresponding rcode, the current + * counter value and the given argument arg. By default counters that have a + * value of 0 is skipped; if options has the ISC_STATSDUMP_VERBOSE flag, even + * such counters are dumped. + * + * Requires: + *\li 'stats' is a valid dns_stats_t created by dns_generalstats_create(). + */ + +isc_result_t +dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp); +/*%< + * Allocate an array of query statistics counters from the memory + * context 'mctx'. + * + * This function is obsoleted. Use dns_xxxstats_create() instead. + */ + +void +dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp); +/*%< + * Free an array of query statistics counters allocated from the memory + * context 'mctx'. + * + * This function is obsoleted. Use dns_stats_destroy() instead. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/time.h b/lib/dns/include/dns/time.h new file mode 100644 index 0000000..db86a49 --- /dev/null +++ b/lib/dns/include/dns/time.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/time.h */ + +/*** + *** Imports + ***/ + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_time64_fromtext(const char *source, int64_t *target); +/*%< + * Convert a date and time in YYYYMMDDHHMMSS text format at 'source' + * into to a 64-bit count of seconds since Jan 1 1970 0:00 GMT. + * Store the count at 'target'. + */ + +isc_result_t +dns_time32_fromtext(const char *source, uint32_t *target); +/*%< + * Like dns_time64_fromtext, but returns the second count modulo 2^32 + * as per RFC2535. + */ + +isc_result_t +dns_time64_totext(int64_t value, isc_buffer_t *target); +/*%< + * Convert a 64-bit count of seconds since Jan 1 1970 0:00 GMT into + * a YYYYMMDDHHMMSS text representation and append it to 'target'. + */ + +isc_result_t +dns_time32_totext(uint32_t value, isc_buffer_t *target); +/*%< + * Like dns_time64_totext, but for a 32-bit cyclic time value. + * Of those dates whose counts of seconds since Jan 1 1970 0:00 GMT + * are congruent with 'value' modulo 2^32, the one closest to the + * current date is chosen. + */ + +int64_t +dns_time64_from32(uint32_t value); +/*%< + * Covert a 32-bit cyclic time value into a 64 bit time stamp. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/tkey.h b/lib/dns/include/dns/tkey.h new file mode 100644 index 0000000..08c76b7 --- /dev/null +++ b/lib/dns/include/dns/tkey.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/tkey.h */ + +#include +#include + +#include + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/* Key agreement modes */ +#define DNS_TKEYMODE_SERVERASSIGNED 1 +#define DNS_TKEYMODE_DIFFIEHELLMAN 2 +#define DNS_TKEYMODE_GSSAPI 3 +#define DNS_TKEYMODE_RESOLVERASSIGNED 4 +#define DNS_TKEYMODE_DELETE 5 + +struct dns_tkeyctx { + dst_key_t *dhkey; + dns_name_t *domain; + dns_gss_cred_id_t gsscred; + isc_mem_t *mctx; + char *gssapi_keytab; +}; + +isc_result_t +dns_tkeyctx_create(isc_mem_t *mctx, dns_tkeyctx_t **tctxp); +/*%< + * Create an empty TKEY context. + * + * Requires: + *\li 'mctx' is not NULL + *\li 'tctx' is not NULL + *\li '*tctx' is NULL + * + * Returns + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li return codes from dns_name_fromtext() + */ + +void +dns_tkeyctx_destroy(dns_tkeyctx_t **tctxp); +/*%< + * Frees all data associated with the TKEY context + * + * Requires: + *\li 'tctx' is not NULL + *\li '*tctx' is not NULL + */ + +isc_result_t +dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, + dns_tsig_keyring_t *ring); +/*%< + * Processes a query containing a TKEY record, adding or deleting TSIG + * keys if necessary, and modifies the message to contain the response. + * + * Requires: + *\li 'msg' is a valid message + *\li 'tctx' is a valid TKEY context + *\li 'ring' is a valid TSIG keyring + * + * Returns + *\li #ISC_R_SUCCESS msg was updated (the TKEY operation succeeded, + * or msg now includes a TKEY with an error set) + * DNS_R_FORMERR the packet was malformed (missing a TKEY + * or KEY). + *\li other An error occurred while processing the message + */ + +isc_result_t +dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key, + const dns_name_t *name, const dns_name_t *algorithm, + isc_buffer_t *nonce, uint32_t lifetime); +/*%< + * Builds a query containing a TKEY that will generate a shared + * secret using a Diffie-Hellman key exchange. The shared key + * will be of the specified algorithm (only DNS_TSIG_HMACMD5_NAME + * is supported), and will be named either 'name', + * 'name' + server chosen domain, or random data + server chosen domain + * if 'name' == dns_rootname. If nonce is not NULL, it supplies + * random data used in the shared secret computation. The key is + * requested to have the specified lifetime (in seconds) + * + * + * Requires: + *\li 'msg' is a valid message + *\li 'key' is a valid Diffie Hellman dst key + *\li 'name' is a valid name + *\li 'algorithm' is a valid name + * + * Returns: + *\li #ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + *\li other an error occurred while building the message + */ + +isc_result_t +dns_tkey_buildgssquery(dns_message_t *msg, const dns_name_t *name, + const dns_name_t *gname, isc_buffer_t *intoken, + uint32_t lifetime, dns_gss_ctx_id_t *context, bool win2k, + isc_mem_t *mctx, char **err_message); +/*%< + * Builds a query containing a TKEY that will generate a GSSAPI context. + * The key is requested to have the specified lifetime (in seconds). + * + * Requires: + *\li 'msg' is a valid message + *\li 'name' is a valid name + *\li 'gname' is a valid name + *\li 'context' is a pointer to a valid gss_ctx_id_t + * (which may have the value GSS_C_NO_CONTEXT) + *\li 'win2k' when true says to turn on some hacks to work + * with the non-standard GSS-TSIG of Windows 2000 + * + * Returns: + *\li ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + *\li other an error occurred while building the message + *\li *err_message optional error message + */ + +isc_result_t +dns_tkey_builddeletequery(dns_message_t *msg, dns_tsigkey_t *key); +/*%< + * Builds a query containing a TKEY record that will delete the + * specified shared secret from the server. + * + * Requires: + *\li 'msg' is a valid message + *\li 'key' is a valid TSIG key + * + * Returns: + *\li #ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + *\li other an error occurred while building the message + */ + +isc_result_t +dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dst_key_t *key, isc_buffer_t *nonce, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring); +/*%< + * Processes a response to a query containing a TKEY that was + * designed to generate a shared secret using a Diffie-Hellman key + * exchange. If the query was successful, a new shared key + * is created and added to the list of shared keys. + * + * Requires: + *\li 'qmsg' is a valid message (the query) + *\li 'rmsg' is a valid message (the response) + *\li 'key' is a valid Diffie Hellman dst key + *\li 'outkey' is either NULL or a pointer to NULL + *\li 'ring' is a valid keyring or NULL + * + * Returns: + *\li #ISC_R_SUCCESS the shared key was successfully added + *\li #ISC_R_NOTFOUND an error occurred while looking for a + * component of the query or response + */ + +isc_result_t +dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg, + const dns_name_t *gname, dns_gss_ctx_id_t *context, + isc_buffer_t *outtoken, dns_tsigkey_t **outkey, + dns_tsig_keyring_t *ring, char **err_message); +/*%< + * XXX + */ + +isc_result_t +dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dns_tsig_keyring_t *ring); +/*%< + * Processes a response to a query containing a TKEY that was + * designed to delete a shared secret. If the query was successful, + * the shared key is deleted from the list of shared keys. + * + * Requires: + *\li 'qmsg' is a valid message (the query) + *\li 'rmsg' is a valid message (the response) + *\li 'ring' is not NULL + * + * Returns: + *\li #ISC_R_SUCCESS the shared key was successfully deleted + *\li #ISC_R_NOTFOUND an error occurred while looking for a + * component of the query or response + */ + +isc_result_t +dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg, + const dns_name_t *server, dns_gss_ctx_id_t *context, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring, + bool win2k, char **err_message); + +/* + * Client side negotiation of GSS-TSIG. Process the response + * to a TKEY, and establish a TSIG key if negotiation was successful. + * Build a response to the input TKEY message. Can take multiple + * calls to successfully establish the context. + * + * Requires: + * 'qmsg' is a valid message, the original TKEY request; + * it will be filled with the new message to send + * 'rmsg' is a valid message, the incoming TKEY message + * 'server' is the server name + * 'context' is the input context handle + * 'outkey' receives the established key, if non-NULL; + * if non-NULL must point to NULL + * 'ring' is the keyring in which to establish the key, + * or NULL + * 'win2k' when true says to turn on some hacks to work + * with the non-standard GSS-TSIG of Windows 2000 + * + * Returns: + * ISC_R_SUCCESS context was successfully established + * ISC_R_NOTFOUND couldn't find a needed part of the query + * or response + * DNS_R_CONTINUE additional context negotiation is required; + * send the new qmsg to the server + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/transport.h b/lib/dns/include/dns/transport.h new file mode 100644 index 0000000..e74ccd7 --- /dev/null +++ b/lib/dns/include/dns/transport.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +typedef enum { + DNS_TRANSPORT_NONE = 0, + DNS_TRANSPORT_UDP = 1, + DNS_TRANSPORT_TCP = 2, + DNS_TRANSPORT_TLS = 3, + DNS_TRANSPORT_HTTP = 4, + DNS_TRANSPORT_COUNT = 5, +} dns_transport_type_t; + +typedef enum { + DNS_HTTP_GET = 0, + DNS_HTTP_POST = 1, +} dns_http_mode_t; + +typedef struct dns_transport dns_transport_t; +typedef struct dns_transport_list dns_transport_list_t; + +dns_transport_t * +dns_transport_new(const dns_name_t *name, dns_transport_type_t type, + dns_transport_list_t *list); +/*%< + * Create a new transport object with name 'name' and type 'type', + * and append it to 'list'. + */ + +dns_transport_type_t +dns_transport_get_type(dns_transport_t *transport); +char * +dns_transport_get_certfile(dns_transport_t *transport); +char * +dns_transport_get_keyfile(dns_transport_t *transport); +char * +dns_transport_get_cafile(dns_transport_t *transport); +char * +dns_transport_get_remote_hostname(dns_transport_t *transport); +char * +dns_transport_get_endpoint(dns_transport_t *transport); +dns_http_mode_t +dns_transport_get_mode(dns_transport_t *transport); +char * +dns_transport_get_ciphers(dns_transport_t *transport); +char * +dns_transport_get_tlsname(dns_transport_t *transport); +uint32_t +dns_transport_get_tls_versions(const dns_transport_t *transport); +bool +dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport, + bool *preferp); +/*%< + * Getter functions: return the type, cert file, key file, CA file, + * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'. + * + * dns_transport_get_prefer_server_ciphers() returns 'true' is value + * was set, 'false' otherwise. The actual value is returned via + * 'preferp' pointer. + */ + +void +dns_transport_set_certfile(dns_transport_t *transport, const char *certfile); +void +dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile); +void +dns_transport_set_cafile(dns_transport_t *transport, const char *cafile); +void +dns_transport_set_remote_hostname(dns_transport_t *transport, + const char *hostname); +void +dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint); +void +dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode); +void +dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers); +void +dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname); + +void +dns_transport_set_tls_versions(dns_transport_t *transport, + const uint32_t tls_versions); +void +dns_transport_set_prefer_server_ciphers(dns_transport_t *transport, + const bool prefer); +/*%< + * Setter functions: set the type, cert file, key file, CA file, + * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'. + * + * Requires: + *\li 'transport' is valid. + *\li 'transport' is of type DNS_TRANSPORT_TLS or DNS_TRANSPORT_HTTP + * (for certfile, keyfile, cafile, or hostname). + *\li 'transport' is of type DNS_TRANSPORT_HTTP (for endpoint or mode). + */ + +void +dns_transport_attach(dns_transport_t *source, dns_transport_t **targetp); +/*%< + * Attach to a transport object. + * + * Requires: + *\li 'source' is a valid transport. + *\li 'targetp' is not NULL and '*targetp' is NULL. + */ + +void +dns_transport_detach(dns_transport_t **transportp); +/*%< + * Detach a transport object; destroy it if there are no remaining + * references. + * + * Requires: + *\li 'transportp' is not NULL. + *\li '*transportp' is a valid transport. + */ + +dns_transport_t * +dns_transport_find(const dns_transport_type_t type, const dns_name_t *name, + dns_transport_list_t *list); +/*%< + * Find a transport matching type 'type' and name `name` in 'list'. + * + * Requires: + *\li 'list' is valid. + *\li 'list' contains a table of type 'type' transports. + */ + +dns_transport_list_t * +dns_transport_list_new(isc_mem_t *mctx); +/*%< + * Create a new transport list. + */ + +void +dns_transport_list_attach(dns_transport_list_t *source, + dns_transport_list_t **targetp); +/*%< + * Attach to a transport list. + * + * Requires: + *\li 'source' is a valid transport list. + *\li 'targetp' is not NULL and '*targetp' is NULL. + */ + +void +dns_transport_list_detach(dns_transport_list_t **listp); +/*%< + * Detach a transport list; destroy it if there are no remaining + * references. + * + * Requires: + *\li 'listp' is not NULL. + *\li '*listp' is a valid transport list. + */ diff --git a/lib/dns/include/dns/tsec.h b/lib/dns/include/dns/tsec.h new file mode 100644 index 0000000..3d9577b --- /dev/null +++ b/lib/dns/include/dns/tsec.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * + * \brief + * The TSEC (Transaction Security) module is an abstraction layer for managing + * DNS transaction mechanisms such as TSIG or SIG(0). A TSEC structure is a + * mechanism-independent object containing key information specific to the + * mechanism, and is expected to be used as an argument to other modules + * that use transaction security in a mechanism-independent manner. + * + * MP: + *\li A TSEC structure is expected to be thread-specific. No inter-thread + * synchronization is ensured in multiple access to a single TSEC + * structure. + * + * Resources: + *\li TBS + * + * Security: + *\li This module does not handle any low-level data directly, and so no + * security issue specific to this module is anticipated. + */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% + * Transaction security types. + */ +typedef enum { + dns_tsectype_none, + dns_tsectype_tsig, + dns_tsectype_sig0 +} dns_tsectype_t; + +isc_result_t +dns_tsec_create(isc_mem_t *mctx, dns_tsectype_t type, dst_key_t *key, + dns_tsec_t **tsecp); +/*%< + * Create a TSEC structure and stores a type-dependent key structure in it. + * For a TSIG key (type is dns_tsectype_tsig), dns_tsec_create() creates a + * TSIG key structure from '*key' and keeps it in the structure. For other + * types, this function simply retains '*key' in the structure. In either + * case, the ownership of '*key' is transferred to the TSEC module; the caller + * must not modify or destroy it after the call to dns_tsec_create(). + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'type' is a valid value of dns_tsectype_t (see above). + * + *\li 'key' is a valid key. + * + *\li tsecp != NULL && *tsecp == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +void +dns_tsec_destroy(dns_tsec_t **tsecp); +/*%< + * Destroy the TSEC structure. The stored key is also detached or destroyed. + * + * Requires + * + *\li '*tsecp' is a valid TSEC structure. + * + * Ensures + * + *\li *tsecp == NULL. + * + */ + +dns_tsectype_t +dns_tsec_gettype(dns_tsec_t *tsec); +/*%< + * Return the TSEC type of '*tsec'. + * + * Requires + * + *\li 'tsec' is a valid TSEC structure. + * + */ + +void +dns_tsec_getkey(dns_tsec_t *tsec, void *keyp); +/*%< + * Return the TSEC key of '*tsec' in '*keyp'. + * + * Requires + * + *\li keyp != NULL + * + * Ensures + * + *\li *tsecp points to a valid key structure depending on the TSEC type. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h new file mode 100644 index 0000000..c66fc39 --- /dev/null +++ b/lib/dns/include/dns/tsig.h @@ -0,0 +1,295 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/tsig.h */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* + * Algorithms. + */ +extern const dns_name_t *dns_tsig_hmacmd5_name; +#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name +extern const dns_name_t *dns_tsig_gssapi_name; +#define DNS_TSIG_GSSAPI_NAME dns_tsig_gssapi_name +extern const dns_name_t *dns_tsig_gssapims_name; +#define DNS_TSIG_GSSAPIMS_NAME dns_tsig_gssapims_name +extern const dns_name_t *dns_tsig_hmacsha1_name; +#define DNS_TSIG_HMACSHA1_NAME dns_tsig_hmacsha1_name +extern const dns_name_t *dns_tsig_hmacsha224_name; +#define DNS_TSIG_HMACSHA224_NAME dns_tsig_hmacsha224_name +extern const dns_name_t *dns_tsig_hmacsha256_name; +#define DNS_TSIG_HMACSHA256_NAME dns_tsig_hmacsha256_name +extern const dns_name_t *dns_tsig_hmacsha384_name; +#define DNS_TSIG_HMACSHA384_NAME dns_tsig_hmacsha384_name +extern const dns_name_t *dns_tsig_hmacsha512_name; +#define DNS_TSIG_HMACSHA512_NAME dns_tsig_hmacsha512_name + +/*% + * Default fudge value. + */ +#define DNS_TSIG_FUDGE 300 + +struct dns_tsig_keyring { + dns_rbt_t *keys; + unsigned int writecount; + isc_rwlock_t lock; + isc_mem_t *mctx; + /* + * LRU list of generated key along with a count of the keys on the + * list and a maximum size. + */ + unsigned int generated; + unsigned int maxgenerated; + ISC_LIST(dns_tsigkey_t) lru; + isc_refcount_t references; +}; + +struct dns_tsigkey { + /* Unlocked */ + unsigned int magic; /*%< Magic number. */ + isc_mem_t *mctx; + dst_key_t *key; /*%< Key */ + dns_name_t name; /*%< Key name */ + const dns_name_t *algorithm; /*%< Algorithm name */ + dns_name_t *creator; /*%< name that created secret */ + bool generated; /*%< was this generated? */ + isc_stdtime_t inception; /*%< start of validity period */ + isc_stdtime_t expire; /*%< end of validity period */ + dns_tsig_keyring_t *ring; /*%< the enclosing keyring */ + isc_refcount_t refs; /*%< reference counter */ + ISC_LINK(dns_tsigkey_t) link; +}; + +ISC_LANG_BEGINDECLS + +const dns_name_t * +dns_tsigkey_identity(const dns_tsigkey_t *tsigkey); +/*%< + * Returns the identity of the provided TSIG key. + * + * Requires: + *\li 'tsigkey' is a valid TSIG key or NULL + * + * Returns: + *\li NULL if 'tsigkey' was NULL + *\li identity of the provided TSIG key + */ + +isc_result_t +dns_tsigkey_create(const dns_name_t *name, const dns_name_t *algorithm, + unsigned char *secret, int length, bool generated, + const dns_name_t *creator, isc_stdtime_t inception, + isc_stdtime_t expire, isc_mem_t *mctx, + dns_tsig_keyring_t *ring, dns_tsigkey_t **key); + +isc_result_t +dns_tsigkey_createfromkey(const dns_name_t *name, const dns_name_t *algorithm, + dst_key_t *dstkey, bool generated, + const dns_name_t *creator, isc_stdtime_t inception, + isc_stdtime_t expire, isc_mem_t *mctx, + dns_tsig_keyring_t *ring, dns_tsigkey_t **key); +/*%< + * Creates a tsig key structure and saves it in the keyring. If key is + * not NULL, *key will contain a copy of the key. The keys validity + * period is specified by (inception, expire), and will not expire if + * inception == expire. If the key was generated, the creating identity, + * if there is one, should be in the creator parameter. Specifying an + * unimplemented algorithm will cause failure only if dstkey != NULL; this + * allows a transient key with an invalid algorithm to exist long enough + * to generate a BADKEY response. + * + * If dns_tsigkey_createfromkey is successful a new reference to 'dstkey' + * will have been made. + * + * Requires: + *\li 'name' is a valid dns_name_t + *\li 'algorithm' is a valid dns_name_t + *\li 'secret' is a valid pointer + *\li 'length' is an integer >= 0 + *\li 'dstkey' is a valid dst key or NULL + *\li 'creator' points to a valid dns_name_t or is NULL + *\li 'mctx' is a valid memory context + *\li 'ring' is a valid TSIG keyring or NULL + *\li 'key' or '*key' must be NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_EXISTS - a key with this name already exists + *\li #ISC_R_NOTIMPLEMENTED - algorithm is not implemented + *\li #ISC_R_NOMEMORY + */ + +void +dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + *\li 'key' is a valid TSIG key + * + * Ensures: + *\li *targetp is attached to source. + */ + +void +dns_tsigkey_detach(dns_tsigkey_t **keyp); +/*%< + * Detaches from the tsig key structure pointed to by '*key'. + * + * Requires: + *\li 'keyp' is not NULL and '*keyp' is a valid TSIG key + * + * Ensures: + *\li 'keyp' points to NULL + */ + +void +dns_tsigkey_setdeleted(dns_tsigkey_t *key); +/*%< + * Prevents this key from being used again. It will be deleted when + * no references exist. + * + * Requires: + *\li 'key' is a valid TSIG key on a keyring + */ + +isc_result_t +dns_tsig_sign(dns_message_t *msg); +/*%< + * Generates a TSIG record for this message + * + * Requires: + *\li 'msg' is a valid message + *\li 'msg->tsigkey' is a valid TSIG key + *\li 'msg->tsig' is NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_NOSPACE + *\li #DNS_R_EXPECTEDTSIG + * - this is a response & msg->querytsig is NULL + */ + +isc_result_t +dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg, + dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2); +/*%< + * Verifies the TSIG record in this message + * + * Requires: + *\li 'source' is a valid buffer containing the unparsed message + *\li 'msg' is a valid message + *\li 'msg->tsigkey' is a valid TSIG key if this is a response + *\li 'msg->tsig' is NULL + *\li 'msg->querytsig' is not NULL if this is a response + *\li 'ring1' and 'ring2' are each either a valid keyring or NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #DNS_R_EXPECTEDTSIG - A TSIG was expected but not seen + *\li #DNS_R_UNEXPECTEDTSIG - A TSIG was seen but not expected + *\li #DNS_R_TSIGERRORSET - the TSIG verified but ->error was set + * and this is a query + *\li #DNS_R_CLOCKSKEW - the TSIG failed to verify because of + * the time was out of the allowed range. + *\li #DNS_R_TSIGVERIFYFAILURE - the TSIG failed to verify + *\li #DNS_R_EXPECTEDRESPONSE - the message was set over TCP and + * should have been a response, + * but was not. + */ + +isc_result_t +dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name, + const dns_name_t *algorithm, dns_tsig_keyring_t *ring); +/*%< + * Returns the TSIG key corresponding to this name and (possibly) + * algorithm. Also increments the key's reference counter. + * + * Requires: + *\li 'tsigkey' is not NULL + *\li '*tsigkey' is NULL + *\li 'name' is a valid dns_name_t + *\li 'algorithm' is a valid dns_name_t or NULL + *\li 'ring' is a valid keyring + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTFOUND + */ + +isc_result_t +dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp); +/*%< + * Create an empty TSIG key ring. + * + * Requires: + *\li 'mctx' is not NULL + *\li 'ringp' is not NULL, and '*ringp' is NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_tsigkeyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name, + dns_tsigkey_t *tkey); +/*%< + * Place a TSIG key onto a key ring. + * + * Requires: + *\li 'name' and 'ring' are not NULL + *\li 'tkey' is a valid TSIG key, which has not been + * added to any other keyrings + * + * Returns: + *\li #ISC_R_SUCCESS + *\li Any other value indicates failure. + */ + +void +dns_tsigkeyring_attach(dns_tsig_keyring_t *source, dns_tsig_keyring_t **target); + +void +dns_tsigkeyring_detach(dns_tsig_keyring_t **ringp); + +isc_result_t +dns_tsigkeyring_dumpanddetach(dns_tsig_keyring_t **ringp, FILE *fp); + +/*%< + * Destroy a TSIG key ring. + * + * Requires: + *\li 'ringp' is not NULL + */ + +void +dns_keyring_restore(dns_tsig_keyring_t *ring, FILE *fp); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/ttl.h b/lib/dns/include/dns/ttl.h new file mode 100644 index 0000000..5495368 --- /dev/null +++ b/lib/dns/include/dns/ttl.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/ttl.h */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target); +/*%< + * Output a TTL or other time interval in a human-readable form. + * The time interval is given as a count of seconds in 'src'. + * The text representation is appended to 'target'. + * + * If 'verbose' is false, use the terse BIND 8 style, like "1w2d3h4m5s". + * + * If 'verbose' is true, use a verbose style like the SOA comments + * in "dig", like "1 week 2 days 3 hours 4 minutes 5 seconds". + * + * If 'upcase' is true, we conform to the BIND 8 style in which + * the unit letter is capitalized if there is only a single unit + * letter to print (for example, "1m30s", but "2M") + * + * If 'upcase' is false, unit letters are always in lower case. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOSPACE + */ + +isc_result_t +dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl); +/*%< + * Converts a counter from either a plain number or a BIND 8 style value. + * + * Returns: + *\li ISC_R_SUCCESS + *\li DNS_R_SYNTAX + */ + +isc_result_t +dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl); +/*%< + * Converts a ttl from either a plain number or a BIND 8 style value. + * + * Returns: + *\li ISC_R_SUCCESS + *\li DNS_R_BADTTL + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h new file mode 100644 index 0000000..6465962 --- /dev/null +++ b/lib/dns/include/dns/types.h @@ -0,0 +1,430 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/types.h + * \brief + * Including this file gives you type declarations suitable for use in + * .h files, which lets us avoid circular type reference problems. + * \brief + * To actually use a type or get declarations of its methods, you must + * include the appropriate .h file too. + */ + +#include +#include +#include + +#include + +typedef struct dns_acl dns_acl_t; +typedef struct dns_aclelement dns_aclelement_t; +typedef struct dns_aclenv dns_aclenv_t; +typedef struct dns_adb dns_adb_t; +typedef struct dns_adbaddrinfo dns_adbaddrinfo_t; +typedef ISC_LIST(dns_adbaddrinfo_t) dns_adbaddrinfolist_t; +typedef struct dns_adbentry dns_adbentry_t; +typedef struct dns_adbfind dns_adbfind_t; +typedef ISC_LIST(dns_adbfind_t) dns_adbfindlist_t; +typedef struct dns_badcache dns_badcache_t; +typedef struct dns_byaddr dns_byaddr_t; +typedef struct dns_catz_zonemodmethods dns_catz_zonemodmethods_t; +typedef struct dns_catz_entry_options dns_catz_options_t; +typedef struct dns_catz_entry dns_catz_entry_t; +typedef struct dns_catz_coo dns_catz_coo_t; +typedef struct dns_catz_zone dns_catz_zone_t; +typedef struct dns_catz_changed dns_catz_changed_t; +typedef struct dns_catz_zones dns_catz_zones_t; +typedef struct dns_client dns_client_t; +typedef void dns_clientrestrans_t; +typedef void dns_clientreqtrans_t; +typedef void dns_clientupdatetrans_t; +typedef struct dns_cache dns_cache_t; +typedef uint16_t dns_cert_t; +typedef struct dns_compress dns_compress_t; +typedef struct dns_db dns_db_t; +typedef struct dns_dbimplementation dns_dbimplementation_t; +typedef struct dns_dbiterator dns_dbiterator_t; +typedef void dns_dbload_t; +typedef void dns_dbnode_t; +typedef struct dns_dbonupdatelistener dns_dbonupdatelistener_t; +typedef void dns_dbversion_t; +typedef struct dns_dlzimplementation dns_dlzimplementation_t; +typedef struct dns_dlzdb dns_dlzdb_t; +typedef ISC_LIST(dns_dlzdb_t) dns_dlzdblist_t; +typedef struct dns_dyndbctx dns_dyndbctx_t; +typedef struct dns_sdlzimplementation dns_sdlzimplementation_t; +typedef struct dns_decompress dns_decompress_t; +typedef struct dns_dispatch dns_dispatch_t; +typedef struct dns_dispatchlist dns_dispatchlist_t; +typedef struct dns_dispatchset dns_dispatchset_t; +typedef struct dns_dispatchmgr dns_dispatchmgr_t; +typedef struct dns_dispentry dns_dispentry_t; +typedef struct dns_dns64 dns_dns64_t; +typedef ISC_LIST(dns_dns64_t) dns_dns64list_t; +typedef struct dns_dnsseckey dns_dnsseckey_t; +typedef ISC_LIST(dns_dnsseckey_t) dns_dnsseckeylist_t; +typedef uint8_t dns_dsdigest_t; +typedef struct dns_dtdata dns_dtdata_t; +typedef struct dns_dtenv dns_dtenv_t; +typedef struct dns_dtmsg dns_dtmsg_t; +typedef uint16_t dns_dtmsgtype_t; +typedef struct dns_dumpctx dns_dumpctx_t; +typedef struct dns_ecs dns_ecs_t; +typedef struct dns_ednsopt dns_ednsopt_t; +typedef struct dns_fetch dns_fetch_t; +typedef struct dns_fixedname dns_fixedname_t; +typedef struct dns_forwarders dns_forwarders_t; +typedef struct dns_forwarder dns_forwarder_t; +typedef struct dns_fwdtable dns_fwdtable_t; +typedef struct dns_geoip_databases dns_geoip_databases_t; +typedef struct dns_iptable dns_iptable_t; +typedef uint32_t dns_iterations_t; +typedef struct dns_kasp dns_kasp_t; +typedef ISC_LIST(dns_kasp_t) dns_kasplist_t; +typedef struct dns_kasp_key dns_kasp_key_t; +typedef ISC_LIST(dns_kasp_key_t) dns_kasp_keylist_t; +typedef struct dns_kasp_nsec3param dns_kasp_nsec3param_t; +typedef uint16_t dns_keyflags_t; +typedef struct dns_keynode dns_keynode_t; +typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t; +typedef struct dns_keytable dns_keytable_t; +typedef uint16_t dns_keytag_t; +typedef struct dns_loadctx dns_loadctx_t; +typedef struct dns_loadmgr dns_loadmgr_t; +typedef struct dns_masterrawheader dns_masterrawheader_t; +typedef uint64_t dns_masterstyle_flags_t; +typedef struct dns_message dns_message_t; +typedef uint16_t dns_messageid_t; +typedef isc_region_t dns_label_t; +typedef struct dns_lookup dns_lookup_t; +typedef struct dns_name dns_name_t; +typedef ISC_LIST(dns_name_t) dns_namelist_t; +typedef struct dns_nta dns_nta_t; +typedef struct dns_ntatable dns_ntatable_t; +typedef uint16_t dns_opcode_t; +typedef unsigned char dns_offsets_t[128]; +typedef struct dns_order dns_order_t; +typedef struct dns_peer dns_peer_t; +typedef struct dns_peerlist dns_peerlist_t; +typedef struct dns_rbt dns_rbt_t; +typedef uint16_t dns_rcode_t; +typedef struct dns_rdata dns_rdata_t; +typedef struct dns_rdatacallbacks dns_rdatacallbacks_t; +typedef uint16_t dns_rdataclass_t; +typedef struct dns_rdatalist dns_rdatalist_t; +typedef struct dns_rdataset dns_rdataset_t; +typedef ISC_LIST(dns_rdataset_t) dns_rdatasetlist_t; +typedef struct dns_rdatasetiter dns_rdatasetiter_t; +typedef uint16_t dns_rdatatype_t; +typedef struct dns_request dns_request_t; +typedef struct dns_requestmgr dns_requestmgr_t; +typedef struct dns_resolver dns_resolver_t; +typedef struct dns_sdbimplementation dns_sdbimplementation_t; +typedef uint8_t dns_secalg_t; +typedef uint8_t dns_secproto_t; +typedef struct dns_signature dns_signature_t; +typedef struct dns_sortlist_arg dns_sortlist_arg_t; +typedef struct dns_ssurule dns_ssurule_t; +typedef struct dns_ssutable dns_ssutable_t; +typedef struct dns_stats dns_stats_t; +typedef uint32_t dns_rdatastatstype_t; +typedef struct dns_tkeyctx dns_tkeyctx_t; +typedef uint16_t dns_trust_t; +typedef struct dns_tsec dns_tsec_t; +typedef struct dns_tsig_keyring dns_tsig_keyring_t; +typedef struct dns_tsigkey dns_tsigkey_t; +typedef uint32_t dns_ttl_t; +typedef struct dns_update_state dns_update_state_t; +typedef struct dns_validator dns_validator_t; +typedef struct dns_view dns_view_t; +typedef ISC_LIST(dns_view_t) dns_viewlist_t; +typedef struct dns_zone dns_zone_t; +typedef ISC_LIST(dns_zone_t) dns_zonelist_t; +typedef struct dns_zonemgr dns_zonemgr_t; +typedef struct dns_zt dns_zt_t; +typedef struct dns_ipkeylist dns_ipkeylist_t; + +typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t; + +typedef enum { dns_hash_sha1 = 1 } dns_hash_t; + +typedef enum { + dns_fwdpolicy_none = 0, + dns_fwdpolicy_first = 1, + dns_fwdpolicy_only = 2 +} dns_fwdpolicy_t; + +typedef enum { + dns_namereln_none = 0, + dns_namereln_contains = 1, + dns_namereln_subdomain = 2, + dns_namereln_equal = 3, + dns_namereln_commonancestor = 4 +} dns_namereln_t; + +typedef enum { dns_one_answer, dns_many_answers } dns_transfer_format_t; + +typedef enum { + dns_dbtype_zone = 0, + dns_dbtype_cache = 1, + dns_dbtype_stub = 3 +} dns_dbtype_t; + +typedef enum { + dns_dbtree_main = 0, + dns_dbtree_nsec = 1, + dns_dbtree_nsec3 = 2 +} dns_dbtree_t; + +typedef enum { + dns_notifytype_no = 0, + dns_notifytype_yes = 1, + dns_notifytype_explicit = 2, + dns_notifytype_masteronly = 3 +} dns_notifytype_t; + +typedef enum { + dns_minimal_no = 0, + dns_minimal_yes = 1, + dns_minimal_noauth = 2, + dns_minimal_noauthrec = 3 +} dns_minimaltype_t; + +typedef enum { + dns_dialuptype_no = 0, + dns_dialuptype_yes = 1, + dns_dialuptype_notify = 2, + dns_dialuptype_notifypassive = 3, + dns_dialuptype_refresh = 4, + dns_dialuptype_passive = 5 +} dns_dialuptype_t; + +typedef enum { + dns_masterformat_none = 0, + dns_masterformat_text = 1, + dns_masterformat_raw = 2, +} dns_masterformat_t; + +/* + * These are generated by gen.c. + */ +#include /* Provides dns_rdataclass_t. */ +#include /* Provides dns_rdatatype_t. */ + +/*% + * rcodes. + */ +enum { + /* + * Standard rcodes. + */ + dns_rcode_noerror = 0, +#define dns_rcode_noerror ((dns_rcode_t)dns_rcode_noerror) + dns_rcode_formerr = 1, +#define dns_rcode_formerr ((dns_rcode_t)dns_rcode_formerr) + dns_rcode_servfail = 2, +#define dns_rcode_servfail ((dns_rcode_t)dns_rcode_servfail) + dns_rcode_nxdomain = 3, +#define dns_rcode_nxdomain ((dns_rcode_t)dns_rcode_nxdomain) + dns_rcode_notimp = 4, +#define dns_rcode_notimp ((dns_rcode_t)dns_rcode_notimp) + dns_rcode_refused = 5, +#define dns_rcode_refused ((dns_rcode_t)dns_rcode_refused) + dns_rcode_yxdomain = 6, +#define dns_rcode_yxdomain ((dns_rcode_t)dns_rcode_yxdomain) + dns_rcode_yxrrset = 7, +#define dns_rcode_yxrrset ((dns_rcode_t)dns_rcode_yxrrset) + dns_rcode_nxrrset = 8, +#define dns_rcode_nxrrset ((dns_rcode_t)dns_rcode_nxrrset) + dns_rcode_notauth = 9, +#define dns_rcode_notauth ((dns_rcode_t)dns_rcode_notauth) + dns_rcode_notzone = 10, +#define dns_rcode_notzone ((dns_rcode_t)dns_rcode_notzone) + /* + * Extended rcodes. + */ + dns_rcode_badvers = 16, +#define dns_rcode_badvers ((dns_rcode_t)dns_rcode_badvers) + dns_rcode_badcookie = 23 +#define dns_rcode_badcookie ((dns_rcode_t)dns_rcode_badcookie) + /* + * Update dns_rcodestats_create() and + *dns_rcodestats_increment() + * and this comment if a rcode > + *dns_rcode_badcookie is assigned. + */ + /* Private space [3841..4095] */ +}; + +/*% + * TSIG errors. + */ +enum { + dns_tsigerror_badsig = 16, + dns_tsigerror_badkey = 17, + dns_tsigerror_badtime = 18, + dns_tsigerror_badmode = 19, + dns_tsigerror_badname = 20, + dns_tsigerror_badalg = 21, + dns_tsigerror_badtrunc = 22 +}; + +/*% + * Opcodes. + */ +enum { + dns_opcode_query = 0, +#define dns_opcode_query ((dns_opcode_t)dns_opcode_query) + dns_opcode_iquery = 1, +#define dns_opcode_iquery ((dns_opcode_t)dns_opcode_iquery) + dns_opcode_status = 2, +#define dns_opcode_status ((dns_opcode_t)dns_opcode_status) + dns_opcode_notify = 4, +#define dns_opcode_notify ((dns_opcode_t)dns_opcode_notify) + dns_opcode_update = 5 /* dynamic update */ +#define dns_opcode_update ((dns_opcode_t)dns_opcode_update) +}; + +/*% + * Trust levels. Must be kept in sync with trustnames[] in masterdump.c. + */ +enum { + /* Sentinel value; no data should have this trust level. */ + dns_trust_none = 0, +#define dns_trust_none ((dns_trust_t)dns_trust_none) + + /*% + * Subject to DNSSEC validation but has not yet been validated + * dns_trust_pending_additional (from the additional section). + */ + dns_trust_pending_additional = 1, +#define dns_trust_pending_additional ((dns_trust_t)dns_trust_pending_additional) + + dns_trust_pending_answer = 2, +#define dns_trust_pending_answer ((dns_trust_t)dns_trust_pending_answer) + + /*% Received in the additional section of a response. */ + dns_trust_additional = 3, +#define dns_trust_additional ((dns_trust_t)dns_trust_additional) + + /* Received in a referral response. */ + dns_trust_glue = 4, +#define dns_trust_glue ((dns_trust_t)dns_trust_glue) + + /* Answer from a non-authoritative server */ + dns_trust_answer = 5, +#define dns_trust_answer ((dns_trust_t)dns_trust_answer) + + /* Received in the authority section as part of an + * authoritative response */ + dns_trust_authauthority = 6, +#define dns_trust_authauthority ((dns_trust_t)dns_trust_authauthority) + + /* Answer from an authoritative server */ + dns_trust_authanswer = 7, +#define dns_trust_authanswer ((dns_trust_t)dns_trust_authanswer) + + /* Successfully DNSSEC validated */ + dns_trust_secure = 8, +#define dns_trust_secure ((dns_trust_t)dns_trust_secure) + + /* This server is authoritative */ + dns_trust_ultimate = 9 +#define dns_trust_ultimate ((dns_trust_t)dns_trust_ultimate) +}; + +#define DNS_TRUST_PENDING(x) \ + ((x) == dns_trust_pending_answer || (x) == dns_trust_pending_additional) +#define DNS_TRUST_ADDITIONAL(x) \ + ((x) == dns_trust_additional || (x) == dns_trust_pending_additional) +#define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue) +#define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer) + +/*% + * Name checking severities. + */ +typedef enum { + dns_severity_ignore, + dns_severity_warn, + dns_severity_fail +} dns_severity_t; + +/*% + * DNS Serial Number Update Method. + * + * \li _none: Keep the current serial. + * \li _increment: Add one to the current serial, skipping 0. + * \li _unixtime: Set to the seconds since 00:00 Jan 1, 1970, + * if possible. + * \li _date: Set to today's date in YYYYMMDDVV format: + * (Year, Month, Day, Version) + */ +typedef enum { + dns_updatemethod_none = 0, + dns_updatemethod_increment, + dns_updatemethod_unixtime, + dns_updatemethod_date +} dns_updatemethod_t; + +typedef enum { + dns_stale_answer_no, + dns_stale_answer_yes, + dns_stale_answer_conf +} dns_stale_answer_t; + +typedef struct { + const char *string; + size_t count; +} dns_indent_t; + +/* + * Functions. + */ +typedef void (*dns_dumpdonefunc_t)(void *, isc_result_t); + +typedef void (*dns_loaddonefunc_t)(void *, isc_result_t); + +typedef void (*dns_rawdatafunc_t)(dns_zone_t *, dns_masterrawheader_t *); + +typedef isc_result_t (*dns_addrdatasetfunc_t)(void *, const dns_name_t *, + dns_rdataset_t *); + +typedef isc_result_t (*dns_additionaldatafunc_t)(void *, const dns_name_t *, + dns_rdatatype_t, + dns_rdataset_t *); + +typedef isc_result_t (*dns_digestfunc_t)(void *, isc_region_t *); + +typedef void (*dns_xfrindone_t)(dns_zone_t *, isc_result_t); + +typedef void (*dns_updatecallback_t)(void *, isc_result_t, dns_message_t *); + +typedef int (*dns_rdatasetorderfunc_t)(const dns_rdata_t *, const void *); + +typedef bool (*dns_checkmxfunc_t)(dns_zone_t *, const dns_name_t *, + const dns_name_t *); + +typedef bool (*dns_checksrvfunc_t)(dns_zone_t *, const dns_name_t *, + const dns_name_t *); + +typedef bool (*dns_checknsfunc_t)(dns_zone_t *, const dns_name_t *, + const dns_name_t *, dns_rdataset_t *, + dns_rdataset_t *); + +typedef bool (*dns_isselffunc_t)(dns_view_t *, dns_tsigkey_t *, + const isc_sockaddr_t *, const isc_sockaddr_t *, + dns_rdataclass_t, void *); + +typedef void (*dns_nseclog_t)(void *val, int, const char *, ...); diff --git a/lib/dns/include/dns/update.h b/lib/dns/include/dns/update.h new file mode 100644 index 0000000..8ca1bf0 --- /dev/null +++ b/lib/dns/include/dns/update.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/update.h */ + +/*** + *** Imports + ***/ + +#include + +#include + +#include +#include + +typedef struct { + void (*func)(void *arg, dns_zone_t *zone, int level, + const char *message); + void *arg; +} dns_update_log_t; + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +uint32_t +dns_update_soaserial(uint32_t serial, dns_updatemethod_t method, + dns_updatemethod_t *used); +/*%< + * Return the next serial number after 'serial', depending on the + * update method 'method': + * + *\li * dns_updatemethod_increment increments the serial number by one + *\li * dns_updatemethod_date sets the serial number to YYYYMMDD00 + *\li * dns_updatemethod_unixtime sets the serial number to the current + * time (seconds since UNIX epoch) + *\li * dns_updatemethod_none just returns the given serial + * + * NOTE: The dns_updatemethod_increment will be used if dns_updatemethod_date or + * dns_updatemethod_unixtime is used and the new serial number would be lower + * than current serial number. + * + * Sets *used to the method that was used. + */ + +isc_result_t +dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *oldver, dns_dbversion_t *newver, + dns_diff_t *diff, uint32_t sigvalidityinterval); + +isc_result_t +dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *oldver, dns_dbversion_t *newver, + dns_diff_t *diff, uint32_t sigvalidityinterval, + dns_update_state_t **state); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h new file mode 100644 index 0000000..383dcb4 --- /dev/null +++ b/lib/dns/include/dns/validator.h @@ -0,0 +1,240 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/validator.h + * + * \brief + * DNS Validator + * This is the BIND 9 validator, the module responsible for validating the + * rdatasets and negative responses (messages). It makes use of zones in + * the view and may fetch RRset to complete trust chains. It implements + * DNSSEC as specified in RFC 4033, 4034 and 4035. + * + * Correct operation is critical to preventing spoofed answers from secure + * zones being accepted. + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li RFCs: 1034, 1035, 2181, 4033, 4034, 4035. + */ + +#include + +#include +#include +#include + +#include +#include +#include /* for dns_rdata_rrsig_t */ +#include + +#include + +/*% + * A dns_validatorevent_t is sent when a 'validation' completes. + * \brief + * 'name', 'rdataset', 'sigrdataset', and 'message' are the values that were + * supplied when dns_validator_create() was called. They are returned to the + * caller so that they may be freed. + * + * If the RESULT is ISC_R_SUCCESS and the answer is secure then + * proofs[] will contain the names of the NSEC records that hold the + * various proofs. Note the same name may appear multiple times. + */ +typedef struct dns_validatorevent { + ISC_EVENT_COMMON(struct dns_validatorevent); + dns_validator_t *validator; + isc_result_t result; + /* + * Name and type of the response to be validated. + */ + dns_name_t *name; + dns_rdatatype_t type; + /* + * Rdata and RRSIG (if any) for positive responses. + */ + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + /* + * The full response. Required for negative responses. + * Also required for positive wildcard responses. + */ + dns_message_t *message; + /* + * Proofs to be cached. + */ + dns_name_t *proofs[4]; + /* + * Optout proof seen. + */ + bool optout; + /* + * Answer is secure. + */ + bool secure; +} dns_validatorevent_t; + +#define DNS_VALIDATOR_NOQNAMEPROOF 0 +#define DNS_VALIDATOR_NODATAPROOF 1 +#define DNS_VALIDATOR_NOWILDCARDPROOF 2 +#define DNS_VALIDATOR_CLOSESTENCLOSER 3 + +/*% + * A validator object represents a validation in progress. + * \brief + * Clients are strongly discouraged from using this type directly, with + * the exception of the 'link' field, which may be used directly for + * whatever purpose the client desires. + */ +struct dns_validator { + /* Unlocked. */ + unsigned int magic; + isc_mutex_t lock; + dns_view_t *view; + /* Locked by lock. */ + unsigned int options; + unsigned int attributes; + dns_validatorevent_t *event; + dns_fetch_t *fetch; + dns_validator_t *subvalidator; + dns_validator_t *parent; + dns_keytable_t *keytable; + dst_key_t *key; + dns_rdata_rrsig_t *siginfo; + isc_task_t *task; + isc_taskaction_t action; + void *arg; + unsigned int labels; + dns_rdataset_t *currentset; + dns_rdataset_t *keyset; + dns_rdataset_t *dsset; + dns_rdataset_t fdsset; + dns_rdataset_t frdataset; + dns_rdataset_t fsigrdataset; + dns_fixedname_t fname; + dns_fixedname_t wild; + dns_fixedname_t closest; + ISC_LINK(dns_validator_t) link; + bool mustbesecure; + unsigned int depth; + unsigned int authcount; + unsigned int authfail; + isc_stdtime_t start; +}; + +/*% + * dns_validator_create() options. + */ +/* obsolete: #define DNS_VALIDATOR_DLV 0x0001U */ +#define DNS_VALIDATOR_DEFER 0x0002U +#define DNS_VALIDATOR_NOCDFLAG 0x0004U +#define DNS_VALIDATOR_NONTA 0x0008U /*% Ignore NTA table */ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_message_t *message, unsigned int options, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_validator_t **validatorp); +/*%< + * Start a DNSSEC validation. + * + * This validates a response to the question given by + * 'name' and 'type'. + * + * To validate a positive response, the response data is + * given by 'rdataset' and 'sigrdataset'. If 'sigrdataset' + * is NULL, the data is presumed insecure and an attempt + * is made to prove its insecurity by finding the appropriate + * null key. + * + * The complete response message may be given in 'message', + * to make available any authority section NSECs that may be + * needed for validation of a response resulting from a + * wildcard expansion (though no such wildcard validation + * is implemented yet). If the complete response message + * is not available, 'message' is NULL. + * + * To validate a negative response, the complete negative response + * message is given in 'message'. The 'rdataset', and + * 'sigrdataset' arguments must be NULL, but the 'name' and 'type' + * arguments must be provided. + * + * The validation is performed in the context of 'view'. + * + * When the validation finishes, a dns_validatorevent_t with + * the given 'action' and 'arg' are sent to 'task'. + * Its 'result' field will be ISC_R_SUCCESS iff the + * response was successfully proven to be either secure or + * part of a known insecure domain. + */ + +void +dns_validator_send(dns_validator_t *validator); +/*%< + * Send a deferred validation request + * + * Requires: + * 'validator' to points to a valid DNSSEC validator. + */ + +void +dns_validator_cancel(dns_validator_t *validator); +/*%< + * Cancel a DNSSEC validation in progress. + * + * Requires: + *\li 'validator' points to a valid DNSSEC validator, which + * may or may not already have completed. + * + * Ensures: + *\li It the validator has not already sent its completion + * event, it will send it with result code ISC_R_CANCELED. + */ + +void +dns_validator_destroy(dns_validator_t **validatorp); +/*%< + * Destroy a DNSSEC validator. + * + * Requires: + *\li '*validatorp' points to a valid DNSSEC validator. + * \li The validator must have completed and sent its completion + * event. + * + * Ensures: + *\li All resources used by the validator are freed. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h new file mode 100644 index 0000000..18b0b33 --- /dev/null +++ b/lib/dns/include/dns/view.h @@ -0,0 +1,1416 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/view.h + * \brief + * DNS View + * + * A "view" is a DNS namespace, together with an optional resolver and a + * forwarding policy. A "DNS namespace" is a (possibly empty) set of + * authoritative zones together with an optional cache and optional + * "hints" information. + * + * Views start out "unfrozen". In this state, core attributes like + * the cache, set of zones, and forwarding policy may be set. While + * "unfrozen", the caller (e.g. nameserver configuration loading + * code), must ensure exclusive access to the view. When the view is + * "frozen", the core attributes become immutable, and the view module + * will ensure synchronization. Freezing allows the view's core attributes + * to be accessed without locking. + * + * MP: + *\li Before the view is frozen, the caller must ensure synchronization. + * + *\li After the view is frozen, the module guarantees appropriate + * synchronization of any data structures it creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li TBS + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ISC_LANG_BEGINDECLS + +struct dns_view { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + dns_rdataclass_t rdclass; + char *name; + dns_zt_t *zonetable; + dns_resolver_t *resolver; + dns_adb_t *adb; + dns_requestmgr_t *requestmgr; + dns_cache_t *cache; + dns_db_t *cachedb; + dns_db_t *hints; + + /* + * security roots and negative trust anchors. + * internal use only; access via * dns_view_getsecroots() + */ + dns_keytable_t *secroots_priv; + dns_ntatable_t *ntatable_priv; + + isc_mutex_t lock; + bool frozen; + isc_task_t *task; + isc_event_t resevent; + isc_event_t adbevent; + isc_event_t reqevent; + isc_stats_t *adbstats; + isc_stats_t *resstats; + dns_stats_t *resquerystats; + bool cacheshared; + + /* Configurable data. */ + dns_transport_list_t *transports; + dns_tsig_keyring_t *statickeys; + dns_tsig_keyring_t *dynamickeys; + dns_peerlist_t *peers; + dns_order_t *order; + dns_fwdtable_t *fwdtable; + bool recursion; + bool qminimization; + bool qmin_strict; + bool auth_nxdomain; + bool use_glue_cache; + bool minimal_any; + dns_minimaltype_t minimalresponses; + bool enablevalidation; + bool acceptexpired; + bool requireservercookie; + bool synthfromdnssec; + bool trust_anchor_telemetry; + bool root_key_sentinel; + dns_transfer_format_t transfer_format; + dns_acl_t *cacheacl; + dns_acl_t *cacheonacl; + dns_acl_t *queryacl; + dns_acl_t *queryonacl; + dns_acl_t *recursionacl; + dns_acl_t *recursiononacl; + dns_acl_t *sortlist; + dns_acl_t *notifyacl; + dns_acl_t *transferacl; + dns_acl_t *updateacl; + dns_acl_t *upfwdacl; + dns_acl_t *denyansweracl; + dns_acl_t *nocasecompress; + bool msgcompression; + dns_rbt_t *answeracl_exclude; + dns_rbt_t *denyanswernames; + dns_rbt_t *answernames_exclude; + dns_rrl_t *rrl; + dns_rbt_t *sfd; + isc_rwlock_t sfd_lock; + bool provideixfr; + bool requestnsid; + bool sendcookie; + dns_ttl_t maxcachettl; + dns_ttl_t maxncachettl; + dns_ttl_t mincachettl; + dns_ttl_t minncachettl; + uint32_t nta_lifetime; + uint32_t nta_recheck; + char *nta_file; + dns_ttl_t prefetch_trigger; + dns_ttl_t prefetch_eligible; + in_port_t dstport; + dns_aclenv_t *aclenv; + dns_rdatatype_t preferred_glue; + bool flush; + dns_namelist_t *delonly; + bool rootdelonly; + dns_namelist_t *rootexclude; + bool checknames; + uint16_t maxudp; + dns_ttl_t staleanswerttl; + dns_stale_answer_t staleanswersok; /* rndc setting */ + bool staleanswersenable; /* named.conf setting + * */ + uint32_t staleanswerclienttimeout; + uint16_t nocookieudp; + uint16_t padding; + dns_acl_t *pad_acl; + unsigned int maxbits; + dns_dns64list_t dns64; + unsigned int dns64cnt; + dns_rpz_zones_t *rpzs; + dns_catz_zones_t *catzs; + dns_dlzdblist_t dlz_searched; + dns_dlzdblist_t dlz_unsearched; + uint32_t fail_ttl; + dns_badcache_t *failcache; + + /* + * Configurable data for server use only, + * locked by server configuration lock. + */ + dns_acl_t *matchclients; + dns_acl_t *matchdestinations; + bool matchrecursiveonly; + + /* Locked by themselves. */ + isc_refcount_t references; + isc_refcount_t weakrefs; + atomic_uint_fast32_t attributes; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_view) link; + dns_viewlist_t *viewlist; + + dns_zone_t *managed_keys; + dns_zone_t *redirect; + dns_name_t *redirectzone; /* points to + * redirectfixed + * when valid */ + dns_fixedname_t redirectfixed; + + /* + * File and configuration data for zones added at runtime + * (only used in BIND9). + * + * XXX: This should be a pointer to an opaque type that + * named implements. + */ + char *new_zone_dir; + char *new_zone_file; + char *new_zone_db; + void *new_zone_dbenv; + uint64_t new_zone_mapsize; + void *new_zone_config; + void (*cfg_destroy)(void **); + isc_mutex_t new_zone_lock; + + unsigned char secret[32]; /* Client secret */ + unsigned int v6bias; + + dns_dtenv_t *dtenv; /* Dnstap environment */ + dns_dtmsgtype_t dttypes; /* Dnstap message types + * to log */ + + /* Registered module instances */ + void *plugins; + void (*plugins_free)(isc_mem_t *, void **); + + /* Hook table */ + void *hooktable; /* ns_hooktable */ + void (*hooktable_free)(isc_mem_t *, void **); +}; + +#define DNS_VIEW_MAGIC ISC_MAGIC('V', 'i', 'e', 'w') +#define DNS_VIEW_VALID(view) ISC_MAGIC_VALID(view, DNS_VIEW_MAGIC) + +#define DNS_VIEWATTR_RESSHUTDOWN 0x01 +#define DNS_VIEWATTR_ADBSHUTDOWN 0x02 +#define DNS_VIEWATTR_REQSHUTDOWN 0x04 + +#ifdef HAVE_LMDB +#define DNS_LMDB_COMMON_FLAGS (MDB_CREATE | MDB_NOSUBDIR | MDB_NOLOCK) +#ifndef __OpenBSD__ +#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS) +#else /* __OpenBSD__ */ +/* + * OpenBSD does not have a unified buffer cache, which requires both reads and + * writes to be performed using mmap(). + */ +#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS | MDB_WRITEMAP) +#endif /* __OpenBSD__ */ +#endif /* HAVE_LMDB */ + +isc_result_t +dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name, + dns_view_t **viewp); +/*%< + * Create a view. + * + * Notes: + * + *\li The newly created view has no cache, no resolver, and an empty + * zone table. The view is not frozen. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'rdclass' is a valid class. + * + *\li 'name' is a valid C string. + * + *\li viewp != NULL && *viewp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_view_attach(dns_view_t *source, dns_view_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen view. + * + *\li 'targetp' points to a NULL dns_view_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + *\li While *targetp is attached, the view will not shut down. + */ + +void +dns_view_detach(dns_view_t **viewp); +/*%< + * Detach '*viewp' from its view. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t * + * + * Ensures: + * + *\li *viewp is NULL. + */ + +void +dns_view_flushanddetach(dns_view_t **viewp); +/*%< + * Detach '*viewp' from its view. If this was the last reference + * uncommitted changed in zones will be flushed to disk. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t * + * + * Ensures: + * + *\li *viewp is NULL. + */ + +void +dns_view_weakattach(dns_view_t *source, dns_view_t **targetp); +/*%< + * Weakly attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen view. + * + *\li 'targetp' points to a NULL dns_view_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + * \li While *targetp is attached, the view will not be freed. + */ + +void +dns_view_weakdetach(dns_view_t **targetp); +/*%< + * Detach '*viewp' from its view. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t *. + * + * Ensures: + * + *\li *viewp is NULL. + */ + +isc_result_t +dns_view_createzonetable(dns_view_t *view); +/*%< + * Create a zonetable for the view. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + *\li 'view' does not have a zonetable already. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_zt_create() can return. + */ + +isc_result_t +dns_view_createresolver(dns_view_t *view, isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm, + isc_timermgr_t *timermgr, unsigned int options, + dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6); +/*%< + * Create a resolver and address database for the view. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + *\li 'view' does not have a resolver already. + * + *\li The requirements of dns_resolver_create() apply to 'taskmgr', + * 'ntasks', 'nm', 'timermgr', 'options', 'dispatchv4', and + * 'dispatchv6'. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_resolver_create() can return. + */ + +void +dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared); +/*%< + * Set the view's cache database. If 'shared' is true, this means the cache + * is created by another view and is shared with that view. dns_view_setcache() + * is a backward compatible version equivalent to setcache2(..., false). + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + *\li 'cache' is a valid cache. + * + * Ensures: + * + * \li The cache of 'view' is 'cached. + * + *\li If this is not the first call to dns_view_setcache() for this + * view, then previously set cache is detached. + */ + +void +dns_view_sethints(dns_view_t *view, dns_db_t *hints); +/*%< + * Set the view's hints database. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view, whose hints database has not been + * set. + * + *\li 'hints' is a valid zone database. + * + * Ensures: + * + * \li The hints database of 'view' is 'hints'. + */ + +void +dns_view_settransports(dns_view_t *view, dns_transport_list_t *list); + +void +dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring); +void +dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring); +/*%< + * Set the view's static TSIG keys + * + * Requires: + * + * \li 'view' is a valid, unfrozen view, whose static TSIG keyring has not + * been set. + * + *\li 'ring' is a valid TSIG keyring + * + * Ensures: + * + *\li The static TSIG keyring of 'view' is 'ring'. + */ + +void +dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp); +/*%< + * Return the views dynamic keys. + * + * \li 'view' is a valid, unfrozen view. + * \li 'ringp' != NULL && ringp == NULL. + */ + +void +dns_view_setdstport(dns_view_t *view, in_port_t dstport); +/*%< + * Set the view's destination port. This is the port to + * which outgoing queries are sent. The default is 53, + * the standard DNS port. + * + * Requires: + * + *\li 'view' is a valid view. + * + *\li 'dstport' is a valid TCP/UDP port number. + * + * Ensures: + *\li External name servers will be assumed to be listening + * on 'dstport'. For servers whose address has already + * obtained obtained at the time of the call, the view may + * continue to use the previously set port until the address + * times out from the view's address database. + */ + +isc_result_t +dns_view_addzone(dns_view_t *view, dns_zone_t *zone); +/*%< + * Add zone 'zone' to 'view'. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + *\li 'zone' is a valid zone. + */ + +void +dns_view_freeze(dns_view_t *view); +/*%< + * Freeze view. No changes can be made to view configuration while frozen. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + * Ensures: + * + *\li 'view' is frozen. + */ + +void +dns_view_thaw(dns_view_t *view); +/*%< + * Thaw view. This allows zones to be added or removed at runtime. This is + * NOT thread-safe; the caller MUST have run isc_task_exclusive() prior to + * thawing the view. + * + * Requires: + * + *\li 'view' is a valid, frozen view. + * + * Ensures: + * + *\li 'view' is no longer frozen. + */ + +isc_result_t +dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, unsigned int options, bool use_hints, + bool use_static_stub, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset); +/*%< + * Find an rdataset whose owner name is 'name', and whose type is + * 'type'. + * In general, this function first searches view's zone and cache DBs for the + * best match data against 'name'. If nothing found there, and if 'use_hints' + * is true, the view's hint DB (if configured) is searched. + * If the view is configured with a static-stub zone which gives the longest + * match for 'name' among the zones, however, the cache DB is not consulted + * unless 'use_static_stub' is false (see below about this argument). + * + * dns_view_find() is a backward compatible version equivalent to + * dns_view_find2() with use_static_stub argument being false. + * + * Notes: + * + *\li See the description of dns_db_find() for information about 'options'. + * If the caller sets #DNS_DBFIND_GLUEOK, it must ensure that 'name' + * and 'type' are appropriate for glue retrieval. + * + *\li If 'now' is zero, then the current time will be used. + * + *\li If 'use_hints' is true, and the view has a hints database, then + * it will be searched last. If the answer is found in the hints + * database, the result code will be DNS_R_HINT. If the name is found + * in the hints database but not the type, the result code will be + * #DNS_R_HINTNXRRSET. + * + *\li If 'use_static_stub' is false and the longest match zone for 'name' + * is a static-stub zone, it's ignored and the cache and/or hints will be + * searched. In the majority of the cases this argument should be + * false. The only known usage of this argument being true is + * if this search is for a "bailiwick" glue A or AAAA RRset that may + * best match a static-stub zone. Consider the following example: + * this view is configured with a static-stub zone "example.com", + * and an attempt of recursive resolution needs to send a query for the + * zone. In this case it's quite likely that the resolver is trying to + * find A/AAAA RRs for the apex name "example.com". And, to honor the + * static-stub configuration it needs to return the glue RRs in the + * static-stub zone even if that exact RRs coming from the authoritative + * zone has been cached. + * In other general cases, the requested data is better to be + * authoritative, either locally configured or retrieved from an external + * server, and the data in the static-stub zone should better be ignored. + * + *\li 'foundname' must meet the requirements of dns_db_find(). + * + *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which + * covers 'type', then 'sigrdataset' will be bound to it. + * + * Requires: + * + *\li 'view' is a valid, frozen view. + * + *\li 'name' is valid name. + * + *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type + * except dns_rdatatype_any. + * + *\li dbp == NULL || *dbp == NULL + * + *\li nodep == NULL || *nodep == NULL. If nodep != NULL, dbp != NULL. + * + *\li 'foundname' is a valid name with a dedicated buffer or NULL. + * + *\li 'rdataset' is a valid, disassociated rdataset. + * + *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset. + * + * Ensures: + * + *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are + * bound to the found data. + * + *\li If dbp != NULL, it points to the database containing the data. + * + *\li If nodep != NULL, it points to the database node containing the data. + * + *\li If foundname != NULL, it contains the full name of the found data. + * + * Returns: + * + *\li Any result that dns_db_find() can return, with the exception of + * #DNS_R_DELEGATION. + */ + +isc_result_t +dns_view_simplefind(dns_view_t *view, const dns_name_t *name, + dns_rdatatype_t type, isc_stdtime_t now, + unsigned int options, bool use_hints, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +/*%< + * Find an rdataset whose owner name is 'name', and whose type is + * 'type'. + * + * Notes: + * + *\li This routine is appropriate for simple, exact-match queries of the + * view. 'name' must be a canonical name; there is no DNAME or CNAME + * processing. + * + *\li See the description of dns_db_find() for information about 'options'. + * If the caller sets DNS_DBFIND_GLUEOK, it must ensure that 'name' + * and 'type' are appropriate for glue retrieval. + * + *\li If 'now' is zero, then the current time will be used. + * + *\li If 'use_hints' is true, and the view has a hints database, then + * it will be searched last. If the answer is found in the hints + * database, the result code will be DNS_R_HINT. If the name is found + * in the hints database but not the type, the result code will be + * DNS_R_HINTNXRRSET. + * + *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which + * covers 'type', then 'sigrdataset' will be bound to it. + * + * Requires: + * + *\li 'view' is a valid, frozen view. + * + *\li 'name' is valid name. + * + *\li 'type' is a valid dns_rdatatype_t, and is not a meta query type + * (e.g. dns_rdatatype_any), or dns_rdatatype_rrsig. + * + *\li 'rdataset' is a valid, disassociated rdataset. + * + *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset. + * + * Ensures: + * + *\li In successful cases, 'rdataset', and possibly 'sigrdataset', are + * bound to the found data. + * + * Returns: + * + *\li #ISC_R_SUCCESS Success; result is desired type. + *\li DNS_R_GLUE Success; result is glue. + *\li DNS_R_HINT Success; result is a hint. + *\li DNS_R_NCACHENXDOMAIN Success; result is a ncache entry. + *\li DNS_R_NCACHENXRRSET Success; result is a ncache entry. + *\li DNS_R_NXDOMAIN The name does not exist. + *\li DNS_R_NXRRSET The rrset does not exist. + *\li #ISC_R_NOTFOUND No matching data found, + * or an error occurred. + */ + +isc_result_t +dns_view_findzonecut(dns_view_t *view, const dns_name_t *name, + dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now, + unsigned int options, bool use_hints, bool use_cache, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +/*%< + * Find the best known zonecut containing 'name'. + * + * This uses local authority, cache, and optionally hints data. + * No external queries are performed. + * + * Notes: + * + *\li If 'now' is zero, then the current time will be used. + * + *\li If 'use_hints' is true, and the view has a hints database, then + * it will be searched last. + * + *\li If 'use_cache' is true, and the view has a cache, then it will be + * searched. + * + *\li If 'sigrdataset' is not NULL, and there is a SIG rdataset which + * covers 'type', then 'sigrdataset' will be bound to it. + * + *\li If the DNS_DBFIND_NOEXACT option is set, then the zonecut returned + * (if any) will be the deepest known ancestor of 'name'. + * + *\li If dcname is not NULL the deepest cached name is copied to it. + * + * Requires: + * + *\li 'view' is a valid, frozen view. + * + *\li 'name' is valid name. + * + *\li 'rdataset' is a valid, disassociated rdataset. + * + *\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset. + * + * Returns: + * + *\li #ISC_R_SUCCESS Success. + * + *\li Many other results are possible. + */ + +isc_result_t +dns_viewlist_find(dns_viewlist_t *list, const char *name, + dns_rdataclass_t rdclass, dns_view_t **viewp); +/*%< + * Search for a view with name 'name' and class 'rdclass' in 'list'. + * If found, '*viewp' is (strongly) attached to it. + * + * Requires: + * + *\li 'viewp' points to a NULL dns_view_t *. + * + * Returns: + * + *\li #ISC_R_SUCCESS A matching view was found. + *\li #ISC_R_NOTFOUND No matching view was found. + */ + +isc_result_t +dns_viewlist_findzone(dns_viewlist_t *list, const dns_name_t *name, + bool allclasses, dns_rdataclass_t rdclass, + dns_zone_t **zonep); + +/*%< + * Search zone with 'name' in view with 'rdclass' in viewlist 'list' + * If found, zone is returned in *zonep. If allclasses is set rdclass is ignored + * + * Returns: + *\li #ISC_R_SUCCESS A matching zone was found. + *\li #ISC_R_NOTFOUND No matching zone was found. + *\li #ISC_R_MULTIPLE Multiple zones with the same name were found. + */ + +isc_result_t +dns_view_findzone(dns_view_t *view, const dns_name_t *name, dns_zone_t **zonep); +/*%< + * Search for the zone 'name' in the zone table of 'view'. + * If found, 'zonep' is (strongly) attached to it. There + * are no partial matches. + * + * Requires: + * + *\li 'zonep' points to a NULL dns_zone_t *. + * + * Returns: + *\li #ISC_R_SUCCESS A matching zone was found. + *\li #ISC_R_NOTFOUND No matching zone was found. + *\li others An error occurred. + */ + +isc_result_t +dns_view_load(dns_view_t *view, bool stop, bool newonly); + +isc_result_t +dns_view_asyncload(dns_view_t *view, bool newonly, dns_zt_allloaded_t callback, + void *arg); +/*%< + * Load zones attached to this view. dns_view_load() loads + * all zones whose master file has changed since the last + * load + * + * dns_view_asyncload() loads zones asynchronously. When all zones + * in the view have finished loading, 'callback' is called with argument + * 'arg' to inform the caller. + * + * If 'stop' is true, stop on the first error and return it. + * If 'stop' is false (or we are loading asynchronously), ignore errors. + * + * If 'newonly' is true load only zones that were never loaded. + * + * Requires: + * + *\li 'view' is valid. + */ + +isc_result_t +dns_view_gettransport(dns_view_t *view, const dns_transport_type_t type, + const dns_name_t *name, dns_transport_t **transportp); + +isc_result_t +dns_view_gettsig(dns_view_t *view, const dns_name_t *keyname, + dns_tsigkey_t **keyp); +/*%< + * Find the TSIG key configured in 'view' with name 'keyname', + * if any. + * + * Requires: + *\li keyp points to a NULL dns_tsigkey_t *. + * + * Returns: + *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it. + *\li #ISC_R_NOTFOUND No key was found. + *\li others An error occurred. + */ + +isc_result_t +dns_view_getpeertsig(dns_view_t *view, const isc_netaddr_t *peeraddr, + dns_tsigkey_t **keyp); +/*%< + * Find the TSIG key configured in 'view' for the server whose + * address is 'peeraddr', if any. + * + * Requires: + * keyp points to a NULL dns_tsigkey_t *. + * + * Returns: + *\li #ISC_R_SUCCESS A key was found and '*keyp' now points to it. + *\li #ISC_R_NOTFOUND No key was found. + *\li others An error occurred. + */ + +isc_result_t +dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg); +/*%< + * Verifies the signature of a message. + * + * Requires: + * + *\li 'view' is a valid view. + *\li 'source' is a valid buffer containing the message + *\li 'msg' is a valid message + * + * Returns: + *\li see dns_tsig_verify() + */ + +void +dns_view_dialup(dns_view_t *view); +/*%< + * Perform dialup-time maintenance on the zones of 'view'. + */ + +isc_result_t +dns_view_dumpdbtostream(dns_view_t *view, FILE *fp); +/*%< + * Dump the current state of the view 'view' to the stream 'fp' + * for purposes of analysis or debugging. + * + * Currently the dumped state includes the view's cache; in the future + * it may also include other state such as the address database. + * It will not not include authoritative data since it is voluminous and + * easily obtainable by other means. + * + * Requires: + * + *\li 'view' is valid. + * + *\li 'fp' refers to a file open for writing. + * + * Returns: + * \li ISC_R_SUCCESS The cache was successfully dumped. + * \li others An error occurred (see dns_master_dump) + */ + +isc_result_t +dns_view_flushcache(dns_view_t *view, bool fixuponly); +/*%< + * Flush the view's cache (and ADB). If 'fixuponly' is true, it only updates + * the internal reference to the cache DB with omitting actual flush operation. + * 'fixuponly' is intended to be used for a view that shares a cache with + * a different view. dns_view_flushcache() is a backward compatible version + * that always sets fixuponly to false. + * + * Requires: + * 'view' is valid. + * + * No other tasks are executing. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_view_flushnode(dns_view_t *view, const dns_name_t *name, bool tree); +/*%< + * Flush the given name from the view's cache (and optionally ADB/badcache). + * + * Flush the given name from the cache, ADB, and bad cache. If 'tree' + * is true, also flush all subdomains of 'name'. + * + * Requires: + *\li 'view' is valid. + *\li 'name' is valid. + * + * Returns: + *\li #ISC_R_SUCCESS + * other returns are failures. + */ + +isc_result_t +dns_view_flushname(dns_view_t *view, const dns_name_t *name); +/*%< + * Flush the given name from the view's cache, ADB and badcache. + * Equivalent to dns_view_flushnode(view, name, false). + * + * + * Requires: + *\li 'view' is valid. + *\li 'name' is valid. + * + * Returns: + *\li #ISC_R_SUCCESS + * other returns are failures. + */ + +void +dns_view_adddelegationonly(dns_view_t *view, const dns_name_t *name); +/*%< + * Add the given name to the delegation only table. + * + * Requires: + *\li 'view' is valid. + *\li 'name' is valid. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +dns_view_excludedelegationonly(dns_view_t *view, const dns_name_t *name); +/*%< + * Add the given name to be excluded from the root-delegation-only. + * + * + * Requires: + *\li 'view' is valid. + *\li 'name' is valid. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +bool +dns_view_isdelegationonly(dns_view_t *view, const dns_name_t *name); +/*%< + * Check if 'name' is in the delegation only table or if + * rootdelonly is set that name is not being excluded. + * + * Requires: + *\li 'view' is valid. + *\li 'name' is valid. + * + * Returns: + *\li #true if the name is the table. + *\li #false otherwise. + */ + +void +dns_view_setrootdelonly(dns_view_t *view, bool value); +/*%< + * Set the root delegation only flag. + * + * Requires: + *\li 'view' is valid. + */ + +bool +dns_view_getrootdelonly(dns_view_t *view); +/*%< + * Get the root delegation only flag. + * + * Requires: + *\li 'view' is valid. + */ + +isc_result_t +dns_view_freezezones(dns_view_t *view, bool freeze); +/*%< + * Freeze/thaw updates to primary zones. + * + * Requires: + * \li 'view' is valid. + */ + +void +dns_view_setadbstats(dns_view_t *view, isc_stats_t *stats); +/*%< + * Set a adb statistics set 'stats' for 'view'. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li stats is a valid statistics supporting adb statistics + * (see dns/stats.h). + */ + +void +dns_view_getadbstats(dns_view_t *view, isc_stats_t **statsp); +/*%< + * Get the adb statistics counter set for 'view'. If a statistics set is + * set '*statsp' will be attached to the set; otherwise, '*statsp' will be + * untouched. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li 'statsp' != NULL && '*statsp' != NULL + */ + +void +dns_view_setresstats(dns_view_t *view, isc_stats_t *stats); +/*%< + * Set a general resolver statistics counter set 'stats' for 'view'. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li stats is a valid statistics supporting resolver statistics counters + * (see dns/stats.h). + */ + +void +dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp); +/*%< + * Get the general statistics counter set for 'view'. If a statistics set is + * set '*statsp' will be attached to the set; otherwise, '*statsp' will be + * untouched. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li 'statsp' != NULL && '*statsp' != NULL + */ + +void +dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats); +/*%< + * Set a statistics counter set of rdata type, 'stats', for 'view'. Once the + * statistic set is installed, view's resolver will count outgoing queries + * per rdata type. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li stats is a valid statistics created by dns_rdatatypestats_create(). + */ + +void +dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp); +/*%< + * Get the rdatatype statistics counter set for 'view'. If a statistics set is + * set '*statsp' will be attached to the set; otherwise, '*statsp' will be + * untouched. + * + * Requires: + * \li 'view' is valid and is not frozen. + * + *\li 'statsp' != NULL && '*statsp' != NULL + */ + +bool +dns_view_iscacheshared(dns_view_t *view); +/*%< + * Check if the view shares the cache created by another view. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li #true if the cache is shared. + *\li #false otherwise. + */ + +isc_result_t +dns_view_initntatable(dns_view_t *view, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr); +/*%< + * Initialize the negative trust anchor table for the view. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_SUCCESS + *\li Any other result indicates failure + */ + +isc_result_t +dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp); +/*%< + * Get the negative trust anchor table for this view. Returns + * ISC_R_NOTFOUND if the table not been initialized for the view. + * + * '*ntp' is attached on success; the caller is responsible for + * detaching it with dns_ntatable_detach(). + * + * Requires: + * \li 'view' is valid. + * \li 'nta' is not NULL and '*nta' is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + */ + +isc_result_t +dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx); +/*%< + * Initialize security roots for the view, detaching any previously + * existing security roots first. (Note that secroots_priv is + * NULL until this function is called, so any function using + * security roots must check that they have been initialized first. + * One way to do this is use dns_view_getsecroots() and check its + * return value.) + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_SUCCESS + *\li Any other result indicates failure + */ + +isc_result_t +dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp); +/*%< + * Get the security roots for this view. Returns ISC_R_NOTFOUND if + * the security roots keytable has not been initialized for the view. + * + * '*ktp' is attached on success; the caller is responsible for + * detaching it with dns_keytable_detach(). + * + * Requires: + * \li 'view' is valid. + * \li 'ktp' is not NULL and '*ktp' is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + */ + +isc_result_t +dns_view_issecuredomain(dns_view_t *view, const dns_name_t *name, + isc_stdtime_t now, bool checknta, bool *ntap, + bool *secure_domain); +/*%< + * Is 'name' at or beneath a trusted key, and not covered by a valid + * negative trust anchor? Put answer in '*secure_domain'. + * + * If 'checknta' is false, ignore the NTA table in determining + * whether this is a secure domain. If 'checknta' is not false, and if + * 'ntap' is non-NULL, then '*ntap' will be updated with true if the + * name is covered by an NTA. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_SUCCESS + *\li Any other value indicates failure + */ + +bool +dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, const dns_name_t *name, + const dns_name_t *anchor); +/*%< + * Is there a current negative trust anchor above 'name' and below 'anchor'? + * + * Requires: + * \li 'view' is valid. + * + * Returns: + *\li ISC_R_TRUE + *\li ISC_R_FALSE + */ + +void +dns_view_untrust(dns_view_t *view, const dns_name_t *keyname, + const dns_rdata_dnskey_t *dnskey); +/*%< + * Remove keys that match 'keyname' and 'dnskey' from the views trust + * anchors. + * + * (NOTE: If the configuration specifies that there should be a + * trust anchor at 'keyname', but no keys are left after this + * operation, that is an error. We fail closed, inserting a NULL + * key so as to prevent validation until a legimitate key has been + * provided.) + * + * Requires: + * \li 'view' is valid. + * \li 'keyname' is valid. + * \li 'dnskey' is valid. + */ + +bool +dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname, + const dns_rdata_dnskey_t *dnskey); +/*%< + * Determine if the key defined by 'keyname' and 'dnskey' is + * trusted by 'view'. + * + * Requires: + * \li 'view' is valid. + * \li 'keyname' is valid. + * \li 'dnskey' is valid. + */ + +isc_result_t +dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx, + void (*cfg_destroy)(void **), uint64_t mapsize); +/*%< + * Set whether or not to allow zones to be created or deleted at runtime. + * + * If 'allow' is true, determines the filename into which new zone + * configuration will be written. Preserves the configuration context + * (a pointer to which is passed in 'cfgctx') for use when parsing new + * zone configuration. 'cfg_destroy' points to a callback routine to + * destroy the configuration context when the view is destroyed. (This + * roundabout method is used in order to avoid libdns having a dependency + * on libisccfg and libbind9.) + * + * If 'allow' is false, removes any existing references to + * configuration context and frees any memory. + * + * Requires: + * \li 'view' is valid. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOSPACE + */ + +void +dns_view_setnewzonedir(dns_view_t *view, const char *dir); +const char * +dns_view_getnewzonedir(dns_view_t *view); +/*%< + * Set/get the path to the directory in which NZF or NZD files should + * be stored. If the path was previously set to a non-NULL value, + * the previous value is freed. + * + * Requires: + * \li 'view' is valid. + */ + +void +dns_view_restorekeyring(dns_view_t *view); + +isc_result_t +dns_view_searchdlz(dns_view_t *view, const dns_name_t *name, + unsigned int minlabels, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_db_t **dbp); + +/*%< + * Search through the DLZ database(s) in view->dlz_searched to find + * one that can answer a query for 'name', using the DLZ driver's + * findzone method. If successful, '*dbp' is set to point to the + * DLZ database. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOTFOUND + * + * Requires: + * \li 'view' is valid. + * \li 'name' is not NULL. + * \li 'dbp' is not NULL and *dbp is NULL. + */ + +uint32_t +dns_view_getfailttl(dns_view_t *view); +/*%< + * Get the view's servfail-ttl. zero => no servfail caching. + * + * Requires: + *\li 'view' to be valid. + */ + +void +dns_view_setfailttl(dns_view_t *view, uint32_t failttl); +/*%< + * Set the view's servfail-ttl. zero => no servfail caching. + * + * Requires: + *\li 'view' to be valid. + */ + +isc_result_t +dns_view_saventa(dns_view_t *view); +/*%< + * Save NTA for names in this view to a file. + * + * Requires: + *\li 'view' to be valid. + */ + +isc_result_t +dns_view_loadnta(dns_view_t *view); +/*%< + * Loads NTA for names in this view from a file. + * + * Requires: + *\li 'view' to be valid. + */ + +void +dns_view_setviewcommit(dns_view_t *view); +/*%< + * Commit dns_zone_setview() calls previously made for all zones in this + * view. + * + * Requires: + *\li 'view' to be valid. + */ + +void +dns_view_setviewrevert(dns_view_t *view); +/*%< + * Revert dns_zone_setview() calls previously made for all zones in this + * view. + * + * Requires: + *\li 'view' to be valid. + */ + +bool +dns_view_staleanswerenabled(dns_view_t *view); +/*%< + * Check if stale answers are enabled for this view. + * + * Requires: + *\li 'view' to be valid. + */ + +void +dns_view_sfd_add(dns_view_t *view, const dns_name_t *name); +/*%< + * Add 'name' to the synth-from-dnssec namespace tree for the + * view. If the tree does not already exist create it. + * + * Requires: + *\li 'view' to be valid. + *\li 'name' to be valid. + */ + +void +dns_view_sfd_del(dns_view_t *view, const dns_name_t *name); +/*%< + * Delete 'name' to the synth-from-dnssec namespace tree for + * the view when the count of previous adds and deletes becomes + * zero. + * + * Requires: + *\li 'view' to be valid. + *\li 'name' to be valid. + */ + +void +dns_view_sfd_find(dns_view_t *view, const dns_name_t *name, + dns_name_t *foundname); +/*%< + * Find the enclosing name to the synth-from-dnssec namespace tree for 'name' + * in the specified view. + * + * Requires: + *\li 'view' to be valid. + *\li 'name' to be valid. + *\li 'foundname' to be valid with a buffer sufficient to hold the name. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h new file mode 100644 index 0000000..dd47c8c --- /dev/null +++ b/lib/dns/include/dns/xfrin.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/xfrin.h + * \brief + * Incoming zone transfers (AXFR + IXFR). + */ + +/*** + *** Imports + ***/ + +#include +#include +#include + +#include +#include + +/*** + *** Types + ***/ + +/*% + * A transfer in progress. This is an opaque type. + */ +typedef struct dns_xfrin_ctx dns_xfrin_ctx_t; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, + const isc_sockaddr_t *primaryaddr, + const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, + dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache, + isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done, + dns_xfrin_ctx_t **xfrp); +/*%< + * Attempt to start an incoming zone transfer of 'zone' + * from 'primaryaddr', creating a dns_xfrin_ctx_t object to + * manage it. Attach '*xfrp' to the newly created object. + * + * Iff ISC_R_SUCCESS is returned, '*done' is called with + * 'zone' and a result code as arguments when the transfer finishes. + * + * Requires: + *\li 'xfrtype' is dns_rdatatype_axfr, dns_rdatatype_ixfr + * or dns_rdatatype_soa (soa query followed by axfr if + * serial is greater than current serial). + * + *\li If 'xfrtype' is dns_rdatatype_ixfr or dns_rdatatype_soa, + * the zone has a database. + */ + +void +dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr); +/*%< + * If the zone transfer 'xfr' has already finished, + * do nothing. Otherwise, abort it and cause it to call + * its done callback with a status of ISC_R_CANCELED. + */ + +void +dns_xfrin_detach(dns_xfrin_ctx_t **xfrp); +/*%< + * Detach a reference to a zone transfer object. + * Caller to maintain external locking if required. + */ + +void +dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target); +/*%< + * Caller to maintain external locking if required. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h new file mode 100644 index 0000000..10ed86c --- /dev/null +++ b/lib/dns/include/dns/zone.h @@ -0,0 +1,2657 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/zone.h */ + +/*** + *** Imports + ***/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + dns_zone_none, + dns_zone_primary, + dns_zone_secondary, + dns_zone_mirror, + dns_zone_stub, + dns_zone_staticstub, + dns_zone_key, + dns_zone_dlz, + dns_zone_redirect +} dns_zonetype_t; + +#ifndef dns_zone_master +#define dns_zone_master dns_zone_primary +#endif /* dns_zone_master */ + +#ifndef dns_zone_slave +#define dns_zone_slave dns_zone_secondary +#endif /* dns_zone_slave */ + +typedef enum { + dns_zonestat_none = 0, + dns_zonestat_terse, + dns_zonestat_full +} dns_zonestat_level_t; + +typedef enum { + DNS_ZONEOPT_MANYERRORS = 1 << 0, /*%< return many errors on load */ + DNS_ZONEOPT_IXFRFROMDIFFS = 1 << 1, /*%< calculate differences */ + DNS_ZONEOPT_NOMERGE = 1 << 2, /*%< don't merge journal */ + DNS_ZONEOPT_CHECKNS = 1 << 3, /*%< check if NS's are addresses */ + DNS_ZONEOPT_FATALNS = 1 << 4, /*%< DNS_ZONEOPT_CHECKNS is fatal */ + DNS_ZONEOPT_MULTIMASTER = 1 << 5, /*%< this zone has multiple + primaries */ + DNS_ZONEOPT_USEALTXFRSRC = 1 << 6, /*%< use alternate transfer sources + */ + DNS_ZONEOPT_CHECKNAMES = 1 << 7, /*%< check-names */ + DNS_ZONEOPT_CHECKNAMESFAIL = 1 << 8, /*%< fatal check-name failures */ + DNS_ZONEOPT_CHECKWILDCARD = 1 << 9, /*%< check for internal wildcards */ + DNS_ZONEOPT_CHECKMX = 1 << 10, /*%< check-mx */ + DNS_ZONEOPT_CHECKMXFAIL = 1 << 11, /*%< fatal check-mx failures */ + DNS_ZONEOPT_CHECKINTEGRITY = 1 << 12, /*%< perform integrity checks */ + DNS_ZONEOPT_CHECKSIBLING = 1 << 13, /*%< perform sibling glue checks */ + DNS_ZONEOPT_NOCHECKNS = 1 << 14, /*%< disable IN NS address checks */ + DNS_ZONEOPT_WARNMXCNAME = 1 << 15, /*%< warn on MX CNAME check */ + DNS_ZONEOPT_IGNOREMXCNAME = 1 << 16, /*%< ignore MX CNAME check */ + DNS_ZONEOPT_WARNSRVCNAME = 1 << 17, /*%< warn on SRV CNAME check */ + DNS_ZONEOPT_IGNORESRVCNAME = 1 << 18, /*%< ignore SRV CNAME check */ + DNS_ZONEOPT_UPDATECHECKKSK = 1 << 19, /*%< check dnskey KSK flag */ + DNS_ZONEOPT_TRYTCPREFRESH = 1 << 20, /*%< try tcp refresh on udp failure + */ + DNS_ZONEOPT_NOTIFYTOSOA = 1 << 21, /*%< Notify the SOA MNAME */ + DNS_ZONEOPT_NSEC3TESTZONE = 1 << 22, /*%< nsec3-test-zone */ + DNS_ZONEOPT_SECURETOINSECURE = 1 << 23, /*%< dnssec-secure-to-insecure + */ + DNS_ZONEOPT_DNSKEYKSKONLY = 1 << 24, /*%< dnssec-dnskey-kskonly */ + DNS_ZONEOPT_CHECKDUPRR = 1 << 25, /*%< check-dup-records */ + DNS_ZONEOPT_CHECKDUPRRFAIL = 1 << 26, /*%< fatal check-dup-records + * failures */ + DNS_ZONEOPT_CHECKSPF = 1 << 27, /*%< check SPF records */ + DNS_ZONEOPT_CHECKTTL = 1 << 28, /*%< check max-zone-ttl */ + DNS_ZONEOPT_AUTOEMPTY = 1 << 29, /*%< automatic empty zone */ + DNS_ZONEOPT___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ +} dns_zoneopt_t; + +/* + * Zone key maintenance options + */ +typedef enum { + DNS_ZONEKEY_ALLOW = 0x00000001U, /*%< fetch keys on command */ + DNS_ZONEKEY_MAINTAIN = 0x00000002U, /*%< publish/sign on schedule */ + DNS_ZONEKEY_CREATE = 0x00000004U, /*%< make keys when needed */ + DNS_ZONEKEY_FULLSIGN = 0x00000008U, /*%< roll to new keys immediately */ + DNS_ZONEKEY_NORESIGN = 0x00000010U, /*%< no automatic resigning */ + DNS_ZONEKEY___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ +} dns_zonekey_t; + +#ifndef DNS_ZONE_MINREFRESH +#define DNS_ZONE_MINREFRESH 300 /*%< 5 minutes */ +#endif /* ifndef DNS_ZONE_MINREFRESH */ +#ifndef DNS_ZONE_MAXREFRESH +#define DNS_ZONE_MAXREFRESH 2419200 /*%< 4 weeks */ +#endif /* ifndef DNS_ZONE_MAXREFRESH */ +#ifndef DNS_ZONE_DEFAULTREFRESH +#define DNS_ZONE_DEFAULTREFRESH 3600 /*%< 1 hour */ +#endif /* ifndef DNS_ZONE_DEFAULTREFRESH */ +#ifndef DNS_ZONE_MINRETRY +#define DNS_ZONE_MINRETRY 300 /*%< 5 minutes */ +#endif /* ifndef DNS_ZONE_MINRETRY */ +#ifndef DNS_ZONE_MAXRETRY +#define DNS_ZONE_MAXRETRY 1209600 /*%< 2 weeks */ +#endif /* ifndef DNS_ZONE_MAXRETRY */ +#ifndef DNS_ZONE_DEFAULTRETRY +#define DNS_ZONE_DEFAULTRETRY \ + 60 /*%< 1 minute, subject to \ + * exponential backoff */ +#endif /* ifndef DNS_ZONE_DEFAULTRETRY */ + +#define DNS_ZONESTATE_XFERRUNNING 1 +#define DNS_ZONESTATE_XFERDEFERRED 2 +#define DNS_ZONESTATE_SOAQUERY 3 +#define DNS_ZONESTATE_ANY 4 +#define DNS_ZONESTATE_AUTOMATIC 5 + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx); +/*%< + * Creates a new empty zone and attach '*zonep' to it. + * + * Requires: + *\li 'zonep' to point to a NULL pointer. + *\li 'mctx' to be a valid memory context. + * + * Ensures: + *\li '*zonep' refers to a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + */ + +void +dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass); +/*%< + * Sets the class of a zone. This operation can only be performed + * once on a zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li dns_zone_setclass() not to have been called since the zone was + * created. + *\li 'rdclass' != dns_rdataclass_none. + */ + +dns_rdataclass_t +dns_zone_getclass(dns_zone_t *zone); +/*%< + * Returns the current zone class. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp); +/*%< + * Returns the current serial number of the zone. On success, the SOA + * serial of the zone will be copied into '*serialp'. + * + * Requires: + *\li 'zone' to be a valid zone. + *\li 'serialp' to be non NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #DNS_R_NOTLOADED zone DB is not loaded + */ + +void +dns_zone_settype(dns_zone_t *zone, dns_zonetype_t type); +/*%< + * Sets the zone type. This operation can only be performed once on + * a zone. + * + * Requires: + *\li 'zone' to be a valid zone. + *\li dns_zone_settype() not to have been called since the zone was + * created. + *\li 'type' != dns_zone_none + */ + +void +dns_zone_setview(dns_zone_t *zone, dns_view_t *view); +/*%< + * Associate the zone with a view. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +dns_view_t * +dns_zone_getview(dns_zone_t *zone); +/*%< + * Returns the zone's associated view. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setviewcommit(dns_zone_t *zone); +/*%< + * Commit the previous view saved internally via dns_zone_setview(). + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setviewrevert(dns_zone_t *zone); +/*%< + * Revert the most recent dns_zone_setview() on this zone, + * restoring the previous view. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin); +/*%< + * Sets the zones origin to 'origin'. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'origin' to be non NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +dns_name_t * +dns_zone_getorigin(dns_zone_t *zone); +/*%< + * Returns the value of the origin. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format, + const dns_master_style_t *style); +/*%< + * Sets the name of the master file in the format of 'format' from which + * the zone loads its database to 'file'. + * + * For zones that have no associated master file, 'file' will be NULL. + * + * For zones with persistent databases, the file name + * setting is ignored. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SUCCESS + */ + +const char * +dns_zone_getfile(dns_zone_t *zone); +/*%< + * Gets the name of the zone's master file, if any. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li Pointer to null-terminated file name, or NULL. + */ + +isc_result_t +dns_zone_setstream(dns_zone_t *zone, const FILE *stream, + dns_masterformat_t format, const dns_master_style_t *style); +/*%< + * Sets the source stream from which the zone will load its database. + * + * Requires: + *\li 'zone' to be a valid zone. + *\li 'stream' to be a valid and open FILE *. + *\li 'zone->masterfile' to be NULL, since we should load data either from + * 'stream' or from a master file, but not both. + * + * Returns: + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SUCCESS + */ + +void +dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t records); +/*%< + * Sets the maximum number of records permitted in a zone. + * 0 implies unlimited. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li void + */ + +uint32_t +dns_zone_getmaxrecords(dns_zone_t *zone); +/*%< + * Gets the maximum number of records permitted in a zone. + * 0 implies unlimited. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li uint32_t maxrecords. + */ + +void +dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl); +/*%< + * Sets the max ttl of the zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li void + */ + +dns_ttl_t +dns_zone_getmaxttl(dns_zone_t *zone); +/*%< + * Gets the max ttl of the zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li dns_ttl_t maxttl. + */ + +void +dns_zone_lock_keyfiles(dns_zone_t *zone); +/*%< + * Lock associated keyfiles for this zone. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_unlock_keyfiles(dns_zone_t *zone); +/*%< + * Unlock associated keyfiles for this zone. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_load(dns_zone_t *zone, bool newonly); + +isc_result_t +dns_zone_loadandthaw(dns_zone_t *zone); + +/*%< + * Cause the database to be loaded from its backing store. + * Confirm that the minimum requirements for the zone type are + * met, otherwise DNS_R_BADZONE is returned. + * + * If newonly is set dns_zone_load() only loads new zones. + * dns_zone_loadandthaw() is similar to dns_zone_load() but will + * also re-enable DNS UPDATEs when the load completes. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_UNEXPECTED + *\li #ISC_R_SUCCESS + *\li DNS_R_CONTINUE Incremental load has been queued. + *\li DNS_R_UPTODATE The zone has already been loaded based on + * file system timestamps. + *\li DNS_R_BADZONE + *\li Any result value from dns_db_load(). + */ + +isc_result_t +dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done, + void *arg); +/*%< + * Cause the database to be loaded from its backing store asynchronously. + * Other zone maintenance functions are suspended until this is complete. + * When finished, 'done' is called to inform the caller, with 'arg' as + * its first argument and 'zone' as its second. (Normally, 'arg' is + * expected to point to the zone table but is left undefined for testing + * purposes.) + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_ALREADYRUNNING + *\li #ISC_R_SUCCESS + *\li #ISC_R_FAILURE + *\li #ISC_R_NOMEMORY + */ + +bool +dns__zone_loadpending(dns_zone_t *zone); +/*%< + * Indicates whether the zone is waiting to be loaded asynchronously. + * (Not currently intended for use outside of this module and associated + * tests.) + */ + +void +dns_zone_attach(dns_zone_t *source, dns_zone_t **target); +/*%< + * Attach '*target' to 'source' incrementing its external + * reference count. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_zone_detach(dns_zone_t **zonep); +/*%< + * Detach from a zone decrementing its external reference count. + * If this was the last external reference to the zone it will be + * shut down and eventually freed. + * + * Require: + *\li 'zonep' to point to a valid zone. + */ + +void +dns_zone_iattach(dns_zone_t *source, dns_zone_t **target); +/*%< + * Attach '*target' to 'source' incrementing its internal + * reference count. This is intended for use by operations + * such as zone transfers that need to prevent the zone + * object from being freed but not from shutting down. + * + * Require: + *\li The caller is running in the context of the zone's task. + *\li 'zone' to be a valid zone. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_zone_idetach(dns_zone_t **zonep); +/*%< + * Detach from a zone decrementing its internal reference count. + * If there are no more internal or external references to the + * zone, it will be freed. + * + * Require: + *\li The caller is running in the context of the zone's task. + *\li 'zonep' to point to a valid zone. + */ + +isc_result_t +dns_zone_getdb(dns_zone_t *zone, dns_db_t **dbp); +/*%< + * Attach '*dbp' to the database to if it exists otherwise + * return DNS_R_NOTLOADED. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'dbp' to be != NULL && '*dbp' == NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li DNS_R_NOTLOADED + */ + +void +dns_zone_setdb(dns_zone_t *zone, dns_db_t *db); +/*%< + * Sets the zone database to 'db'. + * + * This function is expected to be used to configure a zone with a + * database which is not loaded from a file or zone transfer. + * It can be used for a general purpose zone, but right now its use + * is limited to static-stub zones to avoid possible undiscovered + * problems in the general cases. + * + * Require: + *\li 'zone' to be a valid zone of static-stub. + *\li zone doesn't have a database. + */ + +void +dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc, + const char *const *dbargv); +/*%< + * Sets the database type to dbargv[0] and database arguments + * to subsequent dbargv elements. + * 'db_type' is not checked to see if it is a valid database type. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'database' to be non NULL. + *\li 'dbargc' to be >= 1 + *\li 'dbargv' to point to dbargc NULL-terminated strings + */ + +isc_result_t +dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx); +/*%< + * Returns the current dbtype. isc_mem_free() should be used + * to free 'argv' after use. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'argv' to be non NULL and *argv to be NULL. + *\li 'mctx' to be valid. + * + * Returns: + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SUCCESS + */ + +void +dns_zone_markdirty(dns_zone_t *zone); +/*%< + * Mark a zone as 'dirty'. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_expire(dns_zone_t *zone); +/*%< + * Mark the zone as expired. If the zone requires dumping cause it to + * be initiated. Set the refresh and retry intervals to there default + * values and unload the zone. + * + * Require + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_refresh(dns_zone_t *zone); +/*%< + * Initiate zone up to date checks. The zone must already be being + * managed. + * + * Require + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_flush(dns_zone_t *zone); +/*%< + * Write the zone to database if there are uncommitted changes. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_dump(dns_zone_t *zone); +/*%< + * Write the zone to database. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format, + const dns_master_style_t *style, + const uint32_t rawversion); +/*%< + * Write the zone to stream 'fd' in the specified 'format'. + * If the 'format' is dns_masterformat_text (RFC1035), 'style' also + * specifies the file style (e.g., &dns_master_style_default). + * + * dns_zone_dumptostream() is a backward-compatible form of + * dns_zone_dumptostream2(), which always uses the dns_masterformat_text + * format and the dns_master_style_default style. + * + * dns_zone_dumptostream2() is a backward-compatible form of + * dns_zone_dumptostream3(), which always uses the current + * default raw file format version. + * + * Note that dns_zone_dumptostream3() is the most flexible form. It + * can also provide the functionality of dns_zone_fulldumptostream(). + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'fd' to be a stream open for writing. + */ + +void +dns_zone_maintenance(dns_zone_t *zone); +/*%< + * Perform regular maintenance on the zone. This is called as a + * result of a zone being managed. + * + * Require + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count); +/*%< + * Set the list of primary servers for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'primaries' array of isc_sockaddr_t with port set or NULL. + *\li 'count' the number of primaries. + *\li 'keynames' array of dns_name_t's for tsig keys or NULL. + * + *\li If 'primaries' is NULL then 'count' must be zero. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Any result dns_name_dup() can return, if keynames!=NULL + */ + +void +dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count); +/*%< + * Set the list of parental agents for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'parentals' array of isc_sockaddr_t with port set or NULL. + *\li 'count' the number of primaries. + *\li 'keynames' array of dns_name_t's for tsig keys or NULL. + * + *\li If 'parentals' is NULL then 'count' must be zero. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Any result dns_name_dup() can return, if keynames!=NULL + */ + +void +dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count); +/*%< + * Set the list of parental agents for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'parentals' array of isc_sockaddr_t with port set or NULL. + *\li 'count' the number of parentals. + *\li 'keynames' array of dns_name_t's for tsig keys or NULL. + * + *\li If 'parentals' is NULL then 'count' must be zero. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Any result dns_name_dup() can return, if keynames!=NULL + */ + +void +dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count); +/*%< + * Set the list of additional servers to be notified when + * a zone changes. To clear the list use 'count = 0'. + * + * dns_zone_alsonotifywithkeys() allows each notify address to + * be associated with a TSIG key. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'notify' to be non-NULL if count != 0. + *\li 'count' to be the number of notifiees. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +dns_zone_unload(dns_zone_t *zone); +/*%< + * detach the database from the zone structure. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +dns_kasp_t * +dns_zone_getkasp(dns_zone_t *zone); +/*%< + * Returns the current kasp. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp); +/*%< + * Set kasp for zone. If a kasp is already set, it will be detached. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value); +/*%< + * Set the given options on ('value' == true) or off + * ('value' == #false). + * + * Require: + *\li 'zone' to be a valid zone. + */ + +dns_zoneopt_t +dns_zone_getoptions(dns_zone_t *zone); +/*%< + * Returns the current zone options. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setkeyopt(dns_zone_t *zone, unsigned int option, bool value); +/*%< + * Set key options on ('value' == true) or off ('value' == + * #false). + * + * Require: + *\li 'zone' to be a valid zone. + */ + +unsigned int +dns_zone_getkeyopts(dns_zone_t *zone); +/*%< + * Returns the current zone key options. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val); +/*%< + * Set the minimum refresh time. + * + * Requires: + *\li 'zone' is valid. + *\li val > 0. + */ + +void +dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val); +/*%< + * Set the maximum refresh time. + * + * Requires: + *\li 'zone' is valid. + *\li val > 0. + */ + +void +dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val); +/*%< + * Set the minimum retry time. + * + * Requires: + *\li 'zone' is valid. + *\li val > 0. + */ + +void +dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val); +/*%< + * Set the maximum retry time. + * + * Requires: + *\li 'zone' is valid. + * val > 0. + */ + +isc_result_t +dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource); +isc_result_t +dns_zone_setaltxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource); +/*%< + * Set the source address to be used in IPv4 zone transfers. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'xfrsource' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getxfrsource4(dns_zone_t *zone); +isc_sockaddr_t * +dns_zone_getaltxfrsource4(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setxfrsource4 + * call, or the default of inaddr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource); +isc_result_t +dns_zone_setaltxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource); +/*%< + * Set the source address to be used in IPv6 zone transfers. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'xfrsource' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getxfrsource6(dns_zone_t *zone); +isc_sockaddr_t * +dns_zone_getaltxfrsource6(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setxfrsource6 + * call, or the default of in6addr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc); +/*%< + * Set the source address to be used with IPv4 parental DS queries. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'parentalsrc' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getparentalsrc4(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setparentalsrc4 + * call, or the default of inaddr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc); +/*%< + * Set the source address to be used with IPv6 parental DS queries. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'parentalsrc' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getparentalsrc6(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setparentalsrc6 + * call, or the default of in6addr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc); +/*%< + * Set the source address to be used with IPv4 NOTIFY messages. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'notifysrc' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getnotifysrc4(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setnotifysrc4 + * call, or the default of inaddr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc); +/*%< + * Set the source address to be used with IPv6 NOTIFY messages. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'notifysrc' to contain the address. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_sockaddr_t * +dns_zone_getnotifysrc6(dns_zone_t *zone); +/*%< + * Returns the source address set by a previous dns_zone_setnotifysrc6 + * call, or the default of in6addr_any, port 0. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the notify acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be a valid acl. + */ + +void +dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the query acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be a valid acl. + */ + +void +dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the query-on acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be a valid acl. + */ + +void +dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the update acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be valid acl. + */ + +void +dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the forward unsigned updates acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be valid acl. + */ + +void +dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl); +/*%< + * Sets the transfer acl list for the zone. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'acl' to be valid acl. + */ + +dns_acl_t * +dns_zone_getnotifyacl(dns_zone_t *zone); +/*%< + * Returns the current notify acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +dns_acl_t * +dns_zone_getqueryacl(dns_zone_t *zone); +/*%< + * Returns the current query acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +dns_acl_t * +dns_zone_getqueryonacl(dns_zone_t *zone); +/*%< + * Returns the current query-on acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +dns_acl_t * +dns_zone_getupdateacl(dns_zone_t *zone); +/*%< + * Returns the current update acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +dns_acl_t * +dns_zone_getforwardacl(dns_zone_t *zone); +/*%< + * Returns the current forward unsigned updates acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +dns_acl_t * +dns_zone_getxfracl(dns_zone_t *zone); +/*%< + * Returns the current transfer acl or NULL. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li acl a pointer to the acl. + *\li NULL + */ + +void +dns_zone_clearupdateacl(dns_zone_t *zone); +/*%< + * Clear the current update acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_clearforwardacl(dns_zone_t *zone); +/*%< + * Clear the current forward unsigned updates acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_clearnotifyacl(dns_zone_t *zone); +/*%< + * Clear the current notify acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_clearqueryacl(dns_zone_t *zone); +/*%< + * Clear the current query acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_clearqueryonacl(dns_zone_t *zone); +/*%< + * Clear the current query-on acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_clearxfracl(dns_zone_t *zone); +/*%< + * Clear the current transfer acl. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +bool +dns_zone_getupdatedisabled(dns_zone_t *zone); +/*%< + * Return update disabled. + * Transient unless called when running in isc_task_exclusive() mode. + */ + +void +dns_zone_setupdatedisabled(dns_zone_t *zone, bool state); +/*%< + * Set update disabled. + * Should only be called only when running in isc_task_exclusive() mode. + * Failure to do so may result in updates being committed after the + * call has been made. + */ + +bool +dns_zone_getzeronosoattl(dns_zone_t *zone); +/*%< + * Return zero-no-soa-ttl status. + */ + +void +dns_zone_setzeronosoattl(dns_zone_t *zone, bool state); +/*%< + * Set zero-no-soa-ttl status. + */ + +void +dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity); +/*%< + * Set the severity of name checking when loading a zone. + * + * Require: + * \li 'zone' to be a valid zone. + */ + +dns_severity_t +dns_zone_getchecknames(dns_zone_t *zone); +/*%< + * Return the current severity of name checking. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +void +dns_zone_setjournalsize(dns_zone_t *zone, int32_t size); +/*%< + * Sets the journal size for the zone. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +int32_t +dns_zone_getjournalsize(dns_zone_t *zone); +/*%< + * Return the journal size as set with a previous call to + * dns_zone_setjournalsize(). + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, + isc_sockaddr_t *to, dns_message_t *msg); +/*%< + * Tell the zone that it has received a NOTIFY message from another + * server. This may cause some zone maintenance activity to occur. + * + * Requires: + *\li 'zone' to be a valid zone. + *\li '*from' to contain the address of the server from which 'msg' + * was received. + *\li 'msg' a message with opcode NOTIFY and qr clear. + * + * Returns: + *\li DNS_R_REFUSED + *\li DNS_R_NOTIMP + *\li DNS_R_FORMERR + *\li DNS_R_SUCCESS + */ + +void +dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin); +/*%< + * Set the maximum time (in seconds) that a zone transfer in (AXFR/IXFR) + * of this zone will use before being aborted. + * + * Requires: + * \li 'zone' to be valid initialised zone. + */ + +uint32_t +dns_zone_getmaxxfrin(dns_zone_t *zone); +/*%< + * Returns the maximum transfer time for this zone. This will be + * either the value set by the last call to dns_zone_setmaxxfrin() or + * the default value of 1 hour. + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + +void +dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout); +/*%< + * Set the maximum time (in seconds) that a zone transfer out (AXFR/IXFR) + * of this zone will use before being aborted. + * + * Requires: + * \li 'zone' to be valid initialised zone. + */ + +uint32_t +dns_zone_getmaxxfrout(dns_zone_t *zone); +/*%< + * Returns the maximum transfer time for this zone. This will be + * either the value set by the last call to dns_zone_setmaxxfrout() or + * the default value of 1 hour. + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + +isc_result_t +dns_zone_setjournal(dns_zone_t *zone, const char *myjournal); +/*%< + * Sets the filename used for journaling updates / IXFR transfers. + * The default journal name is set by dns_zone_setfile() to be + * "file.jnl". If 'myjournal' is NULL, the zone will have no + * journal name. + * + * Requires: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +char * +dns_zone_getjournal(dns_zone_t *zone); +/*%< + * Returns the journal name associated with this zone. + * If no journal has been set this will be NULL. + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + +dns_zonetype_t +dns_zone_gettype(dns_zone_t *zone); +/*%< + * Returns the type of the zone (primary/secondary/etc.) + * + * Requires: + *\li 'zone' to be valid initialised zone. + */ + +dns_zonetype_t +dns_zone_getredirecttype(dns_zone_t *zone); +/*%< + * Returns whether the redirect zone is configured as a primary or a + * secondary zone. + * + * Requires: + *\li 'zone' to be valid initialised zone. + *\li 'zone' to be a redirect zone. + * + * Returns: + *\li 'dns_zone_primary' + *\li 'dns_zone_secondary' + */ + +void +dns_zone_settask(dns_zone_t *zone, isc_task_t *task); +/*%< + * Give a zone a task to work with. Any current task will be detached. + * + * Requires: + *\li 'zone' to be valid. + *\li 'task' to be valid. + */ + +void +dns_zone_gettask(dns_zone_t *zone, isc_task_t **target); +/*%< + * Attach '*target' to the zone's task. + * + * Requires: + *\li 'zone' to be valid initialised zone. + *\li 'zone' to have a task. + *\li 'target' to be != NULL && '*target' == NULL. + */ + +void +dns_zone_notify(dns_zone_t *zone); +/*%< + * Generate notify events for this zone. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump); +/*%< + * Replace the database of "zone" with a new database "db". + * + * If "dump" is true, then the new zone contents are dumped + * into to the zone's master file for persistence. When replacing + * a zone database by one just loaded from a master file, set + * "dump" to false to avoid a redundant redump of the data just + * loaded. Otherwise, it should be set to true. + * + * If the "diff-on-reload" option is enabled in the configuration file, + * the differences between the old and the new database are added to the + * journal file, and the master file dump is postponed. + * + * Requires: + * \li 'zone' to be a valid zone. + * + * Returns: + * \li DNS_R_SUCCESS + * \li DNS_R_BADZONE zone failed basic consistency checks: + * * a single SOA must exist + * * some NS records must exist. + * Others + */ + +uint32_t +dns_zone_getidlein(dns_zone_t *zone); +/*%< + * Requires: + * \li 'zone' to be a valid zone. + * + * Returns: + * \li number of seconds of idle time before we abort the transfer in. + */ + +void +dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein); +/*%< + * \li Set the idle timeout for transfer the. + * \li Zero set the default value, 1 hour. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +uint32_t +dns_zone_getidleout(dns_zone_t *zone); +/*%< + * + * Requires: + * \li 'zone' to be a valid zone. + * + * Returns: + * \li number of seconds of idle time before we abort a transfer out. + */ + +void +dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout); +/*%< + * \li Set the idle timeout for transfers out. + * \li Zero set the default value, 1 hour. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table); +/*%< + * Get the simple-secure-update policy table. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table); +/*%< + * Set / clear the simple-secure-update policy table. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +isc_mem_t * +dns_zone_getmctx(dns_zone_t *zone); +/*%< + * Get the memory context of a zone. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +dns_zonemgr_t * +dns_zone_getmgr(dns_zone_t *zone); +/*%< + * If 'zone' is managed return the zone manager otherwise NULL. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval); +/*%< + * Set the zone's general signature validity interval. This is the length + * of time for which DNSSEC signatures created as a result of dynamic + * updates to secure zones will remain valid, in seconds. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +uint32_t +dns_zone_getsigvalidityinterval(dns_zone_t *zone); +/*%< + * Get the zone's general signature validity interval. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval); +/*%< + * Set the zone's DNSKEY signature validity interval. This is the length + * of time for which DNSSEC signatures created for DNSKEY records + * will remain valid, in seconds. + * + * If this value is set to zero, then the regular signature validity + * interval (see dns_zone_setsigvalidityinterval(), above) is used + * for all RRSIGs. However, if this value is nonzero, then it is used + * as the validity interval for RRSIGs covering DNSKEY and CDNSKEY + * RRsets. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +uint32_t +dns_zone_getkeyvalidityinterval(dns_zone_t *zone); +/*%< + * Get the zone's DNSKEY signature validity interval. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval); +/*%< + * Set the zone's RRSIG re-signing interval. A dynamic zone's RRSIG's + * will be re-signed 'interval' amount of time before they expire. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +uint32_t +dns_zone_getsigresigninginterval(dns_zone_t *zone); +/*%< + * Get the zone's RRSIG re-signing interval. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +void +dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype); +/*%< + * Sets zone notify method to "notifytype" + */ + +isc_result_t +dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg, + dns_updatecallback_t callback, void *callback_arg); +/*%< + * Forward 'msg' to each primary in turn until we get an answer or we + * have exhausted the list of primaries. 'callback' will be called with + * ISC_R_SUCCESS if we get an answer and the returned message will be + * passed as 'answer_message', otherwise a non ISC_R_SUCCESS result code + * will be passed and answer_message will be NULL. The callback function + * is responsible for destroying 'answer_message'. + * (callback)(callback_arg, result, answer_message); + * + * Require: + *\li 'zone' to be valid + *\li 'msg' to be valid. + *\li 'callback' to be non NULL. + * Returns: + *\li #ISC_R_SUCCESS if the message has been forwarded, + *\li #ISC_R_NOMEMORY + *\li Others + */ + +isc_result_t +dns_zone_next(dns_zone_t *zone, dns_zone_t **next); +/*%< + * Find the next zone in the list of managed zones. + * + * Requires: + *\li 'zone' to be valid + *\li The zone manager for the indicated zone MUST be locked + * by the caller. This is not checked. + *\li 'next' be non-NULL, and '*next' be NULL. + * + * Ensures: + *\li 'next' points to a valid zone (result ISC_R_SUCCESS) or to NULL + * (result ISC_R_NOMORE). + */ + +isc_result_t +dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first); +/*%< + * Find the first zone in the list of managed zones. + * + * Requires: + *\li 'zonemgr' to be valid + *\li The zone manager for the indicated zone MUST be locked + * by the caller. This is not checked. + *\li 'first' be non-NULL, and '*first' be NULL + * + * Ensures: + *\li 'first' points to a valid zone (result ISC_R_SUCCESS) or to NULL + * (result ISC_R_NOMORE). + */ + +isc_result_t +dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory); +/*%< + * Sets the name of the directory where private keys used for + * online signing of dynamic zones are found. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SUCCESS + */ + +const char * +dns_zone_getkeydirectory(dns_zone_t *zone); +/*%< + * Gets the name of the directory where private keys used for + * online signing of dynamic zones are found. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + * Pointer to null-terminated file name, or NULL. + */ + +isc_result_t +dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, dns_dnsseckeylist_t *keys); +/*% + * Find DNSSEC keys used for signing with dnssec-policy. Load these keys + * into 'keys'. + * + * Requires: + *\li 'zone' to be valid initialised zone. + *\li 'keys' to be an initialised DNSSEC keylist. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li Error + */ + +isc_result_t +dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, isc_nm_t *netmgr, + dns_zonemgr_t **zmgrp); +/*%< + * Create a zone manager. Note: the zone manager will not be able to + * manage any zones until dns_zonemgr_setsize() has been run. + * + * Requires: + *\li 'mctx' to be a valid memory context. + *\li 'taskmgr' to be a valid task manager. + *\li 'timermgr' to be a valid timer manager. + *\li 'zmgrp' to point to a NULL pointer. + */ + +isc_result_t +dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones); +/*%< + * Set the size of the zone manager task pool. This must be run + * before zmgr can be used for managing zones. Currently, it can only + * be run once; the task pool cannot be resized. + * + * Requires: + *\li zmgr is a valid zone manager. + *\li zmgr->zonetasks has been initialized. + */ + +isc_result_t +dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep); +/*%< + * Allocate a new zone using a memory context from the + * zone manager's memory context pool. + * + * Require: + *\li 'zmgr' to be a valid zone manager. + *\li 'zonep' != NULL and '*zonep' == NULL. + */ + +isc_result_t +dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone); +/*%< + * Bring the zone under control of a zone manager. + * + * Require: + *\li 'zmgr' to be a valid zone manager. + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr); +/*%< + * Force zone maintenance of all loaded zones managed by 'zmgr' + * to take place at the system's earliest convenience. + */ + +void +dns__zonemgr_run(isc_task_t *task, isc_event_t *event); +/*%< + * Event handler to call dns_zonemgr_forcemaint(); used to start + * zone operations from a unit test. Not intended for use outside + * libdns or related tests. + */ + +void +dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr); +/*%< + * Attempt to start any stalled zone transfers. + */ + +void +dns_zonemgr_shutdown(dns_zonemgr_t *zmgr); +/*%< + * Shut down the zone manager. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +void +dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target); +/*%< + * Attach '*target' to 'source' incrementing its external + * reference count. + * + * Require: + *\li 'zone' to be a valid zone. + *\li 'target' to be non NULL and '*target' to be NULL. + */ + +void +dns_zonemgr_detach(dns_zonemgr_t **zmgrp); +/*%< + * Detach from a zone manager. + * + * Requires: + *\li '*zmgrp' is a valid, non-NULL zone manager pointer. + * + * Ensures: + *\li '*zmgrp' is NULL. + */ + +void +dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone); +/*%< + * Release 'zone' from the managed by 'zmgr'. 'zmgr' is implicitly + * detached from 'zone'. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'zone' to be a valid zone. + *\li 'zmgr' == 'zone->zmgr' + * + * Ensures: + *\li 'zone->zmgr' == NULL; + */ + +isc_taskmgr_t * +dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr); +/*% + * Get the tasmkgr object attached to 'zmgr'. + */ + +isc_timermgr_t * +dns_zonemgr_gettimermgr(dns_zonemgr_t *zmgr); +/*% + * Get the timermgr object attached to 'zmgr'. + */ + +void +dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value); +/*%< + * Set the maximum number of simultaneous transfers in allowed by + * the zone manager. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +uint32_t +dns_zonemgr_gettransfersin(dns_zonemgr_t *zmgr); +/*%< + * Return the maximum number of simultaneous transfers in allowed. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +void +dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value); +/*%< + * Set the number of zone transfers allowed per nameserver. + * + * Requires: + *\li 'zmgr' to be a valid zone manager + */ + +uint32_t +dns_zonemgr_gettransfersperns(dns_zonemgr_t *zmgr); +/*%< + * Return the number of transfers allowed per nameserver. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +void +dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit); +/*%< + * Set the number of simultaneous file descriptors available for + * reading and writing masterfiles. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'iolimit' to be positive. + */ + +uint32_t +dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr); +/*%< + * Get the number of simultaneous file descriptors available for + * reading and writing masterfiles. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +void +dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value); +/*%< + * Set the number of parental DS queries sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager + */ + +void +dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value); +/*%< + * Set the number of NOTIFY requests sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager + */ + +void +dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value); +/*%< + * Set the number of startup NOTIFY requests sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager + */ + +void +dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value); +/*%< + * Set the number of SOA queries sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager + */ + +unsigned int +dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr); +/*%< + * Return the number of NOTIFY requests sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +unsigned int +dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr); +/*%< + * Return the number of startup NOTIFY requests sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +unsigned int +dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr); +/*%< + * Return the number of SOA queries sent per second. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + */ + +unsigned int +dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state); +/*%< + * Returns the number of zones in the specified state. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'state' to be a valid DNS_ZONESTATE_ constant. + */ + +void +dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local, isc_time_t *now); +/*%< + * Add the pair of addresses to the unreachable cache. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'remote' to be a valid sockaddr. + *\li 'local' to be a valid sockaddr. + */ + +bool +dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local, isc_time_t *now); +/*%< + * Returns true if the given local/remote address pair + * is found in the zone maanger's unreachable cache. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'remote' to be a valid sockaddr. + *\li 'local' to be a valid sockaddr. + *\li 'now' != NULL + */ + +void +dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local); +/*%< + * Remove the pair of addresses from the unreachable cache. + * + * Requires: + *\li 'zmgr' to be a valid zone manager. + *\li 'remote' to be a valid sockaddr. + *\li 'local' to be a valid sockaddr. + */ + +void +dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, + isc_tlsctx_cache_t *tlsctx_cache); +/*%< + * Set the TLS client context cache used for zone transfers via + * encrypted transports (e.g. XoT). + * + * Requires: + *\li 'zmgr' is a valid zone manager. + *\li 'tlsctx_cache' is a valid TLS context cache. + */ + +void +dns_zone_forcereload(dns_zone_t *zone); +/*%< + * Force a reload of specified zone. + * + * Requires: + *\li 'zone' to be a valid zone. + */ + +bool +dns_zone_isforced(dns_zone_t *zone); +/*%< + * Check if the zone is waiting a forced reload. + * + * Requires: + * \li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setstatistics(dns_zone_t *zone, bool on); +/*%< + * This function is obsoleted by dns_zone_setrequeststats(). + */ + +uint64_t * +dns_zone_getstatscounters(dns_zone_t *zone); +/*%< + * This function is obsoleted by dns_zone_getrequeststats(). + */ + +void +dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats); +/*%< + * Set a general zone-maintenance statistics set 'stats' for 'zone'. This + * function is expected to be called only on zone creation (when necessary). + * Once installed, it cannot be removed or replaced. Also, there is no + * interface to get the installed stats from the zone; the caller must keep the + * stats to reference (e.g. dump) it later. + * + * Requires: + * \li 'zone' to be a valid zone and does not have a statistics set already + * installed. + * + *\li stats is a valid statistics supporting zone statistics counters + * (see dns/stats.h). + */ + +void +dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats); + +void +dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats); + +void +dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats); +/*%< + * Set additional statistics sets to zone. These are attached to the zone + * but are not counted in the zone module; only the caller updates the + * counters. + * + * Requires: + * \li 'zone' to be a valid zone. + * + *\li stats is a valid statistics. + */ + +isc_stats_t * +dns_zone_getrequeststats(dns_zone_t *zone); + +dns_stats_t * +dns_zone_getrcvquerystats(dns_zone_t *zone); + +dns_stats_t * +dns_zone_getdnssecsignstats(dns_zone_t *zone); +/*%< + * Get the additional statistics for zone, if one is installed. + * + * Requires: + * \li 'zone' to be a valid zone. + * + * Returns: + * \li when available, a pointer to the statistics set installed in zone; + * otherwise NULL. + */ + +void +dns_zone_dialup(dns_zone_t *zone); +/*%< + * Perform dialup-time maintenance on 'zone'. + */ + +void +dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup); +/*%< + * Set the dialup type of 'zone' to 'dialup'. + * + * Requires: + * \li 'zone' to be valid initialised zone. + *\li 'dialup' to be a valid dialup type. + */ + +void +dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level, + const char *prefix, const char *msg, va_list ap); +/*%< + * Log the message 'msg...' at 'level' using log category 'category', including + * text that identifies the message as applying to 'zone'. If the (optional) + * 'prefix' is not NULL, it will be placed at the start of the entire log line. + */ + +void +dns_zone_log(dns_zone_t *zone, int level, const char *msg, ...) + ISC_FORMAT_PRINTF(3, 4); +/*%< + * Log the message 'msg...' at 'level', including text that identifies + * the message as applying to 'zone'. + */ + +void +dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level, + const char *msg, ...) ISC_FORMAT_PRINTF(4, 5); +/*%< + * Log the message 'msg...' at 'level', including text that identifies + * the message as applying to 'zone'. + */ + +void +dns_zone_name(dns_zone_t *zone, char *buf, size_t len); +/*%< + * Return the name of the zone with class and view. + * + * Requires: + *\li 'zone' to be valid. + *\li 'buf' to be non NULL. + */ + +void +dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t len); +/*%< + * Return the name of the zone only. + * + * Requires: + *\li 'zone' to be valid. + *\li 'buf' to be non NULL. + */ + +isc_result_t +dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name, + dns_rdata_t *rdata); +/*%< + * Check if this record meets the check-names policy. + * + * Requires: + * 'zone' to be valid. + * 'name' to be valid. + * 'rdata' to be valid. + * + * Returns: + * DNS_R_SUCCESS passed checks. + * DNS_R_BADOWNERNAME failed ownername checks. + * DNS_R_BADNAME failed rdata checks. + */ + +void +dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx); +/*%< + * Set the post load integrity callback function 'checkmx'. + * 'checkmx' will be called if the MX TARGET is not within the zone. + * + * Require: + * 'zone' to be a valid zone. + */ + +void +dns_zone_setchecksrv(dns_zone_t *zone, dns_checkmxfunc_t checksrv); +/*%< + * Set the post load integrity callback function 'checksrv'. + * 'checksrv' will be called if the SRV TARGET is not within the zone. + * + * Require: + * 'zone' to be a valid zone. + */ + +void +dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns); +/*%< + * Set the post load integrity callback function 'checkns'. + * 'checkns' will be called if the NS TARGET is not within the zone. + * + * Require: + * 'zone' to be a valid zone. + */ + +void +dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay); +/*%< + * Set the minimum delay between sets of notify messages. + * + * Requires: + * 'zone' to be valid. + */ + +uint32_t +dns_zone_getnotifydelay(dns_zone_t *zone); +/*%< + * Get the minimum delay between sets of notify messages. + * + * Requires: + * 'zone' to be valid. + */ + +void +dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg); +/*%< + * Set the isself callback function and argument. + * + * bool + * isself(dns_view_t *myview, dns_tsigkey_t *mykey, + * const isc_netaddr_t *srcaddr, const isc_netaddr_t *destaddr, + * dns_rdataclass_t rdclass, void *arg); + * + * 'isself' returns true if a non-recursive query from 'srcaddr' to + * 'destaddr' with optional key 'mykey' for class 'rdclass' would be + * delivered to 'myview'. + */ + +void +dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes); +/*%< + * Set the number of nodes that will be checked per quantum. + */ + +void +dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures); +/*%< + * Set the number of signatures that will be generated per quantum. + */ + +uint32_t +dns_zone_getsignatures(dns_zone_t *zone); +/*%< + * Get the number of signatures that will be generated per quantum. + */ + +isc_result_t +dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, + bool deleteit); +/*%< + * Initiate/resume signing of the entire zone with the zone DNSKEY(s) + * that match the given algorithm and keyid. + */ + +isc_result_t +dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param); +/*%< + * Incrementally add a NSEC3 chain that corresponds to 'nsec3param'. + */ + +void +dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type); +dns_rdatatype_t +dns_zone_getprivatetype(dns_zone_t *zone); +/* + * Get/Set the private record type. It is expected that these interfaces + * will not be permanent. + */ + +void +dns_zone_rekey(dns_zone_t *zone, bool fullsign); +/*%< + * Update the zone's DNSKEY set from the key repository. + * + * If 'fullsign' is true, trigger an immediate full signing of + * the zone with the new key. Otherwise, if there are no keys or + * if the new keys are for algorithms that have already signed the + * zone, then the zone can be re-signed incrementally. + */ + +isc_result_t +dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + unsigned int *errors); +/*% + * Check if the name servers for the zone are sane (have address, don't + * refer to CNAMEs/DNAMEs. The number of constiancy errors detected in + * returned in '*errors' + * + * Requires: + * \li 'zone' to be valid. + * \li 'db' to be valid. + * \li 'version' to be valid or NULL. + * \li 'errors' to be non NULL. + * + * Returns: + * ISC_R_SUCCESS if there were no errors examining the zone contents. + */ + +isc_result_t +dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version); +/*% + * Check if CSD, CDNSKEY and DNSKEY are consistent. + * + * Requires: + * \li 'zone' to be valid. + * \li 'db' to be valid. + * \li 'version' to be valid or NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #DNS_R_BADCDS + *\li #DNS_R_BADCDNSKEY + * Others + */ + +void +dns_zone_setadded(dns_zone_t *zone, bool added); +/*% + * Sets the value of zone->added, which should be true for + * zones that were originally added by "rndc addzone". + * + * Requires: + * \li 'zone' to be valid. + */ + +bool +dns_zone_getadded(dns_zone_t *zone); +/*% + * Returns true if the zone was originally added at runtime + * using "rndc addzone". + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setautomatic(dns_zone_t *zone, bool automatic); +/*% + * Sets the value of zone->automatic, which should be true for + * zones that were automatically added by named. + * + * Requires: + * \li 'zone' to be valid. + */ + +bool +dns_zone_getautomatic(dns_zone_t *zone); +/*% + * Returns true if the zone was added automatically by named. + * + * Requires: + * \li 'zone' to be valid. + */ + +isc_result_t +dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db); +/*% + * Load the origin names for a writeable DLZ database. + */ + +bool +dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze); +/*% + * Return true iff the zone is "dynamic", in the sense that the zone's + * master file (if any) is written by the server, rather than being + * updated manually and read by the server. + * + * This is true for secondary zones, stub zones, key zones, and zones that + * allow dynamic updates either by having an update policy ("ssutable") + * or an "allow-update" ACL with a value other than exactly "{ none; }". + * + * If 'ignore_freeze' is true, then the zone which has had updates disabled + * will still report itself to be dynamic. + * + * Requires: + * \li 'zone' to be valid. + */ + +isc_result_t +dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval); +/*% + * Sets the frequency, in minutes, with which the key repository will be + * checked to see if the keys for this zone have been updated. Any value + * higher than 1440 minutes (24 hours) will be silently reduced. A + * value of zero will return an out-of-range error. + * + * Requires: + * \li 'zone' to be valid. + */ + +bool +dns_zone_getrequestexpire(dns_zone_t *zone); +/*% + * Returns the true/false value of the request-expire option in the zone. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setrequestexpire(dns_zone_t *zone, bool flag); +/*% + * Sets the request-expire option for the zone. Either true or false. The + * default value is determined by the setting of this option in the view. + * + * Requires: + * \li 'zone' to be valid. + */ + +bool +dns_zone_getrequestixfr(dns_zone_t *zone); +/*% + * Returns the true/false value of the request-ixfr option in the zone. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setrequestixfr(dns_zone_t *zone, bool flag); +/*% + * Sets the request-ixfr option for the zone. Either true or false. The + * default value is determined by the setting of this option in the view. + * + * Requires: + * \li 'zone' to be valid. + */ + +uint32_t +dns_zone_getixfrratio(dns_zone_t *zone); +/*% + * Returns the zone's current IXFR ratio. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio); +/*% + * Sets the ratio of IXFR size to zone size above which we use an AXFR + * response, expressed as a percentage. Cannot exceed 100. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method); +/*% + * Sets the update method to use when incrementing the zone serial number + * due to a DDNS update. Valid options are dns_updatemethod_increment + * and dns_updatemethod_unixtime. + * + * Requires: + * \li 'zone' to be valid. + */ + +dns_updatemethod_t +dns_zone_getserialupdatemethod(dns_zone_t *zone); +/*% + * Returns the update method to be used when incrementing the zone serial + * number due to a DDNS update. + * + * Requires: + * \li 'zone' to be valid. + */ + +isc_result_t +dns_zone_link(dns_zone_t *zone, dns_zone_t *raw); + +void +dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw); + +isc_result_t +dns_zone_keydone(dns_zone_t *zone, const char *data); + +isc_result_t +dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags, + uint16_t iter, uint8_t saltlen, unsigned char *salt, + bool replace, bool resalt); +/*% + * Set the NSEC3 parameters for the zone. + * + * If 'replace' is true, then the existing NSEC3 chain, if any, will + * be replaced with the new one. If 'hash' is zero, then the replacement + * chain will be NSEC rather than NSEC3. If 'resalt' is true, or if 'salt' + * is NULL, generate a new salt with the given salt length. + * + * Requires: + * \li 'zone' to be valid. + */ + +void +dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header); +/*% + * Set the data to be included in the header when the zone is dumped in + * binary format. + */ + +isc_result_t +dns_zone_synckeyzone(dns_zone_t *zone); +/*% + * Force the managed key zone to synchronize, and start the key + * maintenance timer. + */ + +isc_result_t +dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime); +/*% + * Return the time when the zone was last loaded. + */ + +isc_result_t +dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime); +/*% + * Return the time when the (secondary) zone will need to be refreshed. + */ + +isc_result_t +dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime); +/*% + * Return the time when the (secondary) zone will expire. + */ + +isc_result_t +dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime); +/*% + * Return the time of the next scheduled DNSSEC key event. + */ + +unsigned int +dns_zone_getincludes(dns_zone_t *zone, char ***includesp); +/*% + * Return the number include files that were encountered + * during load. If the number is greater than zero, 'includesp' + * will point to an array containing the filenames. + * + * The array and its contents need to be freed using isc_mem_free. + */ + +isc_result_t +dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num); +/*% + * Set the response policy associated with a zone. + */ + +void +dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db); +/*% + * If a zone is a response policy zone, mark its new database. + */ + +dns_rpz_num_t +dns_zone_get_rpz_num(dns_zone_t *zone); + +void +dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs); +/*%< + * Enable zone as catalog zone. + * + * Requires: + * + * \li 'zone' is a valid zone object + * \li 'catzs' is not NULL + * \li prior to calling, zone->catzs is NULL or is equal to 'catzs' + */ + +void +dns_zone_catz_disable(dns_zone_t *zone); +/*%< + * Disable zone as catalog zone, if it is one. Also disables any + * registered callbacks for the catalog zone. + * + * Requires: + * + * \li 'zone' is a valid zone object + */ + +bool +dns_zone_catz_is_enabled(dns_zone_t *zone); +/*%< + * Return a boolean indicating whether the zone is enabled as catalog zone. + * + * Requires: + * + * \li 'zone' is a valid zone object + */ + +void +dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db); +/*%< + * If 'zone' is a catalog zone, then set up a notify-on-update trigger + * in its database. (If not a catalog zone, this function has no effect.) + * + * Requires: + * + * \li 'zone' is a valid zone object + * \li 'db' is not NULL + */ +void +dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz); +/*%< + * Set parent catalog zone for this zone + * + * Requires: + * + * \li 'zone' is a valid zone object + * \li 'catz' is not NULL + */ + +dns_catz_zone_t * +dns_zone_get_parentcatz(const dns_zone_t *zone); +/*%< + * Get parent catalog zone for this zone + * + * Requires: + * + * \li 'zone' is a valid zone object + */ + +void +dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level); + +dns_zonestat_level_t +dns_zone_getstatlevel(dns_zone_t *zone); +/*% + * Set and get the statistics reporting level for the zone; + * full, terse, or none. + */ + +isc_result_t +dns_zone_setserial(dns_zone_t *zone, uint32_t serial); +/*% + * Set the zone's serial to 'serial'. + */ +ISC_LANG_ENDDECLS + +isc_stats_t * +dns_zone_getgluecachestats(dns_zone_t *zone); +/*%< + * Get the glue cache statistics for zone. + * + * Requires: + * \li 'zone' to be a valid zone. + * + * Returns: + * \li if present, a pointer to the statistics set installed in zone; + * otherwise NULL. + */ + +bool +dns_zone_isloaded(dns_zone_t *zone); +/*%< + * Return true if 'zone' was loaded and has not expired yet, return + * false otherwise. + */ + +isc_result_t +dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver); +/*%< + * If 'zone' is a mirror zone, perform DNSSEC validation of version 'ver' of + * its database, 'db'. Ensure that the DNSKEY RRset at zone apex is signed by + * at least one trust anchor specified for the view that 'zone' is assigned to. + * If 'ver' is NULL, use the current version of 'db'. + * + * If 'zone' is not a mirror zone, return ISC_R_SUCCESS immediately. + * + * Returns: + * + * \li #ISC_R_SUCCESS either 'zone' is not a mirror zone or 'zone' is + * a mirror zone and all DNSSEC checks succeeded + * and the DNSKEY RRset at zone apex is signed by + * a trusted key + * + * \li #DNS_R_VERIFYFAILURE any other case + */ + +const char * +dns_zonetype_name(dns_zonetype_t type); +/*%< + * Return the name of the zone type 'type'. + */ + +bool +dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_diff_t *diff, + dst_key_t **keys, unsigned int numkeys); +/**< + * Return whether the zone would enter an inconsistent state where NSEC only + * DNSKEYs are present along NSEC3 chains. + * + * Requires: + * \li 'zone' to be a valid zone. + * \li 'db'is not NULL. + * + * Returns: + * \li 'true' if the check passes, that is the zone remains consistent, + * 'false' if the zone would have NSEC only DNSKEYs and an NSEC3 chain. + */ diff --git a/lib/dns/include/dns/zonekey.h b/lib/dns/include/dns/zonekey.h new file mode 100644 index 0000000..b093fa9 --- /dev/null +++ b/lib/dns/include/dns/zonekey.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/zonekey.h */ + +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +bool +dns_zonekey_iszonekey(dns_rdata_t *keyrdata); +/*%< + * Determines if the key record contained in the rdata is a zone key. + * + * Requires: + * 'keyrdata' is not NULL. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/zoneverify.h b/lib/dns/include/dns/zoneverify.h new file mode 100644 index 0000000..22a680a --- /dev/null +++ b/lib/dns/include/dns/zoneverify.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/zoneverify.h */ + +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * Verify that certain things are sane: + * + * The apex has a DNSKEY record with at least one KSK, and at least + * one ZSK if the -x flag was not used. + * + * The DNSKEY record was signed with at least one of the KSKs in this + * set. + * + * The rest of the zone was signed with at least one of the ZSKs + * present in the DNSKEY RRSET. + * + * Mark all RRsets correctly signed by one of the keys in the DNSKEY RRset at + * zone apex as secure. + * + * If 'secroots' is not NULL, mark the DNSKEY RRset as secure if it is + * correctly signed by at least one key present in 'secroots'. + */ +isc_result_t +dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_name_t *origin, dns_keytable_t *secroots, + isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly, + void (*report)(const char *, ...)); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/zt.h b/lib/dns/include/dns/zt.h new file mode 100644 index 0000000..03601a4 --- /dev/null +++ b/lib/dns/include/dns/zt.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dns/zt.h */ + +#include + +#include +#include + +#include + +#define DNS_ZTFIND_NOEXACT 0x01 +#define DNS_ZTFIND_MIRROR 0x02 + +ISC_LANG_BEGINDECLS + +typedef isc_result_t (*dns_zt_allloaded_t)(void *arg); +/*%< + * Method prototype: when all pending zone loads are complete, + * the zone table can inform the caller via a callback function with + * this signature. + */ + +typedef isc_result_t (*dns_zt_zoneloaded_t)(dns_zt_t *zt, dns_zone_t *zone, + isc_task_t *task); +/*%< + * Method prototype: when a zone finishes loading, the zt object + * can be informed via a callback function with this signature. + */ + +isc_result_t +dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **zt); +/*%< + * Creates a new zone table. + * + * Requires: + * \li 'mctx' to be initialized. + * + * Returns: + * \li #ISC_R_SUCCESS on success. + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone); +/*%< + * Mounts the zone on the zone table. + * + * Requires: + * \li 'zt' to be valid + * \li 'zone' to be valid + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_EXISTS + * \li #ISC_R_NOSPACE + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone); +/*%< + * Unmount the given zone from the table. + * + * Requires: + * 'zt' to be valid + * \li 'zone' to be valid + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + * \li #ISC_R_NOMEMORY + */ + +isc_result_t +dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options, + dns_name_t *foundname, dns_zone_t **zone); +/*%< + * Find the best match for 'name' in 'zt'. If foundname is non NULL + * then the name of the zone found is returned. + * + * Notes: + * \li If the DNS_ZTFIND_NOEXACT is set, the best partial match (if any) + * to 'name' will be returned. + * + * Requires: + * \li 'zt' to be valid + * \li 'name' to be valid + * \li 'foundname' to be initialized and associated with a fixedname or NULL + * \li 'zone' to be non NULL and '*zone' to be NULL + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #DNS_R_PARTIALMATCH + * \li #ISC_R_NOTFOUND + * \li #ISC_R_NOSPACE + */ + +void +dns_zt_detach(dns_zt_t **ztp); +/*%< + * Detach the given zonetable, if the reference count goes to zero the + * zonetable will be freed. In either case 'ztp' is set to NULL. + * + * Requires: + * \li '*ztp' to be valid + */ + +void +dns_zt_flush(dns_zt_t *ztp); +/*%< + * Schedule flushing of the given zonetable, when reference count goes + * to zero. + * + * Requires: + * \li 'ztp' to be valid + */ + +void +dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp); +/*%< + * Attach 'zt' to '*ztp'. + * + * Requires: + * \li 'zt' to be valid + * \li '*ztp' to be NULL + */ + +isc_result_t +dns_zt_load(dns_zt_t *zt, bool stop, bool newonly); + +isc_result_t +dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone, + void *arg); +/*%< + * Load all zones in the table. If 'stop' is true, + * stop on the first error and return it. If 'stop' + * is false, ignore errors. + * + * if newonly is set only zones that were never loaded are loaded. + * dns_zt_asyncload() loads zones asynchronously; when all + * zones in the zone table have finished loaded (or failed due + * to errors), the caller is informed by calling 'alldone' + * with an argument of 'arg'. + * + * Requires: + * \li 'zt' to be valid + */ + +isc_result_t +dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze); +/*%< + * Freeze/thaw updates to primary zones. + * Any pending updates will be flushed. + * Zones will be reloaded on thaw. + */ + +isc_result_t +dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub, + isc_result_t (*action)(dns_zone_t *, void *), void *uap); +/*%< + * Apply a given 'action' to all zone zones in the table. + * If 'stop' is 'true' then walking the zone tree will stop if + * 'action' does not return ISC_R_SUCCESS. + * + * Requires: + * \li 'zt' to be valid. + * \li 'action' to be non NULL. + * + * Returns: + * \li ISC_R_SUCCESS if action was applied to all nodes. If 'stop' is + * false and 'sub' is non NULL then the first error (if any) + * reported by 'action' is returned in '*sub'; + * any error code from 'action'. + */ + +bool +dns_zt_loadspending(dns_zt_t *zt); +/*%< + * Returns true if and only if there are zones still waiting to + * be loaded in zone table 'zt'. + * + * Requires: + * \li 'zt' to be valid. + */ + +void +dns_zt_setviewcommit(dns_zt_t *zt); +/*%< + * Commit dns_zone_setview() calls previously made for all zones in this + * zone table. + * + * Requires: + *\li 'view' to be valid. + */ + +void +dns_zt_setviewrevert(dns_zt_t *zt); +/*%< + * Revert dns_zone_setview() calls previously made for all zones in this + * zone table. + * + * Requires: + *\li 'view' to be valid. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h new file mode 100644 index 0000000..ca292b0 --- /dev/null +++ b/lib/dns/include/dst/dst.h @@ -0,0 +1,1245 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dst/dst.h */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% + * The dst_key structure is opaque. Applications should use the accessor + * functions provided to retrieve key attributes. If an application needs + * to set attributes, new accessor functions will be written. + */ + +typedef struct dst_key dst_key_t; +typedef struct dst_context dst_context_t; + +/*% + * Key states for the DNSSEC records related to a key: DNSKEY, RRSIG (ksk), + * RRSIG (zsk), and DS. + * + * DST_KEY_STATE_HIDDEN: Records of this type are not published in zone. + * This may be because the key parts were never + * introduced in the zone, or because the key has + * retired and has no records of this type left in + * the zone. + * DST_KEY_STATE_RUMOURED: Records of this type are published in zone, but + * not long enough to ensure all resolvers know + * about it. + * DST_KEY_STATE_OMNIPRESENT: Records of this type are published in zone long + * enough so that all resolvers that know about + * these records, no longer have outdated data. + * DST_KEY_STATE_UNRETENTIVE: Records of this type have been removed from the + * zone, but there may be resolvers that still have + * have predecessor records cached. Note that RRSIG + * records in this state may actually still be in the + * zone because they are reused, but retired RRSIG + * records will never be refreshed: A successor key + * is used to create signatures. + * DST_KEY_STATE_NA: The state is not applicable for this record type. + */ +typedef enum dst_key_state { + DST_KEY_STATE_HIDDEN = 0, + DST_KEY_STATE_RUMOURED = 1, + DST_KEY_STATE_OMNIPRESENT = 2, + DST_KEY_STATE_UNRETENTIVE = 3, + DST_KEY_STATE_NA = 4 +} dst_key_state_t; + +/* DST algorithm codes */ +typedef enum dst_algorithm { + DST_ALG_UNKNOWN = 0, + DST_ALG_RSA = 1, /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */ + DST_ALG_RSAMD5 = 1, + DST_ALG_DH = 2, + DST_ALG_DSA = 3, + DST_ALG_ECC = 4, + DST_ALG_RSASHA1 = 5, + DST_ALG_NSEC3DSA = 6, + DST_ALG_NSEC3RSASHA1 = 7, + DST_ALG_RSASHA256 = 8, + DST_ALG_RSASHA512 = 10, + DST_ALG_ECCGOST = 12, + DST_ALG_ECDSA256 = 13, + DST_ALG_ECDSA384 = 14, + DST_ALG_ED25519 = 15, + DST_ALG_ED448 = 16, + + /* + * Do not renumber HMAC algorithms as they are used externally to named + * in legacy K* key pair files. + * Do not add non HMAC between DST_ALG_HMACMD5 and DST_ALG_HMACSHA512. + */ + DST_ALG_HMACMD5 = 157, + DST_ALG_HMAC_FIRST = DST_ALG_HMACMD5, + DST_ALG_GSSAPI = 160, /* Internal use only. Exception. */ + DST_ALG_HMACSHA1 = 161, /* XXXMPA */ + DST_ALG_HMACSHA224 = 162, /* XXXMPA */ + DST_ALG_HMACSHA256 = 163, /* XXXMPA */ + DST_ALG_HMACSHA384 = 164, /* XXXMPA */ + DST_ALG_HMACSHA512 = 165, /* XXXMPA */ + DST_ALG_HMAC_LAST = DST_ALG_HMACSHA512, + + DST_ALG_INDIRECT = 252, + DST_ALG_PRIVATE = 254, + DST_MAX_ALGS = 256, +} dst_algorithm_t; + +/*% A buffer of this size is large enough to hold any key */ +#define DST_KEY_MAXSIZE 1280 + +/*% + * A buffer of this size is large enough to hold the textual representation + * of any key + */ +#define DST_KEY_MAXTEXTSIZE 2048 + +/*% 'Type' for dst_read_key() */ +#define DST_TYPE_KEY 0x1000000 /* KEY key */ +#define DST_TYPE_PRIVATE 0x2000000 +#define DST_TYPE_PUBLIC 0x4000000 +#define DST_TYPE_STATE 0x8000000 +#define DST_TYPE_TEMPLATE 0x10000000 + +/* Key timing metadata definitions */ +#define DST_TIME_CREATED 0 +#define DST_TIME_PUBLISH 1 +#define DST_TIME_ACTIVATE 2 +#define DST_TIME_REVOKE 3 +#define DST_TIME_INACTIVE 4 +#define DST_TIME_DELETE 5 +#define DST_TIME_DSPUBLISH 6 +#define DST_TIME_SYNCPUBLISH 7 +#define DST_TIME_SYNCDELETE 8 +#define DST_TIME_DNSKEY 9 +#define DST_TIME_ZRRSIG 10 +#define DST_TIME_KRRSIG 11 +#define DST_TIME_DS 12 +#define DST_TIME_DSDELETE 13 +#define DST_MAX_TIMES 13 + +/* Numeric metadata definitions */ +#define DST_NUM_PREDECESSOR 0 +#define DST_NUM_SUCCESSOR 1 +#define DST_NUM_MAXTTL 2 +#define DST_NUM_ROLLPERIOD 3 +#define DST_NUM_LIFETIME 4 +#define DST_NUM_DSPUBCOUNT 5 +#define DST_NUM_DSDELCOUNT 6 +#define DST_MAX_NUMERIC 6 + +/* Boolean metadata definitions */ +#define DST_BOOL_KSK 0 +#define DST_BOOL_ZSK 1 +#define DST_MAX_BOOLEAN 1 + +/* Key state metadata definitions */ +#define DST_KEY_DNSKEY 0 +#define DST_KEY_ZRRSIG 1 +#define DST_KEY_KRRSIG 2 +#define DST_KEY_DS 3 +#define DST_KEY_GOAL 4 +#define DST_MAX_KEYSTATES 4 + +/* + * Current format version number of the private key parser. + * + * When parsing a key file with the same major number but a higher minor + * number, the key parser will ignore any fields it does not recognize. + * Thus, DST_MINOR_VERSION should be incremented whenever new + * fields are added to the private key file (such as new metadata). + * + * When rewriting these keys, those fields will be dropped, and the + * format version set back to the current one.. + * + * When a key is seen with a higher major number, the key parser will + * reject it as invalid. Thus, DST_MAJOR_VERSION should be incremented + * and DST_MINOR_VERSION set to zero whenever there is a format change + * which is not backward compatible to previous versions of the dst_key + * parser, such as change in the syntax of an existing field, the removal + * of a currently mandatory field, or a new field added which would + * alter the functioning of the key if it were absent. + */ +#define DST_MAJOR_VERSION 1 +#define DST_MINOR_VERSION 3 + +/*** + *** Functions + ***/ +isc_result_t +dst_lib_init(isc_mem_t *mctx, const char *engine); +/*%< + * Initializes the DST subsystem. + * + * Requires: + * \li "mctx" is a valid memory context + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + * \li DST_R_NOENGINE + * + * Ensures: + * \li DST is properly initialized. + */ + +void +dst_lib_destroy(void); +/*%< + * Releases all resources allocated by DST. + */ + +bool +dst_algorithm_supported(unsigned int alg); +/*%< + * Checks that a given algorithm is supported by DST. + * + * Returns: + * \li true + * \li false + */ + +bool +dst_ds_digest_supported(unsigned int digest_type); +/*%< + * Checks that a given digest algorithm is supported by DST. + * + * Returns: + * \li true + * \li false + */ + +isc_result_t +dst_context_create(dst_key_t *key, isc_mem_t *mctx, isc_logcategory_t *category, + bool useforsigning, int maxbits, dst_context_t **dctxp); +/*%< + * Creates a context to be used for a sign or verify operation. + * + * Requires: + * \li "key" is a valid key. + * \li "mctx" is a valid memory context. + * \li dctxp != NULL && *dctxp == NULL + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + * + * Ensures: + * \li *dctxp will contain a usable context. + */ + +void +dst_context_destroy(dst_context_t **dctxp); +/*%< + * Destroys all memory associated with a context. + * + * Requires: + * \li *dctxp != NULL && *dctxp == NULL + * + * Ensures: + * \li *dctxp == NULL + */ + +isc_result_t +dst_context_adddata(dst_context_t *dctx, const isc_region_t *data); +/*%< + * Incrementally adds data to the context to be used in a sign or verify + * operation. + * + * Requires: + * \li "dctx" is a valid context + * \li "data" is a valid region + * + * Returns: + * \li ISC_R_SUCCESS + * \li DST_R_SIGNFAILURE + * \li all other errors indicate failure + */ + +isc_result_t +dst_context_sign(dst_context_t *dctx, isc_buffer_t *sig); +/*%< + * Computes a signature using the data and key stored in the context. + * + * Requires: + * \li "dctx" is a valid context. + * \li "sig" is a valid buffer. + * + * Returns: + * \li ISC_R_SUCCESS + * \li DST_R_VERIFYFAILURE + * \li all other errors indicate failure + * + * Ensures: + * \li "sig" will contain the signature + */ + +isc_result_t +dst_context_verify(dst_context_t *dctx, isc_region_t *sig); + +isc_result_t +dst_context_verify2(dst_context_t *dctx, unsigned int maxbits, + isc_region_t *sig); +/*%< + * Verifies the signature using the data and key stored in the context. + * + * 'maxbits' specifies the maximum number of bits permitted in the RSA + * exponent. + * + * Requires: + * \li "dctx" is a valid context. + * \li "sig" is a valid region. + * + * Returns: + * \li ISC_R_SUCCESS + * \li all other errors indicate failure + * + * Ensures: + * \li "sig" will contain the signature + */ + +isc_result_t +dst_key_computesecret(const dst_key_t *pub, const dst_key_t *priv, + isc_buffer_t *secret); +/*%< + * Computes a shared secret from two (Diffie-Hellman) keys. + * + * Requires: + * \li "pub" is a valid key that can be used to derive a shared secret + * \li "priv" is a valid private key that can be used to derive a shared secret + * \li "secret" is a valid buffer + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + * \li If successful, secret will contain the derived shared secret. + */ + +isc_result_t +dst_key_getfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, + int type, const char *directory, isc_mem_t *mctx, + isc_buffer_t *buf); +/*%< + * Generates a key filename for the name, algorithm, and + * id, and places it in the buffer 'buf'. If directory is NULL, the + * current directory is assumed. + * + * Requires: + * \li "name" is a valid absolute dns name. + * \li "id" is a valid key tag identifier. + * \li "alg" is a supported key algorithm. + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY + * \li "mctx" is a valid memory context. + * \li "buf" is not NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + */ + +isc_result_t +dst_key_fromfile(dns_name_t *name, dns_keytag_t id, unsigned int alg, int type, + const char *directory, isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Reads a key from permanent storage. The key can either be a public or + * private key, or a key state. It specified by name, algorithm, and id. If + * a private key or key state is specified, the public key must also be + * present. If directory is NULL, the current directory is assumed. + * + * Requires: + * \li "name" is a valid absolute dns name. + * \li "id" is a valid key tag identifier. + * \li "alg" is a supported key algorithm. + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + * \li If successful, *keyp will contain a valid key. + */ + +isc_result_t +dst_key_fromnamedfile(const char *filename, const char *dirname, int type, + isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Reads a key from permanent storage. The key can either be a public or + * private key, or a key state. It is specified by filename. If a private key + * or key state is specified, the public key must also be present. + * + * If 'dirname' is not NULL, and 'filename' is a relative path, + * then the file is looked up relative to the given directory. + * If 'filename' is an absolute path, 'dirname' is ignored. + * + * Requires: + * \li "filename" is not NULL + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union. + * DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * DST_TYPE_STATE to also read the key state. + * \li "mctx" is a valid memory context + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + * \li If successful, *keyp will contain a valid key. + */ + +isc_result_t +dst_key_read_public(const char *filename, int type, isc_mem_t *mctx, + dst_key_t **keyp); +/*%< + * Reads a public key from permanent storage. The key must be a public key. + * + * Requires: + * \li "filename" is not NULL. + * \li "type" is DST_TYPE_KEY look for a KEY record otherwise DNSKEY. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li DST_R_BADKEYTYPE if the key type is not the expected one + * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key + * \li any other result indicates failure + * + * Ensures: + * \li If successful, *keyp will contain a valid key. + */ + +isc_result_t +dst_key_read_state(const char *filename, isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Reads a key state from permanent storage. + * + * Requires: + * \li "filename" is not NULL. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_UNEXPECTEDTOKEN if the file can not be parsed as a public key + * \li any other result indicates failure + */ + +isc_result_t +dst_key_tofile(const dst_key_t *key, int type, const char *directory); +/*%< + * Writes a key to permanent storage. The key can either be a public or + * private key. Public keys are written in DNS format and private keys + * are written as a set of base64 encoded values. If directory is NULL, + * the current directory is assumed. + * + * Requires: + * \li "key" is a valid key. + * \li "type" is DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or the bitwise union + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + */ + +isc_result_t +dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Converts a DNS KEY record into a DST key. + * + * Requires: + * \li "name" is a valid absolute dns name. + * \li "source" is a valid buffer. There must be at least 4 bytes available. + * \li "mctx" is a valid memory context. + * \li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + * \li If successful, *keyp will contain a valid key, and the consumed + * pointer in data will be advanced. + */ + +isc_result_t +dst_key_todns(const dst_key_t *key, isc_buffer_t *target); +/*%< + * Converts a DST key into a DNS KEY record. + * + * Requires: + * \li "key" is a valid key. + * \li "target" is a valid buffer. There must be at least 4 bytes unused. + * + * Returns: + * \li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + * \li If successful, the used pointer in 'target' is advanced by at least 4. + */ + +isc_result_t +dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); +/*%< + * Converts a buffer containing DNS KEY RDATA into a DST key. + * + * Requires: + *\li "name" is a valid absolute dns name. + *\li "alg" is a supported key algorithm. + *\li "source" is a valid buffer. + *\li "mctx" is a valid memory context. + *\li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + *\li If successful, *keyp will contain a valid key, and the consumed + * pointer in source will be advanced. + */ + +isc_result_t +dst_key_tobuffer(const dst_key_t *key, isc_buffer_t *target); +/*%< + * Converts a DST key into DNS KEY RDATA format. + * + * Requires: + *\li "key" is a valid key. + *\li "target" is a valid buffer. + * + * Returns: + *\li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + *\li If successful, the used pointer in 'target' is advanced. + */ + +isc_result_t +dst_key_privatefrombuffer(dst_key_t *key, isc_buffer_t *buffer); +/*%< + * Converts a public key into a private key, reading the private key + * information from the buffer. The buffer should contain the same data + * as the .private key file would. + * + * Requires: + *\li "key" is a valid public key. + *\li "buffer" is not NULL. + * + * Returns: + *\li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + *\li If successful, key will contain a valid private key. + */ + +dns_gss_ctx_id_t +dst_key_getgssctx(const dst_key_t *key); +/*%< + * Returns the opaque key data. + * Be cautions when using this value unless you know what you are doing. + * + * Requires: + *\li "key" is not NULL. + * + * Returns: + *\li gssctx key data, possibly NULL. + */ + +isc_result_t +dst_key_fromgssapi(const dns_name_t *name, dns_gss_ctx_id_t gssctx, + isc_mem_t *mctx, dst_key_t **keyp, isc_region_t *intoken); +/*%< + * Converts a GSSAPI opaque context id into a DST key. + * + * Requires: + *\li "name" is a valid absolute dns name. + *\li "gssctx" is a GSSAPI context id. + *\li "mctx" is a valid memory context. + *\li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + *\li If successful, *keyp will contain a valid key and be responsible for + * the context id. + */ + +#ifdef DST_KEY_INTERNAL +isc_result_t +dst_key_buildinternal(const dns_name_t *name, unsigned int alg, + unsigned int bits, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + void *data, isc_mem_t *mctx, dst_key_t **keyp); +#endif /* ifdef DST_KEY_INTERNAL */ + +isc_result_t +dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + const char *engine, const char *label, const char *pin, + isc_mem_t *mctx, dst_key_t **keyp); + +isc_result_t +dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits, + unsigned int param, unsigned int flags, unsigned int protocol, + dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp, + void (*callback)(int)); + +/*%< + * Generate a DST key (or keypair) with the supplied parameters. The + * interpretation of the "param" field depends on the algorithm: + * \code + * RSA: exponent + * 0 use exponent 3 + * !0 use Fermat4 (2^16 + 1) + * DH: generator + * 0 default - use well known prime if bits == 768 or 1024, + * otherwise use 2 as the generator. + * !0 use this value as the generator. + * DSA: unused + * HMACMD5: entropy + * 0 default - require good entropy + * !0 lack of good entropy is ok + *\endcode + * + * Requires: + *\li "name" is a valid absolute dns name. + *\li "keyp" is not NULL and "*keyp" is NULL. + * + * Returns: + *\li ISC_R_SUCCESS + * \li any other result indicates failure + * + * Ensures: + *\li If successful, *keyp will contain a valid key. + */ + +bool +dst_key_compare(const dst_key_t *key1, const dst_key_t *key2); +/*%< + * Compares two DST keys. Returns true if they match, false otherwise. + * + * Keys ARE NOT considered to match if one of them is the revoked version + * of the other. + * + * Requires: + *\li "key1" is a valid key. + *\li "key2" is a valid key. + * + * Returns: + *\li true + * \li false + */ + +bool +dst_key_pubcompare(const dst_key_t *key1, const dst_key_t *key2, + bool match_revoked_key); +/*%< + * Compares only the public portions of two DST keys. Returns true + * if they match, false otherwise. This allows us, for example, to + * determine whether a public key found in a zone matches up with a + * key pair found on disk. + * + * If match_revoked_key is TRUE, then keys ARE considered to match if one + * of them is the revoked version of the other. Otherwise, they are not. + * + * Requires: + *\li "key1" is a valid key. + *\li "key2" is a valid key. + * + * Returns: + *\li true + * \li false + */ + +bool +dst_key_paramcompare(const dst_key_t *key1, const dst_key_t *key2); +/*%< + * Compares the parameters of two DST keys. This is used to determine if + * two (Diffie-Hellman) keys can be used to derive a shared secret. + * + * Requires: + *\li "key1" is a valid key. + *\li "key2" is a valid key. + * + * Returns: + *\li true + * \li false + */ + +void +dst_key_attach(dst_key_t *source, dst_key_t **target); +/* + * Attach to a existing key increasing the reference count. + * + * Requires: + *\li 'source' to be a valid key. + *\li 'target' to be non-NULL and '*target' to be NULL. + */ + +void +dst_key_free(dst_key_t **keyp); +/*%< + * Decrement the key's reference counter and, when it reaches zero, + * release all memory associated with the key. + * + * Requires: + *\li "keyp" is not NULL and "*keyp" is a valid key. + *\li reference counter greater than zero. + * + * Ensures: + *\li All memory associated with "*keyp" will be freed. + *\li *keyp == NULL + */ + +/*%< + * Accessor functions to obtain key fields. + * + * Require: + *\li "key" is a valid key. + */ +dns_name_t * +dst_key_name(const dst_key_t *key); + +unsigned int +dst_key_size(const dst_key_t *key); + +unsigned int +dst_key_proto(const dst_key_t *key); + +unsigned int +dst_key_alg(const dst_key_t *key); + +uint32_t +dst_key_flags(const dst_key_t *key); + +dns_keytag_t +dst_key_id(const dst_key_t *key); + +dns_keytag_t +dst_key_rid(const dst_key_t *key); + +dns_rdataclass_t +dst_key_class(const dst_key_t *key); + +bool +dst_key_isprivate(const dst_key_t *key); + +bool +dst_key_iszonekey(const dst_key_t *key); + +bool +dst_key_isnullkey(const dst_key_t *key); + +isc_result_t +dst_key_buildfilename(const dst_key_t *key, int type, const char *directory, + isc_buffer_t *out); +/*%< + * Generates the filename used by dst to store the specified key. + * If directory is NULL, the current directory is assumed. + * If tmp is not NULL, generates a template for mkstemp(). + * + * Requires: + *\li "key" is a valid key + *\li "type" is either DST_TYPE_PUBLIC, DST_TYPE_PRIVATE, or 0 for no suffix. + *\li "out" is a valid buffer + *\li "tmp" is a valid buffer or NULL + * + * Ensures: + *\li the file name will be written to "out", and the used pointer will + * be advanced. + */ + +isc_result_t +dst_key_sigsize(const dst_key_t *key, unsigned int *n); +/*%< + * Computes the size of a signature generated by the given key. + * + * Requires: + *\li "key" is a valid key. + *\li "n" is not NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li DST_R_UNSUPPORTEDALG + * + * Ensures: + *\li "n" stores the size of a generated signature + */ + +isc_result_t +dst_key_secretsize(const dst_key_t *key, unsigned int *n); +/*%< + * Computes the size of a shared secret generated by the given key. + * + * Requires: + *\li "key" is a valid key. + *\li "n" is not NULL + * + * Returns: + *\li #ISC_R_SUCCESS + *\li DST_R_UNSUPPORTEDALG + * + * Ensures: + *\li "n" stores the size of a generated shared secret + */ + +uint16_t +dst_region_computeid(const isc_region_t *source); +uint16_t +dst_region_computerid(const isc_region_t *source); +/*%< + * Computes the (revoked) key id of the key stored in the provided + * region. + * + * Requires: + *\li "source" contains a valid, non-NULL region. + * + * Returns: + *\li the key id + */ + +uint16_t +dst_key_getbits(const dst_key_t *key); +/*%< + * Get the number of digest bits required (0 == MAX). + * + * Requires: + * "key" is a valid key. + */ + +void +dst_key_setbits(dst_key_t *key, uint16_t bits); +/*%< + * Set the number of digest bits required (0 == MAX). + * + * Requires: + * "key" is a valid key. + */ + +void +dst_key_setttl(dst_key_t *key, dns_ttl_t ttl); +/*%< + * Set the default TTL to use when converting the key + * to a KEY or DNSKEY RR. + * + * Requires: + * "key" is a valid key. + */ + +dns_ttl_t +dst_key_getttl(const dst_key_t *key); +/*%< + * Get the default TTL to use when converting the key + * to a KEY or DNSKEY RR. + * + * Requires: + * "key" is a valid key. + */ + +isc_result_t +dst_key_setflags(dst_key_t *key, uint32_t flags); +/* + * Set the key flags, and recompute the key ID. + * + * Requires: + * "key" is a valid key. + */ + +isc_result_t +dst_key_getbool(const dst_key_t *key, int type, bool *valuep); +/*%< + * Get a member of the boolean metadata array and place it in '*valuep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + * "valuep" is not null. + */ + +void +dst_key_setbool(dst_key_t *key, int type, bool value); +/*%< + * Set a member of the boolean metadata array. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + +void +dst_key_unsetbool(dst_key_t *key, int type); +/*%< + * Flag a member of the boolean metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_BOOLEAN + */ + +isc_result_t +dst_key_getnum(const dst_key_t *key, int type, uint32_t *valuep); +/*%< + * Get a member of the numeric metadata array and place it in '*valuep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_NUMERIC + * "valuep" is not null. + */ + +void +dst_key_setnum(dst_key_t *key, int type, uint32_t value); +/*%< + * Set a member of the numeric metadata array. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_NUMERIC + */ + +void +dst_key_unsetnum(dst_key_t *key, int type); +/*%< + * Flag a member of the numeric metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_NUMERIC + */ + +isc_result_t +dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep); +/*%< + * Get a member of the timing metadata array and place it in '*timep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_TIMES + * "timep" is not null. + */ + +void +dst_key_settime(dst_key_t *key, int type, isc_stdtime_t when); +/*%< + * Set a member of the timing metadata array. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_TIMES + */ + +void +dst_key_unsettime(dst_key_t *key, int type); +/*%< + * Flag a member of the timing metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_TIMES + */ + +isc_result_t +dst_key_getstate(const dst_key_t *key, int type, dst_key_state_t *statep); +/*%< + * Get a member of the keystate metadata array and place it in '*statep'. + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + * "statep" is not null. + */ + +void +dst_key_setstate(dst_key_t *key, int type, dst_key_state_t state); +/*%< + * Set a member of the keystate metadata array. + * + * Requires: + * "key" is a valid key. + * "state" is a valid state. + * "type" is no larger than DST_MAX_KEYSTATES + */ + +void +dst_key_unsetstate(dst_key_t *key, int type); +/*%< + * Flag a member of the keystate metadata array as "not set". + * + * Requires: + * "key" is a valid key. + * "type" is no larger than DST_MAX_KEYSTATES + */ + +isc_result_t +dst_key_getprivateformat(const dst_key_t *key, int *majorp, int *minorp); +/*%< + * Get the private key format version number. (If the key does not have + * a private key associated with it, the version will be 0.0.) The major + * version number is placed in '*majorp', and the minor version number in + * '*minorp'. + * + * Requires: + * "key" is a valid key. + * "majorp" is not NULL. + * "minorp" is not NULL. + */ + +void +dst_key_setprivateformat(dst_key_t *key, int major, int minor); +/*%< + * Set the private key format version number. + * + * Requires: + * "key" is a valid key. + */ + +#define DST_KEY_FORMATSIZE (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + 7) + +void +dst_key_format(const dst_key_t *key, char *cp, unsigned int size); +/*%< + * Write the uniquely identifying information about the key (name, + * algorithm, key ID) into a string 'cp' of size 'size'. + */ + +isc_buffer_t * +dst_key_tkeytoken(const dst_key_t *key); +/*%< + * Return the token from the TKEY request, if any. If this key was + * not negotiated via TKEY, return NULL. + * + * Requires: + * "key" is a valid key. + */ + +isc_result_t +dst_key_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length); +/*%< + * Allocate 'buffer' and dump the key into it in base64 format. The buffer + * is not NUL terminated. The length of the buffer is returned in *length. + * + * 'buffer' needs to be freed using isc_mem_put(mctx, buffer, length); + * + * Requires: + * 'buffer' to be non NULL and *buffer to be NULL. + * 'length' to be non NULL and *length to be zero. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_NOTIMPLEMENTED + * others. + */ + +isc_result_t +dst_key_restore(dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, + isc_mem_t *mctx, const char *keystr, dst_key_t **keyp); + +bool +dst_key_inactive(const dst_key_t *key); +/*%< + * Determines if the private key is missing due the key being deemed inactive. + * + * Requires: + * 'key' to be valid. + */ + +void +dst_key_setinactive(dst_key_t *key, bool inactive); +/*%< + * Set key inactive state. + * + * Requires: + * 'key' to be valid. + */ + +void +dst_key_setexternal(dst_key_t *key, bool value); +/*%< + * Set key external state. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_isexternal(dst_key_t *key); +/*%< + * Check if this is an external key. + * + * Requires: + * 'key' to be valid. + */ + +void +dst_key_setmodified(dst_key_t *key, bool value); +/*%< + * If 'value' is true, this marks the key to indicate that key file metadata + * has been modified. If 'value' is false, this resets the value, for example + * after you have written the key to file. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_ismodified(const dst_key_t *key); +/*%< + * Check if the key file has been modified. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_haskasp(dst_key_t *key); +/*%< + * Check if this key has state (and thus uses KASP). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_unused(dst_key_t *key); +/*%< + * Check if this key is unused. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish); +/*%< + * Check if it is safe to publish this key (e.g. put the DNSKEY in the zone). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_active(dst_key_t *key, isc_stdtime_t now); +/*%< + * Check if this key is active. This means that it is creating RRSIG records + * (ZSK), or that it is used to create a chain of trust (KSK), or both (CSK). + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_signing(dst_key_t *key, int role, isc_stdtime_t now, + isc_stdtime_t *active); +/*%< + * Check if it is safe to use this key for signing, given the role. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_revoked(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *revoke); +/*%< + * Check if this key is revoked. + * + * Requires: + * 'key' to be valid. + */ + +bool +dst_key_is_removed(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *remove); +/*%< + * Check if this key is removed from the zone (e.g. the DNSKEY record should + * no longer be in the zone). + * + * Requires: + * 'key' to be valid. + */ + +dst_key_state_t +dst_key_goal(dst_key_t *key); +/*%< + * Get the key goal. Should be OMNIPRESENT or HIDDEN. + * This can be used to determine if the key is being introduced or + * is on its way out. + * + * Requires: + * 'key' to be valid. + */ + +isc_result_t +dst_key_role(dst_key_t *key, bool *ksk, bool *zsk); +/*%< + * Get the key role. A key can have the KSK or the ZSK role, or both. + * + * Requires: + * 'key' to be valid. + */ + +void +dst_key_copy_metadata(dst_key_t *to, dst_key_t *from); +/*%< + * Copy key metadata from one key to another. + * + * Requires: + * 'to' and 'from' to be valid. + */ + +const char * +dst_hmac_algorithm_totext(dst_algorithm_t alg); +/*$< + * Return the name associtated with the HMAC algorithm 'alg' + * or return "unknown". + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h new file mode 100644 index 0000000..494b4b0 --- /dev/null +++ b/lib/dns/include/dst/gssapi.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file dst/gssapi.h */ + +#include +#include + +#include +#include +#include + +#include + +typedef void *dns_gss_cred_id_t; +typedef void *dns_gss_ctx_id_t; + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*** + *** Functions + ***/ + +isc_result_t +dst_gssapi_acquirecred(const dns_name_t *name, bool initiate, + dns_gss_cred_id_t *cred); +/* + * Acquires GSS credentials. + * + * Requires: + * 'name' is a valid name, preferably one known by the GSS provider + * 'initiate' indicates whether the credentials are for initiating or + * accepting contexts + * 'cred' is a pointer to NULL, which will be allocated with the + * credential handle. Call dst_gssapi_releasecred to free + * the memory. + * + * Returns: + * ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + * other an error occurred while building the message + */ + +isc_result_t +dst_gssapi_releasecred(dns_gss_cred_id_t *cred); +/* + * Releases GSS credentials. Calling this function does release the + * memory allocated for the credential in dst_gssapi_acquirecred() + * + * Requires: + * 'mctx' is a valid memory context + * 'cred' is a pointer to the credential to be released + * + * Returns: + * ISC_R_SUCCESS credential was released successfully + * other an error occurred while releaseing + * the credential + */ + +isc_result_t +dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, + isc_buffer_t *outtoken, dns_gss_ctx_id_t *gssctx, + isc_mem_t *mctx, char **err_message); +/* + * Initiates a GSS context. + * + * Requires: + * 'name' is a valid name, preferably one known by the GSS + * provider + * 'intoken' is a token received from the acceptor, or NULL if + * there isn't one + * 'outtoken' is a buffer to receive the token generated by + * gss_init_sec_context() to be sent to the acceptor + * 'context' is a pointer to a valid dns_gss_ctx_id_t + * (which may have the value DNS_GSS_C_NO_CONTEXT) + * + * Returns: + * ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + * other an error occurred while building the message + * *err_message optional error message + */ + +isc_result_t +dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + isc_region_t *intoken, isc_buffer_t **outtoken, + dns_gss_ctx_id_t *context, dns_name_t *principal, + isc_mem_t *mctx); +/* + * Accepts a GSS context. + * + * Requires: + * 'mctx' is a valid memory context + * 'cred' is the acceptor's valid GSS credential handle + * 'intoken' is a token received from the initiator + * 'outtoken' is a pointer a buffer pointer used to return the token + * generated by gss_accept_sec_context() to be sent to the + * initiator + * 'context' is a valid pointer to receive the generated context handle. + * On the initial call, it should be a pointer to NULL, which + * will be allocated as a dns_gss_ctx_id_t. Subsequent calls + * should pass in the handle generated on the first call. + * Call dst_gssapi_releasecred to delete the context and free + * the memory. + * + * Requires: + * 'outtoken' to != NULL && *outtoken == NULL. + * + * Returns: + * ISC_R_SUCCESS msg was successfully updated to include the + * query to be sent + * DNS_R_CONTINUE transaction still in progress + * other an error occurred while building the message + */ + +isc_result_t +dst_gssapi_deletectx(isc_mem_t *mctx, dns_gss_ctx_id_t *gssctx); +/* + * Destroys a GSS context. This function deletes the context from the GSS + * provider and then frees the memory used by the context pointer. + * + * Requires: + * 'mctx' is a valid memory context + * 'context' is a valid GSS context + * + * Returns: + * ISC_R_SUCCESS + */ + +void +gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); +/* + * Logging function for GSS. + * + * Requires + * 'level' is the log level to be used, as an integer + * 'fmt' is a printf format specifier + */ + +char * +gss_error_tostring(uint32_t major, uint32_t minor, char *buf, size_t buflen); +/* + * Render a GSS major status/minor status pair into a string + * + * Requires: + * 'major' is a GSS major status code + * 'minor' is a GSS minor status code + * + * Returns: + * A string containing the text representation of the error codes. + * Users should copy the string if they wish to keep it. + */ + +bool +dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain); +/* + * Compare a "signer" (in the format of a Kerberos-format Kerberos5 + * principal: host/example.com@EXAMPLE.COM) to the realm name stored + * in "name" (which represents the realm name). + * + */ + +bool +dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, bool subdomain); +/* + * Compare a "signer" (in the format of a Kerberos-format Kerberos5 + * principal: host/example.com@EXAMPLE.COM) to the realm name stored + * in "name" (which represents the realm name). + * + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/ipkeylist.c b/lib/dns/ipkeylist.c new file mode 100644 index 0000000..431284b --- /dev/null +++ b/lib/dns/ipkeylist.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include + +#include +#include + +void +dns_ipkeylist_init(dns_ipkeylist_t *ipkl) { + ipkl->count = 0; + ipkl->allocated = 0; + ipkl->addrs = NULL; + ipkl->keys = NULL; + ipkl->tlss = NULL; + ipkl->labels = NULL; +} + +void +dns_ipkeylist_clear(isc_mem_t *mctx, dns_ipkeylist_t *ipkl) { + uint32_t i; + + REQUIRE(ipkl != NULL); + + if (ipkl->allocated == 0) { + return; + } + + if (ipkl->addrs != NULL) { + isc_mem_put(mctx, ipkl->addrs, + ipkl->allocated * sizeof(isc_sockaddr_t)); + } + + if (ipkl->keys != NULL) { + for (i = 0; i < ipkl->allocated; i++) { + if (ipkl->keys[i] == NULL) { + continue; + } + if (dns_name_dynamic(ipkl->keys[i])) { + dns_name_free(ipkl->keys[i], mctx); + } + isc_mem_put(mctx, ipkl->keys[i], sizeof(dns_name_t)); + } + isc_mem_put(mctx, ipkl->keys, + ipkl->allocated * sizeof(dns_name_t *)); + } + + if (ipkl->tlss != NULL) { + for (i = 0; i < ipkl->allocated; i++) { + if (ipkl->tlss[i] == NULL) { + continue; + } + if (dns_name_dynamic(ipkl->tlss[i])) { + dns_name_free(ipkl->tlss[i], mctx); + } + isc_mem_put(mctx, ipkl->tlss[i], sizeof(dns_name_t)); + } + isc_mem_put(mctx, ipkl->tlss, + ipkl->allocated * sizeof(dns_name_t *)); + } + + if (ipkl->labels != NULL) { + for (i = 0; i < ipkl->allocated; i++) { + if (ipkl->labels[i] == NULL) { + continue; + } + if (dns_name_dynamic(ipkl->labels[i])) { + dns_name_free(ipkl->labels[i], mctx); + } + isc_mem_put(mctx, ipkl->labels[i], sizeof(dns_name_t)); + } + isc_mem_put(mctx, ipkl->labels, + ipkl->allocated * sizeof(dns_name_t *)); + } + + dns_ipkeylist_init(ipkl); +} + +isc_result_t +dns_ipkeylist_copy(isc_mem_t *mctx, const dns_ipkeylist_t *src, + dns_ipkeylist_t *dst) { + isc_result_t result = ISC_R_SUCCESS; + uint32_t i; + + REQUIRE(dst != NULL); + /* dst might be preallocated, we don't care, but it must be empty */ + REQUIRE(dst->count == 0); + + if (src->count == 0) { + return (ISC_R_SUCCESS); + } + + result = dns_ipkeylist_resize(mctx, dst, src->count); + if (result != ISC_R_SUCCESS) { + return (result); + } + + memmove(dst->addrs, src->addrs, src->count * sizeof(isc_sockaddr_t)); + + if (src->keys != NULL) { + for (i = 0; i < src->count; i++) { + if (src->keys[i] != NULL) { + dst->keys[i] = isc_mem_get(mctx, + sizeof(dns_name_t)); + dns_name_init(dst->keys[i], NULL); + dns_name_dup(src->keys[i], mctx, dst->keys[i]); + } else { + dst->keys[i] = NULL; + } + } + } + + if (src->tlss != NULL) { + for (i = 0; i < src->count; i++) { + if (src->tlss[i] != NULL) { + dst->tlss[i] = isc_mem_get(mctx, + sizeof(dns_name_t)); + dns_name_init(dst->tlss[i], NULL); + dns_name_dup(src->tlss[i], mctx, dst->tlss[i]); + } else { + dst->tlss[i] = NULL; + } + } + } + + if (src->labels != NULL) { + for (i = 0; i < src->count; i++) { + if (src->labels[i] != NULL) { + dst->labels[i] = + isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(dst->labels[i], NULL); + dns_name_dup(src->labels[i], mctx, + dst->labels[i]); + } else { + dst->labels[i] = NULL; + } + } + } + dst->count = src->count; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ipkeylist_resize(isc_mem_t *mctx, dns_ipkeylist_t *ipkl, unsigned int n) { + isc_sockaddr_t *addrs = NULL; + dns_name_t **keys = NULL; + dns_name_t **tlss = NULL; + dns_name_t **labels = NULL; + + REQUIRE(ipkl != NULL); + REQUIRE(n > ipkl->count); + + if (n <= ipkl->allocated) { + return (ISC_R_SUCCESS); + } + + addrs = isc_mem_get(mctx, n * sizeof(isc_sockaddr_t)); + keys = isc_mem_get(mctx, n * sizeof(dns_name_t *)); + tlss = isc_mem_get(mctx, n * sizeof(dns_name_t *)); + labels = isc_mem_get(mctx, n * sizeof(dns_name_t *)); + + if (ipkl->addrs != NULL) { + memmove(addrs, ipkl->addrs, + ipkl->allocated * sizeof(isc_sockaddr_t)); + isc_mem_put(mctx, ipkl->addrs, + ipkl->allocated * sizeof(isc_sockaddr_t)); + } + ipkl->addrs = addrs; + memset(&ipkl->addrs[ipkl->allocated], 0, + (n - ipkl->allocated) * sizeof(isc_sockaddr_t)); + + if (ipkl->keys) { + memmove(keys, ipkl->keys, + ipkl->allocated * sizeof(dns_name_t *)); + isc_mem_put(mctx, ipkl->keys, + ipkl->allocated * sizeof(dns_name_t *)); + } + ipkl->keys = keys; + memset(&ipkl->keys[ipkl->allocated], 0, + (n - ipkl->allocated) * sizeof(dns_name_t *)); + + if (ipkl->tlss) { + memmove(tlss, ipkl->tlss, + ipkl->allocated * sizeof(dns_name_t *)); + isc_mem_put(mctx, ipkl->tlss, + ipkl->allocated * sizeof(dns_name_t *)); + } + ipkl->tlss = tlss; + memset(&ipkl->tlss[ipkl->allocated], 0, + (n - ipkl->allocated) * sizeof(dns_name_t *)); + + if (ipkl->labels != NULL) { + memmove(labels, ipkl->labels, + ipkl->allocated * sizeof(dns_name_t *)); + isc_mem_put(mctx, ipkl->labels, + ipkl->allocated * sizeof(dns_name_t *)); + } + ipkl->labels = labels; + memset(&ipkl->labels[ipkl->allocated], 0, + (n - ipkl->allocated) * sizeof(dns_name_t *)); + + ipkl->allocated = n; + return (ISC_R_SUCCESS); + + isc_mem_put(mctx, addrs, n * sizeof(isc_sockaddr_t)); + isc_mem_put(mctx, tlss, n * sizeof(dns_name_t *)); + isc_mem_put(mctx, keys, n * sizeof(dns_name_t *)); + isc_mem_put(mctx, labels, n * sizeof(dns_name_t *)); + + return (ISC_R_NOMEMORY); +} diff --git a/lib/dns/iptable.c b/lib/dns/iptable.c new file mode 100644 index 0000000..f479d71 --- /dev/null +++ b/lib/dns/iptable.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include + +#include + +static void +destroy_iptable(dns_iptable_t *dtab); + +/* + * Create a new IP table and the underlying radix structure + */ +isc_result_t +dns_iptable_create(isc_mem_t *mctx, dns_iptable_t **target) { + isc_result_t result; + dns_iptable_t *tab; + + tab = isc_mem_get(mctx, sizeof(*tab)); + tab->mctx = NULL; + isc_mem_attach(mctx, &tab->mctx); + isc_refcount_init(&tab->refcount, 1); + tab->radix = NULL; + tab->magic = DNS_IPTABLE_MAGIC; + + result = isc_radix_create(mctx, &tab->radix, RADIX_MAXBITS); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + *target = tab; + return (ISC_R_SUCCESS); + +cleanup: + dns_iptable_detach(&tab); + return (result); +} + +static bool dns_iptable_neg = false; +static bool dns_iptable_pos = true; + +/* + * Add an IP prefix to an existing IP table + */ +isc_result_t +dns_iptable_addprefix(dns_iptable_t *tab, const isc_netaddr_t *addr, + uint16_t bitlen, bool pos) { + isc_result_t result; + isc_prefix_t pfx; + isc_radix_node_t *node = NULL; + int i; + + INSIST(DNS_IPTABLE_VALID(tab)); + INSIST(tab->radix != NULL); + + NETADDR_TO_PREFIX_T(addr, pfx, bitlen); + + result = isc_radix_insert(tab->radix, &node, NULL, &pfx); + if (result != ISC_R_SUCCESS) { + isc_refcount_destroy(&pfx.refcount); + return (result); + } + + /* If a node already contains data, don't overwrite it */ + if (pfx.family == AF_UNSPEC) { + /* "any" or "none" */ + INSIST(pfx.bitlen == 0); + for (i = 0; i < RADIX_FAMILIES; i++) { + if (node->data[i] == NULL) { + node->data[i] = pos ? &dns_iptable_pos + : &dns_iptable_neg; + } + } + } else { + /* any other prefix */ + int fam = ISC_RADIX_FAMILY(&pfx); + if (node->data[fam] == NULL) { + node->data[fam] = pos ? &dns_iptable_pos + : &dns_iptable_neg; + } + } + + isc_refcount_destroy(&pfx.refcount); + return (ISC_R_SUCCESS); +} + +/* + * Merge one IP table into another one. + */ +isc_result_t +dns_iptable_merge(dns_iptable_t *tab, dns_iptable_t *source, bool pos) { + isc_result_t result; + isc_radix_node_t *node, *new_node; + int i, max_node = 0; + + RADIX_WALK(source->radix->head, node) { + new_node = NULL; + result = isc_radix_insert(tab->radix, &new_node, node, NULL); + + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * If we're negating a nested ACL, then we should + * reverse the sense of every node. However, this + * could lead to a negative node in a nested ACL + * becoming a positive match in the parent, which + * could be a security risk. To prevent this, we + * just leave the negative nodes negative. + */ + for (i = 0; i < RADIX_FAMILIES; i++) { + if (!pos) { + if (node->data[i] && *(bool *)node->data[i]) { + new_node->data[i] = &dns_iptable_neg; + } + } + if (node->node_num[i] > max_node) { + max_node = node->node_num[i]; + } + } + } + RADIX_WALK_END; + + tab->radix->num_added_node += max_node; + return (ISC_R_SUCCESS); +} + +void +dns_iptable_attach(dns_iptable_t *source, dns_iptable_t **target) { + REQUIRE(DNS_IPTABLE_VALID(source)); + isc_refcount_increment(&source->refcount); + *target = source; +} + +void +dns_iptable_detach(dns_iptable_t **tabp) { + REQUIRE(tabp != NULL && DNS_IPTABLE_VALID(*tabp)); + dns_iptable_t *tab = *tabp; + *tabp = NULL; + + if (isc_refcount_decrement(&tab->refcount) == 1) { + isc_refcount_destroy(&tab->refcount); + destroy_iptable(tab); + } +} + +static void +destroy_iptable(dns_iptable_t *dtab) { + REQUIRE(DNS_IPTABLE_VALID(dtab)); + + if (dtab->radix != NULL) { + isc_radix_destroy(dtab->radix, NULL); + dtab->radix = NULL; + } + + dtab->magic = 0; + isc_mem_putanddetach(&dtab->mctx, dtab, sizeof(*dtab)); +} diff --git a/lib/dns/journal.c b/lib/dns/journal.c new file mode 100644 index 0000000..c158c41 --- /dev/null +++ b/lib/dns/journal.c @@ -0,0 +1,2856 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! \file + * \brief Journaling. + * + * A journal file consists of + * + * \li A fixed-size header of type journal_rawheader_t. + * + * \li The index. This is an unordered array of index entries + * of type journal_rawpos_t giving the locations + * of some arbitrary subset of the journal's addressable + * transactions. The index entries are used as hints to + * speed up the process of locating a transaction with a given + * serial number. Unused index entries have an "offset" + * field of zero. The size of the index can vary between + * journal files, but does not change during the lifetime + * of a file. The size can be zero. + * + * \li The journal data. This consists of one or more transactions. + * Each transaction begins with a transaction header of type + * journal_rawxhdr_t. The transaction header is followed by a + * sequence of RRs, similar in structure to an IXFR difference + * sequence (RFC1995). That is, the pre-transaction SOA, + * zero or more other deleted RRs, the post-transaction SOA, + * and zero or more other added RRs. Unlike in IXFR, each RR + * is prefixed with a 32-bit length. + * + * The journal data part grows as new transactions are + * appended to the file. Only those transactions + * whose serial number is current-(2^31-1) to current + * are considered "addressable" and may be pointed + * to from the header or index. They may be preceded + * by old transactions that are no longer addressable, + * and they may be followed by transactions that were + * appended to the journal but never committed by updating + * the "end" position in the header. The latter will + * be overwritten when new transactions are added. + */ + +/**************************************************************************/ +/* + * Miscellaneous utilities. + */ + +#define JOURNAL_COMMON_LOGARGS \ + dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_JOURNAL + +#define JOURNAL_DEBUG_LOGARGS(n) JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(n) + +/*% + * It would be non-sensical (or at least obtuse) to use FAIL() with an + * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ +#define FAIL(code) \ + do { \ + result = (code); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define JOURNAL_SERIALSET 0x01U + +static isc_result_t +index_to_disk(dns_journal_t *); + +static uint32_t +decode_uint32(unsigned char *p) { + return (((uint32_t)p[0] << 24) + ((uint32_t)p[1] << 16) + + ((uint32_t)p[2] << 8) + ((uint32_t)p[3] << 0)); +} + +static void +encode_uint32(uint32_t val, unsigned char *p) { + p[0] = (uint8_t)(val >> 24); + p[1] = (uint8_t)(val >> 16); + p[2] = (uint8_t)(val >> 8); + p[3] = (uint8_t)(val >> 0); +} + +isc_result_t +dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, + dns_diffop_t op, dns_difftuple_t **tp) { + isc_result_t result; + dns_dbnode_t *node; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_fixedname_t fixed; + dns_name_t *zonename; + + zonename = dns_fixedname_initname(&fixed); + dns_name_copy(dns_db_origin(db), zonename); + + node = NULL; + result = dns_db_findnode(db, zonename, false, &node); + if (result != ISC_R_SUCCESS) { + goto nonode; + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto freenode; + } + + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) { + goto freenode; + } + + dns_rdataset_current(&rdataset, &rdata); + dns_rdataset_getownercase(&rdataset, zonename); + + result = dns_difftuple_create(mctx, op, zonename, rdataset.ttl, &rdata, + tp); + + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + return (result); + +freenode: + dns_db_detachnode(db, &node); +nonode: + UNEXPECTED_ERROR("missing SOA"); + return (result); +} + +/* Journaling */ + +/*% + * On-disk representation of a "pointer" to a journal entry. + * These are used in the journal header to locate the beginning + * and end of the journal, and in the journal index to locate + * other transactions. + */ +typedef struct { + unsigned char serial[4]; /*%< SOA serial before update. */ + /* + * XXXRTH Should offset be 8 bytes? + * XXXDCL ... probably, since isc_offset_t is 8 bytes on many OSs. + * XXXAG ... but we will not be able to seek >2G anyway on many + * platforms as long as we are using fseek() rather + * than lseek(). + */ + unsigned char offset[4]; /*%< Offset from beginning of file. */ +} journal_rawpos_t; + +/*% + * The header is of a fixed size, with some spare room for future + * extensions. + */ +#define JOURNAL_HEADER_SIZE 64 /* Bytes. */ + +typedef enum { + XHDR_VERSION1 = 1, + XHDR_VERSION2 = 2, +} xhdr_version_t; + +/*% + * The on-disk representation of the journal header. + * All numbers are stored in big-endian order. + */ +typedef union { + struct { + /*% File format version ID. */ + unsigned char format[16]; + /*% Position of the first addressable transaction */ + journal_rawpos_t begin; + /*% Position of the next (yet nonexistent) transaction. */ + journal_rawpos_t end; + /*% Number of index entries following the header. */ + unsigned char index_size[4]; + /*% Source serial number. */ + unsigned char sourceserial[4]; + unsigned char flags; + } h; + /* Pad the header to a fixed size. */ + unsigned char pad[JOURNAL_HEADER_SIZE]; +} journal_rawheader_t; + +/*% + * The on-disk representation of the transaction header, version 2. + * There is one of these at the beginning of each transaction. + */ +typedef struct { + unsigned char size[4]; /*%< In bytes, excluding header. */ + unsigned char count[4]; /*%< Number of records in transaction */ + unsigned char serial0[4]; /*%< SOA serial before update. */ + unsigned char serial1[4]; /*%< SOA serial after update. */ +} journal_rawxhdr_t; + +/*% + * Old-style raw transaction header, version 1, used for backward + * compatibility mode. + */ +typedef struct { + unsigned char size[4]; + unsigned char serial0[4]; + unsigned char serial1[4]; +} journal_rawxhdr_ver1_t; + +/*% + * The on-disk representation of the RR header. + * There is one of these at the beginning of each RR. + */ +typedef struct { + unsigned char size[4]; /*%< In bytes, excluding header. */ +} journal_rawrrhdr_t; + +/*% + * The in-core representation of the journal header. + */ +typedef struct { + uint32_t serial; + isc_offset_t offset; +} journal_pos_t; + +#define POS_VALID(pos) ((pos).offset != 0) +#define POS_INVALIDATE(pos) ((pos).offset = 0, (pos).serial = 0) + +typedef struct { + unsigned char format[16]; + journal_pos_t begin; + journal_pos_t end; + uint32_t index_size; + uint32_t sourceserial; + bool serialset; +} journal_header_t; + +/*% + * The in-core representation of the transaction header. + */ +typedef struct { + uint32_t size; + uint32_t count; + uint32_t serial0; + uint32_t serial1; +} journal_xhdr_t; + +/*% + * The in-core representation of the RR header. + */ +typedef struct { + uint32_t size; +} journal_rrhdr_t; + +/*% + * Initial contents to store in the header of a newly created + * journal file. + * + * The header starts with the magic string ";BIND LOG V9.2\n" + * to identify the file as a BIND 9 journal file. An ASCII + * identification string is used rather than a binary magic + * number to be consistent with BIND 8 (BIND 8 journal files + * are ASCII text files). + */ + +static journal_header_t journal_header_ver1 = { + ";BIND LOG V9\n", { 0, 0 }, { 0, 0 }, 0, 0, 0 +}; +static journal_header_t initial_journal_header = { + ";BIND LOG V9.2\n", { 0, 0 }, { 0, 0 }, 0, 0, 0 +}; + +#define JOURNAL_EMPTY(h) ((h)->begin.offset == (h)->end.offset) + +typedef enum { + JOURNAL_STATE_INVALID, + JOURNAL_STATE_READ, + JOURNAL_STATE_WRITE, + JOURNAL_STATE_TRANSACTION, + JOURNAL_STATE_INLINE +} journal_state_t; + +struct dns_journal { + unsigned int magic; /*%< JOUR */ + isc_mem_t *mctx; /*%< Memory context */ + journal_state_t state; + xhdr_version_t xhdr_version; /*%< Expected transaction header version */ + bool header_ver1; /*%< Transaction header compatibility + * mode is allowed */ + bool recovered; /*%< A recoverable error was found + * while reading the journal */ + char *filename; /*%< Journal file name */ + FILE *fp; /*%< File handle */ + isc_offset_t offset; /*%< Current file offset */ + journal_xhdr_t curxhdr; /*%< Current transaction header */ + journal_header_t header; /*%< In-core journal header */ + unsigned char *rawindex; /*%< In-core buffer for journal index + * in on-disk format */ + journal_pos_t *index; /*%< In-core journal index */ + + /*% Current transaction state (when writing). */ + struct { + unsigned int n_soa; /*%< Number of SOAs seen */ + unsigned int n_rr; /*%< Number of RRs to write */ + journal_pos_t pos[2]; /*%< Begin/end position */ + } x; + + /*% Iteration state (when reading). */ + struct { + /* These define the part of the journal we iterate over. */ + journal_pos_t bpos; /*%< Position before first, */ + journal_pos_t cpos; /*%< before current, */ + journal_pos_t epos; /*%< and after last transaction */ + /* The rest is iterator state. */ + uint32_t current_serial; /*%< Current SOA serial */ + isc_buffer_t source; /*%< Data from disk */ + isc_buffer_t target; /*%< Data from _fromwire check */ + dns_decompress_t dctx; /*%< Dummy decompression ctx */ + dns_name_t name; /*%< Current domain name */ + dns_rdata_t rdata; /*%< Current rdata */ + uint32_t ttl; /*%< Current TTL */ + unsigned int xsize; /*%< Size of transaction data */ + unsigned int xpos; /*%< Current position in it */ + isc_result_t result; /*%< Result of last call */ + } it; +}; + +#define DNS_JOURNAL_MAGIC ISC_MAGIC('J', 'O', 'U', 'R') +#define DNS_JOURNAL_VALID(t) ISC_MAGIC_VALID(t, DNS_JOURNAL_MAGIC) + +static void +journal_pos_decode(journal_rawpos_t *raw, journal_pos_t *cooked) { + cooked->serial = decode_uint32(raw->serial); + cooked->offset = decode_uint32(raw->offset); +} + +static void +journal_pos_encode(journal_rawpos_t *raw, journal_pos_t *cooked) { + encode_uint32(cooked->serial, raw->serial); + encode_uint32(cooked->offset, raw->offset); +} + +static void +journal_header_decode(journal_rawheader_t *raw, journal_header_t *cooked) { + INSIST(sizeof(cooked->format) == sizeof(raw->h.format)); + + memmove(cooked->format, raw->h.format, sizeof(cooked->format)); + journal_pos_decode(&raw->h.begin, &cooked->begin); + journal_pos_decode(&raw->h.end, &cooked->end); + cooked->index_size = decode_uint32(raw->h.index_size); + cooked->sourceserial = decode_uint32(raw->h.sourceserial); + cooked->serialset = ((raw->h.flags & JOURNAL_SERIALSET) != 0); +} + +static void +journal_header_encode(journal_header_t *cooked, journal_rawheader_t *raw) { + unsigned char flags = 0; + + INSIST(sizeof(cooked->format) == sizeof(raw->h.format)); + + memset(raw->pad, 0, sizeof(raw->pad)); + memmove(raw->h.format, cooked->format, sizeof(raw->h.format)); + journal_pos_encode(&raw->h.begin, &cooked->begin); + journal_pos_encode(&raw->h.end, &cooked->end); + encode_uint32(cooked->index_size, raw->h.index_size); + encode_uint32(cooked->sourceserial, raw->h.sourceserial); + if (cooked->serialset) { + flags |= JOURNAL_SERIALSET; + } + raw->h.flags = flags; +} + +/* + * Journal file I/O subroutines, with error checking and reporting. + */ +static isc_result_t +journal_seek(dns_journal_t *j, uint32_t offset) { + isc_result_t result; + + result = isc_stdio_seek(j->fp, (off_t)offset, SEEK_SET); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: seek: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset = offset; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_read(dns_journal_t *j, void *mem, size_t nbytes) { + isc_result_t result; + + result = isc_stdio_read(mem, 1, nbytes, j->fp, NULL); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_EOF) { + return (ISC_R_NOMORE); + } + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: read: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset += (isc_offset_t)nbytes; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_write(dns_journal_t *j, void *mem, size_t nbytes) { + isc_result_t result; + + result = isc_stdio_write(mem, 1, nbytes, j->fp, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: write: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset += (isc_offset_t)nbytes; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_fsync(dns_journal_t *j) { + isc_result_t result; + + result = isc_stdio_flush(j->fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: flush: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + result = isc_stdio_sync(j->fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: fsync: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + return (ISC_R_SUCCESS); +} + +/* + * Read/write a transaction header at the current file position. + */ +static isc_result_t +journal_read_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr) { + isc_result_t result; + + j->it.cpos.offset = j->offset; + + switch (j->xhdr_version) { + case XHDR_VERSION1: { + journal_rawxhdr_ver1_t raw; + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) { + return (result); + } + xhdr->size = decode_uint32(raw.size); + xhdr->count = 0; + xhdr->serial0 = decode_uint32(raw.serial0); + xhdr->serial1 = decode_uint32(raw.serial1); + j->curxhdr = *xhdr; + return (ISC_R_SUCCESS); + } + + case XHDR_VERSION2: { + journal_rawxhdr_t raw; + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) { + return (result); + } + xhdr->size = decode_uint32(raw.size); + xhdr->count = decode_uint32(raw.count); + xhdr->serial0 = decode_uint32(raw.serial0); + xhdr->serial1 = decode_uint32(raw.serial1); + j->curxhdr = *xhdr; + return (ISC_R_SUCCESS); + } + + default: + return (ISC_R_NOTIMPLEMENTED); + } +} + +static isc_result_t +journal_write_xhdr(dns_journal_t *j, uint32_t size, uint32_t count, + uint32_t serial0, uint32_t serial1) { + if (j->header_ver1) { + journal_rawxhdr_ver1_t raw; + encode_uint32(size, raw.size); + encode_uint32(serial0, raw.serial0); + encode_uint32(serial1, raw.serial1); + return (journal_write(j, &raw, sizeof(raw))); + } else { + journal_rawxhdr_t raw; + encode_uint32(size, raw.size); + encode_uint32(count, raw.count); + encode_uint32(serial0, raw.serial0); + encode_uint32(serial1, raw.serial1); + return (journal_write(j, &raw, sizeof(raw))); + } +} + +/* + * Read an RR header at the current file position. + */ + +static isc_result_t +journal_read_rrhdr(dns_journal_t *j, journal_rrhdr_t *rrhdr) { + journal_rawrrhdr_t raw; + isc_result_t result; + + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) { + return (result); + } + rrhdr->size = decode_uint32(raw.size); + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_file_create(isc_mem_t *mctx, bool downgrade, const char *filename) { + FILE *fp = NULL; + isc_result_t result; + journal_header_t header; + journal_rawheader_t rawheader; + int index_size = 56; /* XXX configurable */ + int size; + void *mem = NULL; /* Memory for temporary index image. */ + + INSIST(sizeof(journal_rawheader_t) == JOURNAL_HEADER_SIZE); + + result = isc_stdio_open(filename, "wb", &fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: create: %s", filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + + if (downgrade) { + header = journal_header_ver1; + } else { + header = initial_journal_header; + } + header.index_size = index_size; + journal_header_encode(&header, &rawheader); + + size = sizeof(journal_rawheader_t) + + index_size * sizeof(journal_rawpos_t); + + mem = isc_mem_get(mctx, size); + memset(mem, 0, size); + memmove(mem, &rawheader, sizeof(rawheader)); + + result = isc_stdio_write(mem, 1, (size_t)size, fp, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: write: %s", filename, + isc_result_totext(result)); + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + isc_mem_put(mctx, mem, size); + return (ISC_R_UNEXPECTED); + } + isc_mem_put(mctx, mem, size); + + result = isc_stdio_close(fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: close: %s", filename, + isc_result_totext(result)); + (void)isc_file_remove(filename); + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_open(isc_mem_t *mctx, const char *filename, bool writable, bool create, + bool downgrade, dns_journal_t **journalp) { + FILE *fp = NULL; + isc_result_t result; + journal_rawheader_t rawheader; + dns_journal_t *j; + + REQUIRE(journalp != NULL && *journalp == NULL); + + j = isc_mem_get(mctx, sizeof(*j)); + *j = (dns_journal_t){ .state = JOURNAL_STATE_INVALID, + .filename = isc_mem_strdup(mctx, filename), + .xhdr_version = XHDR_VERSION2 }; + isc_mem_attach(mctx, &j->mctx); + + result = isc_stdio_open(j->filename, writable ? "rb+" : "rb", &fp); + if (result == ISC_R_FILENOTFOUND) { + if (create) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(1), + "journal file %s does not exist, " + "creating it", + j->filename); + CHECK(journal_file_create(mctx, downgrade, filename)); + /* + * Retry. + */ + result = isc_stdio_open(j->filename, "rb+", &fp); + } else { + FAIL(ISC_R_NOTFOUND); + } + } + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: open: %s", j->filename, + isc_result_totext(result)); + FAIL(ISC_R_UNEXPECTED); + } + + j->fp = fp; + + /* + * Set magic early so that seek/read can succeed. + */ + j->magic = DNS_JOURNAL_MAGIC; + + CHECK(journal_seek(j, 0)); + CHECK(journal_read(j, &rawheader, sizeof(rawheader))); + + if (memcmp(rawheader.h.format, journal_header_ver1.format, + sizeof(journal_header_ver1.format)) == 0) + { + /* + * The file header says it's the old format, but it + * still might have the new xhdr format because we + * forgot to change the format string when we introduced + * the new xhdr. When we first try to read it, we assume + * it uses the new xhdr format. If that fails, we'll be + * called a second time with compat set to true, in which + * case we can lower xhdr_version to 1 if we find a + * corrupt transaction. + */ + j->header_ver1 = true; + } else if (memcmp(rawheader.h.format, initial_journal_header.format, + sizeof(initial_journal_header.format)) == 0) + { + /* + * File header says this is format version 2; all + * transactions have to match. + */ + j->header_ver1 = false; + } else { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal format not recognized", j->filename); + FAIL(ISC_R_UNEXPECTED); + } + journal_header_decode(&rawheader, &j->header); + + /* + * If there is an index, read the raw index into a dynamically + * allocated buffer and then convert it into a cooked index. + */ + if (j->header.index_size != 0) { + unsigned int i; + unsigned int rawbytes; + unsigned char *p; + + rawbytes = j->header.index_size * sizeof(journal_rawpos_t); + j->rawindex = isc_mem_get(mctx, rawbytes); + + CHECK(journal_read(j, j->rawindex, rawbytes)); + + j->index = isc_mem_get(mctx, j->header.index_size * + sizeof(journal_pos_t)); + + p = j->rawindex; + for (i = 0; i < j->header.index_size; i++) { + j->index[i].serial = decode_uint32(p); + p += 4; + j->index[i].offset = decode_uint32(p); + p += 4; + } + INSIST(p == j->rawindex + rawbytes); + } + j->offset = -1; /* Invalid, must seek explicitly. */ + + /* + * Initialize the iterator. + */ + dns_name_init(&j->it.name, NULL); + dns_rdata_init(&j->it.rdata); + + /* + * Set up empty initial buffers for unchecked and checked + * wire format RR data. They will be reallocated + * later. + */ + isc_buffer_init(&j->it.source, NULL, 0); + isc_buffer_init(&j->it.target, NULL, 0); + dns_decompress_init(&j->it.dctx, -1, DNS_DECOMPRESS_NONE); + + j->state = writable ? JOURNAL_STATE_WRITE : JOURNAL_STATE_READ; + + *journalp = j; + return (ISC_R_SUCCESS); + +failure: + j->magic = 0; + if (j->rawindex != NULL) { + isc_mem_put(j->mctx, j->rawindex, + j->header.index_size * sizeof(journal_rawpos_t)); + } + if (j->index != NULL) { + isc_mem_put(j->mctx, j->index, + j->header.index_size * sizeof(journal_pos_t)); + } + isc_mem_free(j->mctx, j->filename); + if (j->fp != NULL) { + (void)isc_stdio_close(j->fp); + } + isc_mem_putanddetach(&j->mctx, j, sizeof(*j)); + return (result); +} + +isc_result_t +dns_journal_open(isc_mem_t *mctx, const char *filename, unsigned int mode, + dns_journal_t **journalp) { + isc_result_t result; + size_t namelen; + char backup[1024]; + bool writable, create; + + create = ((mode & DNS_JOURNAL_CREATE) != 0); + writable = ((mode & (DNS_JOURNAL_WRITE | DNS_JOURNAL_CREATE)) != 0); + + result = journal_open(mctx, filename, writable, create, false, + journalp); + if (result == ISC_R_NOTFOUND) { + namelen = strlen(filename); + if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0) + { + namelen -= 4; + } + + result = snprintf(backup, sizeof(backup), "%.*s.jbk", + (int)namelen, filename); + if (result >= sizeof(backup)) { + return (ISC_R_NOSPACE); + } + result = journal_open(mctx, backup, writable, writable, false, + journalp); + } + return (result); +} + +/* + * A comparison function defining the sorting order for + * entries in the IXFR-style journal file. + * + * The IXFR format requires that deletions are sorted before + * additions, and within either one, SOA records are sorted + * before others. + * + * Also sort the non-SOA records by type as a courtesy to the + * server receiving the IXFR - it may help reduce the amount of + * rdataset merging it has to do. + */ +static int +ixfr_order(const void *av, const void *bv) { + dns_difftuple_t const *const *ap = av; + dns_difftuple_t const *const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + int r; + int bop = 0, aop = 0; + + switch (a->op) { + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + aop = 1; + break; + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + aop = 0; + break; + default: + UNREACHABLE(); + } + + switch (b->op) { + case DNS_DIFFOP_DEL: + case DNS_DIFFOP_DELRESIGN: + bop = 1; + break; + case DNS_DIFFOP_ADD: + case DNS_DIFFOP_ADDRESIGN: + bop = 0; + break; + default: + UNREACHABLE(); + } + + r = bop - aop; + if (r != 0) { + return (r); + } + + r = (b->rdata.type == dns_rdatatype_soa) - + (a->rdata.type == dns_rdatatype_soa); + if (r != 0) { + return (r); + } + + r = (a->rdata.type - b->rdata.type); + return (r); +} + +static isc_result_t +maybe_fixup_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr, uint32_t serial, + isc_offset_t offset) { + isc_result_t result = ISC_R_SUCCESS; + + /* + * Handle mixture of version 1 and version 2 + * transaction headers in a version 1 journal. + */ + if ((xhdr->serial0 != serial || + isc_serial_le(xhdr->serial1, xhdr->serial0))) + { + if (j->xhdr_version == XHDR_VERSION1 && xhdr->serial1 == serial) + { + isc_log_write( + JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3), + "%s: XHDR_VERSION1 -> XHDR_VERSION2 at %u", + j->filename, serial); + j->xhdr_version = XHDR_VERSION2; + CHECK(journal_seek(j, offset)); + CHECK(journal_read_xhdr(j, xhdr)); + j->recovered = true; + } else if (j->xhdr_version == XHDR_VERSION2 && + xhdr->count == serial) + { + isc_log_write( + JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3), + "%s: XHDR_VERSION2 -> XHDR_VERSION1 at %u", + j->filename, serial); + j->xhdr_version = XHDR_VERSION1; + CHECK(journal_seek(j, offset)); + CHECK(journal_read_xhdr(j, xhdr)); + j->recovered = true; + } + } + + /* + * Handle transaction header. + */ + if (j->xhdr_version == XHDR_VERSION1) { + uint32_t value; + + CHECK(journal_read(j, &value, sizeof(value))); + if (value != 0L) { + CHECK(journal_seek(j, offset + 12)); + } else { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3), + "%s: XHDR_VERSION1 count zero at %u", + j->filename, serial); + j->xhdr_version = XHDR_VERSION2; + j->recovered = true; + } + } else if (j->xhdr_version == XHDR_VERSION2 && xhdr->count == serial && + xhdr->serial1 == 0U && + isc_serial_gt(xhdr->serial0, xhdr->count)) + { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(3), + "%s: XHDR_VERSION2 count zero at %u", j->filename, + serial); + xhdr->serial1 = xhdr->serial0; + xhdr->serial0 = xhdr->count; + xhdr->count = 0; + j->recovered = true; + } + +failure: + return (result); +} + +/* + * Advance '*pos' to the next journal transaction. + * + * Requires: + * *pos refers to a valid journal transaction. + * + * Ensures: + * When ISC_R_SUCCESS is returned, + * *pos refers to the next journal transaction. + * + * Returns one of: + * + * ISC_R_SUCCESS + * ISC_R_NOMORE *pos pointed at the last transaction + * Other results due to file errors are possible. + */ +static isc_result_t +journal_next(dns_journal_t *j, journal_pos_t *pos) { + isc_result_t result; + journal_xhdr_t xhdr; + size_t hdrsize; + + REQUIRE(DNS_JOURNAL_VALID(j)); + + result = journal_seek(j, pos->offset); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (pos->serial == j->header.end.serial) { + return (ISC_R_NOMORE); + } + + /* + * Read the header of the current transaction. + * This will return ISC_R_NOMORE if we are at EOF. + */ + result = journal_read_xhdr(j, &xhdr); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (j->header_ver1) { + CHECK(maybe_fixup_xhdr(j, &xhdr, pos->serial, pos->offset)); + } + + /* + * Check serial number consistency. + */ + if (xhdr.serial0 != pos->serial || + isc_serial_le(xhdr.serial1, xhdr.serial0)) + { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: " + "expected serial %u, got %u", + j->filename, pos->serial, xhdr.serial0); + return (ISC_R_UNEXPECTED); + } + + /* + * Check for offset wraparound. + */ + hdrsize = (j->xhdr_version == XHDR_VERSION2) + ? sizeof(journal_rawxhdr_t) + : sizeof(journal_rawxhdr_ver1_t); + + if ((isc_offset_t)(pos->offset + hdrsize + xhdr.size) < pos->offset) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: offset too large", j->filename); + return (ISC_R_UNEXPECTED); + } + + pos->offset += hdrsize + xhdr.size; + pos->serial = xhdr.serial1; + return (ISC_R_SUCCESS); + +failure: + return (result); +} + +/* + * If the index of the journal 'j' contains an entry "better" + * than '*best_guess', replace '*best_guess' with it. + * + * "Better" means having a serial number closer to 'serial' + * but not greater than 'serial'. + */ +static void +index_find(dns_journal_t *j, uint32_t serial, journal_pos_t *best_guess) { + unsigned int i; + if (j->index == NULL) { + return; + } + for (i = 0; i < j->header.index_size; i++) { + if (POS_VALID(j->index[i]) && + DNS_SERIAL_GE(serial, j->index[i].serial) && + DNS_SERIAL_GT(j->index[i].serial, best_guess->serial)) + { + *best_guess = j->index[i]; + } + } +} + +/* + * Add a new index entry. If there is no room, make room by removing + * the odd-numbered entries and compacting the others into the first + * half of the index. This decimates old index entries exponentially + * over time, so that the index always contains a much larger fraction + * of recent serial numbers than of old ones. This is deliberate - + * most index searches are for outgoing IXFR, and IXFR tends to request + * recent versions more often than old ones. + */ +static void +index_add(dns_journal_t *j, journal_pos_t *pos) { + unsigned int i; + + if (j->index == NULL) { + return; + } + + /* + * Search for a vacant position. + */ + for (i = 0; i < j->header.index_size; i++) { + if (!POS_VALID(j->index[i])) { + break; + } + } + if (i == j->header.index_size) { + unsigned int k = 0; + /* + * Found no vacant position. Make some room. + */ + for (i = 0; i < j->header.index_size; i += 2) { + j->index[k++] = j->index[i]; + } + i = k; /* 'i' identifies the first vacant position. */ + while (k < j->header.index_size) { + POS_INVALIDATE(j->index[k]); + k++; + } + } + INSIST(i < j->header.index_size); + INSIST(!POS_VALID(j->index[i])); + + /* + * Store the new index entry. + */ + j->index[i] = *pos; +} + +/* + * Invalidate any existing index entries that could become + * ambiguous when a new transaction with number 'serial' is added. + */ +static void +index_invalidate(dns_journal_t *j, uint32_t serial) { + unsigned int i; + if (j->index == NULL) { + return; + } + for (i = 0; i < j->header.index_size; i++) { + if (!DNS_SERIAL_GT(serial, j->index[i].serial)) { + POS_INVALIDATE(j->index[i]); + } + } +} + +/* + * Try to find a transaction with initial serial number 'serial' + * in the journal 'j'. + * + * If found, store its position at '*pos' and return ISC_R_SUCCESS. + * + * If 'serial' is current (= the ending serial number of the + * last transaction in the journal), set '*pos' to + * the position immediately following the last transaction and + * return ISC_R_SUCCESS. + * + * If 'serial' is within the range of addressable serial numbers + * covered by the journal but that particular serial number is missing + * (from the journal, not just from the index), return ISC_R_NOTFOUND. + * + * If 'serial' is outside the range of addressable serial numbers + * covered by the journal, return ISC_R_RANGE. + * + */ +static isc_result_t +journal_find(dns_journal_t *j, uint32_t serial, journal_pos_t *pos) { + isc_result_t result; + journal_pos_t current_pos; + + REQUIRE(DNS_JOURNAL_VALID(j)); + + if (DNS_SERIAL_GT(j->header.begin.serial, serial)) { + return (ISC_R_RANGE); + } + if (DNS_SERIAL_GT(serial, j->header.end.serial)) { + return (ISC_R_RANGE); + } + if (serial == j->header.end.serial) { + *pos = j->header.end; + return (ISC_R_SUCCESS); + } + + current_pos = j->header.begin; + index_find(j, serial, ¤t_pos); + + while (current_pos.serial != serial) { + if (DNS_SERIAL_GT(current_pos.serial, serial)) { + return (ISC_R_NOTFOUND); + } + result = journal_next(j, ¤t_pos); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + *pos = current_pos; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_journal_begin_transaction(dns_journal_t *j) { + uint32_t offset; + isc_result_t result; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(j->state == JOURNAL_STATE_WRITE || + j->state == JOURNAL_STATE_INLINE); + + /* + * Find the file offset where the new transaction should + * be written, and seek there. + */ + if (JOURNAL_EMPTY(&j->header)) { + offset = sizeof(journal_rawheader_t) + + j->header.index_size * sizeof(journal_rawpos_t); + } else { + offset = j->header.end.offset; + } + j->x.pos[0].offset = offset; + j->x.pos[1].offset = offset; /* Initial value, will be incremented. */ + j->x.n_soa = 0; + + CHECK(journal_seek(j, offset)); + + /* + * Write a dummy transaction header of all zeroes to reserve + * space. It will be filled in when the transaction is + * finished. + */ + CHECK(journal_write_xhdr(j, 0, 0, 0, 0)); + j->x.pos[1].offset = j->offset; + + j->state = JOURNAL_STATE_TRANSACTION; + result = ISC_R_SUCCESS; +failure: + return (result); +} + +isc_result_t +dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) { + dns_difftuple_t *t; + isc_buffer_t buffer; + void *mem = NULL; + uint64_t size = 0; + uint32_t rrcount = 0; + isc_result_t result; + isc_region_t used; + + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(j->state == JOURNAL_STATE_TRANSACTION); + + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "writing to journal"); + (void)dns_diff_print(diff, NULL); + + /* + * Pass 1: determine the buffer size needed, and + * keep track of SOA serial numbers. + */ + for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + if (t->rdata.type == dns_rdatatype_soa) { + if (j->x.n_soa < 2) { + j->x.pos[j->x.n_soa].serial = + dns_soa_getserial(&t->rdata); + } + j->x.n_soa++; + } + size += sizeof(journal_rawrrhdr_t); + size += t->name.length; /* XXX should have access macro? */ + size += 10; + size += t->rdata.length; + } + + if (size >= DNS_JOURNAL_SIZE_MAX) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "dns_journal_writediff: %s: journal entry " + "too big to be stored: %" PRIu64 " bytes", + j->filename, size); + return (ISC_R_NOSPACE); + } + + mem = isc_mem_get(j->mctx, size); + + isc_buffer_init(&buffer, mem, size); + + /* + * Pass 2. Write RRs to buffer. + */ + for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + /* + * Write the RR header. + */ + isc_buffer_putuint32(&buffer, + t->name.length + 10 + t->rdata.length); + /* + * Write the owner name, RR header, and RR data. + */ + isc_buffer_putmem(&buffer, t->name.ndata, t->name.length); + isc_buffer_putuint16(&buffer, t->rdata.type); + isc_buffer_putuint16(&buffer, t->rdata.rdclass); + isc_buffer_putuint32(&buffer, t->ttl); + INSIST(t->rdata.length < 65536); + isc_buffer_putuint16(&buffer, (uint16_t)t->rdata.length); + INSIST(isc_buffer_availablelength(&buffer) >= t->rdata.length); + isc_buffer_putmem(&buffer, t->rdata.data, t->rdata.length); + + rrcount++; + } + + isc_buffer_usedregion(&buffer, &used); + INSIST(used.length == size); + + j->x.pos[1].offset += used.length; + j->x.n_rr = rrcount; + + /* + * Write the buffer contents to the journal file. + */ + CHECK(journal_write(j, used.base, used.length)); + + result = ISC_R_SUCCESS; + +failure: + if (mem != NULL) { + isc_mem_put(j->mctx, mem, size); + } + return (result); +} + +isc_result_t +dns_journal_commit(dns_journal_t *j) { + isc_result_t result; + journal_rawheader_t rawheader; + uint64_t total; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(j->state == JOURNAL_STATE_TRANSACTION || + j->state == JOURNAL_STATE_INLINE); + + /* + * Just write out a updated header. + */ + if (j->state == JOURNAL_STATE_INLINE) { + CHECK(journal_fsync(j)); + journal_header_encode(&j->header, &rawheader); + CHECK(journal_seek(j, 0)); + CHECK(journal_write(j, &rawheader, sizeof(rawheader))); + CHECK(journal_fsync(j)); + j->state = JOURNAL_STATE_WRITE; + return (ISC_R_SUCCESS); + } + + /* + * Perform some basic consistency checks. + */ + if (j->x.n_soa != 2) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: malformed transaction: %d SOAs", j->filename, + j->x.n_soa); + return (ISC_R_UNEXPECTED); + } + if (!DNS_SERIAL_GT(j->x.pos[1].serial, j->x.pos[0].serial)) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: malformed transaction: serial number " + "did not increase", + j->filename); + return (ISC_R_UNEXPECTED); + } + if (!JOURNAL_EMPTY(&j->header)) { + if (j->x.pos[0].serial != j->header.end.serial) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "malformed transaction: " + "%s last serial %u != " + "transaction first serial %u", + j->filename, j->header.end.serial, + j->x.pos[0].serial); + return (ISC_R_UNEXPECTED); + } + } + + /* + * We currently don't support huge journal entries. + */ + total = j->x.pos[1].offset - j->x.pos[0].offset; + if (total >= DNS_JOURNAL_SIZE_MAX) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "transaction too big to be stored in journal: " + "%" PRIu64 "b (max is %" PRIu64 "b)", + total, (uint64_t)DNS_JOURNAL_SIZE_MAX); + return (ISC_R_UNEXPECTED); + } + + /* + * Some old journal entries may become non-addressable + * when we increment the current serial number. Purge them + * by stepping header.begin forward to the first addressable + * transaction. Also purge them from the index. + */ + if (!JOURNAL_EMPTY(&j->header)) { + while (!DNS_SERIAL_GT(j->x.pos[1].serial, + j->header.begin.serial)) + { + CHECK(journal_next(j, &j->header.begin)); + } + index_invalidate(j, j->x.pos[1].serial); + } +#ifdef notyet + if (DNS_SERIAL_GT(last_dumped_serial, j->x.pos[1].serial)) { + force_dump(...); + } +#endif /* ifdef notyet */ + + /* + * Commit the transaction data to stable storage. + */ + CHECK(journal_fsync(j)); + + if (j->state == JOURNAL_STATE_TRANSACTION) { + isc_offset_t offset; + offset = (j->x.pos[1].offset - j->x.pos[0].offset) - + (j->header_ver1 ? sizeof(journal_rawxhdr_ver1_t) + : sizeof(journal_rawxhdr_t)); + /* + * Update the transaction header. + */ + CHECK(journal_seek(j, j->x.pos[0].offset)); + CHECK(journal_write_xhdr(j, offset, j->x.n_rr, + j->x.pos[0].serial, + j->x.pos[1].serial)); + } + + /* + * Update the journal header. + */ + if (JOURNAL_EMPTY(&j->header)) { + j->header.begin = j->x.pos[0]; + } + j->header.end = j->x.pos[1]; + journal_header_encode(&j->header, &rawheader); + CHECK(journal_seek(j, 0)); + CHECK(journal_write(j, &rawheader, sizeof(rawheader))); + + /* + * Update the index. + */ + index_add(j, &j->x.pos[0]); + + /* + * Convert the index into on-disk format and write + * it to disk. + */ + CHECK(index_to_disk(j)); + + /* + * Commit the header to stable storage. + */ + CHECK(journal_fsync(j)); + + /* + * We no longer have a transaction open. + */ + j->state = JOURNAL_STATE_WRITE; + + result = ISC_R_SUCCESS; + +failure: + return (result); +} + +isc_result_t +dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff) { + isc_result_t result; + + CHECK(dns_diff_sort(diff, ixfr_order)); + CHECK(dns_journal_begin_transaction(j)); + CHECK(dns_journal_writediff(j, diff)); + CHECK(dns_journal_commit(j)); + result = ISC_R_SUCCESS; +failure: + return (result); +} + +void +dns_journal_destroy(dns_journal_t **journalp) { + dns_journal_t *j = NULL; + + REQUIRE(journalp != NULL); + REQUIRE(DNS_JOURNAL_VALID(*journalp)); + + j = *journalp; + *journalp = NULL; + + j->it.result = ISC_R_FAILURE; + dns_name_invalidate(&j->it.name); + dns_decompress_invalidate(&j->it.dctx); + if (j->rawindex != NULL) { + isc_mem_put(j->mctx, j->rawindex, + j->header.index_size * sizeof(journal_rawpos_t)); + } + if (j->index != NULL) { + isc_mem_put(j->mctx, j->index, + j->header.index_size * sizeof(journal_pos_t)); + } + if (j->it.target.base != NULL) { + isc_mem_put(j->mctx, j->it.target.base, j->it.target.length); + } + if (j->it.source.base != NULL) { + isc_mem_put(j->mctx, j->it.source.base, j->it.source.length); + } + if (j->filename != NULL) { + isc_mem_free(j->mctx, j->filename); + } + if (j->fp != NULL) { + (void)isc_stdio_close(j->fp); + } + j->magic = 0; + isc_mem_putanddetach(&j->mctx, j, sizeof(*j)); +} + +/* + * Roll the open journal 'j' into the database 'db'. + * A new database version will be created. + */ + +/* XXX Share code with incoming IXFR? */ + +isc_result_t +dns_journal_rollforward(dns_journal_t *j, dns_db_t *db, unsigned int options) { + isc_buffer_t source; /* Transaction data from disk */ + isc_buffer_t target; /* Ditto after _fromwire check */ + uint32_t db_serial; /* Database SOA serial */ + uint32_t end_serial; /* Last journal SOA serial */ + isc_result_t result; + dns_dbversion_t *ver = NULL; + journal_pos_t pos; + dns_diff_t diff; + unsigned int n_soa = 0; + unsigned int n_put = 0; + dns_diffop_t op; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(DNS_DB_VALID(db)); + + dns_diff_init(j->mctx, &diff); + + /* + * Set up empty initial buffers for unchecked and checked + * wire format transaction data. They will be reallocated + * later. + */ + isc_buffer_init(&source, NULL, 0); + isc_buffer_init(&target, NULL, 0); + + /* + * Create the new database version. + */ + CHECK(dns_db_newversion(db, &ver)); + + /* + * Get the current database SOA serial number. + */ + CHECK(dns_db_getsoaserial(db, ver, &db_serial)); + + /* + * Locate a journal entry for the current database serial. + */ + CHECK(journal_find(j, db_serial, &pos)); + + end_serial = dns_journal_last_serial(j); + + /* + * If we're reading a version 1 file, scan all the transactions + * to see if the journal needs rewriting: if any outdated + * transaction headers are found, j->recovered will be set. + */ + if (j->header_ver1) { + uint32_t start_serial = dns_journal_first_serial(j); + + CHECK(dns_journal_iter_init(j, start_serial, db_serial, NULL)); + for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + continue; + } + } + + if (db_serial == end_serial) { + CHECK(DNS_R_UPTODATE); + } + + CHECK(dns_journal_iter_init(j, db_serial, end_serial, NULL)); + for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name = NULL; + dns_rdata_t *rdata = NULL; + dns_difftuple_t *tuple = NULL; + uint32_t ttl; + + dns_journal_current_rr(j, &name, &ttl, &rdata); + + if (rdata->type == dns_rdatatype_soa) { + n_soa++; + if (n_soa == 2) { + db_serial = j->it.current_serial; + } + } + + if (n_soa == 3) { + n_soa = 1; + } + if (n_soa == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: missing " + "initial SOA", + j->filename); + FAIL(ISC_R_UNEXPECTED); + } + if ((options & DNS_JOURNALOPT_RESIGN) != 0) { + op = (n_soa == 1) ? DNS_DIFFOP_DELRESIGN + : DNS_DIFFOP_ADDRESIGN; + } else { + op = (n_soa == 1) ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD; + } + + CHECK(dns_difftuple_create(diff.mctx, op, name, ttl, rdata, + &tuple)); + dns_diff_append(&diff, &tuple); + + if (++n_put > 100) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), + "%s: applying diff to database (%u)", + j->filename, db_serial); + (void)dns_diff_print(&diff, NULL); + CHECK(dns_diff_apply(&diff, db, ver)); + dns_diff_clear(&diff); + n_put = 0; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + CHECK(result); + + if (n_put != 0) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), + "%s: applying final diff to database (%u)", + j->filename, db_serial); + (void)dns_diff_print(&diff, NULL); + CHECK(dns_diff_apply(&diff, db, ver)); + dns_diff_clear(&diff); + } + +failure: + if (ver != NULL) { + dns_db_closeversion(db, &ver, + result == ISC_R_SUCCESS ? true : false); + } + + if (source.base != NULL) { + isc_mem_put(j->mctx, source.base, source.length); + } + if (target.base != NULL) { + isc_mem_put(j->mctx, target.base, target.length); + } + + dns_diff_clear(&diff); + + INSIST(ver == NULL); + + return (result); +} + +isc_result_t +dns_journal_print(isc_mem_t *mctx, uint32_t flags, const char *filename, + FILE *file) { + dns_journal_t *j = NULL; + isc_buffer_t source; /* Transaction data from disk */ + isc_buffer_t target; /* Ditto after _fromwire check */ + uint32_t start_serial; /* Database SOA serial */ + uint32_t end_serial; /* Last journal SOA serial */ + isc_result_t result; + dns_diff_t diff; + unsigned int n_soa = 0; + unsigned int n_put = 0; + bool printxhdr = ((flags & DNS_JOURNAL_PRINTXHDR) != 0); + + REQUIRE(filename != NULL); + + result = dns_journal_open(mctx, filename, DNS_JOURNAL_READ, &j); + if (result == ISC_R_NOTFOUND) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no journal file"); + return (DNS_R_NOJOURNAL); + } else if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "journal open failure: %s: %s", + isc_result_totext(result), filename); + return (result); + } + + if (printxhdr) { + fprintf(file, "Journal format = %sHeader version = %d\n", + j->header.format + 1, j->header_ver1 ? 1 : 2); + fprintf(file, "Start serial = %u\n", j->header.begin.serial); + fprintf(file, "End serial = %u\n", j->header.end.serial); + fprintf(file, "Index (size = %u):\n", j->header.index_size); + for (uint32_t i = 0; i < j->header.index_size; i++) { + if (j->index[i].offset == 0) { + fputc('\n', file); + break; + } + fprintf(file, "%lld", (long long)j->index[i].offset); + fputc((i + 1) % 8 == 0 ? '\n' : ' ', file); + } + } + if (j->header.serialset) { + fprintf(file, "Source serial = %u\n", j->header.sourceserial); + } + dns_diff_init(j->mctx, &diff); + + /* + * Set up empty initial buffers for unchecked and checked + * wire format transaction data. They will be reallocated + * later. + */ + isc_buffer_init(&source, NULL, 0); + isc_buffer_init(&target, NULL, 0); + + start_serial = dns_journal_first_serial(j); + end_serial = dns_journal_last_serial(j); + + CHECK(dns_journal_iter_init(j, start_serial, end_serial, NULL)); + + for (result = dns_journal_first_rr(j); result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name = NULL; + dns_rdata_t *rdata = NULL; + dns_difftuple_t *tuple = NULL; + static uint32_t i = 0; + bool print = false; + uint32_t ttl; + + dns_journal_current_rr(j, &name, &ttl, &rdata); + + if (rdata->type == dns_rdatatype_soa) { + n_soa++; + if (n_soa == 3) { + n_soa = 1; + } + if (n_soa == 1) { + print = printxhdr; + } + } + if (n_soa == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: missing " + "initial SOA", + j->filename); + FAIL(ISC_R_UNEXPECTED); + } + + if (print) { + fprintf(file, + "Transaction: version %d offset %lld size %u " + "rrcount %u start %u end %u\n", + j->xhdr_version, (long long)j->it.cpos.offset, + j->curxhdr.size, j->curxhdr.count, + j->curxhdr.serial0, j->curxhdr.serial1); + if (j->it.cpos.offset > j->index[i].offset) { + fprintf(file, + "ERROR: Offset mismatch, " + "expected %lld\n", + (long long)j->index[i].offset); + } else if (j->it.cpos.offset == j->index[i].offset) { + i++; + } + } + CHECK(dns_difftuple_create( + diff.mctx, n_soa == 1 ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD, + name, ttl, rdata, &tuple)); + dns_diff_append(&diff, &tuple); + + if (++n_put > 100 || printxhdr) { + result = dns_diff_print(&diff, file); + dns_diff_clear(&diff); + n_put = 0; + if (result != ISC_R_SUCCESS) { + break; + } + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + CHECK(result); + + if (n_put != 0) { + result = dns_diff_print(&diff, file); + dns_diff_clear(&diff); + } + goto cleanup; + +failure: + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: cannot print: journal file corrupt", j->filename); + +cleanup: + if (source.base != NULL) { + isc_mem_put(j->mctx, source.base, source.length); + } + if (target.base != NULL) { + isc_mem_put(j->mctx, target.base, target.length); + } + + dns_diff_clear(&diff); + dns_journal_destroy(&j); + + return (result); +} + +/**************************************************************************/ +/* + * Miscellaneous accessors. + */ +bool +dns_journal_empty(dns_journal_t *j) { + return (JOURNAL_EMPTY(&j->header)); +} + +bool +dns_journal_recovered(dns_journal_t *j) { + return (j->recovered); +} + +uint32_t +dns_journal_first_serial(dns_journal_t *j) { + return (j->header.begin.serial); +} + +uint32_t +dns_journal_last_serial(dns_journal_t *j) { + return (j->header.end.serial); +} + +void +dns_journal_set_sourceserial(dns_journal_t *j, uint32_t sourceserial) { + REQUIRE(j->state == JOURNAL_STATE_WRITE || + j->state == JOURNAL_STATE_INLINE || + j->state == JOURNAL_STATE_TRANSACTION); + + j->header.sourceserial = sourceserial; + j->header.serialset = true; + if (j->state == JOURNAL_STATE_WRITE) { + j->state = JOURNAL_STATE_INLINE; + } +} + +bool +dns_journal_get_sourceserial(dns_journal_t *j, uint32_t *sourceserial) { + REQUIRE(sourceserial != NULL); + + if (!j->header.serialset) { + return (false); + } + *sourceserial = j->header.sourceserial; + return (true); +} + +/**************************************************************************/ +/* + * Iteration support. + * + * When serving an outgoing IXFR, we transmit a part the journal starting + * at the serial number in the IXFR request and ending at the serial + * number that is current when the IXFR request arrives. The ending + * serial number is not necessarily at the end of the journal: + * the journal may grow while the IXFR is in progress, but we stop + * when we reach the serial number that was current when the IXFR started. + */ + +static isc_result_t +read_one_rr(dns_journal_t *j); + +/* + * Make sure the buffer 'b' is has at least 'size' bytes + * allocated, and clear it. + * + * Requires: + * Either b->base is NULL, or it points to b->length bytes of memory + * previously allocated by isc_mem_get(). + */ + +static isc_result_t +size_buffer(isc_mem_t *mctx, isc_buffer_t *b, unsigned size) { + if (b->length < size) { + void *mem = isc_mem_get(mctx, size); + if (mem == NULL) { + return (ISC_R_NOMEMORY); + } + if (b->base != NULL) { + isc_mem_put(mctx, b->base, b->length); + } + b->base = mem; + b->length = size; + } + isc_buffer_clear(b); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_journal_iter_init(dns_journal_t *j, uint32_t begin_serial, + uint32_t end_serial, size_t *xfrsizep) { + isc_result_t result; + + CHECK(journal_find(j, begin_serial, &j->it.bpos)); + INSIST(j->it.bpos.serial == begin_serial); + + CHECK(journal_find(j, end_serial, &j->it.epos)); + INSIST(j->it.epos.serial == end_serial); + + if (xfrsizep != NULL) { + journal_pos_t pos = j->it.bpos; + journal_xhdr_t xhdr; + uint64_t size = 0; + uint32_t count = 0; + + /* + * We already know the beginning and ending serial + * numbers are in the journal. Scan through them, + * adding up sizes and RR counts so we can calculate + * the IXFR size. + */ + do { + CHECK(journal_seek(j, pos.offset)); + CHECK(journal_read_xhdr(j, &xhdr)); + + if (j->header_ver1) { + CHECK(maybe_fixup_xhdr(j, &xhdr, pos.serial, + pos.offset)); + } + + /* + * Check that xhdr is consistent. + */ + if (xhdr.serial0 != pos.serial || + isc_serial_le(xhdr.serial1, xhdr.serial0)) + { + CHECK(ISC_R_UNEXPECTED); + } + + size += xhdr.size; + count += xhdr.count; + + result = journal_next(j, &pos); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + CHECK(result); + } while (pos.serial != end_serial); + + /* + * For each RR, subtract the length of the RR header, + * as this would not be present in IXFR messages. + * (We don't need to worry about the transaction header + * because that was already excluded from xdr.size.) + */ + *xfrsizep = size - (count * sizeof(journal_rawrrhdr_t)); + } + + result = ISC_R_SUCCESS; +failure: + j->it.result = result; + return (j->it.result); +} + +isc_result_t +dns_journal_first_rr(dns_journal_t *j) { + isc_result_t result; + + /* + * Seek to the beginning of the first transaction we are + * interested in. + */ + CHECK(journal_seek(j, j->it.bpos.offset)); + j->it.current_serial = j->it.bpos.serial; + + j->it.xsize = 0; /* We have no transaction data yet... */ + j->it.xpos = 0; /* ...and haven't used any of it. */ + + return (read_one_rr(j)); + +failure: + return (result); +} + +static isc_result_t +read_one_rr(dns_journal_t *j) { + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + unsigned int rdlen; + uint32_t ttl; + journal_xhdr_t xhdr; + journal_rrhdr_t rrhdr; + dns_journal_t save = *j; + + if (j->offset > j->it.epos.offset) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal corrupt: possible integer overflow", + j->filename); + return (ISC_R_UNEXPECTED); + } + if (j->offset == j->it.epos.offset) { + return (ISC_R_NOMORE); + } + if (j->it.xpos == j->it.xsize) { + /* + * We are at a transaction boundary. + * Read another transaction header. + */ + CHECK(journal_read_xhdr(j, &xhdr)); + if (xhdr.size == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal corrupt: empty transaction", + j->filename); + FAIL(ISC_R_UNEXPECTED); + } + + if (j->header_ver1) { + CHECK(maybe_fixup_xhdr(j, &xhdr, j->it.current_serial, + save.offset)); + } + + if (xhdr.serial0 != j->it.current_serial || + isc_serial_le(xhdr.serial1, xhdr.serial0)) + { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: " + "expected serial %u, got %u", + j->filename, j->it.current_serial, + xhdr.serial0); + FAIL(ISC_R_UNEXPECTED); + } + + j->it.xsize = xhdr.size; + j->it.xpos = 0; + } + /* + * Read an RR. + */ + CHECK(journal_read_rrhdr(j, &rrhdr)); + /* + * Perform a sanity check on the journal RR size. + * The smallest possible RR has a 1-byte owner name + * and a 10-byte header. The largest possible + * RR has 65535 bytes of data, a header, and a maximum- + * size owner name, well below 70 k total. + */ + if (rrhdr.size < 1 + 10 || rrhdr.size > 70000) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal corrupt: impossible RR size " + "(%d bytes)", + j->filename, rrhdr.size); + FAIL(ISC_R_UNEXPECTED); + } + + CHECK(size_buffer(j->mctx, &j->it.source, rrhdr.size)); + CHECK(journal_read(j, j->it.source.base, rrhdr.size)); + isc_buffer_add(&j->it.source, rrhdr.size); + + /* + * The target buffer is made the same size + * as the source buffer, with the assumption that when + * no compression in present, the output of dns_*_fromwire() + * is no larger than the input. + */ + CHECK(size_buffer(j->mctx, &j->it.target, rrhdr.size)); + + /* + * Parse the owner name. We don't know where it + * ends yet, so we make the entire "remaining" + * part of the buffer "active". + */ + isc_buffer_setactive(&j->it.source, + j->it.source.used - j->it.source.current); + CHECK(dns_name_fromwire(&j->it.name, &j->it.source, &j->it.dctx, 0, + &j->it.target)); + + /* + * Check that the RR header is there, and parse it. + */ + if (isc_buffer_remaininglength(&j->it.source) < 10) { + FAIL(DNS_R_FORMERR); + } + + rdtype = isc_buffer_getuint16(&j->it.source); + rdclass = isc_buffer_getuint16(&j->it.source); + ttl = isc_buffer_getuint32(&j->it.source); + rdlen = isc_buffer_getuint16(&j->it.source); + + if (rdlen > DNS_RDATA_MAXLENGTH) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal corrupt: impossible rdlen " + "(%u bytes)", + j->filename, rdlen); + FAIL(ISC_R_FAILURE); + } + + /* + * Parse the rdata. + */ + if (isc_buffer_remaininglength(&j->it.source) != rdlen) { + FAIL(DNS_R_FORMERR); + } + isc_buffer_setactive(&j->it.source, rdlen); + dns_rdata_reset(&j->it.rdata); + CHECK(dns_rdata_fromwire(&j->it.rdata, rdclass, rdtype, &j->it.source, + &j->it.dctx, 0, &j->it.target)); + j->it.ttl = ttl; + + j->it.xpos += sizeof(journal_rawrrhdr_t) + rrhdr.size; + if (rdtype == dns_rdatatype_soa) { + /* XXX could do additional consistency checks here */ + j->it.current_serial = dns_soa_getserial(&j->it.rdata); + } + + result = ISC_R_SUCCESS; + +failure: + j->it.result = result; + return (result); +} + +isc_result_t +dns_journal_next_rr(dns_journal_t *j) { + j->it.result = read_one_rr(j); + return (j->it.result); +} + +void +dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, uint32_t *ttl, + dns_rdata_t **rdata) { + REQUIRE(j->it.result == ISC_R_SUCCESS); + *name = &j->it.name; + *ttl = j->it.ttl; + *rdata = &j->it.rdata; +} + +/**************************************************************************/ +/* + * Generating diffs from databases + */ + +/* + * Construct a diff containing all the RRs at the current name of the + * database iterator 'dbit' in database 'db', version 'ver'. + * Set '*name' to the current name, and append the diff to 'diff'. + * All new tuples will have the operation 'op'. + * + * Requires: 'name' must have buffer large enough to hold the name. + * Typically, a dns_fixedname_t would be used. + */ +static isc_result_t +get_name_diff(dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, + dns_dbiterator_t *dbit, dns_name_t *name, dns_diffop_t op, + dns_diff_t *diff) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_difftuple_t *tuple = NULL; + + result = dns_dbiterator_current(dbit, &node, name); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_allrdatasets(db, node, ver, 0, now, &rdsiter); + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + result = dns_difftuple_create(diff->mctx, op, name, + rdataset.ttl, &rdata, + &tuple); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + goto cleanup_iterator; + } + dns_diff_append(diff, &tuple); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + goto cleanup_iterator; + } + } + if (result != ISC_R_NOMORE) { + goto cleanup_iterator; + } + + result = ISC_R_SUCCESS; + +cleanup_iterator: + dns_rdatasetiter_destroy(&rdsiter); + +cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/* + * Comparison function for use by dns_diff_subtract when sorting + * the diffs to be subtracted. The sort keys are the rdata type + * and the rdata itself. The owner name is ignored, because + * it is known to be the same for all tuples. + */ +static int +rdata_order(const void *av, const void *bv) { + dns_difftuple_t const *const *ap = av; + dns_difftuple_t const *const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + int r; + r = (b->rdata.type - a->rdata.type); + if (r != 0) { + return (r); + } + r = dns_rdata_compare(&a->rdata, &b->rdata); + return (r); +} + +static isc_result_t +dns_diff_subtract(dns_diff_t diff[2], dns_diff_t *r) { + isc_result_t result; + dns_difftuple_t *p[2]; + int i, t; + bool append; + dns_difftuplelist_t add, del; + + CHECK(dns_diff_sort(&diff[0], rdata_order)); + CHECK(dns_diff_sort(&diff[1], rdata_order)); + ISC_LIST_INIT(add); + ISC_LIST_INIT(del); + + for (;;) { + p[0] = ISC_LIST_HEAD(diff[0].tuples); + p[1] = ISC_LIST_HEAD(diff[1].tuples); + if (p[0] == NULL && p[1] == NULL) { + break; + } + + for (i = 0; i < 2; i++) { + if (p[!i] == NULL) { + dns_difftuplelist_t *l = (i == 0) ? &add : &del; + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + ISC_LIST_APPEND(*l, p[i], link); + goto next; + } + } + t = rdata_order(&p[0], &p[1]); + if (t < 0) { + ISC_LIST_UNLINK(diff[0].tuples, p[0], link); + ISC_LIST_APPEND(add, p[0], link); + goto next; + } + if (t > 0) { + ISC_LIST_UNLINK(diff[1].tuples, p[1], link); + ISC_LIST_APPEND(del, p[1], link); + goto next; + } + INSIST(t == 0); + /* + * Identical RRs in both databases; skip them both + * if the ttl differs. + */ + append = (p[0]->ttl != p[1]->ttl); + for (i = 0; i < 2; i++) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + if (append) { + dns_difftuplelist_t *l = (i == 0) ? &add : &del; + ISC_LIST_APPEND(*l, p[i], link); + } else { + dns_difftuple_free(&p[i]); + } + } + next:; + } + ISC_LIST_APPENDLIST(r->tuples, del, link); + ISC_LIST_APPENDLIST(r->tuples, add, link); + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +diff_namespace(dns_db_t *dba, dns_dbversion_t *dbvera, dns_db_t *dbb, + dns_dbversion_t *dbverb, unsigned int options, + dns_diff_t *resultdiff) { + dns_db_t *db[2]; + dns_dbversion_t *ver[2]; + dns_dbiterator_t *dbit[2] = { NULL, NULL }; + bool have[2] = { false, false }; + dns_fixedname_t fixname[2]; + isc_result_t result, itresult[2]; + dns_diff_t diff[2]; + int i, t; + + db[0] = dba, db[1] = dbb; + ver[0] = dbvera, ver[1] = dbverb; + + dns_diff_init(resultdiff->mctx, &diff[0]); + dns_diff_init(resultdiff->mctx, &diff[1]); + + dns_fixedname_init(&fixname[0]); + dns_fixedname_init(&fixname[1]); + + result = dns_db_createiterator(db[0], options, &dbit[0]); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_db_createiterator(db[1], options, &dbit[1]); + if (result != ISC_R_SUCCESS) { + goto cleanup_iterator; + } + + itresult[0] = dns_dbiterator_first(dbit[0]); + itresult[1] = dns_dbiterator_first(dbit[1]); + + for (;;) { + for (i = 0; i < 2; i++) { + if (!have[i] && itresult[i] == ISC_R_SUCCESS) { + CHECK(get_name_diff( + db[i], ver[i], 0, dbit[i], + dns_fixedname_name(&fixname[i]), + i == 0 ? DNS_DIFFOP_ADD + : DNS_DIFFOP_DEL, + &diff[i])); + itresult[i] = dns_dbiterator_next(dbit[i]); + have[i] = true; + } + } + + if (!have[0] && !have[1]) { + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + break; + } + + for (i = 0; i < 2; i++) { + if (!have[!i]) { + ISC_LIST_APPENDLIST(resultdiff->tuples, + diff[i].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[i].tuples)); + have[i] = false; + goto next; + } + } + + t = dns_name_compare(dns_fixedname_name(&fixname[0]), + dns_fixedname_name(&fixname[1])); + if (t < 0) { + ISC_LIST_APPENDLIST(resultdiff->tuples, diff[0].tuples, + link); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + have[0] = false; + continue; + } + if (t > 0) { + ISC_LIST_APPENDLIST(resultdiff->tuples, diff[1].tuples, + link); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[1] = false; + continue; + } + INSIST(t == 0); + CHECK(dns_diff_subtract(diff, resultdiff)); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[0] = have[1] = false; + next:; + } + if (itresult[0] != ISC_R_NOMORE) { + FAIL(itresult[0]); + } + if (itresult[1] != ISC_R_NOMORE) { + FAIL(itresult[1]); + } + + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + +failure: + dns_dbiterator_destroy(&dbit[1]); + +cleanup_iterator: + dns_dbiterator_destroy(&dbit[0]); + dns_diff_clear(&diff[0]); + dns_diff_clear(&diff[1]); + return (result); +} + +/* + * Compare the databases 'dba' and 'dbb' and generate a journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. + */ +isc_result_t +dns_db_diff(isc_mem_t *mctx, dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, const char *filename) { + isc_result_t result; + dns_diff_t diff; + + dns_diff_init(mctx, &diff); + + result = dns_db_diffx(&diff, dba, dbvera, dbb, dbverb, filename); + + dns_diff_clear(&diff); + + return (result); +} + +isc_result_t +dns_db_diffx(dns_diff_t *diff, dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, const char *filename) { + isc_result_t result; + dns_journal_t *journal = NULL; + + if (filename != NULL) { + result = dns_journal_open(diff->mctx, filename, + DNS_JOURNAL_CREATE, &journal); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + CHECK(diff_namespace(dba, dbvera, dbb, dbverb, DNS_DB_NONSEC3, diff)); + CHECK(diff_namespace(dba, dbvera, dbb, dbverb, DNS_DB_NSEC3ONLY, diff)); + + if (journal != NULL) { + if (ISC_LIST_EMPTY(diff->tuples)) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no changes"); + } else { + CHECK(dns_journal_write_transaction(journal, diff)); + } + } + +failure: + if (journal != NULL) { + dns_journal_destroy(&journal); + } + return (result); +} + +static uint32_t +rrcount(unsigned char *buf, unsigned int size) { + isc_buffer_t b; + uint32_t rrsize, count = 0; + + isc_buffer_init(&b, buf, size); + isc_buffer_add(&b, size); + while (isc_buffer_remaininglength(&b) > 0) { + rrsize = isc_buffer_getuint32(&b); + INSIST(isc_buffer_remaininglength(&b) >= rrsize); + isc_buffer_forward(&b, rrsize); + count++; + } + + return (count); +} + +static bool +check_delta(unsigned char *buf, size_t size) { + isc_buffer_t b; + uint32_t rrsize; + + isc_buffer_init(&b, buf, size); + isc_buffer_add(&b, size); + while (isc_buffer_remaininglength(&b) > 0) { + if (isc_buffer_remaininglength(&b) < 4) { + return (false); + } + rrsize = isc_buffer_getuint32(&b); + /* "." + type + class + ttl + rdlen => 11U */ + if (rrsize < 11U || isc_buffer_remaininglength(&b) < rrsize) { + return (false); + } + isc_buffer_forward(&b, rrsize); + } + + return (true); +} + +isc_result_t +dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial, + uint32_t flags, uint32_t target_size) { + unsigned int i; + journal_pos_t best_guess; + journal_pos_t current_pos; + dns_journal_t *j1 = NULL; + dns_journal_t *j2 = NULL; + journal_rawheader_t rawheader; + unsigned int len; + size_t namelen; + unsigned char *buf = NULL; + unsigned int size = 0; + isc_result_t result; + unsigned int indexend; + char newname[PATH_MAX]; + char backup[PATH_MAX]; + bool is_backup = false; + bool rewrite = false; + bool downgrade = false; + + REQUIRE(filename != NULL); + + namelen = strlen(filename); + if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0) { + namelen -= 4; + } + + result = snprintf(newname, sizeof(newname), "%.*s.jnw", (int)namelen, + filename); + RUNTIME_CHECK(result < sizeof(newname)); + + result = snprintf(backup, sizeof(backup), "%.*s.jbk", (int)namelen, + filename); + RUNTIME_CHECK(result < sizeof(backup)); + + result = journal_open(mctx, filename, false, false, false, &j1); + if (result == ISC_R_NOTFOUND) { + is_backup = true; + result = journal_open(mctx, backup, false, false, false, &j1); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Always perform a re-write when processing a version 1 journal. + */ + rewrite = j1->header_ver1; + + /* + * Check whether we need to rewrite the whole journal + * file (for example, to upversion it). + */ + if ((flags & DNS_JOURNAL_COMPACTALL) != 0) { + if ((flags & DNS_JOURNAL_VERSION1) != 0) { + downgrade = true; + } + rewrite = true; + serial = dns_journal_first_serial(j1); + } else if (JOURNAL_EMPTY(&j1->header)) { + dns_journal_destroy(&j1); + return (ISC_R_SUCCESS); + } + + if (DNS_SERIAL_GT(j1->header.begin.serial, serial) || + DNS_SERIAL_GT(serial, j1->header.end.serial)) + { + dns_journal_destroy(&j1); + return (ISC_R_RANGE); + } + + /* + * Cope with very small target sizes. + */ + indexend = sizeof(journal_rawheader_t) + + j1->header.index_size * sizeof(journal_rawpos_t); + if (target_size < DNS_JOURNAL_SIZE_MIN) { + target_size = DNS_JOURNAL_SIZE_MIN; + } + if (target_size < indexend * 2) { + target_size = target_size / 2 + indexend; + } + + /* + * See if there is any work to do. + */ + if (!rewrite && (uint32_t)j1->header.end.offset < target_size) { + dns_journal_destroy(&j1); + return (ISC_R_SUCCESS); + } + + CHECK(journal_open(mctx, newname, true, true, downgrade, &j2)); + CHECK(journal_seek(j2, indexend)); + + /* + * Remove overhead so space test below can succeed. + */ + if (target_size >= indexend) { + target_size -= indexend; + } + + /* + * Find if we can create enough free space. + */ + best_guess = j1->header.begin; + for (i = 0; i < j1->header.index_size; i++) { + if (POS_VALID(j1->index[i]) && + DNS_SERIAL_GE(serial, j1->index[i].serial) && + ((uint32_t)(j1->header.end.offset - j1->index[i].offset) >= + target_size / 2) && + j1->index[i].offset > best_guess.offset) + { + best_guess = j1->index[i]; + } + } + + current_pos = best_guess; + while (current_pos.serial != serial) { + CHECK(journal_next(j1, ¤t_pos)); + if (current_pos.serial == j1->header.end.serial) { + break; + } + + if (DNS_SERIAL_GE(serial, current_pos.serial) && + ((uint32_t)(j1->header.end.offset - current_pos.offset) >= + (target_size / 2)) && + current_pos.offset > best_guess.offset) + { + best_guess = current_pos; + } else { + break; + } + } + + INSIST(best_guess.serial != j1->header.end.serial); + if (best_guess.serial != serial) { + CHECK(journal_next(j1, &best_guess)); + serial = best_guess.serial; + } + + /* + * We should now be roughly half target_size provided + * we did not reach 'serial'. If not we will just copy + * all uncommitted deltas regardless of the size. + */ + len = j1->header.end.offset - best_guess.offset; + if (len != 0) { + CHECK(journal_seek(j1, best_guess.offset)); + + /* Prepare new header */ + j2->header.begin.serial = best_guess.serial; + j2->header.begin.offset = indexend; + j2->header.sourceserial = j1->header.sourceserial; + j2->header.serialset = j1->header.serialset; + j2->header.end.serial = j1->header.end.serial; + + /* + * Only use this method if we're rewriting the + * journal to fix outdated transaction headers; + * otherwise we'll copy the whole journal without + * parsing individual deltas below. + */ + while (rewrite && len > 0) { + journal_xhdr_t xhdr; + isc_offset_t offset = j1->offset; + uint32_t count; + + result = journal_read_xhdr(j1, &xhdr); + if (rewrite && result == ISC_R_NOMORE) { + break; + } + CHECK(result); + + size = xhdr.size; + if (size > len) { + isc_log_write(JOURNAL_COMMON_LOGARGS, + ISC_LOG_ERROR, + "%s: journal file corrupt, " + "transaction too large", + j1->filename); + CHECK(ISC_R_FAILURE); + } + buf = isc_mem_get(mctx, size); + result = journal_read(j1, buf, size); + + /* + * If we're repairing an outdated journal, the + * xhdr format may be wrong. + */ + if (rewrite && (result != ISC_R_SUCCESS || + !check_delta(buf, size))) + { + if (j1->xhdr_version == XHDR_VERSION2) { + /* XHDR_VERSION2 -> XHDR_VERSION1 */ + j1->xhdr_version = XHDR_VERSION1; + CHECK(journal_seek(j1, offset)); + CHECK(journal_read_xhdr(j1, &xhdr)); + } else if (j1->xhdr_version == XHDR_VERSION1) { + /* XHDR_VERSION1 -> XHDR_VERSION2 */ + j1->xhdr_version = XHDR_VERSION2; + CHECK(journal_seek(j1, offset)); + CHECK(journal_read_xhdr(j1, &xhdr)); + } + + /* Check again */ + isc_mem_put(mctx, buf, size); + size = xhdr.size; + if (size > len) { + isc_log_write( + JOURNAL_COMMON_LOGARGS, + ISC_LOG_ERROR, + "%s: journal file corrupt, " + "transaction too large", + j1->filename); + CHECK(ISC_R_FAILURE); + } + buf = isc_mem_get(mctx, size); + CHECK(journal_read(j1, buf, size)); + + if (!check_delta(buf, size)) { + CHECK(ISC_R_UNEXPECTED); + } + } else { + CHECK(result); + } + + /* + * Recover from incorrectly written transaction header. + * The incorrect header was written as size, serial0, + * serial1, and 0. XHDR_VERSION2 is expecting size, + * count, serial0, and serial1. + */ + if (j1->xhdr_version == XHDR_VERSION2 && + xhdr.count == serial && xhdr.serial1 == 0U && + isc_serial_gt(xhdr.serial0, xhdr.count)) + { + xhdr.serial1 = xhdr.serial0; + xhdr.serial0 = xhdr.count; + xhdr.count = 0; + } + + /* + * Check that xhdr is consistent. + */ + if (xhdr.serial0 != serial || + isc_serial_le(xhdr.serial1, xhdr.serial0)) + { + CHECK(ISC_R_UNEXPECTED); + } + + /* + * Extract record count from the transaction. This + * is needed when converting from XHDR_VERSION1 to + * XHDR_VERSION2, and when recovering from an + * incorrectly written XHDR_VERSION2. + */ + count = rrcount(buf, size); + CHECK(journal_write_xhdr(j2, xhdr.size, count, + xhdr.serial0, xhdr.serial1)); + CHECK(journal_write(j2, buf, size)); + + j2->header.end.offset = j2->offset; + + serial = xhdr.serial1; + + len = j1->header.end.offset - j1->offset; + isc_mem_put(mctx, buf, size); + } + + /* + * If we're not rewriting transaction headers, we can use + * this faster method instead. + */ + if (!rewrite) { + size = ISC_MIN(64 * 1024, len); + buf = isc_mem_get(mctx, size); + for (i = 0; i < len; i += size) { + unsigned int blob = ISC_MIN(size, len - i); + CHECK(journal_read(j1, buf, blob)); + CHECK(journal_write(j2, buf, blob)); + } + + j2->header.end.offset = indexend + len; + } + + CHECK(journal_fsync(j2)); + + /* + * Update the journal header. + */ + journal_header_encode(&j2->header, &rawheader); + CHECK(journal_seek(j2, 0)); + CHECK(journal_write(j2, &rawheader, sizeof(rawheader))); + CHECK(journal_fsync(j2)); + + /* + * Build new index. + */ + current_pos = j2->header.begin; + while (current_pos.serial != j2->header.end.serial) { + index_add(j2, ¤t_pos); + CHECK(journal_next(j2, ¤t_pos)); + } + + /* + * Write index. + */ + CHECK(index_to_disk(j2)); + CHECK(journal_fsync(j2)); + + indexend = j2->header.end.offset; + POST(indexend); + } + + /* + * Close both journals before trying to rename files. + */ + dns_journal_destroy(&j1); + dns_journal_destroy(&j2); + + /* + * With a UFS file system this should just succeed and be atomic. + * Any IXFR outs will just continue and the old journal will be + * removed on final close. + * + * With MSDOS / NTFS we need to do a two stage rename, triggered + * by EEXIST. (If any IXFR's are running in other threads, however, + * this will fail, and the journal will not be compacted. But + * if so, hopefully they'll be finished by the next time we + * compact.) + */ + if (rename(newname, filename) == -1) { + if (errno == EEXIST && !is_backup) { + result = isc_file_remove(backup); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + { + goto failure; + } + if (rename(filename, backup) == -1) { + goto maperrno; + } + if (rename(newname, filename) == -1) { + goto maperrno; + } + (void)isc_file_remove(backup); + } else { + maperrno: + result = ISC_R_FAILURE; + goto failure; + } + } + + result = ISC_R_SUCCESS; + +failure: + (void)isc_file_remove(newname); + if (buf != NULL) { + isc_mem_put(mctx, buf, size); + } + if (j1 != NULL) { + dns_journal_destroy(&j1); + } + if (j2 != NULL) { + dns_journal_destroy(&j2); + } + return (result); +} + +static isc_result_t +index_to_disk(dns_journal_t *j) { + isc_result_t result = ISC_R_SUCCESS; + + if (j->header.index_size != 0) { + unsigned int i; + unsigned char *p; + unsigned int rawbytes; + + rawbytes = j->header.index_size * sizeof(journal_rawpos_t); + + p = j->rawindex; + for (i = 0; i < j->header.index_size; i++) { + encode_uint32(j->index[i].serial, p); + p += 4; + encode_uint32(j->index[i].offset, p); + p += 4; + } + INSIST(p == j->rawindex + rawbytes); + + CHECK(journal_seek(j, sizeof(journal_rawheader_t))); + CHECK(journal_write(j, j->rawindex, rawbytes)); + } +failure: + return (result); +} diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c new file mode 100644 index 0000000..d53abfb --- /dev/null +++ b/lib/dns/kasp.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Default TTLsig (maximum zone ttl) */ +#define DEFAULT_TTLSIG 604800 /* one week */ + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) { + dns_kasp_t *kasp; + dns_kasp_t k = { + .magic = DNS_KASP_MAGIC, + }; + + REQUIRE(name != NULL); + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kasp = isc_mem_get(mctx, sizeof(*kasp)); + *kasp = k; + + kasp->mctx = NULL; + isc_mem_attach(mctx, &kasp->mctx); + kasp->name = isc_mem_strdup(mctx, name); + isc_mutex_init(&kasp->lock); + isc_refcount_init(&kasp->references, 1); + + ISC_LINK_INIT(kasp, link); + ISC_LIST_INIT(kasp->keys); + + *kaspp = kasp; + return (ISC_R_SUCCESS); +} + +void +dns_kasp_attach(dns_kasp_t *source, dns_kasp_t **targetp) { + REQUIRE(DNS_KASP_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + *targetp = source; +} + +static void +destroy(dns_kasp_t *kasp) { + dns_kasp_key_t *key; + dns_kasp_key_t *key_next; + + REQUIRE(!ISC_LINK_LINKED(kasp, link)); + + for (key = ISC_LIST_HEAD(kasp->keys); key != NULL; key = key_next) { + key_next = ISC_LIST_NEXT(key, link); + ISC_LIST_UNLINK(kasp->keys, key, link); + dns_kasp_key_destroy(key); + } + INSIST(ISC_LIST_EMPTY(kasp->keys)); + + isc_mutex_destroy(&kasp->lock); + isc_mem_free(kasp->mctx, kasp->name); + isc_mem_putanddetach(&kasp->mctx, kasp, sizeof(*kasp)); +} + +void +dns_kasp_detach(dns_kasp_t **kaspp) { + REQUIRE(kaspp != NULL && DNS_KASP_VALID(*kaspp)); + + dns_kasp_t *kasp = *kaspp; + *kaspp = NULL; + + if (isc_refcount_decrement(&kasp->references) == 1) { + destroy(kasp); + } +} + +const char * +dns_kasp_getname(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + + return (kasp->name); +} + +void +dns_kasp_freeze(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->frozen = true; +} + +void +dns_kasp_thaw(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + kasp->frozen = false; +} + +uint32_t +dns_kasp_signdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->signatures_validity - kasp->signatures_refresh); +} + +uint32_t +dns_kasp_sigrefresh(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->signatures_refresh); +} + +void +dns_kasp_setsigrefresh(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->signatures_refresh = value; +} + +uint32_t +dns_kasp_sigvalidity(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->signatures_validity); +} + +void +dns_kasp_setsigvalidity(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->signatures_validity = value; +} + +uint32_t +dns_kasp_sigvalidity_dnskey(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->signatures_validity_dnskey); +} + +void +dns_kasp_setsigvalidity_dnskey(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->signatures_validity_dnskey = value; +} + +dns_ttl_t +dns_kasp_dnskeyttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->dnskey_ttl); +} + +void +dns_kasp_setdnskeyttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->dnskey_ttl = ttl; +} + +uint32_t +dns_kasp_purgekeys(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->purge_keys); +} + +void +dns_kasp_setpurgekeys(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->purge_keys = value; +} + +uint32_t +dns_kasp_publishsafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->publish_safety); +} + +void +dns_kasp_setpublishsafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->publish_safety = value; +} + +uint32_t +dns_kasp_retiresafety(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->retire_safety); +} + +void +dns_kasp_setretiresafety(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->retire_safety = value; +} + +dns_ttl_t +dns_kasp_zonemaxttl(dns_kasp_t *kasp, bool fallback) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + if (kasp->zone_max_ttl == 0 && fallback) { + return (DEFAULT_TTLSIG); + } + return (kasp->zone_max_ttl); +} + +void +dns_kasp_setzonemaxttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->zone_max_ttl = ttl; +} + +uint32_t +dns_kasp_zonepropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->zone_propagation_delay); +} + +void +dns_kasp_setzonepropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->zone_propagation_delay = value; +} + +dns_ttl_t +dns_kasp_dsttl(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->parent_ds_ttl); +} + +void +dns_kasp_setdsttl(dns_kasp_t *kasp, dns_ttl_t ttl) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->parent_ds_ttl = ttl; +} + +uint32_t +dns_kasp_parentpropagationdelay(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->parent_propagation_delay); +} + +void +dns_kasp_setparentpropagationdelay(dns_kasp_t *kasp, uint32_t value) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + + kasp->parent_propagation_delay = value; +} + +isc_result_t +dns_kasplist_find(dns_kasplist_t *list, const char *name, dns_kasp_t **kaspp) { + dns_kasp_t *kasp = NULL; + + REQUIRE(kaspp != NULL && *kaspp == NULL); + + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + + for (kasp = ISC_LIST_HEAD(*list); kasp != NULL; + kasp = ISC_LIST_NEXT(kasp, link)) + { + if (strcmp(kasp->name, name) == 0) { + break; + } + } + + if (kasp == NULL) { + return (ISC_R_NOTFOUND); + } + + dns_kasp_attach(kasp, kaspp); + return (ISC_R_SUCCESS); +} + +dns_kasp_keylist_t +dns_kasp_keys(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + return (kasp->keys); +} + +bool +dns_kasp_keylist_empty(dns_kasp_t *kasp) { + REQUIRE(DNS_KASP_VALID(kasp)); + + return (ISC_LIST_EMPTY(kasp->keys)); +} + +void +dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(!kasp->frozen); + REQUIRE(key != NULL); + + ISC_LIST_APPEND(kasp->keys, key, link); +} + +isc_result_t +dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) { + dns_kasp_key_t *key; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyp != NULL && *keyp == NULL); + + key = isc_mem_get(kasp->mctx, sizeof(*key)); + key->mctx = NULL; + isc_mem_attach(kasp->mctx, &key->mctx); + + ISC_LINK_INIT(key, link); + + key->lifetime = 0; + key->algorithm = 0; + key->length = -1; + key->role = 0; + *keyp = key; + return (ISC_R_SUCCESS); +} + +void +dns_kasp_key_destroy(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + isc_mem_putanddetach(&key->mctx, key, sizeof(*key)); +} + +uint32_t +dns_kasp_key_algorithm(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + return (key->algorithm); +} + +unsigned int +dns_kasp_key_size(dns_kasp_key_t *key) { + unsigned int size = 0; + unsigned int min = 0; + + REQUIRE(key != NULL); + + switch (key->algorithm) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + case DNS_KEYALG_RSASHA512: + min = (key->algorithm == DNS_KEYALG_RSASHA512) ? 1024 : 512; + if (key->length > -1) { + size = (unsigned int)key->length; + if (size < min) { + size = min; + } + if (size > 4096) { + size = 4096; + } + } else { + size = 2048; + } + break; + case DNS_KEYALG_ECDSA256: + size = 256; + break; + case DNS_KEYALG_ECDSA384: + size = 384; + break; + case DNS_KEYALG_ED25519: + size = 256; + break; + case DNS_KEYALG_ED448: + size = 456; + break; + default: + /* unsupported */ + break; + } + return (size); +} + +uint32_t +dns_kasp_key_lifetime(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + return (key->lifetime); +} + +bool +dns_kasp_key_ksk(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + return (key->role & DNS_KASP_KEY_ROLE_KSK); +} + +bool +dns_kasp_key_zsk(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + return (key->role & DNS_KASP_KEY_ROLE_ZSK); +} + +uint8_t +dns_kasp_nsec3iter(dns_kasp_t *kasp) { + REQUIRE(kasp != NULL); + REQUIRE(kasp->frozen); + REQUIRE(kasp->nsec3); + + return (kasp->nsec3param.iterations); +} + +uint8_t +dns_kasp_nsec3flags(dns_kasp_t *kasp) { + REQUIRE(kasp != NULL); + REQUIRE(kasp->frozen); + REQUIRE(kasp->nsec3); + + if (kasp->nsec3param.optout) { + return (0x01); + } + return (0x00); +} + +uint8_t +dns_kasp_nsec3saltlen(dns_kasp_t *kasp) { + REQUIRE(kasp != NULL); + REQUIRE(kasp->frozen); + REQUIRE(kasp->nsec3); + + return (kasp->nsec3param.saltlen); +} + +bool +dns_kasp_nsec3(dns_kasp_t *kasp) { + REQUIRE(kasp != NULL); + REQUIRE(kasp->frozen); + + return kasp->nsec3; +} + +void +dns_kasp_setnsec3(dns_kasp_t *kasp, bool nsec3) { + REQUIRE(kasp != NULL); + REQUIRE(!kasp->frozen); + + kasp->nsec3 = nsec3; +} + +void +dns_kasp_setnsec3param(dns_kasp_t *kasp, uint8_t iter, bool optout, + uint8_t saltlen) { + REQUIRE(kasp != NULL); + REQUIRE(!kasp->frozen); + REQUIRE(kasp->nsec3); + + kasp->nsec3param.iterations = iter; + kasp->nsec3param.optout = optout; + kasp->nsec3param.saltlen = saltlen; +} diff --git a/lib/dns/key.c b/lib/dns/key.c new file mode 100644 index 0000000..7d4f378 --- /dev/null +++ b/lib/dns/key.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "dst_internal.h" + +uint16_t +dst_region_computeid(const isc_region_t *source) { + uint32_t ac; + const unsigned char *p; + int size; + + REQUIRE(source != NULL); + REQUIRE(source->length >= 4); + + p = source->base; + size = source->length; + + for (ac = 0; size > 1; size -= 2, p += 2) { + ac += ((*p) << 8) + *(p + 1); + } + + if (size > 0) { + ac += ((*p) << 8); + } + ac += (ac >> 16) & 0xffff; + + return ((uint16_t)(ac & 0xffff)); +} + +uint16_t +dst_region_computerid(const isc_region_t *source) { + uint32_t ac; + const unsigned char *p; + int size; + + REQUIRE(source != NULL); + REQUIRE(source->length >= 4); + + p = source->base; + size = source->length; + + ac = ((*p) << 8) + *(p + 1); + ac |= DNS_KEYFLAG_REVOKE; + for (size -= 2, p += 2; size > 1; size -= 2, p += 2) { + ac += ((*p) << 8) + *(p + 1); + } + + if (size > 0) { + ac += ((*p) << 8); + } + ac += (ac >> 16) & 0xffff; + + return ((uint16_t)(ac & 0xffff)); +} + +dns_name_t * +dst_key_name(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_name); +} + +unsigned int +dst_key_size(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_size); +} + +unsigned int +dst_key_proto(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_proto); +} + +unsigned int +dst_key_alg(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_alg); +} + +uint32_t +dst_key_flags(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_flags); +} + +dns_keytag_t +dst_key_id(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_id); +} + +dns_keytag_t +dst_key_rid(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_rid); +} + +dns_rdataclass_t +dst_key_class(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_class); +} + +bool +dst_key_iszonekey(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + if ((key->key_flags & DNS_KEYTYPE_NOAUTH) != 0) { + return (false); + } + if ((key->key_flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) { + return (false); + } + if (key->key_proto != DNS_KEYPROTO_DNSSEC && + key->key_proto != DNS_KEYPROTO_ANY) + { + return (false); + } + return (true); +} + +bool +dst_key_isnullkey(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + if ((key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) { + return (false); + } + if ((key->key_flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) { + return (false); + } + if (key->key_proto != DNS_KEYPROTO_DNSSEC && + key->key_proto != DNS_KEYPROTO_ANY) + { + return (false); + } + return (true); +} + +void +dst_key_setbits(dst_key_t *key, uint16_t bits) { + unsigned int maxbits; + REQUIRE(VALID_KEY(key)); + if (bits != 0) { + RUNTIME_CHECK(dst_key_sigsize(key, &maxbits) == ISC_R_SUCCESS); + maxbits *= 8; + REQUIRE(bits <= maxbits); + } + key->key_bits = bits; +} + +uint16_t +dst_key_getbits(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_bits); +} + +void +dst_key_setttl(dst_key_t *key, dns_ttl_t ttl) { + REQUIRE(VALID_KEY(key)); + key->key_ttl = ttl; +} + +dns_ttl_t +dst_key_getttl(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_ttl); +} + +/*! \file */ diff --git a/lib/dns/keydata.c b/lib/dns/keydata.c new file mode 100644 index 0000000..c8e2d80 --- /dev/null +++ b/lib/dns/keydata.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +isc_result_t +dns_keydata_todnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey, + isc_mem_t *mctx) { + REQUIRE(keydata != NULL && dnskey != NULL); + + dnskey->common.rdtype = dns_rdatatype_dnskey; + dnskey->common.rdclass = keydata->common.rdclass; + dnskey->mctx = mctx; + dnskey->flags = keydata->flags; + dnskey->protocol = keydata->protocol; + dnskey->algorithm = keydata->algorithm; + + dnskey->datalen = keydata->datalen; + + if (mctx == NULL) { + dnskey->data = keydata->data; + } else { + dnskey->data = isc_mem_allocate(mctx, dnskey->datalen); + memmove(dnskey->data, keydata->data, dnskey->datalen); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_keydata_fromdnskey(dns_rdata_keydata_t *keydata, dns_rdata_dnskey_t *dnskey, + uint32_t refresh, uint32_t addhd, uint32_t removehd, + isc_mem_t *mctx) { + REQUIRE(keydata != NULL && dnskey != NULL); + + keydata->common.rdtype = dns_rdatatype_keydata; + keydata->common.rdclass = dnskey->common.rdclass; + keydata->mctx = mctx; + keydata->refresh = refresh; + keydata->addhd = addhd; + keydata->removehd = removehd; + keydata->flags = dnskey->flags; + keydata->protocol = dnskey->protocol; + keydata->algorithm = dnskey->algorithm; + + keydata->datalen = dnskey->datalen; + if (mctx == NULL) { + keydata->data = dnskey->data; + } else { + keydata->data = isc_mem_allocate(mctx, keydata->datalen); + memmove(keydata->data, dnskey->data, keydata->datalen); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c new file mode 100644 index 0000000..106b376 --- /dev/null +++ b/lib/dns/keymgr.c @@ -0,0 +1,2647 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/* + * Set key state to `target` state and change last changed + * to `time`, only if key state has not been set before. + */ +#define INITIALIZE_STATE(key, state, timing, target, time) \ + do { \ + dst_key_state_t s; \ + char keystr[DST_KEY_FORMATSIZE]; \ + if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \ + dst_key_setstate((key), (state), (target)); \ + dst_key_settime((key), (timing), time); \ + \ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { \ + dst_key_format((key), keystr, sizeof(keystr)); \ + isc_log_write( \ + dns_lctx, DNS_LOGCATEGORY_DNSSEC, \ + DNS_LOGMODULE_DNSSEC, \ + ISC_LOG_DEBUG(3), \ + "keymgr: DNSKEY %s (%s) initialize " \ + "%s state to %s (policy %s)", \ + keystr, keymgr_keyrole((key)), \ + keystatetags[state], \ + keystatestrings[target], \ + dns_kasp_getname(kasp)); \ + } \ + } \ + } while (0) + +/* Shorter keywords for better readability. */ +#define HIDDEN DST_KEY_STATE_HIDDEN +#define RUMOURED DST_KEY_STATE_RUMOURED +#define OMNIPRESENT DST_KEY_STATE_OMNIPRESENT +#define UNRETENTIVE DST_KEY_STATE_UNRETENTIVE +#define NA DST_KEY_STATE_NA + +/* Quickly get key state timing metadata. */ +#define NUM_KEYSTATES (DST_MAX_KEYSTATES) +static int keystatetimes[NUM_KEYSTATES] = { DST_TIME_DNSKEY, DST_TIME_ZRRSIG, + DST_TIME_KRRSIG, DST_TIME_DS }; +/* Readable key state types and values. */ +static const char *keystatetags[NUM_KEYSTATES] = { "DNSKEY", "ZRRSIG", "KRRSIG", + "DS" }; +static const char *keystatestrings[4] = { "HIDDEN", "RUMOURED", "OMNIPRESENT", + "UNRETENTIVE" }; + +/* + * Print key role. + * + */ +static const char * +keymgr_keyrole(dst_key_t *key) { + bool ksk = false, zsk = false; + isc_result_t ret; + ret = dst_key_getbool(key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + return ("UNKNOWN"); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + return ("UNKNOWN"); + } + if (ksk && zsk) { + return ("CSK"); + } else if (ksk) { + return ("KSK"); + } else if (zsk) { + return ("ZSK"); + } + return ("NOSIGN"); +} + +/* + * Set the remove time on key given its retire time. + * + */ +static void +keymgr_settime_remove(dns_dnsseckey_t *key, dns_kasp_t *kasp) { + isc_stdtime_t retire = 0, remove = 0, ksk_remove = 0, zsk_remove = 0; + bool zsk = false, ksk = false; + isc_result_t ret; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (ret != ISC_R_SUCCESS) { + return; + } + + ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (ret == ISC_R_SUCCESS && zsk) { + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); + /* ZSK: Iret = Dsgn + Dprp + TTLsig */ + zsk_remove = + retire + ttlsig + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_retiresafety(kasp) + dns_kasp_signdelay(kasp); + } + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + /* KSK: Iret = DprpP + TTLds */ + ksk_remove = retire + dns_kasp_dsttl(kasp) + + dns_kasp_parentpropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + } + + remove = ISC_MAX(ksk_remove, zsk_remove); + dst_key_settime(key->key, DST_TIME_DELETE, remove); +} + +/* + * Set the SyncPublish time (when the DS may be submitted to the parent). + * + */ +static void +keymgr_settime_syncpublish(dns_dnsseckey_t *key, dns_kasp_t *kasp, bool first) { + isc_stdtime_t published, syncpublish; + bool ksk = false; + isc_result_t ret; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &published); + if (ret != ISC_R_SUCCESS) { + return; + } + + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS || !ksk) { + return; + } + + syncpublish = published + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_publishsafety(kasp); + if (first) { + /* Also need to wait until the signatures are omnipresent. */ + isc_stdtime_t zrrsig_present; + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); + zrrsig_present = published + ttlsig + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_publishsafety(kasp); + if (zrrsig_present > syncpublish) { + syncpublish = zrrsig_present; + } + } + dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, syncpublish); +} + +/* + * Calculate prepublication time of a successor key of 'key'. + * This function can have side effects: + * 1. If there is no active time set, which would be super weird, set it now. + * 2. If there is no published time set, also super weird, set it now. + * 3. If there is no syncpublished time set, set it now. + * 4. If the lifetime is not set, it will be set now. + * 5. If there should be a retire time and it is not set, it will be set now. + * 6. The removed time is adjusted accordingly. + * + * This returns when the successor key needs to be published in the zone. + * A special value of 0 means there is no need for a successor. + * + */ +static isc_stdtime_t +keymgr_prepublication_time(dns_dnsseckey_t *key, dns_kasp_t *kasp, + uint32_t lifetime, isc_stdtime_t now) { + isc_result_t ret; + isc_stdtime_t active, retire, pub, prepub; + bool zsk = false, ksk = false; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + active = 0; + pub = 0; + retire = 0; + + /* + * An active key must have publish and activate timing + * metadata. + */ + ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (ret != ISC_R_SUCCESS) { + /* Super weird, but if it happens, set it to now. */ + dst_key_settime(key->key, DST_TIME_ACTIVATE, now); + active = now; + } + ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub); + if (ret != ISC_R_SUCCESS) { + /* Super weird, but if it happens, set it to now. */ + dst_key_settime(key->key, DST_TIME_PUBLISH, now); + pub = now; + } + + /* + * Calculate prepublication time. + */ + prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + isc_stdtime_t syncpub; + + /* + * Set PublishCDS if not set. + */ + ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub); + if (ret != ISC_R_SUCCESS) { + uint32_t tag; + isc_stdtime_t syncpub1, syncpub2; + + syncpub1 = pub + prepub; + syncpub2 = 0; + ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR, + &tag); + if (ret != ISC_R_SUCCESS) { + /* + * No predecessor, wait for zone to be + * completely signed. + */ + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, + true); + syncpub2 = pub + ttlsig + + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + } + + syncpub = ISC_MAX(syncpub1, syncpub2); + dst_key_settime(key->key, DST_TIME_SYNCPUBLISH, + syncpub); + } + } + + /* + * Not sure what to do when dst_key_getbool() fails here. Extending + * the prepublication time anyway is arguably the safest thing to do, + * so ignore the result code. + */ + (void)dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (ret != ISC_R_SUCCESS) { + uint32_t klifetime = 0; + + ret = dst_key_getnum(key->key, DST_NUM_LIFETIME, &klifetime); + if (ret != ISC_R_SUCCESS) { + dst_key_setnum(key->key, DST_NUM_LIFETIME, lifetime); + klifetime = lifetime; + } + if (klifetime == 0) { + /* + * No inactive time and no lifetime, + * so no need to start a rollover. + */ + return (0); + } + + retire = active + klifetime; + dst_key_settime(key->key, DST_TIME_INACTIVE, retire); + } + + /* + * Update remove time. + */ + keymgr_settime_remove(key, kasp); + + /* + * Publish successor 'prepub' time before the 'retire' time of 'key'. + */ + if (prepub > retire) { + /* We should have already prepublished the new key. */ + return (now); + } + return (retire - prepub); +} + +static void +keymgr_key_retire(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now) { + char keystr[DST_KEY_FORMATSIZE]; + isc_result_t ret; + isc_stdtime_t retire; + dst_key_state_t s; + bool ksk = false, zsk = false; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + /* This key wants to retire and hide in a corner. */ + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (ret != ISC_R_SUCCESS || (retire > now)) { + dst_key_settime(key->key, DST_TIME_INACTIVE, now); + } + dst_key_setstate(key->key, DST_KEY_GOAL, HIDDEN); + keymgr_settime_remove(key, kasp); + + /* This key may not have key states set yet. Pretend as if they are + * in the OMNIPRESENT state. + */ + if (dst_key_getstate(key->key, DST_KEY_DNSKEY, &s) != ISC_R_SUCCESS) { + dst_key_setstate(key->key, DST_KEY_DNSKEY, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DNSKEY, now); + } + + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + if (dst_key_getstate(key->key, DST_KEY_KRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_KRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_KRRSIG, now); + } + if (dst_key_getstate(key->key, DST_KEY_DS, &s) != ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_DS, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_DS, now); + } + } + ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (ret == ISC_R_SUCCESS && zsk) { + if (dst_key_getstate(key->key, DST_KEY_ZRRSIG, &s) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_ZRRSIG, OMNIPRESENT); + dst_key_settime(key->key, DST_TIME_ZRRSIG, now); + } + } + + dst_key_format(key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, "keymgr: retire DNSKEY %s (%s)", keystr, + keymgr_keyrole(key->key)); +} + +/* + * Check if a dnsseckey matches kasp key configuration. A dnsseckey matches + * if it has the same algorithm and size, and if it has the same role as the + * kasp key configuration. + * + */ +static bool +keymgr_dnsseckey_kaspkey_match(dns_dnsseckey_t *dkey, dns_kasp_key_t *kkey) { + dst_key_t *key; + isc_result_t ret; + bool role = false; + + REQUIRE(dkey != NULL); + REQUIRE(kkey != NULL); + + key = dkey->key; + + /* Matching algorithms? */ + if (dst_key_alg(key) != dns_kasp_key_algorithm(kkey)) { + return (false); + } + /* Matching length? */ + if (dst_key_size(key) != dns_kasp_key_size(kkey)) { + return (false); + } + /* Matching role? */ + ret = dst_key_getbool(key, DST_BOOL_KSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_ksk(kkey)) { + return (false); + } + ret = dst_key_getbool(key, DST_BOOL_ZSK, &role); + if (ret != ISC_R_SUCCESS || role != dns_kasp_key_zsk(kkey)) { + return (false); + } + + /* Found a match. */ + return (true); +} + +static bool +keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) { + uint16_t id = dst_key_id(newkey); + uint32_t rid = dst_key_rid(newkey); + uint32_t alg = dst_key_alg(newkey); + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keys); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (dst_key_alg(dkey->key) != alg) { + continue; + } + if (dst_key_id(dkey->key) == id || + dst_key_rid(dkey->key) == id || + dst_key_id(dkey->key) == rid || + dst_key_rid(dkey->key) == rid) + { + return (true); + } + } + return (false); +} + +/* + * Create a new key for 'origin' given the kasp key configuration 'kkey'. + * This will check for key id collisions with keys in 'keylist'. + * The created key will be stored in 'dst_key'. + * + */ +static isc_result_t +keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin, + dns_rdataclass_t rdclass, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist, dns_dnsseckeylist_t *newkeys, + dst_key_t **dst_key) { + bool conflict = false; + int keyflags = DNS_KEYOWNER_ZONE; + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *newkey = NULL; + + do { + uint32_t algo = dns_kasp_key_algorithm(kkey); + int size = dns_kasp_key_size(kkey); + + if (dns_kasp_key_ksk(kkey)) { + keyflags |= DNS_KEYFLAG_KSK; + } + RETERR(dst_key_generate(origin, algo, size, 0, keyflags, + DNS_KEYPROTO_DNSSEC, rdclass, mctx, + &newkey, NULL)); + + /* Key collision? */ + conflict = keymgr_keyid_conflict(newkey, keylist); + if (!conflict) { + conflict = keymgr_keyid_conflict(newkey, newkeys); + } + if (conflict) { + /* Try again. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "keymgr: key collision id %d", + dst_key_id(newkey)); + dst_key_free(&newkey); + } + } while (conflict); + + INSIST(!conflict); + dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey)); + dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey)); + dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey)); + *dst_key = newkey; + return (ISC_R_SUCCESS); + +failure: + return (result); +} + +/* + * Return the desired state for this record 'type'. The desired state depends + * on whether the key wants to be active, or wants to retire. This implements + * the edges of our state machine: + * + * ----> OMNIPRESENT ---- + * | | + * | \|/ + * + * RUMOURED <----> UNRETENTIVE + * + * /|\ | + * | | + * ---- HIDDEN <---- + * + * A key that wants to be active eventually wants to have its record types + * in the OMNIPRESENT state (that is, all resolvers that know about these + * type of records know about these records specifically). + * + * A key that wants to be retired eventually wants to have its record types + * in the HIDDEN state (that is, all resolvers that know about these type + * of records specifically don't know about these records). + * + */ +static dst_key_state_t +keymgr_desiredstate(dns_dnsseckey_t *key, dst_key_state_t state) { + dst_key_state_t goal; + + if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal) != ISC_R_SUCCESS) { + /* No goal? No movement. */ + return (state); + } + + if (goal == HIDDEN) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (UNRETENTIVE); + case HIDDEN: + case UNRETENTIVE: + return (HIDDEN); + default: + return (state); + } + } else if (goal == OMNIPRESENT) { + switch (state) { + case RUMOURED: + case OMNIPRESENT: + return (OMNIPRESENT); + case HIDDEN: + case UNRETENTIVE: + return (RUMOURED); + default: + return (state); + } + } + + /* Unknown goal. */ + return (state); +} + +/* + * Check if 'key' matches specific 'states'. + * A state in 'states' that is NA matches any state. + * A state in 'states' that is HIDDEN also matches if the state is not set. + * If 'next_state' is set (not NA), we are pretending as if record 'type' of + * 'subject' key already transitioned to the 'next state'. + * + */ +static bool +keymgr_key_match_state(dst_key_t *key, dst_key_t *subject, int type, + dst_key_state_t next_state, + dst_key_state_t states[NUM_KEYSTATES]) { + REQUIRE(key != NULL); + + for (int i = 0; i < NUM_KEYSTATES; i++) { + dst_key_state_t state; + if (states[i] == NA) { + continue; + } + if (next_state != NA && i == type && + dst_key_id(key) == dst_key_id(subject)) + { + /* Check next state rather than current state. */ + state = next_state; + } else if (dst_key_getstate(key, i, &state) != ISC_R_SUCCESS) { + /* This is fine only if expected state is HIDDEN. */ + if (states[i] != HIDDEN) { + return (false); + } + continue; + } + if (state != states[i]) { + return (false); + } + } + /* Match. */ + return (true); +} + +/* + * Key d directly depends on k if d is the direct predecessor of k. + */ +static bool +keymgr_direct_dep(dst_key_t *d, dst_key_t *k) { + uint32_t s, p; + + if (dst_key_getnum(d, DST_NUM_SUCCESSOR, &s) != ISC_R_SUCCESS) { + return (false); + } + if (dst_key_getnum(k, DST_NUM_PREDECESSOR, &p) != ISC_R_SUCCESS) { + return (false); + } + return (dst_key_id(d) == p && dst_key_id(k) == s); +} + +/* + * Determine which key (if any) has a dependency on k. + */ +static bool +keymgr_dep(dst_key_t *k, dns_dnsseckeylist_t *keyring, uint32_t *dep) { + for (dns_dnsseckey_t *d = ISC_LIST_HEAD(*keyring); d != NULL; + d = ISC_LIST_NEXT(d, link)) + { + /* + * Check if k is a direct successor of d, e.g. d depends on k. + */ + if (keymgr_direct_dep(d->key, k)) { + if (dep != NULL) { + *dep = dst_key_id(d->key); + } + return (true); + } + } + return (false); +} + +/* + * Check if a 'z' is a successor of 'x'. + * This implements Equation(2) of "Flexible and Robust Key Rollover". + */ +static bool +keymgr_key_is_successor(dst_key_t *x, dst_key_t *z, dst_key_t *key, int type, + dst_key_state_t next_state, + dns_dnsseckeylist_t *keyring) { + uint32_t dep_x; + uint32_t dep_z; + + /* + * The successor relation requires that the predecessor key must not + * have any other keys relying on it. In other words, there must be + * nothing depending on x. + */ + if (keymgr_dep(x, keyring, &dep_x)) { + return (false); + } + + /* + * If there is no keys relying on key z, then z is not a successor. + */ + if (!keymgr_dep(z, keyring, &dep_z)) { + return (false); + } + + /* + * x depends on z, thus key z is a direct successor of key x. + */ + if (dst_key_id(x) == dep_z) { + return (true); + } + + /* + * It is possible to roll keys faster than the time required to finish + * the rollover procedure. For example, consider the keys x, y, z. + * Key x is currently published and is going to be replaced by y. The + * DNSKEY for x is removed from the zone and at the same moment the + * DNSKEY for y is introduced. Key y is a direct dependency for key x + * and is therefore the successor of x. However, before the new DNSKEY + * has been propagated, key z will replace key y. The DNSKEY for y is + * removed and moves into the same state as key x. Key y now directly + * depends on key z, and key z will be a new successor key for x. + */ + dst_key_state_t zst[NUM_KEYSTATES] = { NA, NA, NA, NA }; + for (int i = 0; i < NUM_KEYSTATES; i++) { + dst_key_state_t state; + if (dst_key_getstate(z, i, &state) != ISC_R_SUCCESS) { + continue; + } + zst[i] = state; + } + + for (dns_dnsseckey_t *y = ISC_LIST_HEAD(*keyring); y != NULL; + y = ISC_LIST_NEXT(y, link)) + { + if (dst_key_id(y->key) == dst_key_id(z)) { + continue; + } + + if (dst_key_id(y->key) != dep_z) { + continue; + } + /* + * This is another key y, that depends on key z. It may be + * part of the successor relation if the key states match + * those of key z. + */ + + if (keymgr_key_match_state(y->key, key, type, next_state, zst)) + { + /* + * If y is a successor of x, then z is also a + * successor of x. + */ + return (keymgr_key_is_successor(x, y->key, key, type, + next_state, keyring)); + } + } + + return (false); +} + +/* + * Check if a key exists in 'keyring' that matches 'states'. + * + * If 'match_algorithms', the key must also match the algorithm of 'key'. + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'check_successor', we also want to make sure there is a successor + * relationship with the found key that matches 'states2'. + */ +static bool +keymgr_key_exists_with_state(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state, + dst_key_state_t states[NUM_KEYSTATES], + dst_key_state_t states2[NUM_KEYSTATES], + bool check_successor, bool match_algorithms) { + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (!keymgr_key_match_state(dkey->key, key->key, type, + next_state, states)) + { + continue; + } + + /* Found a match. */ + if (!check_successor) { + return (true); + } + + /* + * We have to make sure that the key we are checking, also + * has a successor relationship with another key. + */ + for (dns_dnsseckey_t *skey = ISC_LIST_HEAD(*keyring); + skey != NULL; skey = ISC_LIST_NEXT(skey, link)) + { + if (skey == dkey) { + continue; + } + + if (!keymgr_key_match_state(skey->key, key->key, type, + next_state, states2)) + { + continue; + } + + /* + * Found a possible successor, check. + */ + if (keymgr_key_is_successor(dkey->key, skey->key, + key->key, type, next_state, + keyring)) + { + return (true); + } + } + } + /* No match. */ + return (false); +} + +/* + * Check if a key has a successor. + */ +static bool +keymgr_key_has_successor(dns_dnsseckey_t *predecessor, + dns_dnsseckeylist_t *keyring) { + for (dns_dnsseckey_t *successor = ISC_LIST_HEAD(*keyring); + successor != NULL; successor = ISC_LIST_NEXT(successor, link)) + { + if (keymgr_direct_dep(predecessor->key, successor->key)) { + return (true); + } + } + return (false); +} + +/* + * Check if all keys have their DS hidden. If not, then there must be at + * least one key with an OMNIPRESENT DNSKEY. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_ds_hidden_or_chained(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state, + bool match_algorithms, bool must_be_hidden) { + /* (3e) */ + dst_key_state_t dnskey_chained[NUM_KEYSTATES] = { OMNIPRESENT, NA, + OMNIPRESENT, NA }; + dst_key_state_t ds_hidden[NUM_KEYSTATES] = { NA, NA, NA, HIDDEN }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, ds_hidden)) + { + /* This key has its DS hidden. */ + continue; + } + + if (must_be_hidden) { + return (false); + } + + /* + * This key does not have its DS hidden. There must be at + * least one key with the same algorithm that provides a + * chain of trust (can be this key). + */ + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, dnskey_chained)) + { + /* This DNSKEY and KRRSIG are OMNIPRESENT. */ + continue; + } + + /* + * Perhaps another key provides a chain of trust. + */ + dnskey_chained[DST_KEY_DS] = OMNIPRESENT; + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, dnskey_chained, + na, false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check if all keys have their DNSKEY hidden. If not, then there must be at + * least one key with an OMNIPRESENT ZRRSIG. + * + * If 'next_state' is not NA, we are actually looking for a key as if + * 'key' already transitioned to the next state. + * If 'match_algorithms', only consider keys with same algorithm of 'key'. + * + */ +static bool +keymgr_dnskey_hidden_or_chained(dns_dnsseckeylist_t *keyring, + dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, + bool match_algorithms) { + /* (3i) */ + dst_key_state_t rrsig_chained[NUM_KEYSTATES] = { OMNIPRESENT, + OMNIPRESENT, NA, NA }; + dst_key_state_t dnskey_hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (match_algorithms && + (dst_key_alg(dkey->key) != dst_key_alg(key->key))) + { + continue; + } + + if (keymgr_key_match_state(dkey->key, key->key, type, + next_state, dnskey_hidden)) + { + /* This key has its DNSKEY hidden. */ + continue; + } + + /* + * This key does not have its DNSKEY hidden. There must be at + * least one key with the same algorithm that has its RRSIG + * records OMNIPRESENT. + */ + (void)dst_key_getstate(dkey->key, DST_KEY_DNSKEY, + &rrsig_chained[DST_KEY_DNSKEY]); + if (!keymgr_key_exists_with_state(keyring, key, type, + next_state, rrsig_chained, na, + false, match_algorithms)) + { + /* There is no chain of trust. */ + return (false); + } + } + /* All good. */ + return (true); +} + +/* + * Check for existence of DS. + * + */ +static bool +keymgr_have_ds(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, bool secure_to_insecure) { + /* (3a) */ + dst_key_state_t states[2][NUM_KEYSTATES] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { NA, NA, NA, OMNIPRESENT }, /* DS present */ + { NA, NA, NA, RUMOURED } /* DS introducing */ + }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + /* + * Equation (3a): + * There is a key with the DS in either RUMOURD or OMNIPRESENT state. + */ + return (keymgr_key_exists_with_state(keyring, key, type, next_state, + states[0], na, false, false) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[1], na, false, false) || + (secure_to_insecure && + keymgr_key_exists_with_state(keyring, key, type, next_state, + na, na, false, false))); +} + +/* + * Check for existence of DNSKEY, or at least a good DNSKEY state. + * See equations what are good DNSKEY states. + * + */ +static bool +keymgr_have_dnskey(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type, + dst_key_state_t next_state) { + dst_key_state_t states[9][NUM_KEYSTATES] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, NA, OMNIPRESENT, OMNIPRESENT }, /* (3b) */ + + { OMNIPRESENT, NA, OMNIPRESENT, UNRETENTIVE }, /* (3c)p */ + { OMNIPRESENT, NA, OMNIPRESENT, RUMOURED }, /* (3c)s */ + + { UNRETENTIVE, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { OMNIPRESENT, NA, UNRETENTIVE, OMNIPRESENT }, /* (3d)p */ + { UNRETENTIVE, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)p */ + { RUMOURED, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { OMNIPRESENT, NA, RUMOURED, OMNIPRESENT }, /* (3d)s */ + { RUMOURED, NA, OMNIPRESENT, OMNIPRESENT }, /* (3d)s */ + }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + return ( + /* + * Equation (3b): + * There is a key with the same algorithm with its DNSKEY, + * KRRSIG and DS records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[0], na, false, true) || + /* + * Equation (3c): + * There are two or more keys with an OMNIPRESENT DNSKEY and + * the DS records get swapped. These keys must be in a + * successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[1], states[2], true, + true) || + /* + * Equation (3d): + * There are two or more keys with an OMNIPRESENT DS and + * the DNSKEY records and its KRRSIG records get swapped. + * These keys must be in a successor relation. Since the + * state for DNSKEY and KRRSIG move independently, we have + * to check all combinations for DNSKEY and KRRSIG in + * OMNIPRESENT/UNRETENTIVE state for the predecessor, and + * OMNIPRESENT/RUMOURED state for the successor. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[3], states[6], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[3], states[7], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[3], states[8], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[4], states[6], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[4], states[7], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[4], states[8], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[5], states[6], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[5], states[7], true, + true) || + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[5], states[8], true, + true) || + /* + * Equation (3e): + * The key may be in any state as long as all keys have their + * DS HIDDEN, or when their DS is not HIDDEN, there must be a + * key with its DS in the same state and its DNSKEY omnipresent. + * In other words, if a DS record for the same algorithm is + * is still available to some validators, there must be a + * chain of trust for those validators. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, next_state, + true, false)); +} + +/* + * Check for existence of RRSIG (zsk), or a good RRSIG state. + * See equations what are good RRSIG states. + * + */ +static bool +keymgr_have_rrsig(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, int type, + dst_key_state_t next_state) { + dst_key_state_t states[11][NUM_KEYSTATES] = { + /* DNSKEY, ZRRSIG, KRRSIG, DS */ + { OMNIPRESENT, OMNIPRESENT, NA, NA }, /* (3f) */ + { UNRETENTIVE, OMNIPRESENT, NA, NA }, /* (3g)p */ + { RUMOURED, OMNIPRESENT, NA, NA }, /* (3g)s */ + { OMNIPRESENT, UNRETENTIVE, NA, NA }, /* (3h)p */ + { OMNIPRESENT, RUMOURED, NA, NA }, /* (3h)s */ + }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + return ( + /* + * If all DS records are hidden than this rule can be ignored. + */ + keymgr_ds_hidden_or_chained(keyring, key, type, next_state, + true, true) || + /* + * Equation (3f): + * There is a key with the same algorithm with its DNSKEY and + * ZRRSIG records in OMNIPRESENT state. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[0], na, false, true) || + /* + * Equation (3g): + * There are two or more keys with OMNIPRESENT ZRRSIG + * records and the DNSKEY records get swapped. These keys + * must be in a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[1], states[2], true, + true) || + /* + * Equation (3h): + * There are two or more keys with an OMNIPRESENT DNSKEY + * and the ZRRSIG records get swapped. These keys must be in + * a successor relation. + */ + keymgr_key_exists_with_state(keyring, key, type, next_state, + states[3], states[4], true, + true) || + /* + * Equation (3i): + * If no DNSKEYs are published, the state of the signatures is + * irrelevant. In case a DNSKEY is published however, there + * must be a path that can be validated from there. + */ + keymgr_dnskey_hidden_or_chained(keyring, key, type, next_state, + true)); +} + +/* + * Check if a transition in the state machine is allowed by the policy. + * This means when we do rollovers, we want to follow the rules of the + * 1. Pre-publish rollover method (in case of a ZSK) + * - First introduce the DNSKEY record. + * - Only if the DNSKEY record is OMNIPRESENT, introduce ZRRSIG records. + * + * 2. Double-KSK rollover method (in case of a KSK) + * - First introduce the DNSKEY record, as well as the KRRSIG records. + * - Only if the DNSKEY record is OMNIPRESENT, suggest to introduce the DS. + */ +static bool +keymgr_policy_approval(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next) { + dst_key_state_t dnskeystate = HIDDEN; + dst_key_state_t ksk_present[NUM_KEYSTATES] = { OMNIPRESENT, NA, + OMNIPRESENT, + OMNIPRESENT }; + dst_key_state_t ds_rumoured[NUM_KEYSTATES] = { OMNIPRESENT, NA, + OMNIPRESENT, RUMOURED }; + dst_key_state_t ds_retired[NUM_KEYSTATES] = { OMNIPRESENT, NA, + OMNIPRESENT, + UNRETENTIVE }; + dst_key_state_t ksk_rumoured[NUM_KEYSTATES] = { RUMOURED, NA, NA, + OMNIPRESENT }; + dst_key_state_t ksk_retired[NUM_KEYSTATES] = { UNRETENTIVE, NA, NA, + OMNIPRESENT }; + /* successor n/a */ + dst_key_state_t na[NUM_KEYSTATES] = { NA, NA, NA, NA }; + + if (next != RUMOURED) { + /* + * Local policy only adds an extra barrier on transitions to + * the RUMOURED state. + */ + return (true); + } + + switch (type) { + case DST_KEY_DNSKEY: + /* No restrictions. */ + return (true); + case DST_KEY_ZRRSIG: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + if (dnskeystate == OMNIPRESENT) { + return (true); + } + /* + * Or are we introducing a new key for this algorithm? Because + * in that case allow publishing the RRSIG records before the + * DNSKEY. + */ + return (!(keymgr_key_exists_with_state(keyring, key, type, next, + ksk_present, na, false, + true) || + keymgr_key_exists_with_state(keyring, key, type, next, + ds_retired, ds_rumoured, + true, true) || + keymgr_key_exists_with_state( + keyring, key, type, next, ksk_retired, + ksk_rumoured, true, true))); + case DST_KEY_KRRSIG: + /* Only introduce if the DNSKEY is also introduced. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate != HIDDEN); + case DST_KEY_DS: + /* Make sure the DNSKEY record is OMNIPRESENT. */ + (void)dst_key_getstate(key->key, DST_KEY_DNSKEY, &dnskeystate); + return (dnskeystate == OMNIPRESENT); + default: + return (false); + } +} + +/* + * Check if a transition in the state machine is DNSSEC safe. + * This implements Equation(1) of "Flexible and Robust Key Rollover". + * + */ +static bool +keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state, + bool secure_to_insecure) { + /* Debug logging. */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b; + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); + rule1a = keymgr_have_ds(keyring, key, type, NA, + secure_to_insecure); + rule1b = keymgr_have_ds(keyring, key, type, next_state, + secure_to_insecure); + rule2a = keymgr_have_dnskey(keyring, key, type, NA); + rule2b = keymgr_have_dnskey(keyring, key, type, next_state); + rule3a = keymgr_have_rrsig(keyring, key, type, NA); + rule3b = keymgr_have_rrsig(keyring, key, type, next_state); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: dnssec evaluation of %s %s record %s: " + "rule1=(~%s or %s) rule2=(~%s or %s) " + "rule3=(~%s or %s)", + keymgr_keyrole(key->key), keystr, keystatetags[type], + rule1a ? "true" : "false", rule1b ? "true" : "false", + rule2a ? "true" : "false", rule2b ? "true" : "false", + rule3a ? "true" : "false", rule3b ? "true" : "false"); + } + + return ( + /* + * Rule 1: There must be a DS at all times. + * First check the current situation: if the rule check fails, + * we allow the transition to attempt to move us out of the + * invalid state. If the rule check passes, also check if + * the next state is also still a valid situation. + */ + (!keymgr_have_ds(keyring, key, type, NA, secure_to_insecure) || + keymgr_have_ds(keyring, key, type, next_state, + secure_to_insecure)) && + /* + * Rule 2: There must be a DNSKEY at all times. Again, first + * check the current situation, then assess the next state. + */ + (!keymgr_have_dnskey(keyring, key, type, NA) || + keymgr_have_dnskey(keyring, key, type, next_state)) && + /* + * Rule 3: There must be RRSIG records at all times. Again, + * first check the current situation, then assess the next + * state. + */ + (!keymgr_have_rrsig(keyring, key, type, NA) || + keymgr_have_rrsig(keyring, key, type, next_state))); +} + +/* + * Calculate the time when it is safe to do the next transition. + * + */ +static void +keymgr_transition_time(dns_dnsseckey_t *key, int type, + dst_key_state_t next_state, dns_kasp_t *kasp, + isc_stdtime_t now, isc_stdtime_t *when) { + isc_result_t ret; + isc_stdtime_t lastchange, dstime, nexttime = now; + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); + + /* + * No need to wait if we move things into an uncertain state. + */ + if (next_state == RUMOURED || next_state == UNRETENTIVE) { + *when = now; + return; + } + + ret = dst_key_gettime(key->key, keystatetimes[type], &lastchange); + if (ret != ISC_R_SUCCESS) { + /* No last change, for safety purposes let's set it to now. */ + dst_key_settime(key->key, keystatetimes[type], now); + lastchange = now; + } + + switch (type) { + case DST_KEY_DNSKEY: + case DST_KEY_KRRSIG: + switch (next_state) { + case OMNIPRESENT: + /* + * RFC 7583: The publication interval (Ipub) is the + * amount of time that must elapse after the + * publication of a DNSKEY (plus RRSIG (KSK)) before + * it can be assumed that any resolvers that have the + * relevant RRset cached have a copy of the new + * information. This is the sum of the propagation + * delay (Dprp) and the DNSKEY TTL (TTLkey). This + * translates to zone-propagation-delay + dnskey-ttl. + * We will also add the publish-safety interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_publishsafety(kasp); + break; + case HIDDEN: + /* + * Same as OMNIPRESENT but without the publish-safety + * interval. + */ + nexttime = lastchange + dst_key_getttl(key->key) + + dns_kasp_zonepropagationdelay(kasp); + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_ZRRSIG: + switch (next_state) { + case OMNIPRESENT: + case HIDDEN: + /* + * RFC 7583: The retire interval (Iret) is the amount + * of time that must elapse after a DNSKEY or + * associated data enters the retire state for any + * dependent information (RRSIG ZSK) to be purged from + * validating resolver caches. This is defined as: + * + * Iret = Dsgn + Dprp + TTLsig + * + * Where Dsgn is the Dsgn is the delay needed to + * ensure that all existing RRsets have been re-signed + * with the new key, Dprp is the propagation delay and + * TTLsig is the maximum TTL of all zone RRSIG + * records. This translates to: + * + * Dsgn + zone-propagation-delay + max-zone-ttl. + * + * We will also add the retire-safety interval. + */ + nexttime = lastchange + ttlsig + + dns_kasp_zonepropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + /* + * Only add the sign delay Dsgn if there is an actual + * predecessor or successor key. + */ + uint32_t tag; + ret = dst_key_getnum(key->key, DST_NUM_PREDECESSOR, + &tag); + if (ret != ISC_R_SUCCESS) { + ret = dst_key_getnum(key->key, + DST_NUM_SUCCESSOR, &tag); + } + if (ret == ISC_R_SUCCESS) { + nexttime += dns_kasp_signdelay(kasp); + } + break; + default: + nexttime = now; + break; + } + break; + case DST_KEY_DS: + switch (next_state) { + /* + * RFC 7583: The successor DS record is published in + * the parent zone and after the registration delay + * (Dreg), the time taken after the DS record has been + * submitted to the parent zone manager for it to be + * placed in the zone. Key N (the predecessor) must + * remain in the zone until any caches that contain a + * copy of the DS RRset have a copy containing the new + * DS record. This interval is the retire interval + * (Iret), given by: + * + * Iret = DprpP + TTLds + * + * This translates to: + * + * parent-propagation-delay + parent-ds-ttl. + * + * We will also add the retire-safety interval. + */ + case OMNIPRESENT: + /* Make sure DS has been seen in the parent. */ + ret = dst_key_gettime(key->key, DST_TIME_DSPUBLISH, + &dstime); + if (ret != ISC_R_SUCCESS || dstime > now) { + /* Not yet, try again in an hour. */ + nexttime = now + 3600; + } else { + nexttime = + dstime + dns_kasp_dsttl(kasp) + + dns_kasp_parentpropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + } + break; + case HIDDEN: + /* Make sure DS has been withdrawn from the parent. */ + ret = dst_key_gettime(key->key, DST_TIME_DSDELETE, + &dstime); + if (ret != ISC_R_SUCCESS || dstime > now) { + /* Not yet, try again in an hour. */ + nexttime = now + 3600; + } else { + nexttime = + dstime + dns_kasp_dsttl(kasp) + + dns_kasp_parentpropagationdelay(kasp) + + dns_kasp_retiresafety(kasp); + } + break; + default: + nexttime = now; + break; + } + break; + default: + UNREACHABLE(); + break; + } + + *when = nexttime; +} + +/* + * Update keys. + * This implements Algorithm (1) of "Flexible and Robust Key Rollover". + * + */ +static isc_result_t +keymgr_update(dns_dnsseckeylist_t *keyring, dns_kasp_t *kasp, isc_stdtime_t now, + isc_stdtime_t *nexttime, bool secure_to_insecure) { + bool changed; + + /* Repeat until nothing changed. */ +transition: + changed = false; + + /* For all keys in the zone. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(dkey->key, keystr, sizeof(keystr)); + + /* For all records related to this key. */ + for (int i = 0; i < NUM_KEYSTATES; i++) { + isc_result_t ret; + isc_stdtime_t when; + dst_key_state_t state, next_state; + + ret = dst_key_getstate(dkey->key, i, &state); + if (ret == ISC_R_NOTFOUND) { + /* + * This record type is not applicable for this + * key, continue to the next record type. + */ + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: examine %s %s type %s " + "in state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state]); + + /* Get the desired next state. */ + next_state = keymgr_desiredstate(dkey, state); + if (state == next_state) { + /* + * This record is in a stable state. + * No change needed, continue with the next + * record type. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: %s %s type %s in " + "stable state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], + keystatestrings[state]); + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: can we transition %s %s type %s " + "state %s to state %s?", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* Is the transition allowed according to policy? */ + if (!keymgr_policy_approval(keyring, dkey, i, + next_state)) + { + /* No, please respect rollover methods. */ + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: policy says no to %s %s type " + "%s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + continue; + } + + /* Is the transition DNSSEC safe? */ + if (!keymgr_transition_allowed(keyring, dkey, i, + next_state, + secure_to_insecure)) + { + /* No, this would make the zone bogus. */ + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnssec says no to %s %s type " + "%s " + "state %s to state %s", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + continue; + } + + /* Is it time to make the transition? */ + when = now; + keymgr_transition_time(dkey, i, next_state, kasp, now, + &when); + if (when > now) { + /* Not yet. */ + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: time says no to %s %s type %s " + "state %s to state %s (wait %u " + "seconds)", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state], + when - now); + if (*nexttime == 0 || *nexttime > when) { + *nexttime = when; + } + continue; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: transition %s %s type %s " + "state %s to state %s!", + keymgr_keyrole(dkey->key), keystr, + keystatetags[i], keystatestrings[state], + keystatestrings[next_state]); + + /* It is safe to make the transition. */ + dst_key_setstate(dkey->key, i, next_state); + dst_key_settime(dkey->key, keystatetimes[i], now); + INSIST(dst_key_ismodified(dkey->key)); + changed = true; + } + } + + /* We changed something, continue processing. */ + if (changed) { + goto transition; + } + + return (ISC_R_SUCCESS); +} + +/* + * See if this key needs to be initialized with properties. A key created + * and derived from a dnssec-policy will have the required metadata available, + * otherwise these may be missing and need to be initialized. The key states + * will be initialized according to existing timing metadata. + * + */ +static void +keymgr_key_init(dns_dnsseckey_t *key, dns_kasp_t *kasp, isc_stdtime_t now, + bool csk) { + bool ksk, zsk; + isc_result_t ret; + isc_stdtime_t active = 0, pub = 0, syncpub = 0, retire = 0, remove = 0; + dst_key_state_t dnskey_state = HIDDEN; + dst_key_state_t ds_state = HIDDEN; + dst_key_state_t zrrsig_state = HIDDEN; + dst_key_state_t goal_state = HIDDEN; + + REQUIRE(key != NULL); + REQUIRE(key->key != NULL); + + /* Initialize role. */ + ret = dst_key_getbool(key->key, DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) != 0); + dst_key_setbool(key->key, DST_BOOL_KSK, (ksk || csk)); + } + ret = dst_key_getbool(key->key, DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = ((dst_key_flags(key->key) & DNS_KEYFLAG_KSK) == 0); + dst_key_setbool(key->key, DST_BOOL_ZSK, (zsk || csk)); + } + + /* Get time metadata. */ + ret = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (active <= now && ret == ISC_R_SUCCESS) { + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); + ttlsig += dns_kasp_zonepropagationdelay(kasp); + if ((active + ttlsig) <= now) { + zrrsig_state = OMNIPRESENT; + } else { + zrrsig_state = RUMOURED; + } + goal_state = OMNIPRESENT; + } + ret = dst_key_gettime(key->key, DST_TIME_PUBLISH, &pub); + if (pub <= now && ret == ISC_R_SUCCESS) { + dns_ttl_t key_ttl = dst_key_getttl(key->key); + key_ttl += dns_kasp_zonepropagationdelay(kasp); + if ((pub + key_ttl) <= now) { + dnskey_state = OMNIPRESENT; + } else { + dnskey_state = RUMOURED; + } + goal_state = OMNIPRESENT; + } + ret = dst_key_gettime(key->key, DST_TIME_SYNCPUBLISH, &syncpub); + if (syncpub <= now && ret == ISC_R_SUCCESS) { + dns_ttl_t ds_ttl = dns_kasp_dsttl(kasp); + ds_ttl += dns_kasp_parentpropagationdelay(kasp); + if ((syncpub + ds_ttl) <= now) { + ds_state = OMNIPRESENT; + } else { + ds_state = RUMOURED; + } + goal_state = OMNIPRESENT; + } + ret = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (retire <= now && ret == ISC_R_SUCCESS) { + dns_ttl_t ttlsig = dns_kasp_zonemaxttl(kasp, true); + ttlsig += dns_kasp_zonepropagationdelay(kasp); + if ((retire + ttlsig) <= now) { + zrrsig_state = HIDDEN; + } else { + zrrsig_state = UNRETENTIVE; + } + ds_state = UNRETENTIVE; + goal_state = HIDDEN; + } + ret = dst_key_gettime(key->key, DST_TIME_DELETE, &remove); + if (remove <= now && ret == ISC_R_SUCCESS) { + dns_ttl_t key_ttl = dst_key_getttl(key->key); + key_ttl += dns_kasp_zonepropagationdelay(kasp); + if ((remove + key_ttl) <= now) { + dnskey_state = HIDDEN; + } else { + dnskey_state = UNRETENTIVE; + } + zrrsig_state = HIDDEN; + ds_state = HIDDEN; + goal_state = HIDDEN; + } + + /* Set goal if not already set. */ + if (dst_key_getstate(key->key, DST_KEY_GOAL, &goal_state) != + ISC_R_SUCCESS) + { + dst_key_setstate(key->key, DST_KEY_GOAL, goal_state); + } + + /* Set key states for all keys that do not have them. */ + INITIALIZE_STATE(key->key, DST_KEY_DNSKEY, DST_TIME_DNSKEY, + dnskey_state, now); + if (ksk || csk) { + INITIALIZE_STATE(key->key, DST_KEY_KRRSIG, DST_TIME_KRRSIG, + dnskey_state, now); + INITIALIZE_STATE(key->key, DST_KEY_DS, DST_TIME_DS, ds_state, + now); + } + if (zsk || csk) { + INITIALIZE_STATE(key->key, DST_KEY_ZRRSIG, DST_TIME_ZRRSIG, + zrrsig_state, now); + } +} + +static isc_result_t +keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key, + dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *newkeys, + const dns_name_t *origin, dns_rdataclass_t rdclass, + dns_kasp_t *kasp, uint32_t lifetime, bool rollover, + isc_stdtime_t now, isc_stdtime_t *nexttime, + isc_mem_t *mctx) { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t retire = 0, active = 0, prepub = 0; + dns_dnsseckey_t *new_key = NULL; + dns_dnsseckey_t *candidate = NULL; + dst_key_t *dst_key = NULL; + + /* Do we need to create a successor for the active key? */ + if (active_key != NULL) { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, sizeof(keystr)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) is active in policy %s", + keystr, keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + + /* + * Calculate when the successor needs to be published + * in the zone. + */ + prepub = keymgr_prepublication_time(active_key, kasp, lifetime, + now); + if (prepub > now) { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: new successor needed for " + "DNSKEY %s (%s) (policy %s) in %u " + "seconds", + keystr, keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp), (prepub - now)); + } + } + if (prepub == 0 || prepub > now) { + /* No need to start rollover now. */ + if (*nexttime == 0 || prepub < *nexttime) { + *nexttime = prepub; + } + return (ISC_R_SUCCESS); + } + + if (keymgr_key_has_successor(active_key, keyring)) { + /* Key already has successor. */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, + sizeof(keystr)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: key DNSKEY %s (%s) (policy " + "%s) already has successor", + keystr, keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + return (ISC_R_SUCCESS); + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + dst_key_format(active_key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: need successor for DNSKEY %s " + "(%s) (policy %s)", + keystr, keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + } + + /* + * If rollover is not allowed, warn. + */ + if (!rollover) { + dst_key_format(active_key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "keymgr: DNSKEY %s (%s) is offline in " + "policy %s, cannot start rollover", + keystr, keymgr_keyrole(active_key->key), + dns_kasp_getname(kasp)); + return (ISC_R_SUCCESS); + } + } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namestr, sizeof(namestr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: no active key found for %s (policy %s)", + namestr, dns_kasp_getname(kasp)); + } + + /* It is time to do key rollover, we need a new key. */ + + /* + * Check if there is a key available in pool because keys + * may have been pregenerated with dnssec-keygen. + */ + for (candidate = ISC_LIST_HEAD(*keyring); candidate != NULL; + candidate = ISC_LIST_NEXT(candidate, link)) + { + if (keymgr_dnsseckey_kaspkey_match(candidate, kaspkey) && + dst_key_is_unused(candidate->key)) + { + /* Found a candidate in keyring. */ + break; + } + } + + if (candidate == NULL) { + /* No key available in keyring, create a new one. */ + bool csk = (dns_kasp_key_ksk(kaspkey) && + dns_kasp_key_zsk(kaspkey)); + + isc_result_t result = keymgr_createkey(kaspkey, origin, rdclass, + mctx, keyring, newkeys, + &dst_key); + if (result != ISC_R_SUCCESS) { + return (result); + } + dst_key_setttl(dst_key, dns_kasp_dnskeyttl(kasp)); + dst_key_settime(dst_key, DST_TIME_CREATED, now); + result = dns_dnsseckey_create(mctx, &dst_key, &new_key); + if (result != ISC_R_SUCCESS) { + return (result); + } + keymgr_key_init(new_key, kasp, now, csk); + } else { + new_key = candidate; + } + dst_key_setnum(new_key->key, DST_NUM_LIFETIME, lifetime); + + /* Got a key. */ + if (active_key == NULL) { + /* + * If there is no active key found yet for this kasp + * key configuration, immediately make this key active. + */ + dst_key_settime(new_key->key, DST_TIME_PUBLISH, now); + dst_key_settime(new_key->key, DST_TIME_ACTIVATE, now); + keymgr_settime_syncpublish(new_key, kasp, true); + active = now; + } else { + /* + * This is a successor. Mark the relationship. + */ + isc_stdtime_t created; + (void)dst_key_gettime(new_key->key, DST_TIME_CREATED, &created); + + dst_key_setnum(new_key->key, DST_NUM_PREDECESSOR, + dst_key_id(active_key->key)); + dst_key_setnum(active_key->key, DST_NUM_SUCCESSOR, + dst_key_id(new_key->key)); + (void)dst_key_gettime(active_key->key, DST_TIME_INACTIVE, + &retire); + active = retire; + + /* + * If prepublication time and/or retire time are + * in the past (before the new key was created), use + * creation time as published and active time, + * effectively immediately making the key active. + */ + if (prepub < created) { + active += (created - prepub); + prepub = created; + } + if (active < created) { + active = created; + } + dst_key_settime(new_key->key, DST_TIME_PUBLISH, prepub); + dst_key_settime(new_key->key, DST_TIME_ACTIVATE, active); + keymgr_settime_syncpublish(new_key, kasp, false); + + /* + * Retire predecessor. + */ + dst_key_setstate(active_key->key, DST_KEY_GOAL, HIDDEN); + } + + /* This key wants to be present. */ + dst_key_setstate(new_key->key, DST_KEY_GOAL, OMNIPRESENT); + + /* Do we need to set retire time? */ + if (lifetime > 0) { + dst_key_settime(new_key->key, DST_TIME_INACTIVE, + (active + lifetime)); + keymgr_settime_remove(new_key, kasp); + } + + /* Append dnsseckey to list of new keys. */ + dns_dnssec_get_hints(new_key, now); + new_key->source = dns_keysource_repository; + INSIST(!new_key->legacy); + if (candidate == NULL) { + ISC_LIST_APPEND(*newkeys, new_key, link); + } + + /* Logging. */ + dst_key_format(new_key->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_INFO, "keymgr: DNSKEY %s (%s) %s for policy %s", + keystr, keymgr_keyrole(new_key->key), + (candidate != NULL) ? "selected" : "created", + dns_kasp_getname(kasp)); + return (ISC_R_SUCCESS); +} + +static bool +keymgr_key_may_be_purged(dst_key_t *key, uint32_t after, isc_stdtime_t now) { + bool ksk = false; + bool zsk = false; + dst_key_state_t hidden[NUM_KEYSTATES] = { HIDDEN, NA, NA, NA }; + isc_stdtime_t lastchange = 0; + + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key, keystr, sizeof(keystr)); + + /* If 'purge-keys' is disabled, always retain keys. */ + if (after == 0) { + return (false); + } + + /* Don't purge keys with goal OMNIPRESENT */ + if (dst_key_goal(key) == OMNIPRESENT) { + return (false); + } + + /* Don't purge unused keys. */ + if (dst_key_is_unused(key)) { + return (false); + } + + /* If this key is completely HIDDEN it may be purged. */ + (void)dst_key_getbool(key, DST_BOOL_KSK, &ksk); + (void)dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (ksk) { + hidden[DST_KEY_KRRSIG] = HIDDEN; + hidden[DST_KEY_DS] = HIDDEN; + } + if (zsk) { + hidden[DST_KEY_ZRRSIG] = HIDDEN; + } + if (!keymgr_key_match_state(key, key, 0, NA, hidden)) { + return (false); + } + + /* + * Check 'purge-keys' interval. If the interval has passed since + * the last key change, it may be purged. + */ + for (int i = 0; i < NUM_KEYSTATES; i++) { + isc_stdtime_t change = 0; + (void)dst_key_gettime(key, keystatetimes[i], &change); + if (change > lastchange) { + lastchange = change; + } + } + + return ((lastchange + after) < now); +} + +static void +keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) { + isc_result_t ret; + isc_buffer_t fileb; + char filename[NAME_MAX]; + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + ret = dst_key_buildfilename(key, type, dir, &fileb); + if (ret != ISC_R_SUCCESS) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "keymgr: failed to purge DNSKEY %s (%s): cannot " + "build filename (%s)", + keystr, keymgr_keyrole(key), + isc_result_totext(ret)); + return; + } + + if (unlink(filename) < 0) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "keymgr: failed to purge DNSKEY %s (%s): unlink " + "'%s' failed", + keystr, keymgr_keyrole(key), filename); + } +} + +/* + * Examine 'keys' and match 'kasp' policy. + * + */ +isc_result_t +dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, + const char *directory, isc_mem_t *mctx, + dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys, + dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime) { + isc_result_t result = ISC_R_SUCCESS; + dns_dnsseckeylist_t newkeys; + dns_kasp_key_t *kkey; + dns_dnsseckey_t *newkey = NULL; + isc_dir_t dir; + bool dir_open = false; + bool secure_to_insecure = false; + int numkeys = 0; + int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); + char keystr[DST_KEY_FORMATSIZE]; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + + ISC_LIST_INIT(newkeys); + + isc_dir_init(&dir); + if (directory == NULL) { + directory = "."; + } + + RETERR(isc_dir_open(&dir, directory)); + dir_open = true; + + *nexttime = 0; + + /* Debug logging: what keys are available in the keyring? */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + if (ISC_LIST_EMPTY(*keyring)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring empty (zone %s policy " + "%s)", + namebuf, dns_kasp_getname(kasp)); + } + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: keyring: %s (policy %s)", keystr, + dns_kasp_getname(kasp)); + } + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(1), + "keymgr: dnskeys: %s (policy %s)", keystr, + dns_kasp_getname(kasp)); + } + } + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*dnskeys); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + numkeys++; + } + + /* Do we need to remove keys? */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + bool found_match = false; + + keymgr_key_init(dkey, kasp, now, (numkeys == 1)); + + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { + found_match = true; + break; + } + } + + /* No match, so retire unwanted retire key. */ + if (!found_match) { + keymgr_key_retire(dkey, kasp, now); + } + + /* Check purge-keys interval. */ + if (keymgr_key_may_be_purged(dkey->key, + dns_kasp_purgekeys(kasp), now)) + { + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "keymgr: purge DNSKEY %s (%s) according " + "to policy %s", + keystr, keymgr_keyrole(dkey->key), + dns_kasp_getname(kasp)); + + keymgr_purge_keyfile(dkey->key, directory, + DST_TYPE_PUBLIC); + keymgr_purge_keyfile(dkey->key, directory, + DST_TYPE_PRIVATE); + keymgr_purge_keyfile(dkey->key, directory, + DST_TYPE_STATE); + + dkey->purge = true; + } + } + + /* Create keys according to the policy, if come in short. */ + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + uint32_t lifetime = dns_kasp_key_lifetime(kkey); + dns_dnsseckey_t *active_key = NULL; + bool rollover_allowed = true; + + /* Do we have keys available for this kasp key? */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); + dkey != NULL; dkey = ISC_LIST_NEXT(dkey, link)) + { + if (keymgr_dnsseckey_kaspkey_match(dkey, kkey)) { + /* Found a match. */ + dst_key_format(dkey->key, keystr, + sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) matches " + "policy %s", + keystr, keymgr_keyrole(dkey->key), + dns_kasp_getname(kasp)); + + /* Initialize lifetime if not set. */ + uint32_t l; + if (dst_key_getnum(dkey->key, DST_NUM_LIFETIME, + &l) != ISC_R_SUCCESS) + { + dst_key_setnum(dkey->key, + DST_NUM_LIFETIME, + lifetime); + } + + if (active_key) { + /* We already have an active key that + * matches the kasp policy. + */ + if (!dst_key_is_unused(dkey->key) && + (dst_key_goal(dkey->key) == + OMNIPRESENT) && + !keymgr_dep(dkey->key, keyring, + NULL) && + !keymgr_dep(active_key->key, + keyring, NULL)) + { + /* + * Multiple signing keys match + * the kasp key configuration. + * Retire excess keys in use. + */ + keymgr_key_retire(dkey, kasp, + now); + } + continue; + } + + /* + * Save the matched key only if it is active + * or desires to be active. + */ + if (dst_key_goal(dkey->key) == OMNIPRESENT || + dst_key_is_active(dkey->key, now)) + { + active_key = dkey; + } + } + } + + if (active_key == NULL) { + /* + * We didn't found an active key, perhaps the .private + * key file is offline. If so, we don't want to create + * a successor key. Check if we have an appropriate + * state file. + */ + for (dns_dnsseckey_t *dnskey = ISC_LIST_HEAD(*dnskeys); + dnskey != NULL; + dnskey = ISC_LIST_NEXT(dnskey, link)) + { + if (keymgr_dnsseckey_kaspkey_match(dnskey, + kkey)) + { + /* Found a match. */ + dst_key_format(dnskey->key, keystr, + sizeof(keystr)); + isc_log_write( + dns_lctx, + DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), + "keymgr: DNSKEY %s (%s) " + "offline, policy %s", + keystr, + keymgr_keyrole(dnskey->key), + dns_kasp_getname(kasp)); + rollover_allowed = false; + active_key = dnskey; + break; + } + } + } + + /* See if this key requires a rollover. */ + RETERR(keymgr_key_rollover( + kkey, active_key, keyring, &newkeys, origin, rdclass, + kasp, lifetime, rollover_allowed, now, nexttime, mctx)); + } + + /* Walked all kasp key configurations. Append new keys. */ + if (!ISC_LIST_EMPTY(newkeys)) { + ISC_LIST_APPENDLIST(*keyring, newkeys, link); + } + + /* + * If the policy has an empty key list, this means the zone is going + * back to unsigned. + */ + secure_to_insecure = dns_kasp_keylist_empty(kasp); + + /* Read to update key states. */ + keymgr_update(keyring, kasp, now, nexttime, secure_to_insecure); + + /* Store key states and update hints. */ + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (dst_key_ismodified(dkey->key) && !dkey->purge) { + dns_dnssec_get_hints(dkey, now); + RETERR(dst_key_tofile(dkey->key, options, directory)); + dst_key_setmodified(dkey->key, false); + } + } + + result = ISC_R_SUCCESS; + +failure: + if (dir_open) { + isc_dir_close(&dir); + } + + if (result != ISC_R_SUCCESS) { + while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) { + ISC_LIST_UNLINK(newkeys, newkey, link); + INSIST(newkey->key != NULL); + dst_key_free(&newkey->key); + dns_dnsseckey_destroy(mctx, &newkey); + } + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3), + "keymgr: %s done", namebuf); + } + return (result); +} + +static isc_result_t +keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, isc_stdtime_t when, + bool dspublish, dns_keytag_t id, unsigned int alg, + bool check_id) { + int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); + isc_dir_t dir; + isc_result_t result; + dns_dnsseckey_t *ksk_key = NULL; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + isc_result_t ret; + bool ksk = false; + + ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + if (check_id && dst_key_id(dkey->key) != id) { + continue; + } + if (alg > 0 && dst_key_alg(dkey->key) != alg) { + continue; + } + + if (ksk_key != NULL) { + /* + * Only checkds for one key at a time. + */ + return (DNS_R_TOOMANYKEYS); + } + + ksk_key = dkey; + } + } + + if (ksk_key == NULL) { + return (DNS_R_NOKEYMATCH); + } + + if (dspublish) { + dst_key_state_t s; + dst_key_settime(ksk_key->key, DST_TIME_DSPUBLISH, when); + result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s); + if (result != ISC_R_SUCCESS || s != RUMOURED) { + dst_key_setstate(ksk_key->key, DST_KEY_DS, RUMOURED); + } + } else { + dst_key_state_t s; + dst_key_settime(ksk_key->key, DST_TIME_DSDELETE, when); + result = dst_key_getstate(ksk_key->key, DST_KEY_DS, &s); + if (result != ISC_R_SUCCESS || s != UNRETENTIVE) { + dst_key_setstate(ksk_key->key, DST_KEY_DS, UNRETENTIVE); + } + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_NOTICE)) { + char keystr[DST_KEY_FORMATSIZE]; + char timestr[26]; /* Minimal buf as per ctime_r() spec. */ + + dst_key_format(ksk_key->key, keystr, sizeof(keystr)); + isc_stdtime_tostring(when, timestr, sizeof(timestr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_NOTICE, + "keymgr: checkds DS for key %s seen %s at %s", + keystr, dspublish ? "published" : "withdrawn", + timestr); + } + + /* Store key state and update hints. */ + isc_dir_init(&dir); + if (directory == NULL) { + directory = "."; + } + result = isc_dir_open(&dir, directory); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_dnssec_get_hints(ksk_key, now); + result = dst_key_tofile(ksk_key->key, options, directory); + if (result == ISC_R_SUCCESS) { + dst_key_setmodified(ksk_key->key, false); + } + isc_dir_close(&dir); + + return (result); +} + +isc_result_t +dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, isc_stdtime_t when, + bool dspublish) { + return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, + 0, 0, false)); +} + +isc_result_t +dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, + isc_stdtime_t when, bool dspublish, dns_keytag_t id, + unsigned int alg) { + return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, + id, alg, true)); +} + +static void +keytime_status(dst_key_t *key, isc_stdtime_t now, isc_buffer_t *buf, + const char *pre, int ks, int kt) { + char timestr[26]; /* Minimal buf as per ctime_r() spec. */ + isc_result_t ret; + isc_stdtime_t when = 0; + dst_key_state_t state = NA; + + isc_buffer_printf(buf, "%s", pre); + (void)dst_key_getstate(key, ks, &state); + ret = dst_key_gettime(key, kt, &when); + if (state == RUMOURED || state == OMNIPRESENT) { + isc_buffer_printf(buf, "yes - since "); + } else if (now < when) { + isc_buffer_printf(buf, "no - scheduled "); + } else { + isc_buffer_printf(buf, "no\n"); + return; + } + if (ret == ISC_R_SUCCESS) { + isc_stdtime_tostring(when, timestr, sizeof(timestr)); + isc_buffer_printf(buf, "%s\n", timestr); + } +} + +static void +rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now, + isc_buffer_t *buf, bool zsk) { + char timestr[26]; /* Minimal buf as per ctime_r() spec. */ + isc_result_t ret = ISC_R_SUCCESS; + isc_stdtime_t active_time = 0; + dst_key_state_t state = NA, goal = NA; + int rrsig, active, retire; + dst_key_t *key = dkey->key; + + if (zsk) { + rrsig = DST_KEY_ZRRSIG; + active = DST_TIME_ACTIVATE; + retire = DST_TIME_INACTIVE; + } else { + rrsig = DST_KEY_KRRSIG; + active = DST_TIME_PUBLISH; + retire = DST_TIME_DELETE; + } + + isc_buffer_printf(buf, "\n"); + + (void)dst_key_getstate(key, DST_KEY_GOAL, &goal); + (void)dst_key_getstate(key, rrsig, &state); + (void)dst_key_gettime(key, active, &active_time); + if (active_time == 0) { + // only interested in keys that were once active. + return; + } + + if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) { + isc_stdtime_t remove_time = 0; + // is the key removed yet? + state = NA; + (void)dst_key_getstate(key, DST_KEY_DNSKEY, &state); + if (state == RUMOURED || state == OMNIPRESENT) { + ret = dst_key_gettime(key, DST_TIME_DELETE, + &remove_time); + if (ret == ISC_R_SUCCESS) { + isc_buffer_printf(buf, " Key is retired, will " + "be removed on "); + isc_stdtime_tostring(remove_time, timestr, + sizeof(timestr)); + isc_buffer_printf(buf, "%s", timestr); + } + } else { + isc_buffer_printf( + buf, " Key has been removed from the zone"); + } + } else { + isc_stdtime_t retire_time = 0; + uint32_t lifetime = 0; + (void)dst_key_getnum(key, DST_NUM_LIFETIME, &lifetime); + ret = dst_key_gettime(key, retire, &retire_time); + if (ret == ISC_R_SUCCESS) { + if (now < retire_time) { + if (goal == OMNIPRESENT) { + isc_buffer_printf(buf, + " Next rollover " + "scheduled on "); + retire_time = keymgr_prepublication_time( + dkey, kasp, lifetime, now); + } else { + isc_buffer_printf( + buf, " Key will retire on "); + } + } else { + isc_buffer_printf(buf, + " Rollover is due since "); + } + isc_stdtime_tostring(retire_time, timestr, + sizeof(timestr)); + isc_buffer_printf(buf, "%s", timestr); + } else { + isc_buffer_printf(buf, " No rollover scheduled"); + } + } + isc_buffer_printf(buf, "\n"); +} + +static void +keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) { + dst_key_state_t state = NA; + + (void)dst_key_getstate(key, ks, &state); + switch (state) { + case HIDDEN: + isc_buffer_printf(buf, " - %shidden\n", pre); + break; + case RUMOURED: + isc_buffer_printf(buf, " - %srumoured\n", pre); + break; + case OMNIPRESENT: + isc_buffer_printf(buf, " - %somnipresent\n", pre); + break; + case UNRETENTIVE: + isc_buffer_printf(buf, " - %sunretentive\n", pre); + break; + case NA: + default: + /* print nothing */ + break; + } +} + +void +dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + isc_stdtime_t now, char *out, size_t out_len) { + isc_buffer_t buf; + char timestr[26]; /* Minimal buf as per ctime_r() spec. */ + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + REQUIRE(out != NULL); + + isc_buffer_init(&buf, out, out_len); + + // policy name + isc_buffer_printf(&buf, "dnssec-policy: %s\n", dns_kasp_getname(kasp)); + isc_buffer_printf(&buf, "current time: "); + isc_stdtime_tostring(now, timestr, sizeof(timestr)); + isc_buffer_printf(&buf, "%s\n", timestr); + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + char algstr[DNS_NAME_FORMATSIZE]; + bool ksk = false, zsk = false; + isc_result_t ret; + + if (dst_key_is_unused(dkey->key)) { + continue; + } + + // key data + dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr, + sizeof(algstr)); + isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n", + dst_key_id(dkey->key), algstr, + keymgr_keyrole(dkey->key)); + + // publish status + keytime_status(dkey->key, now, &buf, + " published: ", DST_KEY_DNSKEY, + DST_TIME_PUBLISH); + + // signing status + ret = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); + if (ret == ISC_R_SUCCESS && ksk) { + keytime_status(dkey->key, now, &buf, + " key signing: ", DST_KEY_KRRSIG, + DST_TIME_PUBLISH); + } + ret = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk); + if (ret == ISC_R_SUCCESS && zsk) { + keytime_status(dkey->key, now, &buf, + " zone signing: ", DST_KEY_ZRRSIG, + DST_TIME_ACTIVATE); + } + + // rollover status + rollover_status(dkey, kasp, now, &buf, zsk); + + // key states + keystate_status(dkey->key, &buf, + "goal: ", DST_KEY_GOAL); + keystate_status(dkey->key, &buf, + "dnskey: ", DST_KEY_DNSKEY); + keystate_status(dkey->key, &buf, + "ds: ", DST_KEY_DS); + keystate_status(dkey->key, &buf, + "zone rrsig: ", DST_KEY_ZRRSIG); + keystate_status(dkey->key, &buf, + "key rrsig: ", DST_KEY_KRRSIG); + } +} + +isc_result_t +dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, + const char *directory, isc_stdtime_t now, + isc_stdtime_t when, dns_keytag_t id, + unsigned int algorithm) { + int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); + isc_dir_t dir; + isc_result_t result; + dns_dnsseckey_t *key = NULL; + isc_stdtime_t active, retire, prepub; + + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(keyring != NULL); + + for (dns_dnsseckey_t *dkey = ISC_LIST_HEAD(*keyring); dkey != NULL; + dkey = ISC_LIST_NEXT(dkey, link)) + { + if (dst_key_id(dkey->key) != id) { + continue; + } + if (algorithm > 0 && dst_key_alg(dkey->key) != algorithm) { + continue; + } + if (key != NULL) { + /* + * Only rollover for one key at a time. + */ + return (DNS_R_TOOMANYKEYS); + } + key = dkey; + } + + if (key == NULL) { + return (DNS_R_NOKEYMATCH); + } + + result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (result != ISC_R_SUCCESS || active > now) { + return (DNS_R_KEYNOTACTIVE); + } + + result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &retire); + if (result != ISC_R_SUCCESS) { + /** + * Default to as if this key was not scheduled to + * become retired, as if it had unlimited lifetime. + */ + retire = 0; + } + + /** + * Usually when is set to now, which is before the scheduled + * prepublication time, meaning we reduce the lifetime of the + * key. But in some cases, the lifetime can also be extended. + * We accept it, but we can return an error here if that + * turns out to be unintuitive behavior. + */ + prepub = dst_key_getttl(key->key) + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + retire = when + prepub; + + dst_key_settime(key->key, DST_TIME_INACTIVE, retire); + dst_key_setnum(key->key, DST_NUM_LIFETIME, (retire - active)); + + /* Store key state and update hints. */ + isc_dir_init(&dir); + if (directory == NULL) { + directory = "."; + } + result = isc_dir_open(&dir, directory); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_dnssec_get_hints(key, now); + result = dst_key_tofile(key->key, options, directory); + if (result == ISC_R_SUCCESS) { + dst_key_setmodified(key->key, false); + } + isc_dir_close(&dir); + + return (result); +} diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c new file mode 100644 index 0000000..8634281 --- /dev/null +++ b/lib/dns/keytable.c @@ -0,0 +1,956 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEYTABLE_MAGIC ISC_MAGIC('K', 'T', 'b', 'l') +#define VALID_KEYTABLE(kt) ISC_MAGIC_VALID(kt, KEYTABLE_MAGIC) + +#define KEYNODE_MAGIC ISC_MAGIC('K', 'N', 'o', 'd') +#define VALID_KEYNODE(kn) ISC_MAGIC_VALID(kn, KEYNODE_MAGIC) + +struct dns_keytable { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + isc_rwlock_t rwlock; + /* Locked by rwlock. */ + dns_rbt_t *table; +}; + +struct dns_keynode { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + isc_rwlock_t rwlock; + dns_rdatalist_t *dslist; + dns_rdataset_t dsset; + bool managed; + bool initial; +}; + +static dns_keynode_t * +new_keynode(dns_rdata_ds_t *ds, dns_keytable_t *keytable, bool managed, + bool initial); + +static void +keynode_disassociate(dns_rdataset_t *rdataset); +static isc_result_t +keynode_first(dns_rdataset_t *rdataset); +static isc_result_t +keynode_next(dns_rdataset_t *rdataset); +static void +keynode_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata); +static void +keynode_clone(dns_rdataset_t *source, dns_rdataset_t *target); + +static dns_rdatasetmethods_t methods = { + keynode_disassociate, + keynode_first, + keynode_next, + keynode_current, + keynode_clone, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, + NULL, + NULL /* addglue */ +}; + +static void +keynode_attach(dns_keynode_t *source, dns_keynode_t **target) { + REQUIRE(VALID_KEYNODE(source)); + isc_refcount_increment(&source->refcount); + *target = source; +} + +static void +keynode_detach(isc_mem_t *mctx, dns_keynode_t **keynodep) { + REQUIRE(keynodep != NULL && VALID_KEYNODE(*keynodep)); + dns_keynode_t *knode = *keynodep; + *keynodep = NULL; + + if (isc_refcount_decrement(&knode->refcount) == 1) { + dns_rdata_t *rdata = NULL; + isc_refcount_destroy(&knode->refcount); + isc_rwlock_destroy(&knode->rwlock); + if (knode->dslist != NULL) { + for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); + rdata != NULL; + rdata = ISC_LIST_HEAD(knode->dslist->rdata)) + { + ISC_LIST_UNLINK(knode->dslist->rdata, rdata, + link); + isc_mem_put(mctx, rdata->data, + DNS_DS_BUFFERSIZE); + isc_mem_put(mctx, rdata, sizeof(*rdata)); + } + + isc_mem_put(mctx, knode->dslist, + sizeof(*knode->dslist)); + knode->dslist = NULL; + } + isc_mem_putanddetach(&knode->mctx, knode, + sizeof(dns_keynode_t)); + } +} + +static void +free_keynode(void *node, void *arg) { + dns_keynode_t *keynode = node; + isc_mem_t *mctx = arg; + + keynode_detach(mctx, &keynode); +} + +isc_result_t +dns_keytable_create(isc_mem_t *mctx, dns_keytable_t **keytablep) { + dns_keytable_t *keytable; + isc_result_t result; + + /* + * Create a keytable. + */ + + REQUIRE(keytablep != NULL && *keytablep == NULL); + + keytable = isc_mem_get(mctx, sizeof(*keytable)); + + keytable->table = NULL; + result = dns_rbt_create(mctx, free_keynode, mctx, &keytable->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_keytable; + } + + isc_rwlock_init(&keytable->rwlock, 0, 0); + isc_refcount_init(&keytable->references, 1); + + keytable->mctx = NULL; + isc_mem_attach(mctx, &keytable->mctx); + keytable->magic = KEYTABLE_MAGIC; + *keytablep = keytable; + + return (ISC_R_SUCCESS); + +cleanup_keytable: + isc_mem_putanddetach(&mctx, keytable, sizeof(*keytable)); + + return (result); +} + +void +dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp) { + REQUIRE(VALID_KEYTABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_keytable_detach(dns_keytable_t **keytablep) { + REQUIRE(keytablep != NULL && VALID_KEYTABLE(*keytablep)); + dns_keytable_t *keytable = *keytablep; + *keytablep = NULL; + + if (isc_refcount_decrement(&keytable->references) == 1) { + isc_refcount_destroy(&keytable->references); + dns_rbt_destroy(&keytable->table); + isc_rwlock_destroy(&keytable->rwlock); + keytable->magic = 0; + isc_mem_putanddetach(&keytable->mctx, keytable, + sizeof(*keytable)); + } +} + +static void +add_ds(dns_keynode_t *knode, dns_rdata_ds_t *ds, isc_mem_t *mctx) { + isc_result_t result; + dns_rdata_t *dsrdata = NULL, *rdata = NULL; + void *data = NULL; + bool exists = false; + isc_buffer_t b; + + dsrdata = isc_mem_get(mctx, sizeof(*dsrdata)); + dns_rdata_init(dsrdata); + + data = isc_mem_get(mctx, DNS_DS_BUFFERSIZE); + isc_buffer_init(&b, data, DNS_DS_BUFFERSIZE); + + result = dns_rdata_fromstruct(dsrdata, dns_rdataclass_in, + dns_rdatatype_ds, ds, &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + RWLOCK(&knode->rwlock, isc_rwlocktype_write); + + if (knode->dslist == NULL) { + knode->dslist = isc_mem_get(mctx, sizeof(*knode->dslist)); + dns_rdatalist_init(knode->dslist); + knode->dslist->rdclass = dns_rdataclass_in; + knode->dslist->type = dns_rdatatype_ds; + + INSIST(knode->dsset.methods == NULL); + knode->dsset.methods = &methods; + knode->dsset.rdclass = knode->dslist->rdclass; + knode->dsset.type = knode->dslist->type; + knode->dsset.covers = knode->dslist->covers; + knode->dsset.ttl = knode->dslist->ttl; + knode->dsset.private1 = knode; + knode->dsset.private2 = NULL; + knode->dsset.private3 = NULL; + knode->dsset.privateuint4 = 0; + knode->dsset.private5 = NULL; + knode->dsset.trust = dns_trust_ultimate; + } + + for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL; + rdata = ISC_LIST_NEXT(rdata, link)) + { + if (dns_rdata_compare(rdata, dsrdata) == 0) { + exists = true; + break; + } + } + + if (exists) { + isc_mem_put(mctx, dsrdata->data, DNS_DS_BUFFERSIZE); + isc_mem_put(mctx, dsrdata, sizeof(*dsrdata)); + } else { + ISC_LIST_APPEND(knode->dslist->rdata, dsrdata, link); + } + + RWUNLOCK(&knode->rwlock, isc_rwlocktype_write); +} + +static isc_result_t +delete_ds(dns_keytable_t *keytable, dns_rbtnode_t *node, dns_rdata_ds_t *ds) { + dns_keynode_t *knode = node->data; + isc_result_t result; + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t *rdata = NULL; + unsigned char data[DNS_DS_BUFFERSIZE]; + bool found = false; + isc_buffer_t b; + + RWLOCK(&knode->rwlock, isc_rwlocktype_read); + if (knode->dslist == NULL) { + RWUNLOCK(&knode->rwlock, isc_rwlocktype_read); + return (ISC_R_SUCCESS); + } + + isc_buffer_init(&b, data, DNS_DS_BUFFERSIZE); + + result = dns_rdata_fromstruct(&dsrdata, dns_rdataclass_in, + dns_rdatatype_ds, ds, &b); + if (result != ISC_R_SUCCESS) { + RWUNLOCK(&knode->rwlock, isc_rwlocktype_write); + return (result); + } + + for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL; + rdata = ISC_LIST_NEXT(rdata, link)) + { + if (dns_rdata_compare(rdata, &dsrdata) == 0) { + found = true; + break; + } + } + + if (!found) { + RWUNLOCK(&knode->rwlock, isc_rwlocktype_read); + /* + * The keyname must have matched or we wouldn't be here, + * so we use DNS_R_PARTIALMATCH instead of ISC_R_NOTFOUND. + */ + return (DNS_R_PARTIALMATCH); + } + + /* + * Replace knode with a new instance without the DS. + */ + node->data = new_keynode(NULL, keytable, knode->managed, + knode->initial); + for (rdata = ISC_LIST_HEAD(knode->dslist->rdata); rdata != NULL; + rdata = ISC_LIST_NEXT(rdata, link)) + { + if (dns_rdata_compare(rdata, &dsrdata) != 0) { + dns_rdata_ds_t ds0; + result = dns_rdata_tostruct(rdata, &ds0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + add_ds(node->data, &ds0, keytable->mctx); + } + } + RWUNLOCK(&knode->rwlock, isc_rwlocktype_read); + + keynode_detach(keytable->mctx, &knode); + + return (ISC_R_SUCCESS); +} + +/*% + * Create a keynode for "ds" (or a null key node if "ds" is NULL), set + * "managed" and "initial" as requested and attach the keynode to + * to "node" in "keytable". + */ +static dns_keynode_t * +new_keynode(dns_rdata_ds_t *ds, dns_keytable_t *keytable, bool managed, + bool initial) { + dns_keynode_t *knode = NULL; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(!initial || managed); + + knode = isc_mem_get(keytable->mctx, sizeof(dns_keynode_t)); + *knode = (dns_keynode_t){ .magic = KEYNODE_MAGIC }; + + dns_rdataset_init(&knode->dsset); + isc_refcount_init(&knode->refcount, 1); + isc_rwlock_init(&knode->rwlock, 0, 0); + + /* + * If a DS was supplied, initialize an rdatalist. + */ + if (ds != NULL) { + add_ds(knode, ds, keytable->mctx); + } + + isc_mem_attach(keytable->mctx, &knode->mctx); + knode->managed = managed; + knode->initial = initial; + + return (knode); +} + +/*% + * Add key trust anchor "ds" at "keyname" in "keytable". If an anchor + * already exists at the requested name does not contain "ds", update it. + * If "ds" is NULL, add a null key to indicate that "keyname" should be + * treated as a secure domain without supplying key data which would allow + * the domain to be validated. + */ +static isc_result_t +insert(dns_keytable_t *keytable, bool managed, bool initial, + const dns_name_t *keyname, dns_rdata_ds_t *ds, + dns_keytable_callback_t callback, void *callback_arg) { + dns_rbtnode_t *node = NULL; + isc_result_t result; + + REQUIRE(VALID_KEYTABLE(keytable)); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_write); + + result = dns_rbt_addnode(keytable->table, keyname, &node); + if (result == ISC_R_SUCCESS) { + /* + * There was no node for "keyname" in "keytable" yet, so one + * was created. Create a new key node for the supplied + * trust anchor (or a null key node if "ds" is NULL) + * and attach it to the created node. + */ + node->data = new_keynode(ds, keytable, managed, initial); + if (callback != NULL) { + (*callback)(keyname, callback_arg); + } + } else if (result == ISC_R_EXISTS) { + /* + * A node already exists for "keyname" in "keytable". + */ + if (ds != NULL) { + dns_keynode_t *knode = node->data; + if (knode == NULL) { + node->data = new_keynode(ds, keytable, managed, + initial); + if (callback != NULL) { + (*callback)(keyname, callback_arg); + } + } else { + add_ds(knode, ds, keytable->mctx); + } + } + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_keytable_add(dns_keytable_t *keytable, bool managed, bool initial, + dns_name_t *name, dns_rdata_ds_t *ds, + dns_keytable_callback_t callback, void *callback_arg) { + REQUIRE(ds != NULL); + REQUIRE(!initial || managed); + + return (insert(keytable, managed, initial, name, ds, callback, + callback_arg)); +} + +isc_result_t +dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name) { + return (insert(keytable, true, false, name, NULL, NULL, NULL)); +} + +isc_result_t +dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_keytable_callback_t callback, void *callback_arg) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(keyname != NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_write); + result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (node->data != NULL) { + result = dns_rbt_deletenode(keytable->table, node, + false); + if (callback != NULL) { + (*callback)(keyname, callback_arg); + } + } else { + result = ISC_R_NOTFOUND; + } + } else if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_rdata_dnskey_t *dnskey) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + dns_keynode_t *knode = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[4096], digest[DNS_DS_BUFFERSIZE]; + dns_rdata_ds_t ds; + isc_buffer_t b; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(dnskey != NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_write); + result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + + if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + if (result != ISC_R_SUCCESS) { + goto finish; + } + + if (node->data == NULL) { + result = ISC_R_NOTFOUND; + goto finish; + } + + knode = node->data; + + RWLOCK(&knode->rwlock, isc_rwlocktype_read); + if (knode->dslist == NULL) { + RWUNLOCK(&knode->rwlock, isc_rwlocktype_read); + result = DNS_R_PARTIALMATCH; + goto finish; + } + RWUNLOCK(&knode->rwlock, isc_rwlocktype_read); + + isc_buffer_init(&b, data, sizeof(data)); + result = dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, + dns_rdatatype_dnskey, dnskey, &b); + if (result != ISC_R_SUCCESS) { + goto finish; + } + + result = dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, + digest, &ds); + if (result != ISC_R_SUCCESS) { + goto finish; + } + + result = delete_ds(keytable, node, &ds); + +finish: + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write); + return (result); +} + +isc_result_t +dns_keytable_find(dns_keytable_t *keytable, const dns_name_t *keyname, + dns_keynode_t **keynodep) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(keyname != NULL); + REQUIRE(keynodep != NULL && *keynodep == NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + result = dns_rbt_findnode(keytable->table, keyname, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (node->data != NULL) { + keynode_attach(node->data, keynodep); + } else { + result = ISC_R_NOTFOUND; + } + } else if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + + return (result); +} + +isc_result_t +dns_keytable_finddeepestmatch(dns_keytable_t *keytable, const dns_name_t *name, + dns_name_t *foundname) { + isc_result_t result; + void *data; + + /* + * Search for the deepest match in 'keytable'. + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(foundname != NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + + data = NULL; + result = dns_rbt_findname(keytable->table, name, 0, foundname, &data); + + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + + return (result); +} + +void +dns_keytable_detachkeynode(dns_keytable_t *keytable, dns_keynode_t **keynodep) { + /* + * Give back a keynode found via dns_keytable_findkeynode(). + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(keynodep != NULL && VALID_KEYNODE(*keynodep)); + + keynode_detach(keytable->mctx, keynodep); +} + +isc_result_t +dns_keytable_issecuredomain(dns_keytable_t *keytable, const dns_name_t *name, + dns_name_t *foundname, bool *wantdnssecp) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + /* + * Is 'name' at or beneath a trusted key? + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(wantdnssecp != NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + + result = dns_rbt_findnode(keytable->table, name, foundname, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + INSIST(node->data != NULL); + *wantdnssecp = true; + result = ISC_R_SUCCESS; + } else if (result == ISC_R_NOTFOUND) { + *wantdnssecp = false; + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + + return (result); +} + +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + isc_result_t result; + + result = isc_buffer_reserve(b, strlen(str)); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_putstr(*b, str); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_keytable_dump(dns_keytable_t *keytable, FILE *fp) { + isc_result_t result; + isc_buffer_t *text = NULL; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(fp != NULL); + + isc_buffer_allocate(keytable->mctx, &text, 4096); + + result = dns_keytable_totext(keytable, &text); + + if (isc_buffer_usedlength(text) != 0) { + (void)putstr(&text, "\n"); + } else if (result == ISC_R_SUCCESS) { + (void)putstr(&text, "none"); + } else { + (void)putstr(&text, "could not dump key table: "); + (void)putstr(&text, isc_result_totext(result)); + } + + fprintf(fp, "%.*s", (int)isc_buffer_usedlength(text), + (char *)isc_buffer_base(text)); + + isc_buffer_free(&text); + return (result); +} + +static isc_result_t +keynode_dslist_totext(dns_name_t *name, dns_keynode_t *keynode, + isc_buffer_t **text) { + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + char obuf[DNS_NAME_FORMATSIZE + 200]; + dns_rdataset_t dsset; + + dns_name_format(name, namebuf, sizeof(namebuf)); + + dns_rdataset_init(&dsset); + if (!dns_keynode_dsset(keynode, &dsset)) { + return (ISC_R_SUCCESS); + } + + for (result = dns_rdataset_first(&dsset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&dsset)) + { + char algbuf[DNS_SECALG_FORMATSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_ds_t ds; + + dns_rdataset_current(&dsset, &rdata); + result = dns_rdata_tostruct(&rdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_secalg_format(ds.algorithm, algbuf, sizeof(algbuf)); + + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + snprintf(obuf, sizeof(obuf), "%s/%s/%d ; %s%s\n", namebuf, + algbuf, ds.key_tag, + keynode->initial ? "initializing " : "", + keynode->managed ? "managed" : "static"); + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + + result = putstr(text, obuf); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&dsset); + return (result); + } + } + dns_rdataset_disassociate(&dsset); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_keytable_totext(dns_keytable_t *keytable, isc_buffer_t **text) { + isc_result_t result; + dns_keynode_t *knode; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + dns_name_t *foundname, *origin, *fullname; + dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(text != NULL && *text != NULL); + + origin = dns_fixedname_initname(&fixedorigin); + fullname = dns_fixedname_initname(&fixedfullname); + foundname = dns_fixedname_initname(&fixedfoundname); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + goto cleanup; + } + for (;;) { + dns_rbtnodechain_current(&chain, foundname, origin, &node); + + knode = node->data; + if (knode != NULL && knode->dslist != NULL) { + result = dns_name_concatenate(foundname, origin, + fullname, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = keynode_dslist_totext(fullname, knode, text); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + return (result); +} + +isc_result_t +dns_keytable_forall(dns_keytable_t *keytable, + void (*func)(dns_keytable_t *, dns_keynode_t *, + dns_name_t *, void *), + void *arg) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + dns_fixedname_t fixedfoundname, fixedorigin, fixedfullname; + dns_name_t *foundname, *origin, *fullname; + + REQUIRE(VALID_KEYTABLE(keytable)); + + origin = dns_fixedname_initname(&fixedorigin); + fullname = dns_fixedname_initname(&fixedfullname); + foundname = dns_fixedname_initname(&fixedfoundname); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, keytable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + goto cleanup; + } + + for (;;) { + dns_rbtnodechain_current(&chain, foundname, origin, &node); + if (node->data != NULL) { + result = dns_name_concatenate(foundname, origin, + fullname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + (*func)(keytable, node->data, fullname, arg); + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + return (result); +} + +bool +dns_keynode_dsset(dns_keynode_t *keynode, dns_rdataset_t *rdataset) { + bool result; + REQUIRE(VALID_KEYNODE(keynode)); + REQUIRE(rdataset == NULL || DNS_RDATASET_VALID(rdataset)); + + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + if (keynode->dslist != NULL) { + if (rdataset != NULL) { + keynode_clone(&keynode->dsset, rdataset); + } + result = true; + } else { + result = false; + } + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + return (result); +} + +bool +dns_keynode_managed(dns_keynode_t *keynode) { + bool managed; + + REQUIRE(VALID_KEYNODE(keynode)); + + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + managed = keynode->managed; + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + + return (managed); +} + +bool +dns_keynode_initial(dns_keynode_t *keynode) { + bool initial; + + REQUIRE(VALID_KEYNODE(keynode)); + + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + initial = keynode->initial; + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + + return (initial); +} + +void +dns_keynode_trust(dns_keynode_t *keynode) { + REQUIRE(VALID_KEYNODE(keynode)); + + RWLOCK(&keynode->rwlock, isc_rwlocktype_write); + keynode->initial = false; + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_write); +} + +static void +keynode_disassociate(dns_rdataset_t *rdataset) { + dns_keynode_t *keynode; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->methods == &methods); + + rdataset->methods = NULL; + keynode = rdataset->private1; + rdataset->private1 = NULL; + + keynode_detach(keynode->mctx, &keynode); +} + +static isc_result_t +keynode_first(dns_rdataset_t *rdataset) { + dns_keynode_t *keynode; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->methods == &methods); + + keynode = rdataset->private1; + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + rdataset->private2 = ISC_LIST_HEAD(keynode->dslist->rdata); + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + + if (rdataset->private2 == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +keynode_next(dns_rdataset_t *rdataset) { + dns_keynode_t *keynode; + dns_rdata_t *rdata; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->methods == &methods); + + rdata = rdataset->private2; + if (rdata == NULL) { + return (ISC_R_NOMORE); + } + + keynode = rdataset->private1; + RWLOCK(&keynode->rwlock, isc_rwlocktype_read); + rdataset->private2 = ISC_LIST_NEXT(rdata, link); + RWUNLOCK(&keynode->rwlock, isc_rwlocktype_read); + + if (rdataset->private2 == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +static void +keynode_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + dns_rdata_t *list_rdata; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->methods == &methods); + + list_rdata = rdataset->private2; + INSIST(list_rdata != NULL); + + dns_rdata_clone(list_rdata, rdata); +} + +static void +keynode_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + dns_keynode_t *keynode; + + REQUIRE(source != NULL); + REQUIRE(target != NULL); + REQUIRE(source->methods == &methods); + + keynode = source->private1; + isc_refcount_increment(&keynode->refcount); + + *target = *source; + + /* + * Reset iterator state. + */ + target->private2 = NULL; +} diff --git a/lib/dns/log.c b/lib/dns/log.c new file mode 100644 index 0000000..a8bf01d --- /dev/null +++ b/lib/dns/log.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include + +/*% + * When adding a new category, be sure to add the appropriate + * \#define to . + */ +isc_logcategory_t dns_categories[] = { + { "notify", 0 }, { "database", 0 }, { "security", 0 }, + { "_placeholder", 0 }, { "dnssec", 0 }, { "resolver", 0 }, + { "xfer-in", 0 }, { "xfer-out", 0 }, { "dispatch", 0 }, + { "lame-servers", 0 }, { "delegation-only", 0 }, { "edns-disabled", 0 }, + { "rpz", 0 }, { "rate-limit", 0 }, { "cname", 0 }, + { "spill", 0 }, { "dnstap", 0 }, { "zoneload", 0 }, + { "nsid", 0 }, { "rpz-passthru", 0 }, { NULL, 0 } +}; + +/*% + * When adding a new module, be sure to add the appropriate + * \#define to . + */ +isc_logmodule_t dns_modules[] = { + { "dns/db", 0 }, { "dns/rbtdb", 0 }, + { "dns/rbt", 0 }, { "dns/rdata", 0 }, + { "dns/master", 0 }, { "dns/message", 0 }, + { "dns/cache", 0 }, { "dns/config", 0 }, + { "dns/resolver", 0 }, { "dns/zone", 0 }, + { "dns/journal", 0 }, { "dns/adb", 0 }, + { "dns/xfrin", 0 }, { "dns/xfrout", 0 }, + { "dns/acl", 0 }, { "dns/validator", 0 }, + { "dns/dispatch", 0 }, { "dns/request", 0 }, + { "dns/masterdump", 0 }, { "dns/tsig", 0 }, + { "dns/tkey", 0 }, { "dns/sdb", 0 }, + { "dns/diff", 0 }, { "dns/hints", 0 }, + { "dns/unused1", 0 }, { "dns/dlz", 0 }, + { "dns/dnssec", 0 }, { "dns/crypto", 0 }, + { "dns/packets", 0 }, { "dns/nta", 0 }, + { "dns/dyndb", 0 }, { "dns/dnstap", 0 }, + { "dns/ssu", 0 }, { NULL, 0 } +}; + +isc_log_t *dns_lctx = NULL; + +void +dns_log_init(isc_log_t *lctx) { + REQUIRE(lctx != NULL); + + isc_log_registercategories(lctx, dns_categories); + isc_log_registermodules(lctx, dns_modules); +} + +void +dns_log_setcontext(isc_log_t *lctx) { + dns_lctx = lctx; +} diff --git a/lib/dns/lookup.c b/lib/dns/lookup.c new file mode 100644 index 0000000..d370c19 --- /dev/null +++ b/lib/dns/lookup.c @@ -0,0 +1,445 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct dns_lookup { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + dns_rdatatype_t type; + dns_fixedname_t name; + /* Locked by lock. */ + unsigned int options; + isc_task_t *task; + dns_view_t *view; + dns_lookupevent_t *event; + dns_fetch_t *fetch; + unsigned int restarts; + bool canceled; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; +}; + +#define LOOKUP_MAGIC ISC_MAGIC('l', 'o', 'o', 'k') +#define VALID_LOOKUP(l) ISC_MAGIC_VALID((l), LOOKUP_MAGIC) + +#define MAX_RESTARTS 16 + +static void +lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event); + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + dns_lookup_t *lookup = event->ev_arg; + dns_fetchevent_t *fevent; + + UNUSED(task); + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + REQUIRE(VALID_LOOKUP(lookup)); + REQUIRE(lookup->task == task); + fevent = (dns_fetchevent_t *)event; + REQUIRE(fevent->fetch == lookup->fetch); + + lookup_find(lookup, fevent); +} + +static isc_result_t +start_fetch(dns_lookup_t *lookup) { + isc_result_t result; + + /* + * The caller must be holding the lookup's lock. + */ + + REQUIRE(lookup->fetch == NULL); + + result = dns_resolver_createfetch( + lookup->view->resolver, dns_fixedname_name(&lookup->name), + lookup->type, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, + lookup->task, fetch_done, lookup, &lookup->rdataset, + &lookup->sigrdataset, &lookup->fetch); + + return (result); +} + +static void +build_event(dns_lookup_t *lookup) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdataset_t *sigrdataset = NULL; + + name = isc_mem_get(lookup->mctx, sizeof(dns_name_t)); + dns_name_init(name, NULL); + dns_name_dup(dns_fixedname_name(&lookup->name), lookup->mctx, name); + + if (dns_rdataset_isassociated(&lookup->rdataset)) { + rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); + dns_rdataset_init(rdataset); + dns_rdataset_clone(&lookup->rdataset, rdataset); + } + + if (dns_rdataset_isassociated(&lookup->sigrdataset)) { + sigrdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); + dns_rdataset_init(sigrdataset); + dns_rdataset_clone(&lookup->sigrdataset, sigrdataset); + } + + lookup->event->name = name; + lookup->event->rdataset = rdataset; + lookup->event->sigrdataset = sigrdataset; +} + +static isc_result_t +view_find(dns_lookup_t *lookup, dns_name_t *foundname) { + isc_result_t result; + dns_name_t *name = dns_fixedname_name(&lookup->name); + dns_rdatatype_t type; + + if (lookup->type == dns_rdatatype_rrsig) { + type = dns_rdatatype_any; + } else { + type = lookup->type; + } + + result = dns_view_find(lookup->view, name, type, 0, 0, false, false, + &lookup->event->db, &lookup->event->node, + foundname, &lookup->rdataset, + &lookup->sigrdataset); + return (result); +} + +static void +lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) { + isc_result_t result = ISC_R_SUCCESS; + bool want_restart; + bool send_event; + dns_name_t *name = NULL, *fname = NULL, *prefix = NULL; + dns_fixedname_t foundname, fixed; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + int order; + dns_namereln_t namereln; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + + REQUIRE(VALID_LOOKUP(lookup)); + + LOCK(&lookup->lock); + + name = dns_fixedname_name(&lookup->name); + + do { + lookup->restarts++; + want_restart = false; + send_event = true; + + if (event == NULL && !lookup->canceled) { + fname = dns_fixedname_initname(&foundname); + INSIST(!dns_rdataset_isassociated(&lookup->rdataset)); + INSIST(!dns_rdataset_isassociated( + &lookup->sigrdataset)); + /* + * If we have restarted then clear the old node. + */ + if (lookup->event->node != NULL) { + INSIST(lookup->event->db != NULL); + dns_db_detachnode(lookup->event->db, + &lookup->event->node); + } + if (lookup->event->db != NULL) { + dns_db_detach(&lookup->event->db); + } + result = view_find(lookup, fname); + if (result == ISC_R_NOTFOUND) { + /* + * We don't know anything about the name. + * Launch a fetch. + */ + if (lookup->event->node != NULL) { + INSIST(lookup->event->db != NULL); + dns_db_detachnode(lookup->event->db, + &lookup->event->node); + } + if (lookup->event->db != NULL) { + dns_db_detach(&lookup->event->db); + } + result = start_fetch(lookup); + if (result == ISC_R_SUCCESS) { + send_event = false; + } + goto done; + } + } else if (event != NULL) { + result = event->result; + fname = event->foundname; + dns_resolver_destroyfetch(&lookup->fetch); + INSIST(event->rdataset == &lookup->rdataset); + INSIST(event->sigrdataset == &lookup->sigrdataset); + } + + /* + * If we've been canceled, forget about the result. + */ + if (lookup->canceled) { + result = ISC_R_CANCELED; + } + + switch (result) { + case ISC_R_SUCCESS: + build_event(lookup); + if (event == NULL) { + break; + } + if (event->db != NULL) { + dns_db_attach(event->db, &lookup->event->db); + } + if (event->node != NULL) { + dns_db_attachnode(lookup->event->db, + event->node, + &lookup->event->node); + } + break; + case DNS_R_CNAME: + /* + * Copy the CNAME's target into the lookup's + * query name and start over. + */ + result = dns_rdataset_first(&lookup->rdataset); + if (result != ISC_R_SUCCESS) { + break; + } + dns_rdataset_current(&lookup->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) { + break; + } + dns_name_copy(&cname.cname, name); + dns_rdata_freestruct(&cname); + want_restart = true; + send_event = false; + break; + case DNS_R_DNAME: + namereln = dns_name_fullcompare(name, fname, &order, + &nlabels); + INSIST(namereln == dns_namereln_subdomain); + /* + * Get the target name of the DNAME. + */ + result = dns_rdataset_first(&lookup->rdataset); + if (result != ISC_R_SUCCESS) { + break; + } + dns_rdataset_current(&lookup->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) { + break; + } + /* + * Construct the new query name and start over. + */ + prefix = dns_fixedname_initname(&fixed); + dns_name_split(name, nlabels, prefix, NULL); + result = dns_name_concatenate(prefix, &dname.dname, + name, NULL); + dns_rdata_freestruct(&dname); + if (result == ISC_R_SUCCESS) { + want_restart = true; + send_event = false; + } + break; + default: + send_event = true; + } + + if (dns_rdataset_isassociated(&lookup->rdataset)) { + dns_rdataset_disassociate(&lookup->rdataset); + } + if (dns_rdataset_isassociated(&lookup->sigrdataset)) { + dns_rdataset_disassociate(&lookup->sigrdataset); + } + + done: + if (event != NULL) { + if (event->node != NULL) { + dns_db_detachnode(event->db, &event->node); + } + if (event->db != NULL) { + dns_db_detach(&event->db); + } + isc_event_free(ISC_EVENT_PTR(&event)); + } + + /* + * Limit the number of restarts. + */ + if (want_restart && lookup->restarts == MAX_RESTARTS) { + want_restart = false; + result = ISC_R_QUOTA; + send_event = true; + } + } while (want_restart); + + if (send_event) { + lookup->event->result = result; + lookup->event->ev_sender = lookup; + isc_task_sendanddetach(&lookup->task, + (isc_event_t **)&lookup->event); + dns_view_detach(&lookup->view); + } + + UNLOCK(&lookup->lock); +} + +static void +levent_destroy(isc_event_t *event) { + dns_lookupevent_t *levent; + isc_mem_t *mctx; + + REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE); + mctx = event->ev_destroy_arg; + levent = (dns_lookupevent_t *)event; + + if (levent->name != NULL) { + if (dns_name_dynamic(levent->name)) { + dns_name_free(levent->name, mctx); + } + isc_mem_put(mctx, levent->name, sizeof(dns_name_t)); + } + if (levent->rdataset != NULL) { + dns_rdataset_disassociate(levent->rdataset); + isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t)); + } + if (levent->sigrdataset != NULL) { + dns_rdataset_disassociate(levent->sigrdataset); + isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t)); + } + if (levent->node != NULL) { + dns_db_detachnode(levent->db, &levent->node); + } + if (levent->db != NULL) { + dns_db_detach(&levent->db); + } + isc_mem_put(mctx, event, event->ev_size); +} + +isc_result_t +dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type, + dns_view_t *view, unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) { + dns_lookup_t *lookup; + isc_event_t *ievent; + + lookup = isc_mem_get(mctx, sizeof(*lookup)); + lookup->mctx = NULL; + isc_mem_attach(mctx, &lookup->mctx); + lookup->options = options; + + ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, action, + arg, sizeof(*lookup->event)); + lookup->event = (dns_lookupevent_t *)ievent; + lookup->event->ev_destroy = levent_destroy; + lookup->event->ev_destroy_arg = mctx; + lookup->event->result = ISC_R_FAILURE; + lookup->event->name = NULL; + lookup->event->rdataset = NULL; + lookup->event->sigrdataset = NULL; + lookup->event->db = NULL; + lookup->event->node = NULL; + + lookup->task = NULL; + isc_task_attach(task, &lookup->task); + + isc_mutex_init(&lookup->lock); + + dns_fixedname_init(&lookup->name); + + dns_name_copy(name, dns_fixedname_name(&lookup->name)); + + lookup->type = type; + lookup->view = NULL; + dns_view_attach(view, &lookup->view); + lookup->fetch = NULL; + lookup->restarts = 0; + lookup->canceled = false; + dns_rdataset_init(&lookup->rdataset); + dns_rdataset_init(&lookup->sigrdataset); + lookup->magic = LOOKUP_MAGIC; + + *lookupp = lookup; + + lookup_find(lookup, NULL); + + return (ISC_R_SUCCESS); +} + +void +dns_lookup_cancel(dns_lookup_t *lookup) { + REQUIRE(VALID_LOOKUP(lookup)); + + LOCK(&lookup->lock); + + if (!lookup->canceled) { + lookup->canceled = true; + if (lookup->fetch != NULL) { + INSIST(lookup->view != NULL); + dns_resolver_cancelfetch(lookup->fetch); + } + } + + UNLOCK(&lookup->lock); +} + +void +dns_lookup_destroy(dns_lookup_t **lookupp) { + dns_lookup_t *lookup; + + REQUIRE(lookupp != NULL); + lookup = *lookupp; + *lookupp = NULL; + REQUIRE(VALID_LOOKUP(lookup)); + REQUIRE(lookup->event == NULL); + REQUIRE(lookup->task == NULL); + REQUIRE(lookup->view == NULL); + if (dns_rdataset_isassociated(&lookup->rdataset)) { + dns_rdataset_disassociate(&lookup->rdataset); + } + if (dns_rdataset_isassociated(&lookup->sigrdataset)) { + dns_rdataset_disassociate(&lookup->sigrdataset); + } + + isc_mutex_destroy(&lookup->lock); + lookup->magic = 0; + isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup)); +} diff --git a/lib/dns/master.c b/lib/dns/master.c new file mode 100644 index 0000000..97b9343 --- /dev/null +++ b/lib/dns/master.c @@ -0,0 +1,3200 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + * Grow the number of dns_rdatalist_t (#RDLSZ) and dns_rdata_t (#RDSZ) + * structures by these sizes when we need to. + * + */ +/*% RDLSZ reflects the number of different types with the same name expected. */ +#define RDLSZ 32 +/*% + * RDSZ reflects the number of rdata expected at a give name that can fit into + * 64k. + */ +#define RDSZ 512 + +#define NBUFS 4 +#define MAXWIRESZ 255 + +/*% + * Target buffer size and minimum target size. + * MINTSIZ must be big enough to hold the largest rdata record. + * \brief + * TSIZ >= MINTSIZ + */ +#define TSIZ (128 * 1024) +/*% + * max message size - header - root - type - class - ttl - rdlen + */ +#define MINTSIZ DNS_RDATA_MAXLENGTH +/*% + * Size for tokens in the presentation format, + * The largest tokens are the base64 blocks in KEY and CERT records, + * Largest key allowed is about 1372 bytes but + * there is no fixed upper bound on CERT records. + * 2K is too small for some X.509s, 8K is overkill. + */ +#define TOKENSIZ (8 * 1024) + +/*% + * Buffers sizes for $GENERATE. + */ +#define DNS_MASTER_LHS 2048 +#define DNS_MASTER_RHS MINTSIZ + +#define CHECKNAMESFAIL(x) (((x)&DNS_MASTER_CHECKNAMESFAIL) != 0) + +typedef ISC_LIST(dns_rdatalist_t) rdatalist_head_t; + +typedef struct dns_incctx dns_incctx_t; + +/*% + * Master file load state. + */ + +struct dns_loadctx { + unsigned int magic; + isc_mem_t *mctx; + dns_masterformat_t format; + + dns_rdatacallbacks_t *callbacks; + isc_task_t *task; + dns_loaddonefunc_t done; + void *done_arg; + + /* Common methods */ + isc_result_t (*openfile)(dns_loadctx_t *lctx, const char *filename); + isc_result_t (*load)(dns_loadctx_t *lctx); + + /* Members used by all formats */ + uint32_t maxttl; + + /* Members specific to the text format: */ + isc_lex_t *lex; + bool keep_lex; + unsigned int options; + bool ttl_known; + bool default_ttl_known; + bool warn_1035; + bool warn_tcr; + bool warn_sigexpired; + bool seen_include; + uint32_t ttl; + uint32_t default_ttl; + dns_rdataclass_t zclass; + dns_fixedname_t fixed_top; + dns_name_t *top; /*%< top of zone */ + + /* Members specific to the raw format: */ + FILE *f; + bool first; + dns_masterrawheader_t header; + + /* Which fixed buffers we are using? */ + unsigned int loop_cnt; /*% records per quantum, + * 0 => all. */ + isc_result_t result; + + /* Atomic */ + isc_refcount_t references; + atomic_bool canceled; + + /* locked by lock */ + dns_incctx_t *inc; + uint32_t resign; + isc_stdtime_t now; + + dns_masterincludecb_t include_cb; + void *include_arg; +}; + +struct dns_incctx { + dns_incctx_t *parent; + dns_name_t *origin; + dns_name_t *current; + dns_name_t *glue; + dns_fixedname_t fixed[NBUFS]; /* working buffers */ + unsigned int in_use[NBUFS]; /* covert to bitmap? */ + int glue_in_use; + int current_in_use; + int origin_in_use; + bool origin_changed; + bool drop; + unsigned int glue_line; + unsigned int current_line; +}; + +#define DNS_LCTX_MAGIC ISC_MAGIC('L', 'c', 't', 'x') +#define DNS_LCTX_VALID(lctx) ISC_MAGIC_VALID(lctx, DNS_LCTX_MAGIC) + +#define DNS_AS_STR(t) ((t).value.as_textregion.base) + +static isc_result_t +openfile_text(dns_loadctx_t *lctx, const char *master_file); + +static isc_result_t +load_text(dns_loadctx_t *lctx); + +static isc_result_t +openfile_raw(dns_loadctx_t *lctx, const char *master_file); + +static isc_result_t +load_raw(dns_loadctx_t *lctx); + +static isc_result_t +pushfile(const char *master_file, dns_name_t *origin, dns_loadctx_t *lctx); + +static isc_result_t +commit(dns_rdatacallbacks_t *, dns_loadctx_t *, rdatalist_head_t *, + dns_name_t *, const char *, unsigned int); + +static bool +is_glue(rdatalist_head_t *, dns_name_t *); + +static dns_rdatalist_t * +grow_rdatalist(int, dns_rdatalist_t *, int, rdatalist_head_t *, + rdatalist_head_t *, isc_mem_t *mctx); + +static dns_rdata_t * +grow_rdata(int, dns_rdata_t *, int, rdatalist_head_t *, rdatalist_head_t *, + isc_mem_t *); + +static void +load_quantum(isc_task_t *task, isc_event_t *event); + +static isc_result_t +task_send(dns_loadctx_t *lctx); + +static void +loadctx_destroy(dns_loadctx_t *lctx); + +#define GETTOKENERR(lexer, options, token, eol, err) \ + do { \ + result = gettoken(lexer, options, token, eol, callbacks); \ + switch (result) { \ + case ISC_R_SUCCESS: \ + break; \ + case ISC_R_UNEXPECTED: \ + goto insist_and_cleanup; \ + default: \ + if (MANYERRS(lctx, result)) { \ + SETRESULT(lctx, result); \ + LOGIT(result); \ + read_till_eol = true; \ + err goto next_line; \ + } else \ + goto log_and_cleanup; \ + } \ + if ((token)->type == isc_tokentype_special) { \ + result = DNS_R_SYNTAX; \ + if (MANYERRS(lctx, result)) { \ + SETRESULT(lctx, result); \ + LOGIT(result); \ + read_till_eol = true; \ + goto next_line; \ + } else \ + goto log_and_cleanup; \ + } \ + } while (0) +#define GETTOKEN(lexer, options, token, eol) \ + GETTOKENERR(lexer, options, token, eol, {}) + +#define COMMITALL \ + do { \ + result = commit(callbacks, lctx, ¤t_list, ictx->current, \ + source, ictx->current_line); \ + if (MANYERRS(lctx, result)) { \ + SETRESULT(lctx, result); \ + } else if (result != ISC_R_SUCCESS) \ + goto insist_and_cleanup; \ + result = commit(callbacks, lctx, &glue_list, ictx->glue, \ + source, ictx->glue_line); \ + if (MANYERRS(lctx, result)) { \ + SETRESULT(lctx, result); \ + } else if (result != ISC_R_SUCCESS) \ + goto insist_and_cleanup; \ + rdcount = 0; \ + rdlcount = 0; \ + isc_buffer_init(&target, target_mem, target_size); \ + rdcount_save = rdcount; \ + rdlcount_save = rdlcount; \ + } while (0) + +#define WARNUNEXPECTEDEOF(lexer) \ + do { \ + if (isc_lex_isfile(lexer)) \ + (*callbacks->warn)(callbacks, \ + "%s: file does not end with " \ + "newline", \ + source); \ + } while (0) + +#define EXPECTEOL \ + do { \ + GETTOKEN(lctx->lex, 0, &token, true); \ + if (token.type != isc_tokentype_eol) { \ + isc_lex_ungettoken(lctx->lex, &token); \ + result = DNS_R_EXTRATOKEN; \ + if (MANYERRS(lctx, result)) { \ + SETRESULT(lctx, result); \ + LOGIT(result); \ + read_till_eol = true; \ + break; \ + } else if (result != ISC_R_SUCCESS) \ + goto log_and_cleanup; \ + } \ + } while (0) + +#define MANYERRS(lctx, result) \ + ((result != ISC_R_SUCCESS) && (result != ISC_R_IOERROR) && \ + ((lctx)->options & DNS_MASTER_MANYERRORS) != 0) + +#define SETRESULT(lctx, r) \ + do { \ + if ((lctx)->result == ISC_R_SUCCESS) \ + (lctx)->result = r; \ + } while (0) + +#define LOGITFILE(result, filename) \ + if (result == ISC_R_INVALIDFILE || result == ISC_R_FILENOTFOUND || \ + result == ISC_R_IOERROR || result == ISC_R_TOOMANYOPENFILES || \ + result == ISC_R_NOPERM) \ + (*callbacks->error)(callbacks, "%s: %s:%lu: %s: %s", \ + "dns_master_load", source, line, filename, \ + isc_result_totext(result)); \ + else \ + LOGIT(result) + +#define LOGIT(result) \ + if (result == ISC_R_NOMEMORY) \ + (*callbacks->error)(callbacks, "dns_master_load: %s", \ + isc_result_totext(result)); \ + else \ + (*callbacks->error)(callbacks, "%s: %s:%lu: %s", \ + "dns_master_load", source, line, \ + isc_result_totext(result)) + +static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA"; +static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 }; +static dns_name_t const in_addr_arpa = + DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets); + +static unsigned char ip6_int_data[] = "\003IP6\003INT"; +static unsigned char ip6_int_offsets[] = { 0, 4, 8 }; +static dns_name_t const ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data, + ip6_int_offsets); + +static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA"; +static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 }; +static dns_name_t const ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data, + ip6_arpa_offsets); + +static bool +dns_master_isprimary(dns_loadctx_t *lctx) { + return ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SECONDARY) == 0 && + (lctx->options & DNS_MASTER_KEY) == 0); +} + +static isc_result_t +gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *token, bool eol, + dns_rdatacallbacks_t *callbacks) { + isc_result_t result; + + options |= ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | ISC_LEXOPT_DNSMULTILINE | + ISC_LEXOPT_ESCAPE; + result = isc_lex_gettoken(lex, options, token); + if (result != ISC_R_SUCCESS) { + switch (result) { + case ISC_R_NOMEMORY: + return (ISC_R_NOMEMORY); + default: + (*callbacks->error)(callbacks, + "dns_master_load: %s:%lu:" + " isc_lex_gettoken() failed: %s", + isc_lex_getsourcename(lex), + isc_lex_getsourceline(lex), + isc_result_totext(result)); + return (result); + } + /*NOTREACHED*/ + } + if (eol != true) { + if (token->type == isc_tokentype_eol || + token->type == isc_tokentype_eof) + { + { + unsigned long int line; + const char *what; + const char *file; + file = isc_lex_getsourcename(lex); + line = isc_lex_getsourceline(lex); + if (token->type == isc_tokentype_eol) { + line--; + what = "line"; + } else { + what = "file"; + } + (*callbacks->error)(callbacks, + "dns_master_load: %s:%lu: " + "unexpected end of %s", + file, line, what); + return (ISC_R_UNEXPECTEDEND); + } + } + } + return (ISC_R_SUCCESS); +} + +void +dns_loadctx_attach(dns_loadctx_t *source, dns_loadctx_t **target) { + REQUIRE(target != NULL && *target == NULL); + REQUIRE(DNS_LCTX_VALID(source)); + + isc_refcount_increment(&source->references); + + *target = source; +} + +void +dns_loadctx_detach(dns_loadctx_t **lctxp) { + dns_loadctx_t *lctx; + + REQUIRE(lctxp != NULL); + lctx = *lctxp; + *lctxp = NULL; + REQUIRE(DNS_LCTX_VALID(lctx)); + + if (isc_refcount_decrement(&lctx->references) == 1) { + loadctx_destroy(lctx); + } +} + +static void +incctx_destroy(isc_mem_t *mctx, dns_incctx_t *ictx) { + dns_incctx_t *parent; + +again: + parent = ictx->parent; + ictx->parent = NULL; + + isc_mem_put(mctx, ictx, sizeof(*ictx)); + + if (parent != NULL) { + ictx = parent; + goto again; + } +} + +static void +loadctx_destroy(dns_loadctx_t *lctx) { + REQUIRE(DNS_LCTX_VALID(lctx)); + + isc_refcount_destroy(&lctx->references); + + lctx->magic = 0; + if (lctx->inc != NULL) { + incctx_destroy(lctx->mctx, lctx->inc); + } + + if (lctx->f != NULL) { + isc_result_t result = isc_stdio_close(lctx->f); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_stdio_close() failed: %s", + isc_result_totext(result)); + } + } + + /* isc_lex_destroy() will close all open streams */ + if (lctx->lex != NULL && !lctx->keep_lex) { + isc_lex_destroy(&lctx->lex); + } + + if (lctx->task != NULL) { + isc_task_detach(&lctx->task); + } + + isc_mem_putanddetach(&lctx->mctx, lctx, sizeof(*lctx)); +} + +static isc_result_t +incctx_create(isc_mem_t *mctx, dns_name_t *origin, dns_incctx_t **ictxp) { + dns_incctx_t *ictx; + isc_region_t r; + int i; + + ictx = isc_mem_get(mctx, sizeof(*ictx)); + + for (i = 0; i < NBUFS; i++) { + dns_fixedname_init(&ictx->fixed[i]); + ictx->in_use[i] = false; + } + + ictx->origin_in_use = 0; + ictx->origin = dns_fixedname_name(&ictx->fixed[ictx->origin_in_use]); + ictx->in_use[ictx->origin_in_use] = true; + dns_name_toregion(origin, &r); + dns_name_fromregion(ictx->origin, &r); + + ictx->glue = NULL; + ictx->current = NULL; + ictx->glue_in_use = -1; + ictx->current_in_use = -1; + ictx->parent = NULL; + ictx->drop = false; + ictx->glue_line = 0; + ictx->current_line = 0; + ictx->origin_changed = true; + + *ictxp = ictx; + return (ISC_R_SUCCESS); +} + +static isc_result_t +loadctx_create(dns_masterformat_t format, isc_mem_t *mctx, unsigned int options, + uint32_t resign, dns_name_t *top, dns_rdataclass_t zclass, + dns_name_t *origin, dns_rdatacallbacks_t *callbacks, + isc_task_t *task, dns_loaddonefunc_t done, void *done_arg, + dns_masterincludecb_t include_cb, void *include_arg, + isc_lex_t *lex, dns_loadctx_t **lctxp) { + dns_loadctx_t *lctx; + isc_result_t result; + isc_region_t r; + isc_lexspecials_t specials; + + REQUIRE(lctxp != NULL && *lctxp == NULL); + REQUIRE(callbacks != NULL); + REQUIRE(callbacks->add != NULL); + REQUIRE(callbacks->error != NULL); + REQUIRE(callbacks->warn != NULL); + REQUIRE(mctx != NULL); + REQUIRE(dns_name_isabsolute(top)); + REQUIRE(dns_name_isabsolute(origin)); + REQUIRE((task == NULL && done == NULL) || + (task != NULL && done != NULL)); + + lctx = isc_mem_get(mctx, sizeof(*lctx)); + + lctx->inc = NULL; + result = incctx_create(mctx, origin, &lctx->inc); + if (result != ISC_R_SUCCESS) { + goto cleanup_ctx; + } + + lctx->maxttl = 0; + + lctx->format = format; + switch (format) { + case dns_masterformat_text: + lctx->openfile = openfile_text; + lctx->load = load_text; + break; + case dns_masterformat_raw: + lctx->openfile = openfile_raw; + lctx->load = load_raw; + break; + default: + UNREACHABLE(); + } + + if (lex != NULL) { + lctx->lex = lex; + lctx->keep_lex = true; + } else { + lctx->lex = NULL; + result = isc_lex_create(mctx, TOKENSIZ, &lctx->lex); + if (result != ISC_R_SUCCESS) { + goto cleanup_inc; + } + lctx->keep_lex = false; + /* + * If specials change update dns_test_rdatafromstring() + * in lib/dns/tests/dnstest.c. + */ + memset(specials, 0, sizeof(specials)); + specials[0] = 1; + specials['('] = 1; + specials[')'] = 1; + specials['"'] = 1; + isc_lex_setspecials(lctx->lex, specials); + isc_lex_setcomments(lctx->lex, ISC_LEXCOMMENT_DNSMASTERFILE); + } + + lctx->ttl_known = ((options & DNS_MASTER_NOTTL) != 0); + lctx->ttl = 0; + lctx->default_ttl_known = lctx->ttl_known; + lctx->default_ttl = 0; + lctx->warn_1035 = true; /* XXX Argument? */ + lctx->warn_tcr = true; /* XXX Argument? */ + lctx->warn_sigexpired = true; /* XXX Argument? */ + lctx->options = options; + lctx->seen_include = false; + lctx->zclass = zclass; + lctx->resign = resign; + lctx->result = ISC_R_SUCCESS; + lctx->include_cb = include_cb; + lctx->include_arg = include_arg; + isc_stdtime_get(&lctx->now); + + lctx->top = dns_fixedname_initname(&lctx->fixed_top); + dns_name_toregion(top, &r); + dns_name_fromregion(lctx->top, &r); + + lctx->f = NULL; + lctx->first = true; + dns_master_initrawheader(&lctx->header); + + lctx->loop_cnt = (done != NULL) ? 100 : 0; + lctx->callbacks = callbacks; + lctx->task = NULL; + if (task != NULL) { + isc_task_attach(task, &lctx->task); + } + lctx->done = done; + lctx->done_arg = done_arg; + atomic_init(&lctx->canceled, false); + lctx->mctx = NULL; + isc_mem_attach(mctx, &lctx->mctx); + + isc_refcount_init(&lctx->references, 1); /* Implicit attach. */ + + lctx->magic = DNS_LCTX_MAGIC; + *lctxp = lctx; + return (ISC_R_SUCCESS); + +cleanup_inc: + incctx_destroy(mctx, lctx->inc); +cleanup_ctx: + isc_mem_put(mctx, lctx, sizeof(*lctx)); + return (result); +} + +static const char *hex = "0123456789abcdef0123456789ABCDEF"; + +/*% + * Convert value into a nibble sequence from least significant to most + * significant nibble. Zero fill upper most significant nibbles if + * required to make the width. + * + * Returns the number of characters that should have been written without + * counting the terminating NUL. + */ +static unsigned int +nibbles(char *numbuf, size_t length, unsigned int width, char mode, int value) { + unsigned int count = 0; + + /* + * This reserve space for the NUL string terminator. + */ + if (length > 0U) { + *numbuf = '\0'; + length--; + } + do { + char val = hex[(value & 0x0f) + ((mode == 'n') ? 0 : 16)]; + value >>= 4; + if (length > 0U) { + *numbuf++ = val; + *numbuf = '\0'; + length--; + } + if (width > 0) { + width--; + } + count++; + /* + * If width is non zero then we need to add a label separator. + * If value is non zero then we need to add another label and + * that requires a label separator. + */ + if (width > 0 || value != 0) { + if (length > 0U) { + *numbuf++ = '.'; + *numbuf = '\0'; + length--; + } + if (width > 0) { + width--; + } + count++; + } + } while (value != 0 || width > 0); + return (count); +} + +static isc_result_t +genname(char *name, int it, char *buffer, size_t length) { + char fmt[sizeof("%04000000000d")]; + char numbuf[128]; + char *cp; + char mode[2] = { 0 }; + char brace[2] = { 0 }; + char comma1[2] = { 0 }; + char comma2[2] = { 0 }; + int delta = 0; + isc_textregion_t r; + unsigned int n; + unsigned int width; + bool nibblemode; + + r.base = buffer; + r.length = (unsigned int)length; + + while (*name != '\0') { + if (*name == '$') { + name++; + if (*name == '$') { + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = *name++; + isc_textregion_consume(&r, 1); + continue; + } + nibblemode = false; + strlcpy(fmt, "%d", sizeof(fmt)); + /* Get format specifier. */ + if (*name == '{') { + n = sscanf(name, + "{%d%1[,}]%u%1[,}]%1[doxXnN]%1[}]", + &delta, comma1, &width, comma2, mode, + brace); + if (n < 2 || n > 6) { + return (DNS_R_SYNTAX); + } + if (comma1[0] == '}') { + /* %{delta} */ + } else if (comma1[0] == ',' && comma2[0] == '}') + { + /* %{delta,width} */ + n = snprintf(fmt, sizeof(fmt), "%%0%ud", + width); + } else if (comma1[0] == ',' && + comma2[0] == ',' && mode[0] != 0 && + brace[0] == '}') + { + /* %{delta,width,format} */ + if (mode[0] == 'n' || mode[0] == 'N') { + nibblemode = true; + } + n = snprintf(fmt, sizeof(fmt), + "%%0%u%c", width, mode[0]); + } else { + return (DNS_R_SYNTAX); + } + if (n >= sizeof(fmt)) { + return (ISC_R_NOSPACE); + } + /* Skip past closing brace. */ + while (*name != '\0' && *name++ != '}') { + continue; + } + } + /* + * 'it' is >= 0 so we don't need to check for + * underflow. + */ + if ((it > 0 && delta > INT_MAX - it)) { + return (ISC_R_RANGE); + } + if (nibblemode) { + n = nibbles(numbuf, sizeof(numbuf), width, + mode[0], it + delta); + } else { + n = snprintf(numbuf, sizeof(numbuf), fmt, + it + delta); + } + if (n >= sizeof(numbuf)) { + return (ISC_R_NOSPACE); + } + cp = numbuf; + while (*cp != '\0') { + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = *cp++; + isc_textregion_consume(&r, 1); + } + } else if (*name == '\\') { + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = *name++; + isc_textregion_consume(&r, 1); + if (*name == '\0') { + continue; + } + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = *name++; + isc_textregion_consume(&r, 1); + } else { + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = *name++; + isc_textregion_consume(&r, 1); + } + } + if (r.length == 0) { + return (ISC_R_NOSPACE); + } + r.base[0] = '\0'; + return (ISC_R_SUCCESS); +} + +static isc_result_t +generate(dns_loadctx_t *lctx, char *range, char *lhs, char *gtype, char *rhs, + const char *source, unsigned int line) { + char *target_mem = NULL; + char *lhsbuf = NULL; + char *rhsbuf = NULL; + dns_fixedname_t ownerfixed; + dns_name_t *owner; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatacallbacks_t *callbacks; + dns_rdatalist_t rdatalist; + dns_rdatatype_t type; + rdatalist_head_t head; + int target_size = MINTSIZ; /* only one rdata at a time */ + isc_buffer_t buffer; + isc_buffer_t target; + isc_result_t result; + isc_textregion_t r; + int n, start, stop, step = 0; + unsigned int i; + dns_incctx_t *ictx; + char dummy[2]; + + ictx = lctx->inc; + callbacks = lctx->callbacks; + owner = dns_fixedname_initname(&ownerfixed); + ISC_LIST_INIT(head); + + target_mem = isc_mem_get(lctx->mctx, target_size); + rhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_RHS); + lhsbuf = isc_mem_get(lctx->mctx, DNS_MASTER_LHS); + if (target_mem == NULL || rhsbuf == NULL || lhsbuf == NULL) { + result = ISC_R_NOMEMORY; + goto error_cleanup; + } + isc_buffer_init(&target, target_mem, target_size); + + n = sscanf(range, "%d-%d%1[/]%d", &start, &stop, dummy, &step); + if ((n != 2 && n != 4) || (start < 0) || (stop < 0) || + (n == 4 && step < 1) || (stop < start)) + { + (*callbacks->error)(callbacks, "%s: %s:%lu: invalid range '%s'", + "$GENERATE", source, line, range); + result = DNS_R_SYNTAX; + goto insist_cleanup; + } + if (n == 2) { + step = 1; + } + + /* + * Get type. + */ + r.base = gtype; + r.length = strlen(gtype); + result = dns_rdatatype_fromtext(&type, &r); + if (result != ISC_R_SUCCESS) { + (*callbacks->error)(callbacks, + "%s: %s:%lu: unknown RR type '%s'", + "$GENERATE", source, line, gtype); + goto insist_cleanup; + } + + /* + * RFC2930: TKEY and TSIG are not allowed to be loaded + * from master files. + */ + if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) { + (*callbacks->error)(callbacks, "%s: %s:%lu: meta RR type '%s'", + "$GENERATE", source, line, gtype); + result = DNS_R_METATYPE; + goto insist_cleanup; + } + + for (i = start; i <= (unsigned int)stop; i += step) { + result = genname(lhs, i, lhsbuf, DNS_MASTER_LHS); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + result = genname(rhs, i, rhsbuf, DNS_MASTER_RHS); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + + isc_buffer_init(&buffer, lhsbuf, strlen(lhsbuf)); + isc_buffer_add(&buffer, strlen(lhsbuf)); + isc_buffer_setactive(&buffer, strlen(lhsbuf)); + result = dns_name_fromtext(owner, &buffer, ictx->origin, 0, + NULL); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + + if (dns_master_isprimary(lctx) && + !dns_name_issubdomain(owner, lctx->top)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(owner, namebuf, sizeof(namebuf)); + /* + * Ignore out-of-zone data. + */ + (*callbacks->warn)(callbacks, + "%s:%lu: " + "ignoring out-of-zone data (%s)", + source, line, namebuf); + continue; + } + + isc_buffer_init(&buffer, rhsbuf, strlen(rhsbuf)); + isc_buffer_add(&buffer, strlen(rhsbuf)); + isc_buffer_setactive(&buffer, strlen(rhsbuf)); + + result = isc_lex_openbuffer(lctx->lex, &buffer); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + + isc_buffer_init(&target, target_mem, target_size); + result = dns_rdata_fromtext(&rdata, lctx->zclass, type, + lctx->lex, ictx->origin, 0, + lctx->mctx, &target, callbacks); + RUNTIME_CHECK(isc_lex_close(lctx->lex) == ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + + dns_rdatalist_init(&rdatalist); + rdatalist.type = type; + rdatalist.rdclass = lctx->zclass; + rdatalist.ttl = lctx->ttl; + ISC_LIST_PREPEND(head, &rdatalist, link); + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + result = commit(callbacks, lctx, &head, owner, source, line); + ISC_LIST_UNLINK(rdatalist.rdata, &rdata, link); + if (result != ISC_R_SUCCESS) { + goto error_cleanup; + } + dns_rdata_reset(&rdata); + } + result = ISC_R_SUCCESS; + goto cleanup; + +error_cleanup: + if (result == ISC_R_NOMEMORY) { + (*callbacks->error)(callbacks, "$GENERATE: %s", + isc_result_totext(result)); + } else { + (*callbacks->error)(callbacks, "$GENERATE: %s:%lu: %s", source, + line, isc_result_totext(result)); + } + +insist_cleanup: + INSIST(result != ISC_R_SUCCESS); + +cleanup: + if (target_mem != NULL) { + isc_mem_put(lctx->mctx, target_mem, target_size); + } + if (lhsbuf != NULL) { + isc_mem_put(lctx->mctx, lhsbuf, DNS_MASTER_LHS); + } + if (rhsbuf != NULL) { + isc_mem_put(lctx->mctx, rhsbuf, DNS_MASTER_RHS); + } + return (result); +} + +static void +limit_ttl(dns_rdatacallbacks_t *callbacks, const char *source, + unsigned int line, uint32_t *ttlp) { + if (*ttlp > 0x7fffffffUL) { + (callbacks->warn)(callbacks, + "%s: %s:%lu: " + "$TTL %lu > MAXTTL, " + "setting $TTL to 0", + "dns_master_load", source, line, *ttlp); + *ttlp = 0; + } +} + +static isc_result_t +check_ns(dns_loadctx_t *lctx, isc_token_t *token, const char *source, + unsigned long line) { + char *tmp = NULL; + isc_result_t result = ISC_R_SUCCESS; + void (*callback)(struct dns_rdatacallbacks *, const char *, ...); + + if ((lctx->options & DNS_MASTER_FATALNS) != 0) { + callback = lctx->callbacks->error; + } else { + callback = lctx->callbacks->warn; + } + + if (token->type == isc_tokentype_string) { + struct in_addr addr; + struct in6_addr addr6; + + tmp = isc_mem_strdup(lctx->mctx, DNS_AS_STR(*token)); + /* + * Catch both "1.2.3.4" and "1.2.3.4." + */ + if (tmp[strlen(tmp) - 1] == '.') { + tmp[strlen(tmp) - 1] = '\0'; + } + if (inet_pton(AF_INET, tmp, &addr) == 1 || + inet_pton(AF_INET6, tmp, &addr6) == 1) + { + result = DNS_R_NSISADDRESS; + } + } + if (result != ISC_R_SUCCESS) { + (*callback)(lctx->callbacks, + "%s:%lu: NS record '%s' " + "appears to be an address", + source, line, DNS_AS_STR(*token)); + } + if (tmp != NULL) { + isc_mem_free(lctx->mctx, tmp); + } + return (result); +} + +static void +check_wildcard(dns_incctx_t *ictx, const char *source, unsigned long line, + dns_rdatacallbacks_t *callbacks) { + dns_name_t *name; + + name = (ictx->glue != NULL) ? ictx->glue : ictx->current; + if (dns_name_internalwildcard(name)) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + (*callbacks->warn)(callbacks, + "%s:%lu: warning: ownername " + "'%s' contains an non-terminal wildcard", + source, line, namebuf); + } +} + +static isc_result_t +openfile_text(dns_loadctx_t *lctx, const char *master_file) { + return (isc_lex_openfile(lctx->lex, master_file)); +} + +static int +find_free_name(dns_incctx_t *incctx) { + int i; + + for (i = 0; i < (NBUFS - 1); i++) { + if (!incctx->in_use[i]) { + break; + } + } + INSIST(!incctx->in_use[i]); + return (i); +} + +static isc_result_t +load_text(dns_loadctx_t *lctx) { + dns_rdataclass_t rdclass; + dns_rdatatype_t type, covers; + uint32_t ttl_offset = 0; + dns_name_t *new_name; + bool current_has_delegation = false; + bool done = false; + bool finish_origin = false; + bool finish_include = false; + bool read_till_eol = false; + bool initialws; + char *include_file = NULL; + isc_token_t token; + isc_result_t result = ISC_R_UNEXPECTED; + rdatalist_head_t glue_list; + rdatalist_head_t current_list; + dns_rdatalist_t *this; + dns_rdatalist_t *rdatalist = NULL; + dns_rdatalist_t *new_rdatalist; + int rdlcount = 0; + int rdlcount_save = 0; + int rdatalist_size = 0; + isc_buffer_t buffer; + isc_buffer_t target; + isc_buffer_t target_ft; + isc_buffer_t target_save; + dns_rdata_t *rdata = NULL; + dns_rdata_t *new_rdata; + int rdcount = 0; + int rdcount_save = 0; + int rdata_size = 0; + unsigned char *target_mem = NULL; + int target_size = TSIZ; + int new_in_use; + unsigned int loop_cnt = 0; + isc_mem_t *mctx; + dns_rdatacallbacks_t *callbacks; + dns_incctx_t *ictx; + char *range = NULL; + char *lhs = NULL; + char *gtype = NULL; + char *rhs = NULL; + const char *source; + unsigned long line = 0; + bool explicit_ttl; + char classname1[DNS_RDATACLASS_FORMATSIZE]; + char classname2[DNS_RDATACLASS_FORMATSIZE]; + unsigned int options = 0; + + REQUIRE(DNS_LCTX_VALID(lctx)); + callbacks = lctx->callbacks; + mctx = lctx->mctx; + ictx = lctx->inc; + + ISC_LIST_INIT(glue_list); + ISC_LIST_INIT(current_list); + + /* + * Allocate target_size of buffer space. This is greater than twice + * the maximum individual RR data size. + */ + target_mem = isc_mem_get(mctx, target_size); + isc_buffer_init(&target, target_mem, target_size); + target_save = target; + + if ((lctx->options & DNS_MASTER_CHECKNAMES) != 0) { + options |= DNS_RDATA_CHECKNAMES; + } + if ((lctx->options & DNS_MASTER_CHECKNAMESFAIL) != 0) { + options |= DNS_RDATA_CHECKNAMESFAIL; + } + if ((lctx->options & DNS_MASTER_CHECKMX) != 0) { + options |= DNS_RDATA_CHECKMX; + } + if ((lctx->options & DNS_MASTER_CHECKMXFAIL) != 0) { + options |= DNS_RDATA_CHECKMXFAIL; + } + source = isc_lex_getsourcename(lctx->lex); + do { + initialws = false; + line = isc_lex_getsourceline(lctx->lex); + GETTOKEN(lctx->lex, ISC_LEXOPT_INITIALWS | ISC_LEXOPT_QSTRING, + &token, true); + line = isc_lex_getsourceline(lctx->lex); + + if (token.type == isc_tokentype_eof) { + if (read_till_eol) { + WARNUNEXPECTEDEOF(lctx->lex); + } + /* Pop the include stack? */ + if (ictx->parent != NULL) { + COMMITALL; + lctx->inc = ictx->parent; + ictx->parent = NULL; + incctx_destroy(lctx->mctx, ictx); + RUNTIME_CHECK(isc_lex_close(lctx->lex) == + ISC_R_SUCCESS); + line = isc_lex_getsourceline(lctx->lex); + POST(line); + source = isc_lex_getsourcename(lctx->lex); + ictx = lctx->inc; + continue; + } + done = true; + continue; + } + + if (token.type == isc_tokentype_eol) { + read_till_eol = false; + continue; /* blank line */ + } + + if (read_till_eol) { + continue; + } + + if (token.type == isc_tokentype_initialws) { + /* + * Still working on the same name. + */ + initialws = true; + } else if (token.type == isc_tokentype_string || + token.type == isc_tokentype_qstring) + { + /* + * "$" Support. + * + * "$ORIGIN" and "$INCLUDE" can both take domain names. + * The processing of "$ORIGIN" and "$INCLUDE" extends + * across the normal domain name processing. + */ + + if (strcasecmp(DNS_AS_STR(token), "$ORIGIN") == 0) { + GETTOKEN(lctx->lex, 0, &token, false); + finish_origin = true; + } else if (strcasecmp(DNS_AS_STR(token), "$TTL") == 0) { + GETTOKENERR(lctx->lex, 0, &token, false, + lctx->ttl = 0; + lctx->default_ttl_known = true;); + result = dns_ttl_fromtext( + &token.value.as_textregion, &lctx->ttl); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + lctx->ttl = 0; + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + limit_ttl(callbacks, source, line, &lctx->ttl); + lctx->default_ttl = lctx->ttl; + lctx->default_ttl_known = true; + EXPECTEOL; + continue; + } else if (strcasecmp(DNS_AS_STR(token), "$INCLUDE") == + 0) + { + COMMITALL; + if ((lctx->options & DNS_MASTER_NOINCLUDE) != 0) + { + (callbacks->error)(callbacks, + "%s: %s:%lu: " + "$INCLUDE not " + "allowed", + "dns_master_load", + source, line); + result = DNS_R_REFUSED; + goto insist_and_cleanup; + } + if (ttl_offset != 0) { + (callbacks->error)(callbacks, + "%s: %s:%lu: " + "$INCLUDE " + "may not be used " + "with $DATE", + "dns_master_load", + source, line); + result = DNS_R_SYNTAX; + goto insist_and_cleanup; + } + GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token, + false); + if (include_file != NULL) { + isc_mem_free(mctx, include_file); + } + include_file = + isc_mem_strdup(mctx, DNS_AS_STR(token)); + GETTOKEN(lctx->lex, 0, &token, true); + + if (token.type == isc_tokentype_eol || + token.type == isc_tokentype_eof) + { + if (token.type == isc_tokentype_eof) { + WARNUNEXPECTEDEOF(lctx->lex); + } + /* + * No origin field. + */ + result = pushfile(include_file, + ictx->origin, lctx); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + LOGITFILE(result, include_file); + continue; + } else if (result != ISC_R_SUCCESS) { + LOGITFILE(result, include_file); + goto insist_and_cleanup; + } + ictx = lctx->inc; + source = isc_lex_getsourcename( + lctx->lex); + line = isc_lex_getsourceline(lctx->lex); + POST(line); + continue; + } + /* + * There is an origin field. Fall through + * to domain name processing code and do + * the actual inclusion later. + */ + finish_include = true; + } else if (strcasecmp(DNS_AS_STR(token), "$DATE") == 0) + { + int64_t dump_time64; + isc_stdtime_t dump_time, current_time; + GETTOKEN(lctx->lex, 0, &token, false); + isc_stdtime_get(¤t_time); + result = dns_time64_fromtext(DNS_AS_STR(token), + &dump_time64); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + LOGIT(result); + dump_time64 = 0; + } else if (result != ISC_R_SUCCESS) { + goto log_and_cleanup; + } + dump_time = (isc_stdtime_t)dump_time64; + if (dump_time != dump_time64) { + UNEXPECTED_ERROR("%s: %s:%lu: $DATE " + "outside epoch", + "dns_master_load", + source, line); + result = ISC_R_UNEXPECTED; + goto insist_and_cleanup; + } + if (dump_time > current_time) { + UNEXPECTED_ERROR("%s: %s:%lu: " + "$DATE in future, " + "using current date", + "dns_master_load", + source, line); + dump_time = current_time; + } + ttl_offset = current_time - dump_time; + EXPECTEOL; + continue; + } else if (strcasecmp(DNS_AS_STR(token), "$GENERATE") == + 0) + { + /* + * Lazy cleanup. + */ + if (range != NULL) { + isc_mem_free(mctx, range); + } + if (lhs != NULL) { + isc_mem_free(mctx, lhs); + } + if (gtype != NULL) { + isc_mem_free(mctx, gtype); + } + if (rhs != NULL) { + isc_mem_free(mctx, rhs); + } + range = lhs = gtype = rhs = NULL; + /* RANGE */ + GETTOKEN(lctx->lex, 0, &token, false); + range = isc_mem_strdup(mctx, DNS_AS_STR(token)); + /* LHS */ + GETTOKEN(lctx->lex, 0, &token, false); + lhs = isc_mem_strdup(mctx, DNS_AS_STR(token)); + rdclass = 0; + explicit_ttl = false; + /* CLASS? */ + GETTOKEN(lctx->lex, 0, &token, false); + if (dns_rdataclass_fromtext( + &rdclass, + &token.value.as_textregion) == + ISC_R_SUCCESS) + { + GETTOKEN(lctx->lex, 0, &token, false); + } + /* TTL? */ + if (dns_ttl_fromtext(&token.value.as_textregion, + &lctx->ttl) == + ISC_R_SUCCESS) + { + limit_ttl(callbacks, source, line, + &lctx->ttl); + lctx->ttl_known = true; + explicit_ttl = true; + GETTOKEN(lctx->lex, 0, &token, false); + } + /* CLASS? */ + if (rdclass == 0 && + dns_rdataclass_fromtext( + &rdclass, + &token.value.as_textregion) == + ISC_R_SUCCESS) + { + GETTOKEN(lctx->lex, 0, &token, false); + } + /* TYPE */ + gtype = isc_mem_strdup(mctx, DNS_AS_STR(token)); + /* RHS */ + GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, &token, + false); + rhs = isc_mem_strdup(mctx, DNS_AS_STR(token)); + if (!lctx->ttl_known && + !lctx->default_ttl_known) + { + (*callbacks->error)(callbacks, + "%s: %s:%lu: no " + "TTL specified", + "dns_master_load", + source, line); + result = DNS_R_NOTTL; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + lctx->ttl = 0; + } else { + goto insist_and_cleanup; + } + } else if (!explicit_ttl && + lctx->default_ttl_known) + { + lctx->ttl = lctx->default_ttl; + } + /* + * If the class specified does not match the + * zone's class print out a error message and + * exit. + */ + if (rdclass != 0 && rdclass != lctx->zclass) { + goto bad_class; + } + result = generate(lctx, range, lhs, gtype, rhs, + source, line); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + EXPECTEOL; + continue; + } else if (strncasecmp(DNS_AS_STR(token), "$", 1) == 0) + { + (callbacks->error)(callbacks, + "%s: %s:%lu: " + "unknown $ directive '%s'", + "dns_master_load", source, + line, DNS_AS_STR(token)); + result = DNS_R_SYNTAX; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else { + goto insist_and_cleanup; + } + } + + /* + * Normal processing resumes. + */ + new_in_use = find_free_name(ictx); + new_name = dns_fixedname_initname( + &ictx->fixed[new_in_use]); + isc_buffer_init(&buffer, token.value.as_region.base, + token.value.as_region.length); + isc_buffer_add(&buffer, token.value.as_region.length); + isc_buffer_setactive(&buffer, + token.value.as_region.length); + result = dns_name_fromtext(new_name, &buffer, + ictx->origin, 0, NULL); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + LOGIT(result); + read_till_eol = true; + continue; + } else if (result != ISC_R_SUCCESS) { + goto log_and_cleanup; + } + + /* + * Finish $ORIGIN / $INCLUDE processing if required. + */ + if (finish_origin) { + if (ictx->origin_in_use != -1) { + ictx->in_use[ictx->origin_in_use] = + false; + } + ictx->origin_in_use = new_in_use; + ictx->in_use[ictx->origin_in_use] = true; + ictx->origin = new_name; + ictx->origin_changed = true; + finish_origin = false; + EXPECTEOL; + continue; + } + if (finish_include) { + finish_include = false; + EXPECTEOL; + result = pushfile(include_file, new_name, lctx); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + LOGITFILE(result, include_file); + continue; + } else if (result != ISC_R_SUCCESS) { + LOGITFILE(result, include_file); + goto insist_and_cleanup; + } + ictx = lctx->inc; + ictx->origin_changed = true; + source = isc_lex_getsourcename(lctx->lex); + line = isc_lex_getsourceline(lctx->lex); + POST(line); + continue; + } + + /* + * "$" Processing Finished + */ + + /* + * If we are processing glue and the new name does + * not match the current glue name, commit the glue + * and pop stacks leaving us in 'normal' processing + * state. Linked lists are undone by commit(). + */ + if (ictx->glue != NULL && + !dns_name_caseequal(ictx->glue, new_name)) + { + result = commit(callbacks, lctx, &glue_list, + ictx->glue, source, + ictx->glue_line); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + if (ictx->glue_in_use != -1) { + ictx->in_use[ictx->glue_in_use] = false; + } + ictx->glue_in_use = -1; + ictx->glue = NULL; + rdcount = rdcount_save; + rdlcount = rdlcount_save; + target = target_save; + } + + /* + * If we are in 'normal' processing state and the new + * name does not match the current name, see if the + * new name is for glue and treat it as such, + * otherwise we have a new name so commit what we + * have. + */ + if ((ictx->glue == NULL) && + (ictx->current == NULL || + !dns_name_caseequal(ictx->current, new_name))) + { + if (current_has_delegation && + is_glue(¤t_list, new_name)) + { + rdcount_save = rdcount; + rdlcount_save = rdlcount; + target_save = target; + ictx->glue = new_name; + ictx->glue_in_use = new_in_use; + ictx->in_use[ictx->glue_in_use] = true; + } else { + result = commit(callbacks, lctx, + ¤t_list, + ictx->current, source, + ictx->current_line); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + rdcount = 0; + rdlcount = 0; + if (ictx->current_in_use != -1) { + ictx->in_use + [ictx->current_in_use] = + false; + } + ictx->current_in_use = new_in_use; + ictx->in_use[ictx->current_in_use] = + true; + ictx->current = new_name; + current_has_delegation = false; + isc_buffer_init(&target, target_mem, + target_size); + } + /* + * Check for internal wildcards. + */ + if ((lctx->options & + DNS_MASTER_CHECKWILDCARD) != 0) + { + check_wildcard(ictx, source, line, + callbacks); + } + } + if (dns_master_isprimary(lctx) && + !dns_name_issubdomain(new_name, lctx->top)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(new_name, namebuf, + sizeof(namebuf)); + /* + * Ignore out-of-zone data. + */ + (*callbacks->warn)(callbacks, + "%s:%lu: " + "ignoring out-of-zone data " + "(%s)", + source, line, namebuf); + ictx->drop = true; + } else { + ictx->drop = false; + } + } else { + UNEXPECTED_ERROR("%s:%lu: isc_lex_gettoken() returned " + "unexpected token type (%d)", + source, line, token.type); + result = ISC_R_UNEXPECTED; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + LOGIT(result); + continue; + } else { + goto insist_and_cleanup; + } + } + + /* + * Find TTL, class and type. Both TTL and class are optional + * and may occur in any order if they exist. TTL and class + * come before type which must exist. + * + * [] [] + * [] [] + */ + + type = 0; + rdclass = 0; + + GETTOKEN(lctx->lex, 0, &token, initialws); + + if (initialws) { + if (token.type == isc_tokentype_eol) { + read_till_eol = false; + continue; /* blank line */ + } + + if (token.type == isc_tokentype_eof) { + WARNUNEXPECTEDEOF(lctx->lex); + read_till_eol = false; + isc_lex_ungettoken(lctx->lex, &token); + continue; + } + + if (ictx->current == NULL) { + (*callbacks->error)(callbacks, + "%s:%lu: no current owner " + "name", + source, line); + result = DNS_R_NOOWNER; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + continue; + } else { + goto insist_and_cleanup; + } + } + + if (ictx->origin_changed) { + char cbuf[DNS_NAME_FORMATSIZE]; + char obuf[DNS_NAME_FORMATSIZE]; + dns_name_format(ictx->current, cbuf, + sizeof(cbuf)); + dns_name_format(ictx->origin, obuf, + sizeof(obuf)); + (*callbacks->warn)(callbacks, + "%s:%lu: record with " + "inherited " + "owner (%s) immediately " + "after " + "$ORIGIN (%s)", + source, line, cbuf, obuf); + } + } + + ictx->origin_changed = false; + + if (dns_rdataclass_fromtext(&rdclass, + &token.value.as_textregion) == + ISC_R_SUCCESS) + { + GETTOKEN(lctx->lex, 0, &token, false); + } + + explicit_ttl = false; + result = dns_ttl_fromtext(&token.value.as_textregion, + &lctx->ttl); + if (result == ISC_R_SUCCESS) { + limit_ttl(callbacks, source, line, &lctx->ttl); + explicit_ttl = true; + lctx->ttl_known = true; + GETTOKEN(lctx->lex, 0, &token, false); + } + + if (token.type != isc_tokentype_string) { + UNEXPECTED_ERROR("isc_lex_gettoken() returned " + "unexpected token type"); + result = ISC_R_UNEXPECTED; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + continue; + } else { + goto insist_and_cleanup; + } + } + + if (rdclass == 0 && + dns_rdataclass_fromtext(&rdclass, + &token.value.as_textregion) == + ISC_R_SUCCESS) + { + GETTOKEN(lctx->lex, 0, &token, false); + } + + if (token.type != isc_tokentype_string) { + UNEXPECTED_ERROR("isc_lex_gettoken() returned " + "unexpected token type"); + result = ISC_R_UNEXPECTED; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + continue; + } else { + goto insist_and_cleanup; + } + } + + result = dns_rdatatype_fromtext(&type, + &token.value.as_textregion); + if (result != ISC_R_SUCCESS) { + (*callbacks->warn)( + callbacks, "%s:%lu: unknown RR type '%.*s'", + source, line, token.value.as_textregion.length, + token.value.as_textregion.base); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + continue; + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + } + + /* + * If the class specified does not match the zone's class + * print out a error message and exit. + */ + if (rdclass != 0 && rdclass != lctx->zclass) { + bad_class: + + dns_rdataclass_format(rdclass, classname1, + sizeof(classname1)); + dns_rdataclass_format(lctx->zclass, classname2, + sizeof(classname2)); + (*callbacks->error)(callbacks, + "%s:%lu: class '%s' != " + "zone class '%s'", + source, line, classname1, + classname2); + result = DNS_R_BADCLASS; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + continue; + } else { + goto insist_and_cleanup; + } + } + + if (type == dns_rdatatype_ns && ictx->glue == NULL) { + current_has_delegation = true; + } + + /* + * RFC1123: MD and MF are not allowed to be loaded from + * master files. + */ + if (dns_master_isprimary(lctx) && + (type == dns_rdatatype_md || type == dns_rdatatype_mf)) + { + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + result = DNS_R_OBSOLETE; + + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s", + source, line, "type", typebuf, + isc_result_totext(result)); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else { + goto insist_and_cleanup; + } + } + + /* + * RFC2930: TKEY and TSIG are not allowed to be loaded + * from master files. + */ + if (dns_master_isprimary(lctx) && dns_rdatatype_ismeta(type)) { + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + result = DNS_R_METATYPE; + + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + (*callbacks->error)(callbacks, "%s:%lu: %s '%s': %s", + source, line, "type", typebuf, + isc_result_totext(result)); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else { + goto insist_and_cleanup; + } + } + + /* + * Find a rdata structure. + */ + if (rdcount == rdata_size) { + new_rdata = grow_rdata(rdata_size + RDSZ, rdata, + rdata_size, ¤t_list, + &glue_list, mctx); + if (new_rdata == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + rdata_size += RDSZ; + rdata = new_rdata; + } + + /* + * Peek at the NS record. + */ + if (type == dns_rdatatype_ns && + lctx->zclass == dns_rdataclass_in && + (lctx->options & DNS_MASTER_CHECKNS) != 0) + { + GETTOKEN(lctx->lex, 0, &token, false); + result = check_ns(lctx, &token, source, line); + isc_lex_ungettoken(lctx->lex, &token); + if ((lctx->options & DNS_MASTER_FATALNS) != 0) { + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + } + } + + /* + * Check owner name. + */ + options &= ~DNS_RDATA_CHECKREVERSE; + if ((lctx->options & DNS_MASTER_CHECKNAMES) != 0) { + bool ok; + dns_name_t *name; + + name = (ictx->glue != NULL) ? ictx->glue + : ictx->current; + ok = dns_rdata_checkowner(name, lctx->zclass, type, + true); + if (!ok) { + char namebuf[DNS_NAME_FORMATSIZE]; + const char *desc; + dns_name_format(name, namebuf, sizeof(namebuf)); + result = DNS_R_BADOWNERNAME; + desc = isc_result_totext(result); + if (CHECKNAMESFAIL(lctx->options) || + type == dns_rdatatype_nsec3) + { + (*callbacks->error)( + callbacks, "%s:%lu: %s: %s", + source, line, namebuf, desc); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else { + goto cleanup; + } + } else { + (*callbacks->warn)( + callbacks, "%s:%lu: %s: %s", + source, line, namebuf, desc); + } + } + if (type == dns_rdatatype_ptr && + !dns_name_isdnssd(name) && + (dns_name_issubdomain(name, &in_addr_arpa) || + dns_name_issubdomain(name, &ip6_arpa) || + dns_name_issubdomain(name, &ip6_int))) + { + options |= DNS_RDATA_CHECKREVERSE; + } + } + + /* + * Read rdata contents. + */ + dns_rdata_init(&rdata[rdcount]); + target_ft = target; + result = dns_rdata_fromtext(&rdata[rdcount], lctx->zclass, type, + lctx->lex, ictx->origin, options, + lctx->mctx, &target, callbacks); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + continue; + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + + if (ictx->drop) { + target = target_ft; + continue; + } + + if (type == dns_rdatatype_soa && + (lctx->options & DNS_MASTER_ZONE) != 0 && + !dns_name_equal(ictx->current, lctx->top)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(ictx->current, namebuf, + sizeof(namebuf)); + (*callbacks->error)(callbacks, + "%s:%lu: SOA " + "record not at top of zone (%s)", + source, line, namebuf); + result = DNS_R_NOTZONETOP; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + read_till_eol = true; + target = target_ft; + continue; + } else { + goto insist_and_cleanup; + } + } + + if (dns_rdatatype_atparent(type) && + dns_master_isprimary(lctx) && + dns_name_equal(ictx->current, lctx->top)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(ictx->current, namebuf, + sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + (*callbacks->error)( + callbacks, + "%s:%lu: %s record at top of zone (%s)", source, + line, typebuf, namebuf); + result = DNS_R_ATZONETOP; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + target = target_ft; + continue; + } else { + goto insist_and_cleanup; + } + } + + if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) { + covers = dns_rdata_covers(&rdata[rdcount]); + } else { + covers = 0; + } + + if (!lctx->ttl_known && !lctx->default_ttl_known) { + if (type == dns_rdatatype_soa) { + (*callbacks->warn)(callbacks, + "%s:%lu: no TTL specified; " + "using SOA MINTTL instead", + source, line); + lctx->ttl = dns_soa_getminimum(&rdata[rdcount]); + limit_ttl(callbacks, source, line, &lctx->ttl); + lctx->default_ttl = lctx->ttl; + lctx->default_ttl_known = true; + } else if ((lctx->options & DNS_MASTER_HINT) != 0) { + /* + * Zero TTL's are fine for hints. + */ + lctx->ttl = 0; + lctx->default_ttl = lctx->ttl; + lctx->default_ttl_known = true; + } else { + (*callbacks->warn)(callbacks, + "%s:%lu: no TTL specified; " + "zone rejected", + source, line); + result = DNS_R_NOTTL; + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + lctx->ttl = 0; + } else { + goto insist_and_cleanup; + } + } + } else if (!explicit_ttl && lctx->default_ttl_known) { + lctx->ttl = lctx->default_ttl; + } else if (!explicit_ttl && lctx->warn_1035) { + (*callbacks->warn)(callbacks, + "%s:%lu: " + "using RFC1035 TTL semantics", + source, line); + lctx->warn_1035 = false; + } + + if (type == dns_rdatatype_rrsig && lctx->warn_sigexpired) { + dns_rdata_rrsig_t sig; + result = dns_rdata_tostruct(&rdata[rdcount], &sig, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (isc_serial_lt(sig.timeexpire, lctx->now)) { + (*callbacks->warn)(callbacks, + "%s:%lu: " + "signature has expired", + source, line); + lctx->warn_sigexpired = false; + } + } + + if ((type == dns_rdatatype_sig || type == dns_rdatatype_nxt) && + lctx->warn_tcr && dns_master_isprimary(lctx)) + { + (*callbacks->warn)(callbacks, + "%s:%lu: old style DNSSEC " + " zone detected", + source, line); + lctx->warn_tcr = false; + } + + if ((lctx->options & DNS_MASTER_AGETTL) != 0) { + /* + * Adjust the TTL for $DATE. If the RR has + * already expired, set its TTL to 0. This + * should be okay even if the TTL stretching + * feature is not in effect, because it will + * just be quickly expired by the cache, and the + * way this was written before the patch it + * could potentially add 0 TTLs anyway. + */ + if (lctx->ttl < ttl_offset) { + lctx->ttl = 0; + } else { + lctx->ttl -= ttl_offset; + } + } + + /* + * Find type in rdatalist. + * If it does not exist create new one and prepend to list + * as this will minimise list traversal. + */ + if (ictx->glue != NULL) { + this = ISC_LIST_HEAD(glue_list); + } else { + this = ISC_LIST_HEAD(current_list); + } + + while (this != NULL) { + if (this->type == type && this->covers == covers) { + break; + } + this = ISC_LIST_NEXT(this, link); + } + + if (this == NULL) { + if (rdlcount == rdatalist_size) { + new_rdatalist = grow_rdatalist( + rdatalist_size + RDLSZ, rdatalist, + rdatalist_size, ¤t_list, + &glue_list, mctx); + if (new_rdatalist == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + rdatalist = new_rdatalist; + rdatalist_size += RDLSZ; + } + this = &rdatalist[rdlcount++]; + dns_rdatalist_init(this); + this->type = type; + this->covers = covers; + this->rdclass = lctx->zclass; + this->ttl = lctx->ttl; + if (ictx->glue != NULL) { + ISC_LIST_INITANDPREPEND(glue_list, this, link); + } else { + ISC_LIST_INITANDPREPEND(current_list, this, + link); + } + } else if (this->ttl != lctx->ttl) { + (*callbacks->warn)(callbacks, + "%s:%lu: " + "TTL set to prior TTL (%lu)", + source, line, this->ttl); + lctx->ttl = this->ttl; + } + + if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 && + lctx->ttl > lctx->maxttl) + { + (callbacks->error)(callbacks, + "dns_master_load: %s:%lu: " + "TTL %d exceeds configured " + "max-zone-ttl %d", + source, line, lctx->ttl, + lctx->maxttl); + result = ISC_R_RANGE; + goto log_and_cleanup; + } + + ISC_LIST_APPEND(this->rdata, &rdata[rdcount], link); + if (ictx->glue != NULL) { + ictx->glue_line = line; + } else { + ictx->current_line = line; + } + rdcount++; + + /* + * We must have at least 64k as rdlen is 16 bits. + * If we don't commit everything we have so far. + */ + if ((target.length - target.used) < MINTSIZ) { + COMMITALL; + } + next_line:; + } while (!done && (lctx->loop_cnt == 0 || loop_cnt++ < lctx->loop_cnt)); + + /* + * Commit what has not yet been committed. + */ + result = commit(callbacks, lctx, ¤t_list, ictx->current, source, + ictx->current_line); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + result = commit(callbacks, lctx, &glue_list, ictx->glue, source, + ictx->glue_line); + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + goto insist_and_cleanup; + } + + if (!done) { + INSIST(lctx->done != NULL && lctx->task != NULL); + result = DNS_R_CONTINUE; + } else if (result == ISC_R_SUCCESS && lctx->result != ISC_R_SUCCESS) { + result = lctx->result; + } else if (result == ISC_R_SUCCESS && lctx->seen_include) { + result = DNS_R_SEENINCLUDE; + } + goto cleanup; + +log_and_cleanup: + LOGIT(result); + +insist_and_cleanup: + INSIST(result != ISC_R_SUCCESS); + +cleanup: + while ((this = ISC_LIST_HEAD(current_list)) != NULL) { + ISC_LIST_UNLINK(current_list, this, link); + } + while ((this = ISC_LIST_HEAD(glue_list)) != NULL) { + ISC_LIST_UNLINK(glue_list, this, link); + } + if (rdatalist != NULL) { + isc_mem_put(mctx, rdatalist, + rdatalist_size * sizeof(*rdatalist)); + } + if (rdata != NULL) { + isc_mem_put(mctx, rdata, rdata_size * sizeof(*rdata)); + } + if (target_mem != NULL) { + isc_mem_put(mctx, target_mem, target_size); + } + if (include_file != NULL) { + isc_mem_free(mctx, include_file); + } + if (range != NULL) { + isc_mem_free(mctx, range); + } + if (lhs != NULL) { + isc_mem_free(mctx, lhs); + } + if (gtype != NULL) { + isc_mem_free(mctx, gtype); + } + if (rhs != NULL) { + isc_mem_free(mctx, rhs); + } + return (result); +} + +static isc_result_t +pushfile(const char *master_file, dns_name_t *origin, dns_loadctx_t *lctx) { + isc_result_t result; + dns_incctx_t *ictx; + dns_incctx_t *newctx = NULL; + isc_region_t r; + + REQUIRE(master_file != NULL); + REQUIRE(DNS_LCTX_VALID(lctx)); + + ictx = lctx->inc; + lctx->seen_include = true; + + result = incctx_create(lctx->mctx, origin, &newctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Push origin_changed. + */ + newctx->origin_changed = ictx->origin_changed; + + /* Set current domain. */ + if (ictx->glue != NULL || ictx->current != NULL) { + newctx->current_in_use = find_free_name(newctx); + newctx->current = dns_fixedname_name( + &newctx->fixed[newctx->current_in_use]); + newctx->in_use[newctx->current_in_use] = true; + dns_name_toregion( + (ictx->glue != NULL) ? ictx->glue : ictx->current, &r); + dns_name_fromregion(newctx->current, &r); + newctx->drop = ictx->drop; + } + + result = (lctx->openfile)(lctx, master_file); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + newctx->parent = ictx; + lctx->inc = newctx; + + if (lctx->include_cb != NULL) { + lctx->include_cb(master_file, lctx->include_arg); + } + return (ISC_R_SUCCESS); + +cleanup: + incctx_destroy(lctx->mctx, newctx); + return (result); +} + +/* + * Fill/check exists buffer with 'len' bytes. Track remaining bytes to be + * read when incrementally filling the buffer. + */ +static isc_result_t +read_and_check(bool do_read, isc_buffer_t *buffer, size_t len, FILE *f, + uint32_t *totallen) { + isc_result_t result; + + REQUIRE(totallen != NULL); + + if (do_read) { + INSIST(isc_buffer_availablelength(buffer) >= len); + result = isc_stdio_read(isc_buffer_used(buffer), 1, len, f, + NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_add(buffer, (unsigned int)len); + if (*totallen < len) { + return (ISC_R_RANGE); + } + *totallen -= (uint32_t)len; + } else if (isc_buffer_remaininglength(buffer) < len) { + return (ISC_R_RANGE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_header(dns_loadctx_t *lctx) { + isc_result_t result = ISC_R_SUCCESS; + dns_masterrawheader_t header; + dns_rdatacallbacks_t *callbacks; + size_t commonlen = sizeof(header.format) + sizeof(header.version); + size_t remainder; + unsigned char data[sizeof(header)]; + isc_buffer_t target; + + REQUIRE(DNS_LCTX_VALID(lctx)); + + if (lctx->format != dns_masterformat_raw) { + return (ISC_R_NOTIMPLEMENTED); + } + + callbacks = lctx->callbacks; + dns_master_initrawheader(&header); + + INSIST(commonlen <= sizeof(header)); + isc_buffer_init(&target, data, sizeof(data)); + + result = isc_stdio_read(data, 1, commonlen, lctx->f, NULL); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_stdio_read failed: %s", + isc_result_totext(result)); + return (result); + } + + isc_buffer_add(&target, (unsigned int)commonlen); + header.format = isc_buffer_getuint32(&target); + if (header.format != lctx->format) { + (*callbacks->error)(callbacks, + "dns_master_load: " + "file format mismatch (not raw)"); + return (ISC_R_NOTIMPLEMENTED); + } + + header.version = isc_buffer_getuint32(&target); + + switch (header.version) { + case 0: + remainder = sizeof(header.dumptime); + break; + case DNS_RAWFORMAT_VERSION: + remainder = sizeof(header) - commonlen; + break; + default: + (*callbacks->error)(callbacks, "dns_master_load: " + "unsupported file format " + "version"); + return (ISC_R_NOTIMPLEMENTED); + } + + result = isc_stdio_read(data + commonlen, 1, remainder, lctx->f, NULL); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_stdio_read failed: %s", + isc_result_totext(result)); + return (result); + } + + isc_buffer_add(&target, (unsigned int)remainder); + header.dumptime = isc_buffer_getuint32(&target); + if (header.version == DNS_RAWFORMAT_VERSION) { + header.flags = isc_buffer_getuint32(&target); + header.sourceserial = isc_buffer_getuint32(&target); + header.lastxfrin = isc_buffer_getuint32(&target); + } + + lctx->first = false; + lctx->header = header; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openfile_raw(dns_loadctx_t *lctx, const char *master_file) { + isc_result_t result; + + result = isc_stdio_open(master_file, "rb", &lctx->f); + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + UNEXPECTED_ERROR("isc_stdio_open() failed: %s", + isc_result_totext(result)); + } + + return (result); +} + +static isc_result_t +load_raw(dns_loadctx_t *lctx) { + isc_result_t result = ISC_R_SUCCESS; + bool done = false; + unsigned int loop_cnt = 0; + dns_rdatacallbacks_t *callbacks; + unsigned char namebuf[DNS_NAME_MAXWIRE]; + dns_fixedname_t fixed; + dns_name_t *name; + rdatalist_head_t head, dummy; + dns_rdatalist_t rdatalist; + isc_mem_t *mctx = lctx->mctx; + dns_rdata_t *rdata = NULL; + unsigned int rdata_size = 0; + int target_size = TSIZ; + isc_buffer_t target, buf; + unsigned char *target_mem = NULL; + dns_decompress_t dctx; + + callbacks = lctx->callbacks; + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); + + if (lctx->first) { + result = load_header(lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + ISC_LIST_INIT(head); + ISC_LIST_INIT(dummy); + + /* + * Allocate target_size of buffer space. This is greater than twice + * the maximum individual RR data size. + */ + target_mem = isc_mem_get(mctx, target_size); + isc_buffer_init(&target, target_mem, target_size); + + name = dns_fixedname_initname(&fixed); + + /* + * In the following loop, we regard any error fatal regardless of + * whether "MANYERRORS" is set in the context option. This is because + * normal errors should already have been checked at creation time. + * Besides, it is very unlikely that we can recover from an error + * in this format, and so trying to continue parsing erroneous data + * does not really make sense. + */ + for (loop_cnt = 0; (lctx->loop_cnt == 0 || loop_cnt < lctx->loop_cnt); + loop_cnt++) + { + unsigned int i, rdcount; + uint16_t namelen; + uint32_t totallen; + size_t minlen, readlen; + bool sequential_read = false; + + /* Read the data length */ + isc_buffer_clear(&target); + INSIST(isc_buffer_availablelength(&target) >= sizeof(totallen)); + result = isc_stdio_read(target.base, 1, sizeof(totallen), + lctx->f, NULL); + if (result == ISC_R_EOF) { + result = ISC_R_SUCCESS; + done = true; + break; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_add(&target, sizeof(totallen)); + totallen = isc_buffer_getuint32(&target); + + /* + * Validation: the input data must at least contain the common + * header. + */ + minlen = sizeof(totallen) + sizeof(uint16_t) + + sizeof(uint16_t) + sizeof(uint16_t) + + sizeof(uint32_t) + sizeof(uint32_t); + if (totallen < minlen) { + result = ISC_R_RANGE; + goto cleanup; + } + totallen -= sizeof(totallen); + + isc_buffer_clear(&target); + if (totallen > isc_buffer_availablelength(&target)) { + /* + * The default buffer size should typically be large + * enough to store the entire RRset. We could try to + * allocate enough space if this is not the case, but + * it might cause a hazardous result when "totallen" + * is forged. Thus, we'd rather take an inefficient + * but robust approach in this atypical case: read + * data step by step, and commit partial data when + * necessary. Note that the buffer must be large + * enough to store the "header part", owner name, and + * at least one rdata (however large it is). + */ + sequential_read = true; + readlen = minlen - sizeof(totallen); + } else { + /* + * Typical case. We can read the whole RRset at once + * with the default buffer. + */ + readlen = totallen; + } + result = isc_stdio_read(target.base, 1, readlen, lctx->f, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_add(&target, (unsigned int)readlen); + totallen -= (uint32_t)readlen; + + /* Construct RRset headers */ + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = isc_buffer_getuint16(&target); + if (lctx->zclass != rdatalist.rdclass) { + result = DNS_R_BADCLASS; + goto cleanup; + } + rdatalist.type = isc_buffer_getuint16(&target); + rdatalist.covers = isc_buffer_getuint16(&target); + rdatalist.ttl = isc_buffer_getuint32(&target); + rdcount = isc_buffer_getuint32(&target); + if (rdcount == 0 || rdcount > 0xffff) { + result = ISC_R_RANGE; + goto cleanup; + } + INSIST(isc_buffer_consumedlength(&target) <= readlen); + + /* Owner name: length followed by name */ + result = read_and_check(sequential_read, &target, + sizeof(namelen), lctx->f, &totallen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + namelen = isc_buffer_getuint16(&target); + if (namelen > sizeof(namebuf)) { + result = ISC_R_RANGE; + goto cleanup; + } + + result = read_and_check(sequential_read, &target, namelen, + lctx->f, &totallen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_buffer_setactive(&target, (unsigned int)namelen); + result = dns_name_fromwire(name, &target, &dctx, 0, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if ((lctx->options & DNS_MASTER_CHECKTTL) != 0 && + rdatalist.ttl > lctx->maxttl) + { + (callbacks->error)(callbacks, + "dns_master_load: " + "TTL %d exceeds configured " + "max-zone-ttl %d", + rdatalist.ttl, lctx->maxttl); + result = ISC_R_RANGE; + goto cleanup; + } + + /* Rdata contents. */ + if (rdcount > rdata_size) { + dns_rdata_t *new_rdata = NULL; + + new_rdata = grow_rdata(rdcount + RDSZ, rdata, + rdata_size, &head, &dummy, mctx); + if (new_rdata == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + rdata_size = rdcount + RDSZ; + rdata = new_rdata; + } + + continue_read: + for (i = 0; i < rdcount; i++) { + uint16_t rdlen; + + dns_rdata_init(&rdata[i]); + + if (sequential_read && + isc_buffer_availablelength(&target) < MINTSIZ) + { + unsigned int j; + + INSIST(i > 0); /* detect an infinite loop */ + + /* Partial Commit. */ + ISC_LIST_APPEND(head, &rdatalist, link); + result = commit(callbacks, lctx, &head, name, + NULL, 0); + for (j = 0; j < i; j++) { + ISC_LIST_UNLINK(rdatalist.rdata, + &rdata[j], link); + dns_rdata_reset(&rdata[j]); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Rewind the buffer and continue */ + isc_buffer_clear(&target); + + rdcount -= i; + + goto continue_read; + } + + /* rdata length */ + result = read_and_check(sequential_read, &target, + sizeof(rdlen), lctx->f, + &totallen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + rdlen = isc_buffer_getuint16(&target); + + /* rdata */ + result = read_and_check(sequential_read, &target, rdlen, + lctx->f, &totallen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_setactive(&target, (unsigned int)rdlen); + /* + * It is safe to have the source active region and + * the target available region be the same if + * decompression is disabled (see dctx above) and we + * are not downcasing names (options == 0). + */ + isc_buffer_init(&buf, isc_buffer_current(&target), + (unsigned int)rdlen); + result = dns_rdata_fromwire( + &rdata[i], rdatalist.rdclass, rdatalist.type, + &target, &dctx, 0, &buf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ISC_LIST_APPEND(rdatalist.rdata, &rdata[i], link); + } + + /* + * Sanity check. Still having remaining space is not + * necessarily critical, but it very likely indicates broken + * or malformed data. + */ + if (isc_buffer_remaininglength(&target) != 0 || totallen != 0) { + result = ISC_R_RANGE; + goto cleanup; + } + + ISC_LIST_APPEND(head, &rdatalist, link); + + /* Commit this RRset. rdatalist will be unlinked. */ + result = commit(callbacks, lctx, &head, name, NULL, 0); + + for (i = 0; i < rdcount; i++) { + ISC_LIST_UNLINK(rdatalist.rdata, &rdata[i], link); + dns_rdata_reset(&rdata[i]); + } + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + if (!done) { + INSIST(lctx->done != NULL && lctx->task != NULL); + result = DNS_R_CONTINUE; + } else if (result == ISC_R_SUCCESS && lctx->result != ISC_R_SUCCESS) { + result = lctx->result; + } + + if (result == ISC_R_SUCCESS && callbacks->rawdata != NULL) { + (*callbacks->rawdata)(callbacks->zone, &lctx->header); + } + +cleanup: + if (rdata != NULL) { + isc_mem_put(mctx, rdata, rdata_size * sizeof(*rdata)); + } + if (target_mem != NULL) { + isc_mem_put(mctx, target_mem, target_size); + } + if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE) { + (*callbacks->error)(callbacks, "dns_master_load: %s", + isc_result_totext(result)); + } + + return (result); +} + +isc_result_t +dns_master_loadfile(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, uint32_t resign, + dns_rdatacallbacks_t *callbacks, + dns_masterincludecb_t include_cb, void *include_arg, + isc_mem_t *mctx, dns_masterformat_t format, + dns_ttl_t maxttl) { + dns_loadctx_t *lctx = NULL; + isc_result_t result; + + result = loadctx_create(format, mctx, options, resign, top, zclass, + origin, callbacks, NULL, NULL, NULL, include_cb, + include_arg, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + lctx->maxttl = maxttl; + + result = (lctx->openfile)(lctx, master_file); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = (lctx->load)(lctx); + INSIST(result != DNS_R_CONTINUE); + +cleanup: + dns_loadctx_detach(&lctx); + return (result); +} + +isc_result_t +dns_master_loadfileinc(const char *master_file, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, uint32_t resign, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **lctxp, dns_masterincludecb_t include_cb, + void *include_arg, isc_mem_t *mctx, + dns_masterformat_t format, uint32_t maxttl) { + dns_loadctx_t *lctx = NULL; + isc_result_t result; + + REQUIRE(task != NULL); + REQUIRE(done != NULL); + + result = loadctx_create(format, mctx, options, resign, top, zclass, + origin, callbacks, task, done, done_arg, + include_cb, include_arg, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + lctx->maxttl = maxttl; + + result = (lctx->openfile)(lctx, master_file); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = task_send(lctx); + if (result == ISC_R_SUCCESS) { + dns_loadctx_attach(lctx, lctxp); + return (DNS_R_CONTINUE); + } + +cleanup: + dns_loadctx_detach(&lctx); + return (result); +} + +isc_result_t +dns_master_loadstream(FILE *stream, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(stream != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, NULL, NULL, NULL, + NULL, NULL, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = isc_lex_openstream(lctx->lex, stream); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = (lctx->load)(lctx); + INSIST(result != DNS_R_CONTINUE); + +cleanup: + if (lctx != NULL) { + dns_loadctx_detach(&lctx); + } + return (result); +} + +isc_result_t +dns_master_loadstreaminc(FILE *stream, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **lctxp, isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(stream != NULL); + REQUIRE(task != NULL); + REQUIRE(done != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, task, done, done_arg, + NULL, NULL, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = isc_lex_openstream(lctx->lex, stream); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = task_send(lctx); + if (result == ISC_R_SUCCESS) { + dns_loadctx_attach(lctx, lctxp); + return (DNS_R_CONTINUE); + } + +cleanup: + if (lctx != NULL) { + dns_loadctx_detach(&lctx); + } + return (result); +} + +isc_result_t +dns_master_loadbuffer(isc_buffer_t *buffer, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(buffer != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, NULL, NULL, NULL, + NULL, NULL, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_lex_openbuffer(lctx->lex, buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = (lctx->load)(lctx); + INSIST(result != DNS_R_CONTINUE); + +cleanup: + dns_loadctx_detach(&lctx); + return (result); +} + +isc_result_t +dns_master_loadbufferinc(isc_buffer_t *buffer, dns_name_t *top, + dns_name_t *origin, dns_rdataclass_t zclass, + unsigned int options, dns_rdatacallbacks_t *callbacks, + isc_task_t *task, dns_loaddonefunc_t done, + void *done_arg, dns_loadctx_t **lctxp, + isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(buffer != NULL); + REQUIRE(task != NULL); + REQUIRE(done != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, task, done, done_arg, + NULL, NULL, NULL, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_lex_openbuffer(lctx->lex, buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = task_send(lctx); + if (result == ISC_R_SUCCESS) { + dns_loadctx_attach(lctx, lctxp); + return (DNS_R_CONTINUE); + } + +cleanup: + dns_loadctx_detach(&lctx); + return (result); +} + +isc_result_t +dns_master_loadlexer(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(lex != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, NULL, NULL, NULL, + NULL, NULL, lex, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = (lctx->load)(lctx); + INSIST(result != DNS_R_CONTINUE); + + dns_loadctx_detach(&lctx); + return (result); +} + +isc_result_t +dns_master_loadlexerinc(isc_lex_t *lex, dns_name_t *top, dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_task_t *task, + dns_loaddonefunc_t done, void *done_arg, + dns_loadctx_t **lctxp, isc_mem_t *mctx) { + isc_result_t result; + dns_loadctx_t *lctx = NULL; + + REQUIRE(lex != NULL); + REQUIRE(task != NULL); + REQUIRE(done != NULL); + + result = loadctx_create(dns_masterformat_text, mctx, options, 0, top, + zclass, origin, callbacks, task, done, done_arg, + NULL, NULL, lex, &lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = task_send(lctx); + if (result == ISC_R_SUCCESS) { + dns_loadctx_attach(lctx, lctxp); + return (DNS_R_CONTINUE); + } + + dns_loadctx_detach(&lctx); + return (result); +} + +/* + * Grow the slab of dns_rdatalist_t structures. + * Re-link glue and current list. + */ +static dns_rdatalist_t * +grow_rdatalist(int new_len, dns_rdatalist_t *oldlist, int old_len, + rdatalist_head_t *current, rdatalist_head_t *glue, + isc_mem_t *mctx) { + dns_rdatalist_t *newlist; + int rdlcount = 0; + ISC_LIST(dns_rdatalist_t) save; + dns_rdatalist_t *this; + + newlist = isc_mem_get(mctx, new_len * sizeof(*newlist)); + if (newlist == NULL) { + return (NULL); + } + + ISC_LIST_INIT(save); + while ((this = ISC_LIST_HEAD(*current)) != NULL) { + ISC_LIST_UNLINK(*current, this, link); + ISC_LIST_APPEND(save, this, link); + } + while ((this = ISC_LIST_HEAD(save)) != NULL) { + ISC_LIST_UNLINK(save, this, link); + INSIST(rdlcount < new_len); + newlist[rdlcount] = *this; + ISC_LIST_APPEND(*current, &newlist[rdlcount], link); + rdlcount++; + } + + ISC_LIST_INIT(save); + while ((this = ISC_LIST_HEAD(*glue)) != NULL) { + ISC_LIST_UNLINK(*glue, this, link); + ISC_LIST_APPEND(save, this, link); + } + while ((this = ISC_LIST_HEAD(save)) != NULL) { + ISC_LIST_UNLINK(save, this, link); + INSIST(rdlcount < new_len); + newlist[rdlcount] = *this; + ISC_LIST_APPEND(*glue, &newlist[rdlcount], link); + rdlcount++; + } + + INSIST(rdlcount == old_len); + if (oldlist != NULL) { + isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist)); + } + return (newlist); +} + +/* + * Grow the slab of rdata structs. + * Re-link the current and glue chains. + */ +static dns_rdata_t * +grow_rdata(int new_len, dns_rdata_t *oldlist, int old_len, + rdatalist_head_t *current, rdatalist_head_t *glue, isc_mem_t *mctx) { + dns_rdata_t *newlist; + int rdcount = 0; + ISC_LIST(dns_rdata_t) save; + dns_rdatalist_t *this; + dns_rdata_t *rdata; + + newlist = isc_mem_get(mctx, new_len * sizeof(*newlist)); + if (newlist == NULL) { + return (NULL); + } + memset(newlist, 0, new_len * sizeof(*newlist)); + + /* + * Copy current relinking. + */ + this = ISC_LIST_HEAD(*current); + while (this != NULL) { + ISC_LIST_INIT(save); + while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) { + ISC_LIST_UNLINK(this->rdata, rdata, link); + ISC_LIST_APPEND(save, rdata, link); + } + while ((rdata = ISC_LIST_HEAD(save)) != NULL) { + ISC_LIST_UNLINK(save, rdata, link); + INSIST(rdcount < new_len); + newlist[rdcount] = *rdata; + ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link); + rdcount++; + } + this = ISC_LIST_NEXT(this, link); + } + + /* + * Copy glue relinking. + */ + this = ISC_LIST_HEAD(*glue); + while (this != NULL) { + ISC_LIST_INIT(save); + while ((rdata = ISC_LIST_HEAD(this->rdata)) != NULL) { + ISC_LIST_UNLINK(this->rdata, rdata, link); + ISC_LIST_APPEND(save, rdata, link); + } + while ((rdata = ISC_LIST_HEAD(save)) != NULL) { + ISC_LIST_UNLINK(save, rdata, link); + INSIST(rdcount < new_len); + newlist[rdcount] = *rdata; + ISC_LIST_APPEND(this->rdata, &newlist[rdcount], link); + rdcount++; + } + this = ISC_LIST_NEXT(this, link); + } + INSIST(rdcount == old_len || rdcount == 0); + if (oldlist != NULL) { + isc_mem_put(mctx, oldlist, old_len * sizeof(*oldlist)); + } + return (newlist); +} + +static uint32_t +resign_fromlist(dns_rdatalist_t *this, dns_loadctx_t *lctx) { + dns_rdata_t *rdata; + dns_rdata_rrsig_t sig; + uint32_t when; + + rdata = ISC_LIST_HEAD(this->rdata); + INSIST(rdata != NULL); + (void)dns_rdata_tostruct(rdata, &sig, NULL); + if (isc_serial_gt(sig.timesigned, lctx->now)) { + when = lctx->now; + } else { + when = sig.timeexpire - lctx->resign; + } + + rdata = ISC_LIST_NEXT(rdata, link); + while (rdata != NULL) { + (void)dns_rdata_tostruct(rdata, &sig, NULL); + if (isc_serial_gt(sig.timesigned, lctx->now)) { + when = lctx->now; + } else if (sig.timeexpire - lctx->resign < when) { + when = sig.timeexpire - lctx->resign; + } + rdata = ISC_LIST_NEXT(rdata, link); + } + return (when); +} + +/* + * Convert each element from a rdatalist_t to rdataset then call commit. + * Unlink each element as we go. + */ + +static isc_result_t +commit(dns_rdatacallbacks_t *callbacks, dns_loadctx_t *lctx, + rdatalist_head_t *head, dns_name_t *owner, const char *source, + unsigned int line) { + dns_rdatalist_t *this; + dns_rdataset_t dataset; + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + void (*error)(struct dns_rdatacallbacks *, const char *, ...); + + this = ISC_LIST_HEAD(*head); + error = callbacks->error; + + if (this == NULL) { + return (ISC_R_SUCCESS); + } + do { + dns_rdataset_init(&dataset); + RUNTIME_CHECK(dns_rdatalist_tordataset(this, &dataset) == + ISC_R_SUCCESS); + dataset.trust = dns_trust_ultimate; + /* + * If this is a secure dynamic zone set the re-signing time. + */ + if (dataset.type == dns_rdatatype_rrsig && + (lctx->options & DNS_MASTER_RESIGN) != 0) + { + dataset.attributes |= DNS_RDATASETATTR_RESIGN; + dataset.resign = resign_fromlist(this, lctx); + } + result = ((*callbacks->add)(callbacks->add_private, owner, + &dataset)); + if (result == ISC_R_NOMEMORY) { + (*error)(callbacks, "dns_master_load: %s", + isc_result_totext(result)); + } else if (result != ISC_R_SUCCESS) { + dns_name_format(owner, namebuf, sizeof(namebuf)); + if (source != NULL) { + (*error)(callbacks, "%s: %s:%lu: %s: %s", + "dns_master_load", source, line, + namebuf, isc_result_totext(result)); + } else { + (*error)(callbacks, "%s: %s: %s", + "dns_master_load", namebuf, + isc_result_totext(result)); + } + } + if (MANYERRS(lctx, result)) { + SETRESULT(lctx, result); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + ISC_LIST_UNLINK(*head, this, link); + this = ISC_LIST_HEAD(*head); + } while (this != NULL); + return (ISC_R_SUCCESS); +} + +/* + * Returns true if one of the NS rdata's contains 'owner'. + */ + +static bool +is_glue(rdatalist_head_t *head, dns_name_t *owner) { + dns_rdatalist_t *this; + dns_rdata_t *rdata; + isc_region_t region; + dns_name_t name; + + /* + * Find NS rrset. + */ + this = ISC_LIST_HEAD(*head); + while (this != NULL) { + if (this->type == dns_rdatatype_ns) { + break; + } + this = ISC_LIST_NEXT(this, link); + } + if (this == NULL) { + return (false); + } + + rdata = ISC_LIST_HEAD(this->rdata); + while (rdata != NULL) { + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + if (dns_name_equal(&name, owner)) { + return (true); + } + rdata = ISC_LIST_NEXT(rdata, link); + } + return (false); +} + +static void +load_quantum(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_loadctx_t *lctx; + + REQUIRE(event != NULL); + lctx = event->ev_arg; + REQUIRE(DNS_LCTX_VALID(lctx)); + + if (atomic_load_acquire(&lctx->canceled)) { + result = ISC_R_CANCELED; + } else { + result = (lctx->load)(lctx); + } + if (result == DNS_R_CONTINUE) { + event->ev_arg = lctx; + isc_task_send(task, &event); + } else { + (lctx->done)(lctx->done_arg, result); + isc_event_free(&event); + dns_loadctx_detach(&lctx); + } +} + +static isc_result_t +task_send(dns_loadctx_t *lctx) { + isc_event_t *event; + + event = isc_event_allocate(lctx->mctx, NULL, DNS_EVENT_MASTERQUANTUM, + load_quantum, lctx, sizeof(*event)); + isc_task_send(lctx->task, &event); + return (ISC_R_SUCCESS); +} + +void +dns_loadctx_cancel(dns_loadctx_t *lctx) { + REQUIRE(DNS_LCTX_VALID(lctx)); + + atomic_store_release(&lctx->canceled, true); +} + +void +dns_master_initrawheader(dns_masterrawheader_t *header) { + memset(header, 0, sizeof(dns_masterrawheader_t)); +} diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c new file mode 100644 index 0000000..4946d0d --- /dev/null +++ b/lib/dns/masterdump.c @@ -0,0 +1,2094 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DNS_DCTX_MAGIC ISC_MAGIC('D', 'c', 't', 'x') +#define DNS_DCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DCTX_MAGIC) + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +#define CHECK(x) \ + do { \ + if ((x) != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +struct dns_master_style { + dns_masterstyle_flags_t flags; /* DNS_STYLEFLAG_* */ + unsigned int ttl_column; + unsigned int class_column; + unsigned int type_column; + unsigned int rdata_column; + unsigned int line_length; + unsigned int tab_width; + unsigned int split_width; +}; + +/*% + * The maximum length of the newline+indentation that is output + * when inserting a line break in an RR. This effectively puts an + * upper limits on the value of "rdata_column", because if it is + * very large, the tabs and spaces needed to reach it will not fit. + */ +#define DNS_TOTEXT_LINEBREAK_MAXLEN 100 + +/*% Does the rdataset 'r' contain a stale answer? */ +#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0) +/*% Does the rdataset 'r' contain an expired answer? */ +#define ANCIENT(r) (((r)->attributes & DNS_RDATASETATTR_ANCIENT) != 0) + +/*% + * Context structure for a masterfile dump in progress. + */ +typedef struct dns_totext_ctx { + dns_master_style_t style; + bool class_printed; + char *linebreak; + char linebreak_buf[DNS_TOTEXT_LINEBREAK_MAXLEN]; + dns_name_t *origin; + dns_name_t *neworigin; + dns_fixedname_t origin_fixname; + uint32_t current_ttl; + bool current_ttl_valid; + dns_ttl_t serve_stale_ttl; + dns_indent_t indent; +} dns_totext_ctx_t; + +const dns_master_style_t dns_master_style_keyzone = { + DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS | + DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA | + DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL | + DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_KEYDATA, + 24, + 24, + 24, + 32, + 80, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_default = { + DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS | + DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA | + DNS_STYLEFLAG_OMIT_TTL | DNS_STYLEFLAG_TTL | + DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_MULTILINE, + 24, + 24, + 24, + 32, + 80, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_full = { + DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RESIGN, + 46, + 46, + 46, + 64, + 120, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_explicitttl = { + DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS | + DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_REL_DATA | + DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_MULTILINE, + 24, + 32, + 32, + 40, + 80, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_cache = { + DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS | + DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE, + 24, + 32, + 32, + 40, + 80, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_cache_with_expired = { + DNS_STYLEFLAG_OMIT_OWNER | DNS_STYLEFLAG_OMIT_CLASS | + DNS_STYLEFLAG_MULTILINE | DNS_STYLEFLAG_RRCOMMENT | + DNS_STYLEFLAG_TRUST | DNS_STYLEFLAG_NCACHE | + DNS_STYLEFLAG_EXPIRED, + 24, + 32, + 32, + 40, + 80, + 8, + UINT_MAX +}; + +const dns_master_style_t dns_master_style_simple = { 0, 24, 32, 32, + 40, 80, 8, UINT_MAX }; + +/*% + * A style suitable for dns_rdataset_totext(). + */ +const dns_master_style_t dns_master_style_debug = { + DNS_STYLEFLAG_REL_OWNER, 24, 32, 40, 48, 80, 8, UINT_MAX +}; + +/*% + * Similar, but indented (i.e., prepended with indentctx.string). + */ +const dns_master_style_t dns_master_style_indent = { + DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT, + 24, + 32, + 40, + 48, + 80, + 8, + UINT_MAX +}; + +/*% + * Similar, but with each line commented out. + */ +const dns_master_style_t dns_master_style_comment = { + DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_MULTILINE | + DNS_STYLEFLAG_RRCOMMENT | DNS_STYLEFLAG_COMMENTDATA, + 24, + 32, + 40, + 48, + 80, + 8, + UINT_MAX +}; + +/*% + * YAML style + */ +const dns_master_style_t dns_master_style_yaml = { + DNS_STYLEFLAG_YAML | DNS_STYLEFLAG_REL_OWNER | DNS_STYLEFLAG_INDENT, + 24, + 32, + 40, + 48, + 80, + 8, + UINT_MAX +}; + +#define N_SPACES 10 +static char spaces[N_SPACES + 1] = " "; + +#define N_TABS 10 +static char tabs[N_TABS + 1] = "\t\t\t\t\t\t\t\t\t\t"; + +struct dns_dumpctx { + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_refcount_t references; + atomic_bool canceled; + bool do_date; + isc_stdtime_t now; + FILE *f; + dns_db_t *db; + dns_dbversion_t *version; + dns_dbiterator_t *dbiter; + dns_totext_ctx_t tctx; + isc_task_t *task; + dns_dumpdonefunc_t done; + void *done_arg; + /* dns_master_dumpasync() */ + isc_result_t result; + char *file; + char *tmpfile; + dns_masterformat_t format; + dns_masterrawheader_t header; + isc_result_t (*dumpsets)(isc_mem_t *mctx, const dns_name_t *name, + dns_rdatasetiter_t *rdsiter, + dns_totext_ctx_t *ctx, isc_buffer_t *buffer, + FILE *f); +}; + +#define NXDOMAIN(x) (((x)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) + +static const dns_indent_t default_indent = { "\t", 1 }; +static const dns_indent_t default_yamlindent = { " ", 1 }; + +/*% + * Output tabs and spaces to go from column '*current' to + * column 'to', and update '*current' to reflect the new + * current column. + */ +static isc_result_t +indent(unsigned int *current, unsigned int to, int tabwidth, + isc_buffer_t *target) { + isc_region_t r; + unsigned char *p; + unsigned int from; + int ntabs, nspaces, t; + + from = *current; + + if (to < from + 1) { + to = from + 1; + } + + ntabs = to / tabwidth - from / tabwidth; + if (ntabs < 0) { + ntabs = 0; + } + + if (ntabs > 0) { + isc_buffer_availableregion(target, &r); + if (r.length < (unsigned)ntabs) { + return (ISC_R_NOSPACE); + } + p = r.base; + + t = ntabs; + while (t) { + int n = t; + if (n > N_TABS) { + n = N_TABS; + } + memmove(p, tabs, n); + p += n; + t -= n; + } + isc_buffer_add(target, ntabs); + from = (to / tabwidth) * tabwidth; + } + + nspaces = to - from; + INSIST(nspaces >= 0); + + isc_buffer_availableregion(target, &r); + if (r.length < (unsigned)nspaces) { + return (ISC_R_NOSPACE); + } + p = r.base; + + t = nspaces; + while (t) { + int n = t; + if (n > N_SPACES) { + n = N_SPACES; + } + memmove(p, spaces, n); + p += n; + t -= n; + } + isc_buffer_add(target, nspaces); + + *current = to; + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_ctx_init(const dns_master_style_t *style, const dns_indent_t *indentctx, + dns_totext_ctx_t *ctx) { + isc_result_t result; + + REQUIRE(style->tab_width != 0); + + if (indentctx == NULL) { + if ((style->flags & DNS_STYLEFLAG_YAML) != 0) { + indentctx = &default_yamlindent; + } else { + indentctx = &default_indent; + } + } + + ctx->style = *style; + ctx->class_printed = false; + + dns_fixedname_init(&ctx->origin_fixname); + + /* + * Set up the line break string if needed. + */ + if ((ctx->style.flags & DNS_STYLEFLAG_MULTILINE) != 0) { + isc_buffer_t buf; + isc_region_t r; + unsigned int col = 0; + + isc_buffer_init(&buf, ctx->linebreak_buf, + sizeof(ctx->linebreak_buf)); + + isc_buffer_availableregion(&buf, &r); + if (r.length < 1) { + return (DNS_R_TEXTTOOLONG); + } + r.base[0] = '\n'; + isc_buffer_add(&buf, 1); + + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + unsigned int i, len = strlen(indentctx->string); + for (i = 0; i < indentctx->count; i++) { + if (isc_buffer_availablelength(&buf) < len) { + return (DNS_R_TEXTTOOLONG); + } + isc_buffer_putstr(&buf, indentctx->string); + } + } + + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) { + isc_buffer_availableregion(&buf, &r); + if (r.length < 1) { + return (DNS_R_TEXTTOOLONG); + } + r.base[0] = ';'; + isc_buffer_add(&buf, 1); + } + + result = indent(&col, ctx->style.rdata_column, + ctx->style.tab_width, &buf); + /* + * Do not return ISC_R_NOSPACE if the line break string + * buffer is too small, because that would just make + * dump_rdataset() retry indefinitely with ever + * bigger target buffers. That's a different buffer, + * so it won't help. Use DNS_R_TEXTTOOLONG as a substitute. + */ + if (result == ISC_R_NOSPACE) { + return (DNS_R_TEXTTOOLONG); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_availableregion(&buf, &r); + if (r.length < 1) { + return (DNS_R_TEXTTOOLONG); + } + r.base[0] = '\0'; + isc_buffer_add(&buf, 1); + ctx->linebreak = ctx->linebreak_buf; + } else { + ctx->linebreak = NULL; + } + + ctx->origin = NULL; + ctx->neworigin = NULL; + ctx->current_ttl = 0; + ctx->current_ttl_valid = false; + ctx->serve_stale_ttl = 0; + ctx->indent = *indentctx; + + return (ISC_R_SUCCESS); +} + +#define INDENT_TO(col) \ + do { \ + if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { \ + if ((result = str_totext(" ", target)) != \ + ISC_R_SUCCESS) \ + return ((result)); \ + } else if ((result = indent(&column, ctx->style.col, \ + ctx->style.tab_width, target)) != \ + ISC_R_SUCCESS) \ + return ((result)); \ + } while (0) + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +ncache_summary(dns_rdataset_t *rdataset, bool omit_final_dot, + dns_totext_ctx_t *ctx, isc_buffer_t *target) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdataset_t rds; + dns_name_t name; + + dns_rdataset_init(&rds); + dns_name_init(&name, NULL); + + do { + dns_ncache_current(rdataset, &name, &rds); + for (result = dns_rdataset_first(&rds); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rds)) + { + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + unsigned int i; + for (i = 0; i < ctx->indent.count; i++) { + CHECK(str_totext(ctx->indent.string, + target)); + } + } + + if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { + CHECK(str_totext("- ", target)); + } else { + CHECK(str_totext("; ", target)); + } + + CHECK(dns_name_totext(&name, omit_final_dot, target)); + CHECK(str_totext(" ", target)); + CHECK(dns_rdatatype_totext(rds.type, target)); + if (rds.type == dns_rdatatype_rrsig) { + CHECK(str_totext(" ", target)); + CHECK(dns_rdatatype_totext(rds.covers, target)); + CHECK(str_totext(" ...\n", target)); + } else { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rds, &rdata); + CHECK(str_totext(" ", target)); + CHECK(dns_rdata_tofmttext(&rdata, dns_rootname, + 0, 0, 0, " ", + target)); + CHECK(str_totext("\n", target)); + } + } + dns_rdataset_disassociate(&rds); + result = dns_rdataset_next(rdataset); + } while (result == ISC_R_SUCCESS); + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +cleanup: + if (dns_rdataset_isassociated(&rds)) { + dns_rdataset_disassociate(&rds); + } + + return (result); +} + +/* + * Convert 'rdataset' to master file text format according to 'ctx', + * storing the result in 'target'. If 'owner_name' is NULL, it + * is omitted; otherwise 'owner_name' must be valid and have at least + * one label. + */ + +static isc_result_t +rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_totext_ctx_t *ctx, bool omit_final_dot, + isc_buffer_t *target) { + isc_result_t result; + unsigned int column; + bool first = true; + uint32_t current_ttl; + bool current_ttl_valid; + dns_rdatatype_t type; + unsigned int type_start; + dns_fixedname_t fixed; + dns_name_t *name = NULL; + unsigned int i; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + rdataset->attributes |= DNS_RDATASETATTR_LOADORDER; + result = dns_rdataset_first(rdataset); + + current_ttl = ctx->current_ttl; + current_ttl_valid = ctx->current_ttl_valid; + + if (owner_name != NULL) { + name = dns_fixedname_initname(&fixed); + dns_name_copy(owner_name, name); + dns_rdataset_getownercase(rdataset, name); + } + + while (result == ISC_R_SUCCESS) { + column = 0; + + /* + * Indent? + */ + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + for (i = 0; i < ctx->indent.count; i++) { + RETERR(str_totext(ctx->indent.string, target)); + } + } + + /* + * YAML or comment prefix? + */ + if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { + RETERR(str_totext("- ", target)); + } else if ((ctx->style.flags & DNS_STYLEFLAG_COMMENTDATA) != 0) + { + RETERR(str_totext(";", target)); + } + + /* + * Owner name. + */ + if (name != NULL && + !((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0 && + !first)) + { + unsigned int name_start = target->used; + RETERR(dns_name_totext(name, omit_final_dot, target)); + column += target->used - name_start; + } + + /* + * TTL. + */ + if ((ctx->style.flags & DNS_STYLEFLAG_NO_TTL) == 0 && + !((ctx->style.flags & DNS_STYLEFLAG_OMIT_TTL) != 0 && + current_ttl_valid && rdataset->ttl == current_ttl)) + { + char ttlbuf[64]; + isc_region_t r; + unsigned int length; + + INDENT_TO(ttl_column); + if ((ctx->style.flags & DNS_STYLEFLAG_TTL_UNITS) != 0) { + length = target->used; + result = dns_ttl_totext(rdataset->ttl, false, + false, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + column += target->used - length; + } else { + length = snprintf(ttlbuf, sizeof(ttlbuf), "%u", + rdataset->ttl); + INSIST(length <= sizeof(ttlbuf)); + isc_buffer_availableregion(target, &r); + if (r.length < length) { + return (ISC_R_NOSPACE); + } + memmove(r.base, ttlbuf, length); + isc_buffer_add(target, length); + column += length; + } + + /* + * If the $TTL directive is not in use, the TTL we + * just printed becomes the default for subsequent RRs. + */ + if ((ctx->style.flags & DNS_STYLEFLAG_TTL) == 0) { + current_ttl = rdataset->ttl; + current_ttl_valid = true; + } + } + + /* + * Class. + */ + if ((ctx->style.flags & DNS_STYLEFLAG_NO_CLASS) == 0 && + ((ctx->style.flags & DNS_STYLEFLAG_OMIT_CLASS) == 0 || + !ctx->class_printed)) + { + unsigned int class_start; + INDENT_TO(class_column); + class_start = target->used; + if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != + 0) + { + result = dns_rdataclass_tounknowntext( + rdataset->rdclass, target); + } else { + result = dns_rdataclass_totext( + rdataset->rdclass, target); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + column += (target->used - class_start); + } + + /* + * Type. + */ + + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + type = rdataset->covers; + } else { + type = rdataset->type; + } + + INDENT_TO(type_column); + type_start = target->used; + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + RETERR(str_totext("\\-", target)); + } + switch (type) { + case dns_rdatatype_keydata: +#define KEYDATA "KEYDATA" + if ((ctx->style.flags & DNS_STYLEFLAG_KEYDATA) != 0) { + if (isc_buffer_availablelength(target) < + (sizeof(KEYDATA) - 1)) + { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(target, KEYDATA); + break; + } + FALLTHROUGH; + default: + if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != + 0) + { + result = dns_rdatatype_tounknowntext(type, + target); + } else { + result = dns_rdatatype_totext(type, target); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + } + column += (target->used - type_start); + + /* + * Rdata. + */ + INDENT_TO(rdata_column); + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + if (NXDOMAIN(rdataset)) { + RETERR(str_totext(";-$NXDOMAIN\n", target)); + } else { + RETERR(str_totext(";-$NXRRSET\n", target)); + } + /* + * Print a summary of the cached records which make + * up the negative response. + */ + RETERR(ncache_summary(rdataset, omit_final_dot, ctx, + target)); + break; + } else { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r; + + dns_rdataset_current(rdataset, &rdata); + + RETERR(dns_rdata_tofmttext( + &rdata, ctx->origin, ctx->style.flags, + ctx->style.line_length - + ctx->style.rdata_column, + ctx->style.split_width, ctx->linebreak, + target)); + + isc_buffer_availableregion(target, &r); + if (r.length < 1) { + return (ISC_R_NOSPACE); + } + r.base[0] = '\n'; + isc_buffer_add(target, 1); + } + + first = false; + result = dns_rdataset_next(rdataset); + } + + if (result != ISC_R_NOMORE) { + return (result); + } + + /* + * Update the ctx state to reflect what we just printed. + * This is done last, only when we are sure we will return + * success, because this function may be called multiple + * times with increasing buffer sizes until it succeeds, + * and failed attempts must not update the state prematurely. + */ + ctx->class_printed = true; + ctx->current_ttl = current_ttl; + ctx->current_ttl_valid = current_ttl_valid; + + return (ISC_R_SUCCESS); +} + +/* + * Print the name, type, and class of an empty rdataset, + * such as those used to represent the question section + * of a DNS message. + */ +static isc_result_t +question_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_totext_ctx_t *ctx, bool omit_final_dot, + isc_buffer_t *target) { + unsigned int column; + isc_result_t result; + isc_region_t r; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + result = dns_rdataset_first(rdataset); + REQUIRE(result == ISC_R_NOMORE); + + column = 0; + + /* Owner name */ + { + unsigned int name_start = target->used; + RETERR(dns_name_totext(owner_name, omit_final_dot, target)); + column += target->used - name_start; + } + + /* Class */ + { + unsigned int class_start; + INDENT_TO(class_column); + class_start = target->used; + if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) { + result = dns_rdataclass_tounknowntext(rdataset->rdclass, + target); + } else { + result = dns_rdataclass_totext(rdataset->rdclass, + target); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + column += (target->used - class_start); + } + + /* Type */ + { + unsigned int type_start; + INDENT_TO(type_column); + type_start = target->used; + if ((ctx->style.flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) { + result = dns_rdatatype_tounknowntext(rdataset->type, + target); + } else { + result = dns_rdatatype_totext(rdataset->type, target); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + column += (target->used - type_start); + } + + isc_buffer_availableregion(target, &r); + if (r.length < 1) { + return (ISC_R_NOSPACE); + } + r.base[0] = '\n'; + isc_buffer_add(target, 1); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdataset_totext(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + bool omit_final_dot, bool question, isc_buffer_t *target) { + dns_totext_ctx_t ctx; + isc_result_t result; + result = totext_ctx_init(&dns_master_style_debug, NULL, &ctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("could not set master file style"); + return (ISC_R_UNEXPECTED); + } + + /* + * The caller might want to give us an empty owner + * name (e.g. if they are outputting into a master + * file and this rdataset has the same name as the + * previous one.) + */ + if (dns_name_countlabels(owner_name) == 0) { + owner_name = NULL; + } + + if (question) { + return (question_totext(rdataset, owner_name, &ctx, + omit_final_dot, target)); + } else { + return (rdataset_totext(rdataset, owner_name, &ctx, + omit_final_dot, target)); + } +} + +isc_result_t +dns_master_rdatasettotext(const dns_name_t *owner_name, + dns_rdataset_t *rdataset, + const dns_master_style_t *style, dns_indent_t *indent, + isc_buffer_t *target) { + dns_totext_ctx_t ctx; + isc_result_t result; + result = totext_ctx_init(style, indent, &ctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("could not set master file style"); + return (ISC_R_UNEXPECTED); + } + + return (rdataset_totext(rdataset, owner_name, &ctx, false, target)); +} + +isc_result_t +dns_master_questiontotext(const dns_name_t *owner_name, + dns_rdataset_t *rdataset, + const dns_master_style_t *style, + isc_buffer_t *target) { + dns_totext_ctx_t ctx; + isc_result_t result; + result = totext_ctx_init(style, NULL, &ctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("could not set master file style"); + return (ISC_R_UNEXPECTED); + } + + return (question_totext(rdataset, owner_name, &ctx, false, target)); +} + +/* + * Print an rdataset. 'buffer' is a scratch buffer, which must have been + * dynamically allocated by the caller. It must be large enough to + * hold the result from dns_ttl_totext(). If more than that is needed, + * the buffer will be grown automatically. + */ + +static isc_result_t +dump_rdataset(isc_mem_t *mctx, const dns_name_t *name, dns_rdataset_t *rdataset, + dns_totext_ctx_t *ctx, isc_buffer_t *buffer, FILE *f) { + isc_region_t r; + isc_result_t result; + + REQUIRE(buffer->length > 0); + + /* + * Output a $TTL directive if needed. + */ + + if ((ctx->style.flags & DNS_STYLEFLAG_TTL) != 0) { + if (!ctx->current_ttl_valid || + ctx->current_ttl != rdataset->ttl) + { + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0) { + isc_buffer_clear(buffer); + result = dns_ttl_totext(rdataset->ttl, true, + true, buffer); + INSIST(result == ISC_R_SUCCESS); + isc_buffer_usedregion(buffer, &r); + fprintf(f, "$TTL %u\t; %.*s\n", rdataset->ttl, + (int)r.length, (char *)r.base); + } else { + fprintf(f, "$TTL %u\n", rdataset->ttl); + } + ctx->current_ttl = rdataset->ttl; + ctx->current_ttl_valid = true; + } + } + + isc_buffer_clear(buffer); + + /* + * Generate the text representation of the rdataset into + * the buffer. If the buffer is too small, grow it. + */ + for (;;) { + int newlength; + void *newmem; + result = rdataset_totext(rdataset, name, ctx, false, buffer); + if (result != ISC_R_NOSPACE) { + break; + } + + newlength = buffer->length * 2; + newmem = isc_mem_get(mctx, newlength); + isc_mem_put(mctx, buffer->base, buffer->length); + isc_buffer_init(buffer, newmem, newlength); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Write the buffer contents to the master file. + */ + isc_buffer_usedregion(buffer, &r); + result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL); + + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("master file write failed: %s", + isc_result_totext(result)); + return (result); + } + + return (ISC_R_SUCCESS); +} + +/* + * Define the order in which rdatasets should be printed in zone + * files. We will print SOA and NS records before others, SIGs + * immediately following the things they sign, and order everything + * else by RR number. This is all just for aesthetics and + * compatibility with buggy software that expects the SOA to be first; + * the DNS specifications allow any order. + */ + +static int +dump_order(const dns_rdataset_t *rds) { + int t; + int sig; + if (rds->type == dns_rdatatype_rrsig) { + t = rds->covers; + sig = 1; + } else { + t = rds->type; + sig = 0; + } + switch (t) { + case dns_rdatatype_soa: + t = 0; + break; + case dns_rdatatype_ns: + t = 1; + break; + default: + t += 2; + break; + } + return ((t << 1) + sig); +} + +static int +dump_order_compare(const void *a, const void *b) { + return (dump_order(*((const dns_rdataset_t *const *)a)) - + dump_order(*((const dns_rdataset_t *const *)b))); +} + +/* + * Dump all the rdatasets of a domain name to a master file. We make + * a "best effort" attempt to sort the RRsets in a nice order, but if + * there are more than MAXSORT RRsets, we punt and only sort them in + * groups of MAXSORT. This is not expected to ever happen in practice + * since much less than 64 RR types have been registered with the + * IANA, so far, and the output will be correct (though not + * aesthetically pleasing) even if it does happen. + */ + +#define MAXSORT 64 + +static isc_result_t +dump_rdatasets_text(isc_mem_t *mctx, const dns_name_t *name, + dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx, + isc_buffer_t *buffer, FILE *f) { + isc_result_t itresult, dumpresult; + isc_region_t r; + dns_rdataset_t rdatasets[MAXSORT]; + dns_rdataset_t *sorted[MAXSORT]; + int i, n; + + itresult = dns_rdatasetiter_first(rdsiter); + dumpresult = ISC_R_SUCCESS; + + if (itresult == ISC_R_SUCCESS && ctx->neworigin != NULL) { + isc_buffer_clear(buffer); + itresult = dns_name_totext(ctx->neworigin, false, buffer); + RUNTIME_CHECK(itresult == ISC_R_SUCCESS); + isc_buffer_usedregion(buffer, &r); + fprintf(f, "$ORIGIN %.*s\n", (int)r.length, (char *)r.base); + ctx->neworigin = NULL; + } + +again: + for (i = 0; itresult == ISC_R_SUCCESS && i < MAXSORT; + itresult = dns_rdatasetiter_next(rdsiter), i++) + { + dns_rdataset_init(&rdatasets[i]); + dns_rdatasetiter_current(rdsiter, &rdatasets[i]); + sorted[i] = &rdatasets[i]; + } + n = i; + INSIST(n <= MAXSORT); + + qsort(sorted, n, sizeof(sorted[0]), dump_order_compare); + + for (i = 0; i < n; i++) { + dns_rdataset_t *rds = sorted[i]; + + if (ANCIENT(rds) && + (ctx->style.flags & DNS_STYLEFLAG_EXPIRED) == 0) + { + /* Omit expired entries */ + dns_rdataset_disassociate(rds); + continue; + } + + if ((ctx->style.flags & DNS_STYLEFLAG_TRUST) != 0) { + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + unsigned int j; + for (j = 0; j < ctx->indent.count; j++) { + fprintf(f, "%s", ctx->indent.string); + } + } + fprintf(f, "; %s\n", dns_trust_totext(rds->trust)); + } + if (((rds->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) && + (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0) + { + /* Omit negative cache entries */ + } else { + isc_result_t result; + if (STALE(rds)) { + fprintf(f, "; stale\n"); + } else if (ANCIENT(rds)) { + isc_buffer_t b; + char buf[sizeof("YYYYMMDDHHMMSS")]; + memset(buf, 0, sizeof(buf)); + isc_buffer_init(&b, buf, sizeof(buf) - 1); + dns_time64_totext((uint64_t)rds->ttl, &b); + fprintf(f, + "; expired since %s " + "(awaiting cleanup)\n", + buf); + } + result = dump_rdataset(mctx, name, rds, ctx, buffer, f); + if (result != ISC_R_SUCCESS) { + dumpresult = result; + } + if ((ctx->style.flags & DNS_STYLEFLAG_OMIT_OWNER) != 0) + { + name = NULL; + } + } + if (((ctx->style.flags & DNS_STYLEFLAG_RESIGN) != 0) && + ((rds->attributes & DNS_RDATASETATTR_RESIGN) != 0)) + { + isc_buffer_t b; + char buf[sizeof("YYYYMMDDHHMMSS")]; + memset(buf, 0, sizeof(buf)); + isc_buffer_init(&b, buf, sizeof(buf) - 1); + dns_time64_totext((uint64_t)rds->resign, &b); + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + unsigned int j; + for (j = 0; j < ctx->indent.count; j++) { + fprintf(f, "%s", ctx->indent.string); + } + } + fprintf(f, "; resign=%s\n", buf); + } + dns_rdataset_disassociate(rds); + } + + if (dumpresult != ISC_R_SUCCESS) { + return (dumpresult); + } + + /* + * If we got more data than could be sorted at once, + * go handle the rest. + */ + if (itresult == ISC_R_SUCCESS) { + goto again; + } + + if (itresult == ISC_R_NOMORE) { + itresult = ISC_R_SUCCESS; + } + + return (itresult); +} + +/* + * Dump given RRsets in the "raw" format. + */ +static isc_result_t +dump_rdataset_raw(isc_mem_t *mctx, const dns_name_t *name, + dns_rdataset_t *rdataset, isc_buffer_t *buffer, FILE *f) { + isc_result_t result; + uint32_t totallen; + uint16_t dlen; + isc_region_t r, r_hdr; + + REQUIRE(buffer->length > 0); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + rdataset->attributes |= DNS_RDATASETATTR_LOADORDER; +restart: + totallen = 0; + result = dns_rdataset_first(rdataset); + REQUIRE(result == ISC_R_SUCCESS); + + isc_buffer_clear(buffer); + + /* + * Common header and owner name (length followed by name) + * These fields should be in a moderate length, so we assume we + * can store all of them in the initial buffer. + */ + isc_buffer_availableregion(buffer, &r_hdr); + INSIST(r_hdr.length >= sizeof(dns_masterrawrdataset_t)); + isc_buffer_putuint32(buffer, totallen); /* XXX: leave space */ + isc_buffer_putuint16(buffer, rdataset->rdclass); /* 16-bit class */ + isc_buffer_putuint16(buffer, rdataset->type); /* 16-bit type */ + isc_buffer_putuint16(buffer, rdataset->covers); /* same as type */ + isc_buffer_putuint32(buffer, rdataset->ttl); /* 32-bit TTL */ + isc_buffer_putuint32(buffer, dns_rdataset_count(rdataset)); + totallen = isc_buffer_usedlength(buffer); + INSIST(totallen <= sizeof(dns_masterrawrdataset_t)); + + dns_name_toregion(name, &r); + INSIST(isc_buffer_availablelength(buffer) >= (sizeof(dlen) + r.length)); + dlen = (uint16_t)r.length; + isc_buffer_putuint16(buffer, dlen); + isc_buffer_copyregion(buffer, &r); + totallen += sizeof(dlen) + r.length; + + do { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(rdataset, &rdata); + dns_rdata_toregion(&rdata, &r); + INSIST(r.length <= 0xffffU); + dlen = (uint16_t)r.length; + + /* + * Copy the rdata into the buffer. If the buffer is too small, + * grow it. This should be rare, so we'll simply restart the + * entire procedure (or should we copy the old data and + * continue?). + */ + if (isc_buffer_availablelength(buffer) < + sizeof(dlen) + r.length) + { + int newlength; + void *newmem; + + newlength = buffer->length * 2; + newmem = isc_mem_get(mctx, newlength); + isc_mem_put(mctx, buffer->base, buffer->length); + isc_buffer_init(buffer, newmem, newlength); + goto restart; + } + isc_buffer_putuint16(buffer, dlen); + isc_buffer_copyregion(buffer, &r); + totallen += sizeof(dlen) + r.length; + + result = dns_rdataset_next(rdataset); + } while (result == ISC_R_SUCCESS); + + if (result != ISC_R_NOMORE) { + return (result); + } + + /* + * Fill in the total length field. + * XXX: this is a bit tricky. Since we have already "used" the space + * for the total length in the buffer, we first remember the entire + * buffer length in the region, "rewind", and then write the value. + */ + isc_buffer_usedregion(buffer, &r); + isc_buffer_clear(buffer); + isc_buffer_putuint32(buffer, totallen); + INSIST(isc_buffer_usedlength(buffer) < totallen); + + /* + * Write the buffer contents to the raw master file. + */ + result = isc_stdio_write(r.base, 1, (size_t)r.length, f, NULL); + + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("raw master file write failed: %s", + isc_result_totext(result)); + return (result); + } + + return (result); +} + +static isc_result_t +dump_rdatasets_raw(isc_mem_t *mctx, const dns_name_t *owner_name, + dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx, + isc_buffer_t *buffer, FILE *f) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + dns_name_copy(owner_name, name); + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + dns_rdataset_getownercase(&rdataset, name); + + if (((rdataset.attributes & DNS_RDATASETATTR_NEGATIVE) != 0) && + (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0) + { + /* Omit negative cache entries */ + } else { + result = dump_rdataset_raw(mctx, name, &rdataset, + buffer, f); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +/* + * Initial size of text conversion buffer. The buffer is used + * for several purposes: converting origin names, rdatasets, + * $DATE timestamps, and comment strings for $TTL directives. + * + * When converting rdatasets, it is dynamically resized, but + * when converting origins, timestamps, etc it is not. Therefore, + * the initial size must large enough to hold the longest possible + * text representation of any domain name (for $ORIGIN). + */ +static const int initial_buffer_length = 1200; + +static isc_result_t +dumptostream(dns_dumpctx_t *dctx); + +static void +dumpctx_destroy(dns_dumpctx_t *dctx) { + dctx->magic = 0; + isc_mutex_destroy(&dctx->lock); + dns_dbiterator_destroy(&dctx->dbiter); + if (dctx->version != NULL) { + dns_db_closeversion(dctx->db, &dctx->version, false); + } + dns_db_detach(&dctx->db); + if (dctx->task != NULL) { + isc_task_detach(&dctx->task); + } + if (dctx->file != NULL) { + isc_mem_free(dctx->mctx, dctx->file); + } + if (dctx->tmpfile != NULL) { + isc_mem_free(dctx->mctx, dctx->tmpfile); + } + isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx)); +} + +void +dns_dumpctx_attach(dns_dumpctx_t *source, dns_dumpctx_t **target) { + REQUIRE(DNS_DCTX_VALID(source)); + REQUIRE(target != NULL && *target == NULL); + + isc_refcount_increment(&source->references); + + *target = source; +} + +void +dns_dumpctx_detach(dns_dumpctx_t **dctxp) { + dns_dumpctx_t *dctx; + + REQUIRE(dctxp != NULL); + dctx = *dctxp; + *dctxp = NULL; + REQUIRE(DNS_DCTX_VALID(dctx)); + + if (isc_refcount_decrement(&dctx->references) == 1) { + dumpctx_destroy(dctx); + } +} + +dns_dbversion_t * +dns_dumpctx_version(dns_dumpctx_t *dctx) { + REQUIRE(DNS_DCTX_VALID(dctx)); + return (dctx->version); +} + +dns_db_t * +dns_dumpctx_db(dns_dumpctx_t *dctx) { + REQUIRE(DNS_DCTX_VALID(dctx)); + return (dctx->db); +} + +void +dns_dumpctx_cancel(dns_dumpctx_t *dctx) { + REQUIRE(DNS_DCTX_VALID(dctx)); + + atomic_store_release(&dctx->canceled, true); +} + +static isc_result_t +flushandsync(FILE *f, isc_result_t result, const char *temp) { + bool logit = (result == ISC_R_SUCCESS); + + if (result == ISC_R_SUCCESS) { + result = isc_stdio_flush(f); + } + if (result != ISC_R_SUCCESS && logit) { + if (temp != NULL) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping to master file: %s: flush: %s", + temp, isc_result_totext(result)); + } else { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping to stream: flush: %s", + isc_result_totext(result)); + } + logit = false; + } + + if (result == ISC_R_SUCCESS) { + result = isc_stdio_sync(f); + } + if (result != ISC_R_SUCCESS && logit) { + if (temp != NULL) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping to master file: %s: fsync: %s", + temp, isc_result_totext(result)); + } else { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping to stream: fsync: %s", + isc_result_totext(result)); + } + } + return (result); +} + +static isc_result_t +closeandrename(FILE *f, isc_result_t result, const char *temp, + const char *file) { + isc_result_t tresult; + bool logit = (result == ISC_R_SUCCESS); + + result = flushandsync(f, result, temp); + if (result != ISC_R_SUCCESS) { + logit = false; + } + + tresult = isc_stdio_close(f); + if (result == ISC_R_SUCCESS) { + result = tresult; + } + if (result != ISC_R_SUCCESS && logit) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping master file: %s: fclose: %s", temp, + isc_result_totext(result)); + logit = false; + } + if (result == ISC_R_SUCCESS) { + result = isc_file_rename(temp, file); + } else { + (void)isc_file_remove(temp); + } + if (result != ISC_R_SUCCESS && logit) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping master file: rename: %s: %s", file, + isc_result_totext(result)); + } + return (result); +} + +/* + * This will run in a libuv threadpool thread. + */ +static void +master_dump_cb(void *data) { + isc_result_t result = ISC_R_UNSET; + dns_dumpctx_t *dctx = data; + REQUIRE(DNS_DCTX_VALID(dctx)); + + if (atomic_load_acquire(&dctx->canceled)) { + result = ISC_R_CANCELED; + } else { + result = dumptostream(dctx); + } + + if (dctx->file != NULL) { + isc_result_t tresult = ISC_R_UNSET; + tresult = closeandrename(dctx->f, result, dctx->tmpfile, + dctx->file); + if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) { + result = tresult; + } + } else { + result = flushandsync(dctx->f, result, NULL); + } + + dctx->result = result; +} + +/* + * This will run in a network/task manager thread when the dump is complete. + */ +static void +master_dump_done_cb(void *data, isc_result_t result) { + dns_dumpctx_t *dctx = data; + + if (result == ISC_R_SUCCESS && dctx->result != ISC_R_SUCCESS) { + result = dctx->result; + } + + (dctx->done)(dctx->done_arg, result); + dns_dumpctx_detach(&dctx); +} + +/* + * This must be run from a network/task manager thread. + */ +static void +setup_dump(isc_task_t *task, isc_event_t *event) { + dns_dumpctx_t *dctx = NULL; + + REQUIRE(isc_nm_tid() >= 0); + REQUIRE(event != NULL); + + dctx = event->ev_arg; + + REQUIRE(DNS_DCTX_VALID(dctx)); + + isc_nm_work_offload(isc_task_getnetmgr(task), master_dump_cb, + master_dump_done_cb, dctx); + + isc_event_free(&event); +} + +static isc_result_t +task_send(dns_dumpctx_t *dctx) { + isc_event_t *event; + + event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_DUMPQUANTUM, + setup_dump, dctx, sizeof(*event)); + isc_task_send(dctx->task, &event); + return (ISC_R_SUCCESS); +} + +static isc_result_t +dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, FILE *f, dns_dumpctx_t **dctxp, + dns_masterformat_t format, dns_masterrawheader_t *header) { + dns_dumpctx_t *dctx; + isc_result_t result; + unsigned int options; + + dctx = isc_mem_get(mctx, sizeof(*dctx)); + + dctx->mctx = NULL; + dctx->f = f; + dctx->dbiter = NULL; + dctx->db = NULL; + dctx->version = NULL; + dctx->done = NULL; + dctx->done_arg = NULL; + dctx->task = NULL; + atomic_init(&dctx->canceled, false); + dctx->file = NULL; + dctx->tmpfile = NULL; + dctx->format = format; + if (header == NULL) { + dns_master_initrawheader(&dctx->header); + } else { + dctx->header = *header; + } + + switch (format) { + case dns_masterformat_text: + dctx->dumpsets = dump_rdatasets_text; + break; + case dns_masterformat_raw: + dctx->dumpsets = dump_rdatasets_raw; + break; + default: + UNREACHABLE(); + } + + result = totext_ctx_init(style, NULL, &dctx->tctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("could not set master file style"); + goto cleanup; + } + + isc_stdtime_get(&dctx->now); + dns_db_attach(db, &dctx->db); + + dctx->do_date = dns_db_iscache(dctx->db); + if (dctx->do_date) { + (void)dns_db_getservestalettl(dctx->db, + &dctx->tctx.serve_stale_ttl); + } + + if (dctx->format == dns_masterformat_text && + (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0) + { + options = DNS_DB_RELATIVENAMES; + } else { + options = 0; + } + result = dns_db_createiterator(dctx->db, options, &dctx->dbiter); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_mutex_init(&dctx->lock); + + if (version != NULL) { + dns_db_attachversion(dctx->db, version, &dctx->version); + } else if (!dns_db_iscache(db)) { + dns_db_currentversion(dctx->db, &dctx->version); + } + isc_mem_attach(mctx, &dctx->mctx); + + isc_refcount_init(&dctx->references, 1); + dctx->magic = DNS_DCTX_MAGIC; + *dctxp = dctx; + return (ISC_R_SUCCESS); + +cleanup: + if (dctx->dbiter != NULL) { + dns_dbiterator_destroy(&dctx->dbiter); + } + if (dctx->db != NULL) { + dns_db_detach(&dctx->db); + } + isc_mem_put(mctx, dctx, sizeof(*dctx)); + return (result); +} + +static isc_result_t +writeheader(dns_dumpctx_t *dctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_buffer_t buffer; + char *bufmem; + isc_region_t r; + dns_masterrawheader_t rawheader; + uint32_t rawversion, now32; + + bufmem = isc_mem_get(dctx->mctx, initial_buffer_length); + + isc_buffer_init(&buffer, bufmem, initial_buffer_length); + + switch (dctx->format) { + case dns_masterformat_text: + /* + * If the database has cache semantics, output an + * RFC2540 $DATE directive so that the TTLs can be + * adjusted when it is reloaded. For zones it is not + * really needed, and it would make the file + * incompatible with pre-RFC2540 software, so we omit + * it in the zone case. + */ + if (dctx->do_date) { + fprintf(dctx->f, "; using a %u second stale ttl\n", + dctx->tctx.serve_stale_ttl); + result = dns_time32_totext(dctx->now, &buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_usedregion(&buffer, &r); + fprintf(dctx->f, "$DATE %.*s\n", (int)r.length, + (char *)r.base); + } + break; + case dns_masterformat_raw: + r.base = (unsigned char *)&rawheader; + r.length = sizeof(rawheader); + isc_buffer_region(&buffer, &r); + now32 = dctx->now; + rawversion = 1; + if ((dctx->header.flags & DNS_MASTERRAW_COMPAT) != 0) { + rawversion = 0; + } + + isc_buffer_putuint32(&buffer, dctx->format); + isc_buffer_putuint32(&buffer, rawversion); + isc_buffer_putuint32(&buffer, now32); + + if (rawversion == 1) { + isc_buffer_putuint32(&buffer, dctx->header.flags); + isc_buffer_putuint32(&buffer, + dctx->header.sourceserial); + isc_buffer_putuint32(&buffer, dctx->header.lastxfrin); + } + + INSIST(isc_buffer_usedlength(&buffer) <= sizeof(rawheader)); + result = isc_stdio_write(buffer.base, 1, + isc_buffer_usedlength(&buffer), + dctx->f, NULL); + if (result != ISC_R_SUCCESS) { + break; + } + + break; + default: + UNREACHABLE(); + } + + isc_mem_put(dctx->mctx, buffer.base, buffer.length); + return (result); +} + +static isc_result_t +dumptostream(dns_dumpctx_t *dctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_buffer_t buffer; + char *bufmem; + dns_name_t *name; + dns_fixedname_t fixname; + unsigned int options = DNS_DB_STALEOK; + + if ((dctx->tctx.style.flags & DNS_STYLEFLAG_EXPIRED) != 0) { + options |= DNS_DB_EXPIREDOK; + } + + bufmem = isc_mem_get(dctx->mctx, initial_buffer_length); + + isc_buffer_init(&buffer, bufmem, initial_buffer_length); + + name = dns_fixedname_initname(&fixname); + + CHECK(writeheader(dctx)); + + result = dns_dbiterator_first(dctx->dbiter); + if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) { + goto cleanup; + } + + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_t *rdsiter = NULL; + dns_dbnode_t *node = NULL; + + result = dns_dbiterator_current(dctx->dbiter, &node, name); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + break; + } + if (result == DNS_R_NEWORIGIN) { + dns_name_t *origin = + dns_fixedname_name(&dctx->tctx.origin_fixname); + result = dns_dbiterator_origin(dctx->dbiter, origin); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if ((dctx->tctx.style.flags & DNS_STYLEFLAG_REL_DATA) != + 0) + { + dctx->tctx.origin = origin; + } + dctx->tctx.neworigin = origin; + } + + result = dns_dbiterator_pause(dctx->dbiter); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = dns_db_allrdatasets(dctx->db, node, dctx->version, + options, dctx->now, &rdsiter); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(dctx->db, &node); + goto cleanup; + } + result = (dctx->dumpsets)(dctx->mctx, name, rdsiter, + &dctx->tctx, &buffer, dctx->f); + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(dctx->db, &node); + goto cleanup; + } + dns_db_detachnode(dctx->db, &node); + result = dns_dbiterator_next(dctx->dbiter); + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +cleanup: + RUNTIME_CHECK(dns_dbiterator_pause(dctx->dbiter) == ISC_R_SUCCESS); + isc_mem_put(dctx->mctx, buffer.base, buffer.length); + return (result); +} + +isc_result_t +dns_master_dumptostreamasync(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, + const dns_master_style_t *style, FILE *f, + isc_task_t *task, dns_dumpdonefunc_t done, + void *done_arg, dns_dumpctx_t **dctxp) { + dns_dumpctx_t *dctx = NULL; + isc_result_t result; + + REQUIRE(task != NULL); + REQUIRE(f != NULL); + REQUIRE(done != NULL); + + result = dumpctx_create(mctx, db, version, style, f, &dctx, + dns_masterformat_text, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_task_attach(task, &dctx->task); + dctx->done = done; + dctx->done_arg = done_arg; + + result = task_send(dctx); + if (result == ISC_R_SUCCESS) { + dns_dumpctx_attach(dctx, dctxp); + return (DNS_R_CONTINUE); + } + + dns_dumpctx_detach(&dctx); + return (result); +} + +isc_result_t +dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, + dns_masterformat_t format, + dns_masterrawheader_t *header, FILE *f) { + dns_dumpctx_t *dctx = NULL; + isc_result_t result; + + result = dumpctx_create(mctx, db, version, style, f, &dctx, format, + header); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dumptostream(dctx); + INSIST(result != DNS_R_CONTINUE); + dns_dumpctx_detach(&dctx); + + result = flushandsync(f, result, NULL); + return (result); +} + +static isc_result_t +opentmp(isc_mem_t *mctx, dns_masterformat_t format, const char *file, + char **tempp, FILE **fp) { + FILE *f = NULL; + isc_result_t result; + char *tempname = NULL; + int tempnamelen; + + tempnamelen = strlen(file) + 20; + tempname = isc_mem_allocate(mctx, tempnamelen); + + result = isc_file_mktemplate(file, tempname, tempnamelen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (format == dns_masterformat_text) { + result = isc_file_openunique(tempname, &f); + } else { + result = isc_file_bopenunique(tempname, &f); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping master file: %s: open: %s", tempname, + isc_result_totext(result)); + goto cleanup; + } + +#if defined(POSIX_FADV_DONTNEED) + posix_fadvise(fileno(f), 0, 0, POSIX_FADV_DONTNEED); +#endif + + *tempp = tempname; + *fp = f; + return (ISC_R_SUCCESS); + +cleanup: + isc_mem_free(mctx, tempname); + return (result); +} + +isc_result_t +dns_master_dumpasync(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, const char *filename, + isc_task_t *task, dns_dumpdonefunc_t done, void *done_arg, + dns_dumpctx_t **dctxp, dns_masterformat_t format, + dns_masterrawheader_t *header) { + FILE *f = NULL; + isc_result_t result; + char *tempname = NULL; + char *file = NULL; + dns_dumpctx_t *dctx = NULL; + + file = isc_mem_strdup(mctx, filename); + + result = opentmp(mctx, format, filename, &tempname, &f); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dumpctx_create(mctx, db, version, style, f, &dctx, format, + header); + if (result != ISC_R_SUCCESS) { + (void)isc_stdio_close(f); + (void)isc_file_remove(tempname); + goto cleanup; + } + + isc_task_attach(task, &dctx->task); + dctx->done = done; + dctx->done_arg = done_arg; + dctx->file = file; + file = NULL; + dctx->tmpfile = tempname; + tempname = NULL; + + result = task_send(dctx); + if (result == ISC_R_SUCCESS) { + dns_dumpctx_attach(dctx, dctxp); + return (DNS_R_CONTINUE); + } + +cleanup: + if (dctx != NULL) { + dns_dumpctx_detach(&dctx); + } + if (file != NULL) { + isc_mem_free(mctx, file); + } + if (tempname != NULL) { + isc_mem_free(mctx, tempname); + } + return (result); +} + +isc_result_t +dns_master_dump(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + const dns_master_style_t *style, const char *filename, + dns_masterformat_t format, dns_masterrawheader_t *header) { + FILE *f = NULL; + isc_result_t result; + char *tempname; + dns_dumpctx_t *dctx = NULL; + + result = opentmp(mctx, format, filename, &tempname, &f); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dumpctx_create(mctx, db, version, style, f, &dctx, format, + header); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dumptostream(dctx); + INSIST(result != DNS_R_CONTINUE); + dns_dumpctx_detach(&dctx); + + result = closeandrename(f, result, tempname, filename); + +cleanup: + isc_mem_free(mctx, tempname); + return (result); +} + +/* + * Dump a database node into a master file. + * XXX: this function assumes the text format. + */ +isc_result_t +dns_master_dumpnodetostream(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *name, + const dns_master_style_t *style, FILE *f) { + isc_result_t result; + isc_buffer_t buffer; + char *bufmem; + isc_stdtime_t now; + dns_totext_ctx_t ctx; + dns_rdatasetiter_t *rdsiter = NULL; + unsigned int options = DNS_DB_STALEOK; + + if ((style->flags & DNS_STYLEFLAG_EXPIRED) != 0) { + options |= DNS_DB_EXPIREDOK; + } + + result = totext_ctx_init(style, NULL, &ctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("could not set master file style"); + return (ISC_R_UNEXPECTED); + } + + isc_stdtime_get(&now); + + bufmem = isc_mem_get(mctx, initial_buffer_length); + + isc_buffer_init(&buffer, bufmem, initial_buffer_length); + + result = dns_db_allrdatasets(db, node, version, options, now, &rdsiter); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dump_rdatasets_text(mctx, name, rdsiter, &ctx, &buffer, f); + if (result != ISC_R_SUCCESS) { + goto failure; + } + dns_rdatasetiter_destroy(&rdsiter); + + result = ISC_R_SUCCESS; + +failure: + isc_mem_put(mctx, buffer.base, buffer.length); + return (result); +} + +isc_result_t +dns_master_dumpnode(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, const dns_name_t *name, + const dns_master_style_t *style, const char *filename) { + FILE *f = NULL; + isc_result_t result; + + result = isc_stdio_open(filename, "w", &f); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping node to file: %s: open: %s", filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + + result = dns_master_dumpnodetostream(mctx, db, version, node, name, + style, f); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping master file: %s: dump: %s", filename, + isc_result_totext(result)); + (void)isc_stdio_close(f); + return (ISC_R_UNEXPECTED); + } + + result = isc_stdio_close(f); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, ISC_LOG_ERROR, + "dumping master file: %s: close: %s", filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + + return (result); +} + +dns_masterstyle_flags_t +dns_master_styleflags(const dns_master_style_t *style) { + REQUIRE(style != NULL); + return (style->flags); +} + +isc_result_t +dns_master_stylecreate(dns_master_style_t **stylep, + dns_masterstyle_flags_t flags, unsigned int ttl_column, + unsigned int class_column, unsigned int type_column, + unsigned int rdata_column, unsigned int line_length, + unsigned int tab_width, unsigned int split_width, + isc_mem_t *mctx) { + dns_master_style_t *style; + + REQUIRE(stylep != NULL && *stylep == NULL); + style = isc_mem_get(mctx, sizeof(*style)); + + style->flags = flags; + style->ttl_column = ttl_column; + style->class_column = class_column; + style->type_column = type_column; + style->rdata_column = rdata_column; + style->line_length = line_length; + style->tab_width = tab_width; + style->split_width = split_width; + *stylep = style; + return (ISC_R_SUCCESS); +} + +void +dns_master_styledestroy(dns_master_style_t **stylep, isc_mem_t *mctx) { + dns_master_style_t *style; + + REQUIRE(stylep != NULL && *stylep != NULL); + style = *stylep; + *stylep = NULL; + isc_mem_put(mctx, style, sizeof(*style)); +} diff --git a/lib/dns/message.c b/lib/dns/message.c new file mode 100644 index 0000000..1b983d9 --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,4849 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include +#include +#include + +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SKAN_MSG_DEBUG +static void +hexdump(const char *msg, const char *msg2, void *base, size_t len) { + unsigned char *p; + unsigned int cnt; + + p = base; + cnt = 0; + + printf("*** %s [%s] (%u bytes @ %p)\n", msg, msg2, (unsigned)len, base); + + while (cnt < len) { + if (cnt % 16 == 0) { + printf("%p: ", p); + } else if (cnt % 8 == 0) { + printf(" |"); + } + printf(" %02x %c", *p, (isprint(*p) ? *p : ' ')); + p++; + cnt++; + + if (cnt % 16 == 0) { + printf("\n"); + } + } + + if (cnt % 16 != 0) { + printf("\n"); + } +} +#endif /* ifdef SKAN_MSG_DEBUG */ + +#define DNS_MESSAGE_OPCODE_MASK 0x7800U +#define DNS_MESSAGE_OPCODE_SHIFT 11 +#define DNS_MESSAGE_RCODE_MASK 0x000fU +#define DNS_MESSAGE_FLAG_MASK 0x8ff0U +#define DNS_MESSAGE_EDNSRCODE_MASK 0xff000000U +#define DNS_MESSAGE_EDNSRCODE_SHIFT 24 +#define DNS_MESSAGE_EDNSVERSION_MASK 0x00ff0000U +#define DNS_MESSAGE_EDNSVERSION_SHIFT 16 + +#define VALID_NAMED_SECTION(s) \ + (((s) > DNS_SECTION_ANY) && ((s) < DNS_SECTION_MAX)) +#define VALID_SECTION(s) (((s) >= DNS_SECTION_ANY) && ((s) < DNS_SECTION_MAX)) +#define ADD_STRING(b, s) \ + { \ + if (strlen(s) >= isc_buffer_availablelength(b)) { \ + result = ISC_R_NOSPACE; \ + goto cleanup; \ + } else \ + isc_buffer_putstr(b, s); \ + } +#define VALID_PSEUDOSECTION(s) \ + (((s) >= DNS_PSEUDOSECTION_ANY) && ((s) < DNS_PSEUDOSECTION_MAX)) + +#define OPTOUT(x) (((x)->attributes & DNS_RDATASETATTR_OPTOUT) != 0) + +/*% + * This is the size of each individual scratchpad buffer, and the numbers + * of various block allocations used within the server. + * XXXMLG These should come from a config setting. + */ +#define SCRATCHPAD_SIZE 1232 +#define NAME_FILLCOUNT 4 +#define NAME_FREEMAX 8 * NAME_FILLCOUNT +#define OFFSET_COUNT 4 +#define RDATA_COUNT 8 +#define RDATALIST_COUNT 8 +#define RDATASET_FILLCOUNT 4 +#define RDATASET_FREEMAX 8 * RDATASET_FILLCOUNT + +/*% + * Text representation of the different items, for message_totext + * functions. + */ +static const char *sectiontext[] = { "QUESTION", "ANSWER", "AUTHORITY", + "ADDITIONAL" }; + +static const char *updsectiontext[] = { "ZONE", "PREREQUISITE", "UPDATE", + "ADDITIONAL" }; + +static const char *opcodetext[] = { "QUERY", "IQUERY", "STATUS", + "RESERVED3", "NOTIFY", "UPDATE", + "RESERVED6", "RESERVED7", "RESERVED8", + "RESERVED9", "RESERVED10", "RESERVED11", + "RESERVED12", "RESERVED13", "RESERVED14", + "RESERVED15" }; + +static const char *edetext[] = { "Other", + "Unsupported DNSKEY Algorithm", + "Unsupported DS Digest Type", + "Stale Answer", + "Forged Answer", + "DNSSEC Indeterminate", + "DNSSEC Bogus", + "Signature Expired", + "Signature Not Yet Valid", + "DNSKEY Missing", + "RRSIGs Missing", + "No Zone Key Bit Set", + "NSEC Missing", + "Cached Error", + "Not Ready", + "Blocked", + "Censored", + "Filtered", + "Prohibited", + "Stale NXDOMAIN Answer", + "Not Authoritative", + "Not Supported", + "No Reachable Authority", + "Network Error", + "Invalid Data" }; + +/*% + * "helper" type, which consists of a block of some type, and is linkable. + * For it to work, sizeof(dns_msgblock_t) must be a multiple of the pointer + * size, or the allocated elements will not be aligned correctly. + */ +struct dns_msgblock { + unsigned int count; + unsigned int remaining; + ISC_LINK(dns_msgblock_t) link; +}; /* dynamically sized */ + +static dns_msgblock_t * +msgblock_allocate(isc_mem_t *, unsigned int, unsigned int); + +#define msgblock_get(block, type) \ + ((type *)msgblock_internalget(block, sizeof(type))) + +static void * +msgblock_internalget(dns_msgblock_t *, unsigned int); + +static void +msgblock_reset(dns_msgblock_t *); + +static void +msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int); + +static void +logfmtpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, isc_logcategory_t *category, + isc_logmodule_t *module, const dns_master_style_t *style, + int level, isc_mem_t *mctx); + +/* + * Allocate a new dns_msgblock_t, and return a pointer to it. If no memory + * is free, return NULL. + */ +static dns_msgblock_t * +msgblock_allocate(isc_mem_t *mctx, unsigned int sizeof_type, + unsigned int count) { + dns_msgblock_t *block; + unsigned int length; + + length = sizeof(dns_msgblock_t) + (sizeof_type * count); + + block = isc_mem_get(mctx, length); + + block->count = count; + block->remaining = count; + + ISC_LINK_INIT(block, link); + + return (block); +} + +/* + * Return an element from the msgblock. If no more are available, return + * NULL. + */ +static void * +msgblock_internalget(dns_msgblock_t *block, unsigned int sizeof_type) { + void *ptr; + + if (block == NULL || block->remaining == 0) { + return (NULL); + } + + block->remaining--; + + ptr = (((unsigned char *)block) + sizeof(dns_msgblock_t) + + (sizeof_type * block->remaining)); + + return (ptr); +} + +static void +msgblock_reset(dns_msgblock_t *block) { + block->remaining = block->count; +} + +/* + * Release memory associated with a message block. + */ +static void +msgblock_free(isc_mem_t *mctx, dns_msgblock_t *block, + unsigned int sizeof_type) { + unsigned int length; + + length = sizeof(dns_msgblock_t) + (sizeof_type * block->count); + + isc_mem_put(mctx, block, length); +} + +/* + * Allocate a new dynamic buffer, and attach it to this message as the + * "current" buffer. (which is always the last on the list, for our + * uses) + */ +static isc_result_t +newbuffer(dns_message_t *msg, unsigned int size) { + isc_buffer_t *dynbuf; + + dynbuf = NULL; + isc_buffer_allocate(msg->mctx, &dynbuf, size); + + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + return (ISC_R_SUCCESS); +} + +static isc_buffer_t * +currentbuffer(dns_message_t *msg) { + isc_buffer_t *dynbuf; + + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + + return (dynbuf); +} + +static void +releaserdata(dns_message_t *msg, dns_rdata_t *rdata) { + ISC_LIST_PREPEND(msg->freerdata, rdata, link); +} + +static dns_rdata_t * +newrdata(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_rdata_t *rdata; + + rdata = ISC_LIST_HEAD(msg->freerdata); + if (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + return (rdata); + } + + msgblock = ISC_LIST_TAIL(msg->rdatas); + rdata = msgblock_get(msgblock, dns_rdata_t); + if (rdata == NULL) { + msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdata_t), + RDATA_COUNT); + ISC_LIST_APPEND(msg->rdatas, msgblock, link); + + rdata = msgblock_get(msgblock, dns_rdata_t); + } + + dns_rdata_init(rdata); + return (rdata); +} + +static void +releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) { + ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link); +} + +static dns_rdatalist_t * +newrdatalist(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_rdatalist_t *rdatalist; + + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + if (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + goto out; + } + + msgblock = ISC_LIST_TAIL(msg->rdatalists); + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + if (rdatalist == NULL) { + msgblock = msgblock_allocate(msg->mctx, sizeof(dns_rdatalist_t), + RDATALIST_COUNT); + ISC_LIST_APPEND(msg->rdatalists, msgblock, link); + + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + } +out: + dns_rdatalist_init(rdatalist); + return (rdatalist); +} + +static dns_offsets_t * +newoffsets(dns_message_t *msg) { + dns_msgblock_t *msgblock; + dns_offsets_t *offsets; + + msgblock = ISC_LIST_TAIL(msg->offsets); + offsets = msgblock_get(msgblock, dns_offsets_t); + if (offsets == NULL) { + msgblock = msgblock_allocate(msg->mctx, sizeof(dns_offsets_t), + OFFSET_COUNT); + ISC_LIST_APPEND(msg->offsets, msgblock, link); + + offsets = msgblock_get(msgblock, dns_offsets_t); + } + + return (offsets); +} + +static void +msginitheader(dns_message_t *m) { + m->id = 0; + m->flags = 0; + m->rcode = 0; + m->opcode = 0; + m->rdclass = 0; +} + +static void +msginitprivate(dns_message_t *m) { + unsigned int i; + + for (i = 0; i < DNS_SECTION_MAX; i++) { + m->cursors[i] = NULL; + m->counts[i] = 0; + } + m->opt = NULL; + m->sig0 = NULL; + m->sig0name = NULL; + m->tsig = NULL; + m->tsigname = NULL; + m->state = DNS_SECTION_ANY; /* indicate nothing parsed or rendered */ + m->opt_reserved = 0; + m->sig_reserved = 0; + m->reserved = 0; + m->padding = 0; + m->padding_off = 0; + m->buffer = NULL; +} + +static void +msginittsig(dns_message_t *m) { + m->tsigstatus = dns_rcode_noerror; + m->querytsigstatus = dns_rcode_noerror; + m->tsigkey = NULL; + m->tsigctx = NULL; + m->sigstart = -1; + m->sig0key = NULL; + m->sig0status = dns_rcode_noerror; + m->timeadjust = 0; +} + +/* + * Init elements to default state. Used both when allocating a new element + * and when resetting one. + */ +static void +msginit(dns_message_t *m) { + msginitheader(m); + msginitprivate(m); + msginittsig(m); + m->header_ok = 0; + m->question_ok = 0; + m->tcp_continuation = 0; + m->verified_sig = 0; + m->verify_attempted = 0; + m->order = NULL; + m->order_arg.env = NULL; + m->order_arg.acl = NULL; + m->order_arg.element = NULL; + m->query.base = NULL; + m->query.length = 0; + m->free_query = 0; + m->saved.base = NULL; + m->saved.length = 0; + m->free_saved = 0; + m->cc_ok = 0; + m->cc_bad = 0; + m->tkey = 0; + m->rdclass_set = 0; + m->querytsig = NULL; + m->indent.string = "\t"; + m->indent.count = 0; +} + +static void +msgresetnames(dns_message_t *msg, unsigned int first_section) { + unsigned int i; + dns_name_t *name, *next_name; + dns_rdataset_t *rds, *next_rds; + + /* + * Clean up name lists by calling the rdataset disassociate function. + */ + for (i = first_section; i < DNS_SECTION_MAX; i++) { + name = ISC_LIST_HEAD(msg->sections[i]); + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(msg->sections[i], name, link); + + rds = ISC_LIST_HEAD(name->list); + while (rds != NULL) { + next_rds = ISC_LIST_NEXT(rds, link); + ISC_LIST_UNLINK(name->list, rds, link); + + INSIST(dns_rdataset_isassociated(rds)); + dns_rdataset_disassociate(rds); + isc_mempool_put(msg->rdspool, rds); + rds = next_rds; + } + dns_message_puttempname(msg, &name); + name = next_name; + } + } +} + +static void +msgresetopt(dns_message_t *msg) { + if (msg->opt != NULL) { + if (msg->opt_reserved > 0) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + } + INSIST(dns_rdataset_isassociated(msg->opt)); + dns_rdataset_disassociate(msg->opt); + isc_mempool_put(msg->rdspool, msg->opt); + msg->opt = NULL; + msg->cc_ok = 0; + msg->cc_bad = 0; + } +} + +static void +msgresetsigs(dns_message_t *msg, bool replying) { + if (msg->sig_reserved > 0) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + } + if (msg->tsig != NULL) { + INSIST(dns_rdataset_isassociated(msg->tsig)); + INSIST(msg->namepool != NULL); + if (replying) { + INSIST(msg->querytsig == NULL); + msg->querytsig = msg->tsig; + } else { + dns_rdataset_disassociate(msg->tsig); + isc_mempool_put(msg->rdspool, msg->tsig); + if (msg->querytsig != NULL) { + dns_rdataset_disassociate(msg->querytsig); + isc_mempool_put(msg->rdspool, msg->querytsig); + } + } + dns_message_puttempname(msg, &msg->tsigname); + msg->tsig = NULL; + } else if (msg->querytsig != NULL && !replying) { + dns_rdataset_disassociate(msg->querytsig); + isc_mempool_put(msg->rdspool, msg->querytsig); + msg->querytsig = NULL; + } + if (msg->sig0 != NULL) { + INSIST(dns_rdataset_isassociated(msg->sig0)); + dns_rdataset_disassociate(msg->sig0); + isc_mempool_put(msg->rdspool, msg->sig0); + msg->sig0 = NULL; + } + if (msg->sig0name != NULL) { + dns_message_puttempname(msg, &msg->sig0name); + } +} + +/* + * Free all but one (or everything) for this message. This is used by + * both dns_message_reset() and dns__message_destroy(). + */ +static void +msgreset(dns_message_t *msg, bool everything) { + dns_msgblock_t *msgblock, *next_msgblock; + isc_buffer_t *dynbuf, *next_dynbuf; + dns_rdata_t *rdata; + dns_rdatalist_t *rdatalist; + + msgresetnames(msg, 0); + msgresetopt(msg); + msgresetsigs(msg, false); + + /* + * Clean up linked lists. + */ + + /* + * Run through the free lists, and just unlink anything found there. + * The memory isn't lost since these are part of message blocks we + * have allocated. + */ + rdata = ISC_LIST_HEAD(msg->freerdata); + while (rdata != NULL) { + ISC_LIST_UNLINK(msg->freerdata, rdata, link); + rdata = ISC_LIST_HEAD(msg->freerdata); + } + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + while (rdatalist != NULL) { + ISC_LIST_UNLINK(msg->freerdatalist, rdatalist, link); + rdatalist = ISC_LIST_HEAD(msg->freerdatalist); + } + + dynbuf = ISC_LIST_HEAD(msg->scratchpad); + INSIST(dynbuf != NULL); + if (!everything) { + isc_buffer_clear(dynbuf); + dynbuf = ISC_LIST_NEXT(dynbuf, link); + } + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->scratchpad, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + msgblock = ISC_LIST_HEAD(msg->rdatas); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatas, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdata_t)); + msgblock = next_msgblock; + } + + /* + * rdatalists could be empty. + */ + + msgblock = ISC_LIST_HEAD(msg->rdatalists); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->rdatalists, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_rdatalist_t)); + msgblock = next_msgblock; + } + + msgblock = ISC_LIST_HEAD(msg->offsets); + if (!everything && msgblock != NULL) { + msgblock_reset(msgblock); + msgblock = ISC_LIST_NEXT(msgblock, link); + } + while (msgblock != NULL) { + next_msgblock = ISC_LIST_NEXT(msgblock, link); + ISC_LIST_UNLINK(msg->offsets, msgblock, link); + msgblock_free(msg->mctx, msgblock, sizeof(dns_offsets_t)); + msgblock = next_msgblock; + } + + if (msg->tsigkey != NULL) { + dns_tsigkey_detach(&msg->tsigkey); + msg->tsigkey = NULL; + } + + if (msg->tsigctx != NULL) { + dst_context_destroy(&msg->tsigctx); + } + + if (msg->query.base != NULL) { + if (msg->free_query != 0) { + isc_mem_put(msg->mctx, msg->query.base, + msg->query.length); + } + msg->query.base = NULL; + msg->query.length = 0; + } + + if (msg->saved.base != NULL) { + if (msg->free_saved != 0) { + isc_mem_put(msg->mctx, msg->saved.base, + msg->saved.length); + } + msg->saved.base = NULL; + msg->saved.length = 0; + } + + /* + * cleanup the buffer cleanup list + */ + dynbuf = ISC_LIST_HEAD(msg->cleanup); + while (dynbuf != NULL) { + next_dynbuf = ISC_LIST_NEXT(dynbuf, link); + ISC_LIST_UNLINK(msg->cleanup, dynbuf, link); + isc_buffer_free(&dynbuf); + dynbuf = next_dynbuf; + } + + if (msg->order_arg.env != NULL) { + dns_aclenv_detach(&msg->order_arg.env); + } + if (msg->order_arg.acl != NULL) { + dns_acl_detach(&msg->order_arg.acl); + } + + /* + * Set other bits to normal default values. + */ + if (!everything) { + msginit(msg); + } + + ENSURE(isc_mempool_getallocated(msg->namepool) == 0); + ENSURE(isc_mempool_getallocated(msg->rdspool) == 0); +} + +static unsigned int +spacefortsig(dns_tsigkey_t *key, int otherlen) { + isc_region_t r1, r2; + unsigned int x; + isc_result_t result; + + /* + * The space required for an TSIG record is: + * + * n1 bytes for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the rdlength + * n2 bytes for the algorithm name + * 6 bytes for the time signed + * 2 bytes for the fudge + * 2 bytes for the MAC size + * x bytes for the MAC + * 2 bytes for the original id + * 2 bytes for the error + * 2 bytes for the other data length + * y bytes for the other data (at most) + * --------------------------------- + * 26 + n1 + n2 + x + y bytes + */ + + dns_name_toregion(&key->name, &r1); + dns_name_toregion(key->algorithm, &r2); + if (key->key == NULL) { + x = 0; + } else { + result = dst_key_sigsize(key->key, &x); + if (result != ISC_R_SUCCESS) { + x = 0; + } + } + return (26 + r1.length + r2.length + x + otherlen); +} + +void +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) { + dns_message_t *m = NULL; + isc_buffer_t *dynbuf = NULL; + unsigned int i; + + REQUIRE(mctx != NULL); + REQUIRE(msgp != NULL); + REQUIRE(*msgp == NULL); + REQUIRE(intent == DNS_MESSAGE_INTENTPARSE || + intent == DNS_MESSAGE_INTENTRENDER); + + m = isc_mem_get(mctx, sizeof(dns_message_t)); + *m = (dns_message_t){ .from_to_wire = intent }; + isc_mem_attach(mctx, &m->mctx); + msginit(m); + + for (i = 0; i < DNS_SECTION_MAX; i++) { + ISC_LIST_INIT(m->sections[i]); + } + + ISC_LIST_INIT(m->scratchpad); + ISC_LIST_INIT(m->cleanup); + ISC_LIST_INIT(m->rdatas); + ISC_LIST_INIT(m->rdatalists); + ISC_LIST_INIT(m->offsets); + ISC_LIST_INIT(m->freerdata); + ISC_LIST_INIT(m->freerdatalist); + + isc_mempool_create(m->mctx, sizeof(dns_fixedname_t), &m->namepool); + isc_mempool_setfillcount(m->namepool, NAME_FILLCOUNT); + isc_mempool_setfreemax(m->namepool, NAME_FREEMAX); + isc_mempool_setname(m->namepool, "msg:names"); + + isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), &m->rdspool); + isc_mempool_setfillcount(m->rdspool, RDATASET_FILLCOUNT); + isc_mempool_setfreemax(m->rdspool, RDATASET_FREEMAX); + isc_mempool_setname(m->rdspool, "msg:rdataset"); + + isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE); + ISC_LIST_APPEND(m->scratchpad, dynbuf, link); + + isc_refcount_init(&m->refcount, 1); + m->magic = DNS_MESSAGE_MAGIC; + + *msgp = m; +} + +void +dns_message_reset(dns_message_t *msg, unsigned int intent) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(intent == DNS_MESSAGE_INTENTPARSE || + intent == DNS_MESSAGE_INTENTRENDER); + + msgreset(msg, false); + msg->from_to_wire = intent; +} + +static void +dns__message_destroy(dns_message_t *msg) { + REQUIRE(msg != NULL); + REQUIRE(DNS_MESSAGE_VALID(msg)); + + msgreset(msg, true); + isc_mempool_destroy(&msg->namepool); + isc_mempool_destroy(&msg->rdspool); + isc_refcount_destroy(&msg->refcount); + msg->magic = 0; + isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t)); +} + +void +dns_message_attach(dns_message_t *source, dns_message_t **target) { + REQUIRE(DNS_MESSAGE_VALID(source)); + + isc_refcount_increment(&source->refcount); + *target = source; +} + +void +dns_message_detach(dns_message_t **messagep) { + REQUIRE(messagep != NULL && DNS_MESSAGE_VALID(*messagep)); + dns_message_t *msg = *messagep; + *messagep = NULL; + + if (isc_refcount_decrement(&msg->refcount) == 1) { + dns__message_destroy(msg); + } +} + +static isc_result_t +findname(dns_name_t **foundname, const dns_name_t *target, + dns_namelist_t *section) { + dns_name_t *curr; + + for (curr = ISC_LIST_TAIL(*section); curr != NULL; + curr = ISC_LIST_PREV(curr, link)) + { + if (dns_name_equal(curr, target)) { + if (foundname != NULL) { + *foundname = curr; + } + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t type, dns_rdatatype_t covers, + dns_rdataset_t **rdataset) { + dns_rdataset_t *curr; + + REQUIRE(name != NULL); + REQUIRE(rdataset == NULL || *rdataset == NULL); + + for (curr = ISC_LIST_TAIL(name->list); curr != NULL; + curr = ISC_LIST_PREV(curr, link)) + { + if (curr->rdclass == rdclass && curr->type == type && + curr->covers == covers) + { + if (rdataset != NULL) { + *rdataset = curr; + } + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_rdataset_t **rdataset) { + dns_rdataset_t *curr; + + REQUIRE(name != NULL); + REQUIRE(rdataset == NULL || *rdataset == NULL); + + for (curr = ISC_LIST_TAIL(name->list); curr != NULL; + curr = ISC_LIST_PREV(curr, link)) + { + if (curr->type == type && curr->covers == covers) { + if (rdataset != NULL) { + *rdataset = curr; + } + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +/* + * Read a name from buffer "source". + */ +static isc_result_t +getname(dns_name_t *name, isc_buffer_t *source, dns_message_t *msg, + dns_decompress_t *dctx) { + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + + scratch = currentbuffer(msg); + + /* + * First try: use current buffer. + * Second try: allocate a new buffer and use that. + */ + tries = 0; + while (tries < 2) { + result = dns_name_fromwire(name, source, dctx, 0, scratch); + + if (result == ISC_R_NOSPACE) { + tries++; + + result = newbuffer(msg, SCRATCHPAD_SIZE); + if (result != ISC_R_SUCCESS) { + return (result); + } + + scratch = currentbuffer(msg); + dns_name_reset(name); + } else { + return (result); + } + } + + UNREACHABLE(); +} + +static isc_result_t +getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, + unsigned int rdatalen, dns_rdata_t *rdata) { + isc_buffer_t *scratch; + isc_result_t result; + unsigned int tries; + unsigned int trysize; + + scratch = currentbuffer(msg); + + isc_buffer_setactive(source, rdatalen); + + /* + * First try: use current buffer. + * Second try: allocate a new buffer of size + * max(SCRATCHPAD_SIZE, 2 * compressed_rdatalen) + * (the data will fit if it was not more than 50% compressed) + * Subsequent tries: double buffer size on each try. + */ + tries = 0; + trysize = 0; + /* XXX possibly change this to a while (tries < 2) loop */ + for (;;) { + result = dns_rdata_fromwire(rdata, rdclass, rdtype, source, + dctx, 0, scratch); + + if (result == ISC_R_NOSPACE) { + if (tries == 0) { + trysize = 2 * rdatalen; + if (trysize < SCRATCHPAD_SIZE) { + trysize = SCRATCHPAD_SIZE; + } + } else { + INSIST(trysize != 0); + if (trysize >= 65535) { + return (ISC_R_NOSPACE); + } + /* XXX DNS_R_RRTOOLONG? */ + trysize *= 2; + } + tries++; + result = newbuffer(msg, trysize); + if (result != ISC_R_SUCCESS) { + return (result); + } + + scratch = currentbuffer(msg); + } else { + return (result); + } + } +} + +#define DO_ERROR(r) \ + do { \ + if (best_effort) { \ + seen_problem = true; \ + } else { \ + result = r; \ + goto cleanup; \ + } \ + } while (0) + +static isc_result_t +getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + unsigned int options) { + isc_region_t r; + unsigned int count; + dns_name_t *name = NULL; + dns_name_t *name2 = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section = &msg->sections[DNS_SECTION_QUESTION]; + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool seen_problem = false; + bool free_name = false; + + for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) { + name = NULL; + result = dns_message_gettempname(msg, &name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + name->offsets = (unsigned char *)newoffsets(msg); + free_name = true; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the allocated + * name since we no longer need it, and set our name pointer + * to point to the name we found. + */ + result = findname(&name2, name, section); + + /* + * If it is the first name in the section, accept it. + * + * If it is not, but is not the same as the name already + * in the question section, append to the section. Note that + * here in the question section this is illegal, so return + * FORMERR. In the future, check the opcode to see if + * this should be legal or not. In either case we no longer + * need this name pointer. + */ + if (result != ISC_R_SUCCESS) { + if (!ISC_LIST_EMPTY(*section)) { + DO_ERROR(DNS_R_FORMERR); + } + ISC_LIST_APPEND(*section, name, link); + free_name = false; + } else { + dns_message_puttempname(msg, &name); + name = name2; + name2 = NULL; + free_name = false; + } + + /* + * Get type and class. + */ + isc_buffer_remainingregion(source, &r); + if (r.length < 4) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If this class is different than the one we already read, + * this is an error. + */ + if (msg->rdclass_set == 0) { + msg->rdclass = rdclass; + msg->rdclass_set = 1; + } else if (msg->rdclass != rdclass) { + DO_ERROR(DNS_R_FORMERR); + } + + /* + * Is this a TKEY query? + */ + if (rdtype == dns_rdatatype_tkey) { + msg->tkey = 1; + } + + /* + * Can't ask the same question twice. + */ + result = dns_message_find(name, rdclass, rdtype, 0, NULL); + if (result == ISC_R_SUCCESS) { + DO_ERROR(DNS_R_FORMERR); + } + + /* + * Allocate a new rdatalist. + */ + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + rdataset = isc_mempool_get(msg->rdspool); + + /* + * Convert rdatalist to rdataset, and attach the latter to + * the name. + */ + rdatalist->type = rdtype; + rdatalist->rdclass = rdclass; + + dns_rdataset_init(rdataset); + result = dns_rdatalist_tordataset(rdatalist, rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + rdataset->attributes |= DNS_RDATASETATTR_QUESTION; + + ISC_LIST_APPEND(name->list, rdataset, link); + rdataset = NULL; + } + + if (seen_problem) { + return (DNS_R_RECOVERABLE); + } + return (ISC_R_SUCCESS); + +cleanup: + if (rdataset != NULL) { + INSIST(!dns_rdataset_isassociated(rdataset)); + isc_mempool_put(msg->rdspool, rdataset); + } + if (free_name) { + dns_message_puttempname(msg, &name); + } + + return (result); +} + +static bool +update(dns_section_t section, dns_rdataclass_t rdclass) { + if (section == DNS_SECTION_PREREQUISITE) { + return (rdclass == dns_rdataclass_any || + rdclass == dns_rdataclass_none); + } + if (section == DNS_SECTION_UPDATE) { + return (rdclass == dns_rdataclass_any); + } + return (false); +} + +/* + * Check to confirm that all DNSSEC records (DS, NSEC, NSEC3) have + * covering RRSIGs. + */ +static bool +auth_signed(dns_namelist_t *section) { + dns_name_t *name; + + for (name = ISC_LIST_HEAD(*section); name != NULL; + name = ISC_LIST_NEXT(name, link)) + { + int auth_dnssec = 0, auth_rrsig = 0; + dns_rdataset_t *rds; + + for (rds = ISC_LIST_HEAD(name->list); rds != NULL; + rds = ISC_LIST_NEXT(rds, link)) + { + switch (rds->type) { + case dns_rdatatype_ds: + auth_dnssec |= 0x1; + break; + case dns_rdatatype_nsec: + auth_dnssec |= 0x2; + break; + case dns_rdatatype_nsec3: + auth_dnssec |= 0x4; + break; + case dns_rdatatype_rrsig: + break; + default: + continue; + } + + switch (rds->covers) { + case dns_rdatatype_ds: + auth_rrsig |= 0x1; + break; + case dns_rdatatype_nsec: + auth_rrsig |= 0x2; + break; + case dns_rdatatype_nsec3: + auth_rrsig |= 0x4; + break; + default: + break; + } + } + + if (auth_dnssec != auth_rrsig) { + return (false); + } + } + + return (true); +} + +static isc_result_t +getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_section_t sectionid, unsigned int options) { + isc_region_t r; + unsigned int count, rdatalen; + dns_name_t *name = NULL; + dns_name_t *name2 = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + isc_result_t result; + dns_rdatatype_t rdtype, covers; + dns_rdataclass_t rdclass; + dns_rdata_t *rdata = NULL; + dns_ttl_t ttl; + dns_namelist_t *section = &msg->sections[sectionid]; + bool free_name = false, free_rdataset = false, seen_problem = false; + bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0); + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool isedns, issigzero, istsig; + + for (count = 0; count < msg->counts[sectionid]; count++) { + int recstart = source->current; + bool skip_name_search, skip_type_search; + + skip_name_search = false; + skip_type_search = false; + free_rdataset = false; + isedns = false; + issigzero = false; + istsig = false; + + name = NULL; + result = dns_message_gettempname(msg, &name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + name->offsets = (unsigned char *)newoffsets(msg); + free_name = true; + + /* + * Parse the name out of this packet. + */ + isc_buffer_remainingregion(source, &r); + isc_buffer_setactive(source, r.length); + result = getname(name, source, msg, dctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Get type, class, ttl, and rdatalen. Verify that at least + * rdatalen bytes remain. (Some of this is deferred to + * later.) + */ + isc_buffer_remainingregion(source, &r); + if (r.length < 2 + 2 + 4 + 2) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + rdtype = isc_buffer_getuint16(source); + rdclass = isc_buffer_getuint16(source); + + /* + * If there was no question section, we may not yet have + * established a class. Do so now. + */ + if (msg->rdclass_set == 0 && + rdtype != dns_rdatatype_opt && /* class is UDP SIZE */ + rdtype != dns_rdatatype_tsig && /* class is ANY */ + rdtype != dns_rdatatype_tkey) + { /* class is undefined */ + msg->rdclass = rdclass; + msg->rdclass_set = 1; + } + + /* + * If this class is different than the one in the question + * section, bail. + */ + if (msg->opcode != dns_opcode_update && + rdtype != dns_rdatatype_tsig && + rdtype != dns_rdatatype_opt && + rdtype != dns_rdatatype_key && /* in a TKEY query */ + rdtype != dns_rdatatype_sig && /* SIG(0) */ + rdtype != dns_rdatatype_tkey && /* Win2000 TKEY */ + msg->rdclass != dns_rdataclass_any && + msg->rdclass != rdclass) + { + DO_ERROR(DNS_R_FORMERR); + } + + /* + * If this is not a TKEY query/response then the KEY + * record's class needs to match. + */ + if (msg->opcode != dns_opcode_update && !msg->tkey && + rdtype == dns_rdatatype_key && + msg->rdclass != dns_rdataclass_any && + msg->rdclass != rdclass) + { + DO_ERROR(DNS_R_FORMERR); + } + + /* + * Special type handling for TSIG, OPT, and TKEY. + */ + if (rdtype == dns_rdatatype_tsig) { + /* + * If it is a tsig, verify that it is in the + * additional data section. + */ + if (sectionid != DNS_SECTION_ADDITIONAL || + rdclass != dns_rdataclass_any || + count != msg->counts[sectionid] - 1) + { + DO_ERROR(DNS_R_BADTSIG); + } else { + skip_name_search = true; + skip_type_search = true; + istsig = true; + } + } else if (rdtype == dns_rdatatype_opt) { + /* + * The name of an OPT record must be ".", it + * must be in the additional data section, and + * it must be the first OPT we've seen. + */ + if (!dns_name_equal(dns_rootname, name) || + sectionid != DNS_SECTION_ADDITIONAL || + msg->opt != NULL) + { + DO_ERROR(DNS_R_FORMERR); + } else { + skip_name_search = true; + skip_type_search = true; + isedns = true; + } + } else if (rdtype == dns_rdatatype_tkey) { + /* + * A TKEY must be in the additional section if this + * is a query, and the answer section if this is a + * response. Unless it's a Win2000 client. + * + * Its class is ignored. + */ + dns_section_t tkeysection; + + if ((msg->flags & DNS_MESSAGEFLAG_QR) == 0) { + tkeysection = DNS_SECTION_ADDITIONAL; + } else { + tkeysection = DNS_SECTION_ANSWER; + } + if (sectionid != tkeysection && + sectionid != DNS_SECTION_ANSWER) + { + DO_ERROR(DNS_R_FORMERR); + } + } + + /* + * ... now get ttl and rdatalen, and check buffer. + */ + ttl = isc_buffer_getuint32(source); + rdatalen = isc_buffer_getuint16(source); + r.length -= (2 + 2 + 4 + 2); + if (r.length < rdatalen) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + /* + * Read the rdata from the wire format. Interpret the + * rdata according to its actual class, even if it had a + * DynDNS meta-class in the packet (unless this is a TSIG). + * Then put the meta-class back into the finished rdata. + */ + rdata = newrdata(msg); + if (rdata == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + if (msg->opcode == dns_opcode_update && + update(sectionid, rdclass)) + { + if (rdatalen != 0) { + result = DNS_R_FORMERR; + goto cleanup; + } + /* + * When the rdata is empty, the data pointer is + * never dereferenced, but it must still be non-NULL. + * Casting 1 rather than "" avoids warnings about + * discarding the const attribute of a string, + * for compilers that would warn about such things. + */ + rdata->data = (unsigned char *)1; + rdata->length = 0; + rdata->rdclass = rdclass; + rdata->type = rdtype; + rdata->flags = DNS_RDATA_UPDATE; + result = ISC_R_SUCCESS; + } else if (rdclass == dns_rdataclass_none && + msg->opcode == dns_opcode_update && + sectionid == DNS_SECTION_UPDATE) + { + result = getrdata(source, msg, dctx, msg->rdclass, + rdtype, rdatalen, rdata); + } else { + result = getrdata(source, msg, dctx, rdclass, rdtype, + rdatalen, rdata); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + rdata->rdclass = rdclass; + if (rdtype == dns_rdatatype_rrsig && rdata->flags == 0) { + covers = dns_rdata_covers(rdata); + if (covers == 0) { + DO_ERROR(DNS_R_FORMERR); + } + } else if (rdtype == dns_rdatatype_sig /* SIG(0) */ && + rdata->flags == 0) + { + covers = dns_rdata_covers(rdata); + if (covers == 0) { + if (sectionid != DNS_SECTION_ADDITIONAL || + count != msg->counts[sectionid] - 1 || + !dns_name_equal(name, dns_rootname)) + { + DO_ERROR(DNS_R_BADSIG0); + } else { + skip_name_search = true; + skip_type_search = true; + issigzero = true; + } + } else { + if (msg->rdclass != dns_rdataclass_any && + msg->rdclass != rdclass) + { + DO_ERROR(DNS_R_FORMERR); + } + } + } else { + covers = 0; + } + + /* + * Check the ownername of NSEC3 records + */ + if (rdtype == dns_rdatatype_nsec3 && + !dns_rdata_checkowner(name, msg->rdclass, rdtype, false)) + { + result = DNS_R_BADOWNERNAME; + goto cleanup; + } + + /* + * If we are doing a dynamic update or this is a meta-type, + * don't bother searching for a name, just append this one + * to the end of the message. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_name_search) + { + if (!isedns && !istsig && !issigzero) { + ISC_LIST_APPEND(*section, name, link); + free_name = false; + } + } else { + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the + * allocated name since we no longer need it, and set + * our name pointer to point to the name we found. + */ + result = findname(&name2, name, section); + + /* + * If it is a new name, append to the section. + */ + if (result == ISC_R_SUCCESS) { + dns_message_puttempname(msg, &name); + name = name2; + } else { + ISC_LIST_APPEND(*section, name, link); + } + free_name = false; + } + + /* + * Search name for the particular type and class. + * Skip this stage if in update mode or this is a meta-type. + */ + if (preserve_order || msg->opcode == dns_opcode_update || + skip_type_search) + { + result = ISC_R_NOTFOUND; + } else { + /* + * If this is a type that can only occur in + * the question section, fail. + */ + if (dns_rdatatype_questiononly(rdtype)) { + DO_ERROR(DNS_R_FORMERR); + } + + rdataset = NULL; + result = dns_message_find(name, rdclass, rdtype, covers, + &rdataset); + } + + /* + * If we found an rdataset that matches, we need to + * append this rdata to that set. If we did not, we need + * to create a new rdatalist, store the important bits there, + * convert it to an rdataset, and link the latter to the name. + * Yuck. When appending, make certain that the type isn't + * a singleton type, such as SOA or CNAME. + * + * Note that this check will be bypassed when preserving order, + * the opcode is an update, or the type search is skipped. + */ + if (result == ISC_R_SUCCESS) { + if (dns_rdatatype_issingleton(rdtype)) { + dns_rdata_t *first; + dns_rdatalist_fromrdataset(rdataset, + &rdatalist); + first = ISC_LIST_HEAD(rdatalist->rdata); + INSIST(first != NULL); + if (dns_rdata_compare(rdata, first) != 0) { + DO_ERROR(DNS_R_FORMERR); + } + } + } + + if (result == ISC_R_NOTFOUND) { + rdataset = isc_mempool_get(msg->rdspool); + free_rdataset = true; + + rdatalist = newrdatalist(msg); + if (rdatalist == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + rdatalist->type = rdtype; + rdatalist->covers = covers; + rdatalist->rdclass = rdclass; + rdatalist->ttl = ttl; + + dns_rdataset_init(rdataset); + RUNTIME_CHECK( + dns_rdatalist_tordataset(rdatalist, rdataset) == + ISC_R_SUCCESS); + dns_rdataset_setownercase(rdataset, name); + + if (!isedns && !istsig && !issigzero) { + ISC_LIST_APPEND(name->list, rdataset, link); + free_rdataset = false; + } + } + + /* + * Minimize TTLs. + * + * Section 5.2 of RFC2181 says we should drop + * nonauthoritative rrsets where the TTLs differ, but we + * currently treat them the as if they were authoritative and + * minimize them. + */ + if (ttl != rdataset->ttl) { + rdataset->attributes |= DNS_RDATASETATTR_TTLADJUSTED; + if (ttl < rdataset->ttl) { + rdataset->ttl = ttl; + } + } + + /* Append this rdata to the rdataset. */ + dns_rdatalist_fromrdataset(rdataset, &rdatalist); + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + + /* + * If this is an OPT, SIG(0) or TSIG record, remember it. + * Also, set the extended rcode for TSIG. + * + * Note msg->opt, msg->sig0 and msg->tsig will only be + * already set if best-effort parsing is enabled otherwise + * there will only be at most one of each. + */ + if (isedns) { + dns_rcode_t ercode; + + msg->opt = rdataset; + rdataset = NULL; + free_rdataset = false; + ercode = (dns_rcode_t)((msg->opt->ttl & + DNS_MESSAGE_EDNSRCODE_MASK) >> + 20); + msg->rcode |= ercode; + dns_message_puttempname(msg, &name); + free_name = false; + } else if (issigzero) { + msg->sig0 = rdataset; + msg->sig0name = name; + msg->sigstart = recstart; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } else if (istsig) { + msg->tsig = rdataset; + msg->tsigname = name; + msg->sigstart = recstart; + /* + * Windows doesn't like TSIG names to be compressed. + */ + msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } + + if (seen_problem) { + if (free_name) { + dns_message_puttempname(msg, &name); + } + if (free_rdataset) { + isc_mempool_put(msg->rdspool, rdataset); + } + free_name = free_rdataset = false; + } + INSIST(!free_name); + INSIST(!free_rdataset); + } + + /* + * If any of DS, NSEC or NSEC3 appeared in the + * authority section of a query response without + * a covering RRSIG, FORMERR + */ + if (sectionid == DNS_SECTION_AUTHORITY && + msg->opcode == dns_opcode_query && + ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) && + ((msg->flags & DNS_MESSAGEFLAG_TC) == 0) && !preserve_order && + !auth_signed(section)) + { + DO_ERROR(DNS_R_FORMERR); + } + + if (seen_problem) { + return (DNS_R_RECOVERABLE); + } + return (ISC_R_SUCCESS); + +cleanup: + if (free_name) { + dns_message_puttempname(msg, &name); + } + if (free_rdataset) { + isc_mempool_put(msg->rdspool, rdataset); + } + + return (result); +} + +isc_result_t +dns_message_parse(dns_message_t *msg, isc_buffer_t *source, + unsigned int options) { + isc_region_t r; + dns_decompress_t dctx; + isc_result_t ret; + uint16_t tmpflags; + isc_buffer_t origsource; + bool seen_problem; + bool ignore_tc; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(source != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + seen_problem = false; + ignore_tc = ((options & DNS_MESSAGEPARSE_IGNORETRUNCATION) != 0); + + origsource = *source; + + msg->header_ok = 0; + msg->question_ok = 0; + + if ((options & DNS_MESSAGEPARSE_CLONEBUFFER) == 0) { + isc_buffer_usedregion(&origsource, &msg->saved); + } else { + msg->saved.length = isc_buffer_usedlength(&origsource); + msg->saved.base = isc_mem_get(msg->mctx, msg->saved.length); + memmove(msg->saved.base, isc_buffer_base(&origsource), + msg->saved.length); + msg->free_saved = 1; + } + + isc_buffer_remainingregion(source, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) { + return (ISC_R_UNEXPECTEDEND); + } + + msg->id = isc_buffer_getuint16(source); + tmpflags = isc_buffer_getuint16(source); + msg->opcode = ((tmpflags & DNS_MESSAGE_OPCODE_MASK) >> + DNS_MESSAGE_OPCODE_SHIFT); + msg->rcode = (dns_rcode_t)(tmpflags & DNS_MESSAGE_RCODE_MASK); + msg->flags = (tmpflags & DNS_MESSAGE_FLAG_MASK); + msg->counts[DNS_SECTION_QUESTION] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ANSWER] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_AUTHORITY] = isc_buffer_getuint16(source); + msg->counts[DNS_SECTION_ADDITIONAL] = isc_buffer_getuint16(source); + + msg->header_ok = 1; + msg->state = DNS_SECTION_QUESTION; + + /* + * -1 means no EDNS. + */ + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY); + + dns_decompress_setmethods(&dctx, DNS_COMPRESS_GLOBAL14); + + ret = getquestions(source, msg, &dctx, options); + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + goto truncated; + } + if (ret == DNS_R_RECOVERABLE) { + seen_problem = true; + ret = ISC_R_SUCCESS; + } + if (ret != ISC_R_SUCCESS) { + return (ret); + } + msg->question_ok = 1; + + ret = getsection(source, msg, &dctx, DNS_SECTION_ANSWER, options); + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + goto truncated; + } + if (ret == DNS_R_RECOVERABLE) { + seen_problem = true; + ret = ISC_R_SUCCESS; + } + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + ret = getsection(source, msg, &dctx, DNS_SECTION_AUTHORITY, options); + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + goto truncated; + } + if (ret == DNS_R_RECOVERABLE) { + seen_problem = true; + ret = ISC_R_SUCCESS; + } + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + ret = getsection(source, msg, &dctx, DNS_SECTION_ADDITIONAL, options); + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + goto truncated; + } + if (ret == DNS_R_RECOVERABLE) { + seen_problem = true; + ret = ISC_R_SUCCESS; + } + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + isc_buffer_remainingregion(source, &r); + if (r.length != 0) { + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MESSAGE, ISC_LOG_DEBUG(3), + "message has %u byte(s) of trailing garbage", + r.length); + } + +truncated: + + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) { + return (DNS_R_RECOVERABLE); + } + if (seen_problem) { + return (DNS_R_RECOVERABLE); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_renderbegin(dns_message_t *msg, dns_compress_t *cctx, + isc_buffer_t *buffer) { + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(msg->buffer == NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + + msg->cctx = cctx; + + /* + * Erase the contents of this buffer. + */ + isc_buffer_clear(buffer); + + /* + * Make certain there is enough for at least the header in this + * buffer. + */ + isc_buffer_availableregion(buffer, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) { + return (ISC_R_NOSPACE); + } + + if (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved) { + return (ISC_R_NOSPACE); + } + + /* + * Reserve enough space for the header in this buffer. + */ + isc_buffer_add(buffer, DNS_MESSAGE_HEADERLEN); + + msg->buffer = buffer; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_renderchangebuffer(dns_message_t *msg, isc_buffer_t *buffer) { + isc_region_t r, rn; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(msg->buffer != NULL); + + /* + * Ensure that the new buffer is empty, and has enough space to + * hold the current contents. + */ + isc_buffer_clear(buffer); + + isc_buffer_availableregion(buffer, &rn); + isc_buffer_usedregion(msg->buffer, &r); + REQUIRE(rn.length > r.length); + + /* + * Copy the contents from the old to the new buffer. + */ + isc_buffer_add(buffer, r.length); + memmove(rn.base, r.base, r.length); + + msg->buffer = buffer; + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderrelease(dns_message_t *msg, unsigned int space) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(space <= msg->reserved); + + msg->reserved -= space; +} + +isc_result_t +dns_message_renderreserve(dns_message_t *msg, unsigned int space) { + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->buffer != NULL) { + isc_buffer_availableregion(msg->buffer, &r); + if (r.length < (space + msg->reserved)) { + return (ISC_R_NOSPACE); + } + } + + msg->reserved += space; + + return (ISC_R_SUCCESS); +} + +static bool +wrong_priority(dns_rdataset_t *rds, int pass, dns_rdatatype_t preferred_glue) { + int pass_needed; + + /* + * If we are not rendering class IN, this ordering is bogus. + */ + if (rds->rdclass != dns_rdataclass_in) { + return (false); + } + + switch (rds->type) { + case dns_rdatatype_a: + case dns_rdatatype_aaaa: + if (preferred_glue == rds->type) { + pass_needed = 4; + } else { + pass_needed = 3; + } + break; + case dns_rdatatype_rrsig: + case dns_rdatatype_dnskey: + pass_needed = 2; + break; + default: + pass_needed = 1; + } + + if (pass_needed >= pass) { + return (false); + } + + return (true); +} + +static isc_result_t +renderset(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_compress_t *cctx, isc_buffer_t *target, unsigned int reserved, + unsigned int options, unsigned int *countp) { + isc_result_t result; + + /* + * Shrink the space in the buffer by the reserved amount. + */ + if (target->length - target->used < reserved) { + return (ISC_R_NOSPACE); + } + + target->length -= reserved; + result = dns_rdataset_towire(rdataset, owner_name, cctx, target, + options, countp); + target->length += reserved; + + return (result); +} + +static void +maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) { + if (msg->counts[sectionid] == 0 && + (sectionid == DNS_SECTION_ANSWER || + (sectionid == DNS_SECTION_AUTHORITY && + msg->counts[DNS_SECTION_ANSWER] == 0))) + { + msg->flags &= ~DNS_MESSAGEFLAG_AD; + } +} + +static void +update_min_section_ttl(dns_message_t *restrict msg, + const dns_section_t sectionid, + dns_rdataset_t *restrict rdataset) { + if (!msg->minttl[sectionid].is_set || + rdataset->ttl < msg->minttl[sectionid].ttl) + { + msg->minttl[sectionid].is_set = true; + msg->minttl[sectionid].ttl = rdataset->ttl; + } +} + +isc_result_t +dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid, + unsigned int options) { + dns_namelist_t *section; + dns_name_t *name, *next_name; + dns_rdataset_t *rdataset, *next_rdataset; + unsigned int count, total; + isc_result_t result; + isc_buffer_t st; /* for rollbacks */ + int pass; + bool partial = false; + unsigned int rd_options; + dns_rdatatype_t preferred_glue = 0; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + REQUIRE(VALID_NAMED_SECTION(sectionid)); + + section = &msg->sections[sectionid]; + + if ((sectionid == DNS_SECTION_ADDITIONAL) && + (options & DNS_MESSAGERENDER_ORDERED) == 0) + { + if ((options & DNS_MESSAGERENDER_PREFER_A) != 0) { + preferred_glue = dns_rdatatype_a; + pass = 4; + } else if ((options & DNS_MESSAGERENDER_PREFER_AAAA) != 0) { + preferred_glue = dns_rdatatype_aaaa; + pass = 4; + } else { + pass = 3; + } + } else { + pass = 1; + } + + if ((options & DNS_MESSAGERENDER_OMITDNSSEC) == 0) { + rd_options = 0; + } else { + rd_options = DNS_RDATASETTOWIRE_OMITDNSSEC; + } + + /* + * Shrink the space in the buffer by the reserved amount. + */ + if (msg->buffer->length - msg->buffer->used < msg->reserved) { + return (ISC_R_NOSPACE); + } + msg->buffer->length -= msg->reserved; + + total = 0; + if (msg->reserved == 0 && (options & DNS_MESSAGERENDER_PARTIAL) != 0) { + partial = true; + } + + /* + * Render required glue first. Set TC if it won't fit. + */ + name = ISC_LIST_HEAD(*section); + if (name != NULL) { + rdataset = ISC_LIST_HEAD(name->list); + if (rdataset != NULL && + (rdataset->attributes & DNS_RDATASETATTR_REQUIREDGLUE) != + 0 && + (rdataset->attributes & DNS_RDATASETATTR_RENDERED) == 0) + { + const void *order_arg = &msg->order_arg; + st = *(msg->buffer); + count = 0; + if (partial) { + result = dns_rdataset_towirepartial( + rdataset, name, msg->cctx, msg->buffer, + msg->order, order_arg, rd_options, + &count, NULL); + } else { + result = dns_rdataset_towiresorted( + rdataset, name, msg->cctx, msg->buffer, + msg->order, order_arg, rd_options, + &count); + } + total += count; + if (partial && result == ISC_R_NOSPACE) { + msg->flags |= DNS_MESSAGEFLAG_TC; + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + if (result == ISC_R_NOSPACE) { + msg->flags |= DNS_MESSAGEFLAG_TC; + } + if (result != ISC_R_SUCCESS) { + INSIST(st.used < 65536); + dns_compress_rollback(msg->cctx, + (uint16_t)st.used); + *(msg->buffer) = st; /* rollback */ + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + + update_min_section_ttl(msg, sectionid, rdataset); + + rdataset->attributes |= DNS_RDATASETATTR_RENDERED; + } + } + + do { + name = ISC_LIST_HEAD(*section); + if (name == NULL) { + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (ISC_R_SUCCESS); + } + + while (name != NULL) { + next_name = ISC_LIST_NEXT(name, link); + + rdataset = ISC_LIST_HEAD(name->list); + while (rdataset != NULL) { + next_rdataset = ISC_LIST_NEXT(rdataset, link); + + if ((rdataset->attributes & + DNS_RDATASETATTR_RENDERED) != 0) + { + goto next; + } + + if (((options & DNS_MESSAGERENDER_ORDERED) == + 0) && + (sectionid == DNS_SECTION_ADDITIONAL) && + wrong_priority(rdataset, pass, + preferred_glue)) + { + goto next; + } + + st = *(msg->buffer); + + count = 0; + if (partial) { + result = dns_rdataset_towirepartial( + rdataset, name, msg->cctx, + msg->buffer, msg->order, + &msg->order_arg, rd_options, + &count, NULL); + } else { + result = dns_rdataset_towiresorted( + rdataset, name, msg->cctx, + msg->buffer, msg->order, + &msg->order_arg, rd_options, + &count); + } + + total += count; + + /* + * If out of space, record stats on what we + * rendered so far, and return that status. + * + * XXXMLG Need to change this when + * dns_rdataset_towire() can render partial + * sets starting at some arbitrary point in the + * set. This will include setting a bit in the + * rdataset to indicate that a partial + * rendering was done, and some state saved + * somewhere (probably in the message struct) + * to indicate where to continue from. + */ + if (partial && result == ISC_R_NOSPACE) { + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + return (result); + } + if (result != ISC_R_SUCCESS) { + INSIST(st.used < 65536); + dns_compress_rollback( + msg->cctx, (uint16_t)st.used); + *(msg->buffer) = st; /* rollback */ + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + maybe_clear_ad(msg, sectionid); + return (result); + } + + /* + * If we have rendered non-validated data, + * ensure that the AD bit is not set. + */ + if (rdataset->trust != dns_trust_secure && + (sectionid == DNS_SECTION_ANSWER || + sectionid == DNS_SECTION_AUTHORITY)) + { + msg->flags &= ~DNS_MESSAGEFLAG_AD; + } + if (OPTOUT(rdataset)) { + msg->flags &= ~DNS_MESSAGEFLAG_AD; + } + + update_min_section_ttl(msg, sectionid, + rdataset); + + rdataset->attributes |= + DNS_RDATASETATTR_RENDERED; + + next: + rdataset = next_rdataset; + } + + name = next_name; + } + } while (--pass != 0); + + msg->buffer->length += msg->reserved; + msg->counts[sectionid] += total; + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) { + uint16_t tmp; + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + + isc_buffer_availableregion(target, &r); + REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN); + + isc_buffer_putuint16(target, msg->id); + + tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT) & + DNS_MESSAGE_OPCODE_MASK); + tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK); + tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK); + + INSIST(msg->counts[DNS_SECTION_QUESTION] < 65536 && + msg->counts[DNS_SECTION_ANSWER] < 65536 && + msg->counts[DNS_SECTION_AUTHORITY] < 65536 && + msg->counts[DNS_SECTION_ADDITIONAL] < 65536); + + isc_buffer_putuint16(target, tmp); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_QUESTION]); + isc_buffer_putuint16(target, (uint16_t)msg->counts[DNS_SECTION_ANSWER]); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_AUTHORITY]); + isc_buffer_putuint16(target, + (uint16_t)msg->counts[DNS_SECTION_ADDITIONAL]); +} + +isc_result_t +dns_message_renderend(dns_message_t *msg) { + isc_buffer_t tmpbuf; + isc_region_t r; + int result; + unsigned int count; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + + if ((msg->rcode & ~DNS_MESSAGE_RCODE_MASK) != 0 && msg->opt == NULL) { + /* + * We have an extended rcode but are not using EDNS. + */ + return (DNS_R_FORMERR); + } + + /* + * If we're adding a OPT, TSIG or SIG(0) to a truncated message, + * clear all rdatasets from the message except for the question + * before adding the OPT, TSIG or SIG(0). If the question doesn't + * fit, don't include it. + */ + if ((msg->tsigkey != NULL || msg->sig0key != NULL || msg->opt) && + (msg->flags & DNS_MESSAGEFLAG_TC) != 0) + { + isc_buffer_t *buf; + + msgresetnames(msg, DNS_SECTION_ANSWER); + buf = msg->buffer; + dns_message_renderreset(msg); + msg->buffer = buf; + isc_buffer_clear(msg->buffer); + isc_buffer_add(msg->buffer, DNS_MESSAGE_HEADERLEN); + dns_compress_rollback(msg->cctx, 0); + result = dns_message_rendersection(msg, DNS_SECTION_QUESTION, + 0); + if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE) { + return (result); + } + } + + /* + * If we've got an OPT record, render it. + */ + if (msg->opt != NULL) { + dns_message_renderrelease(msg, msg->opt_reserved); + msg->opt_reserved = 0; + /* + * Set the extended rcode. Cast msg->rcode to dns_ttl_t + * so that we do a unsigned shift. + */ + msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK; + msg->opt->ttl |= (((dns_ttl_t)(msg->rcode) << 20) & + DNS_MESSAGE_EDNSRCODE_MASK); + /* + * Render. + */ + count = 0; + result = renderset(msg->opt, dns_rootname, msg->cctx, + msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* + * Deal with EDNS padding. + * + * padding_off is the length of the OPT with the 0-length PAD + * at the end. + */ + if (msg->padding_off > 0) { + unsigned char *cp = isc_buffer_used(msg->buffer); + unsigned int used, remaining; + uint16_t len, padsize = 0; + + /* Check PAD */ + if ((cp[-4] != 0) || (cp[-3] != DNS_OPT_PAD) || (cp[-2] != 0) || + (cp[-1] != 0)) + { + return (ISC_R_UNEXPECTED); + } + + /* + * Zero-fill the PAD to the computed size; + * patch PAD length and OPT rdlength + */ + + /* Aligned used length + reserved to padding block */ + used = isc_buffer_usedlength(msg->buffer); + if (msg->padding != 0) { + padsize = ((uint16_t)used + msg->reserved) % + msg->padding; + } + if (padsize != 0) { + padsize = msg->padding - padsize; + } + /* Stay below the available length */ + remaining = isc_buffer_availablelength(msg->buffer); + if (padsize > remaining) { + padsize = remaining; + } + + isc_buffer_add(msg->buffer, padsize); + memset(cp, 0, padsize); + cp[-2] = (unsigned char)((padsize & 0xff00U) >> 8); + cp[-1] = (unsigned char)(padsize & 0x00ffU); + cp -= msg->padding_off; + len = ((uint16_t)(cp[-2])) << 8; + len |= ((uint16_t)(cp[-1])); + len += padsize; + cp[-2] = (unsigned char)((len & 0xff00U) >> 8); + cp[-1] = (unsigned char)(len & 0x00ffU); + } + + /* + * If we're adding a TSIG record, generate and render it. + */ + if (msg->tsigkey != NULL) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + result = dns_tsig_sign(msg); + if (result != ISC_R_SUCCESS) { + return (result); + } + count = 0; + result = renderset(msg->tsig, msg->tsigname, msg->cctx, + msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* + * If we're adding a SIG(0) record, generate and render it. + */ + if (msg->sig0key != NULL) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + result = dns_dnssec_signmessage(msg, msg->sig0key); + if (result != ISC_R_SUCCESS) { + return (result); + } + count = 0; + /* + * Note: dns_rootname is used here, not msg->sig0name, since + * the owner name of a SIG(0) is irrelevant, and will not + * be set in a message being rendered. + */ + result = renderset(msg->sig0, dns_rootname, msg->cctx, + msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + isc_buffer_usedregion(msg->buffer, &r); + isc_buffer_init(&tmpbuf, r.base, r.length); + + dns_message_renderheader(msg, &tmpbuf); + + msg->buffer = NULL; /* forget about this buffer only on success XXX */ + + return (ISC_R_SUCCESS); +} + +void +dns_message_renderreset(dns_message_t *msg) { + unsigned int i; + dns_name_t *name; + dns_rdataset_t *rds; + + /* + * Reset the message so that it may be rendered again. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + + msg->buffer = NULL; + + for (i = 0; i < DNS_SECTION_MAX; i++) { + msg->cursors[i] = NULL; + msg->counts[i] = 0; + for (name = ISC_LIST_HEAD(msg->sections[i]); name != NULL; + name = ISC_LIST_NEXT(name, link)) + { + for (rds = ISC_LIST_HEAD(name->list); rds != NULL; + rds = ISC_LIST_NEXT(rds, link)) + { + rds->attributes &= ~DNS_RDATASETATTR_RENDERED; + } + } + } + if (msg->tsigname != NULL) { + dns_message_puttempname(msg, &msg->tsigname); + } + if (msg->tsig != NULL) { + dns_rdataset_disassociate(msg->tsig); + dns_message_puttemprdataset(msg, &msg->tsig); + } + if (msg->sig0name != NULL) { + dns_message_puttempname(msg, &msg->sig0name); + } + if (msg->sig0 != NULL) { + dns_rdataset_disassociate(msg->sig0); + dns_message_puttemprdataset(msg, &msg->sig0); + } +} + +isc_result_t +dns_message_firstname(dns_message_t *msg, dns_section_t section) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + + msg->cursors[section] = ISC_LIST_HEAD(msg->sections[section]); + + if (msg->cursors[section] == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_nextname(dns_message_t *msg, dns_section_t section) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + REQUIRE(msg->cursors[section] != NULL); + + msg->cursors[section] = ISC_LIST_NEXT(msg->cursors[section], link); + + if (msg->cursors[section] == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +void +dns_message_currentname(dns_message_t *msg, dns_section_t section, + dns_name_t **name) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(VALID_NAMED_SECTION(section)); + REQUIRE(name != NULL && *name == NULL); + REQUIRE(msg->cursors[section] != NULL); + + *name = msg->cursors[section]; +} + +isc_result_t +dns_message_findname(dns_message_t *msg, dns_section_t section, + const dns_name_t *target, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_name_t **name, + dns_rdataset_t **rdataset) { + dns_name_t *foundname; + isc_result_t result; + + /* + * XXX These requirements are probably too intensive, especially + * where things can be NULL, but as they are they ensure that if + * something is NON-NULL, indicating that the caller expects it + * to be filled in, that we can in fact fill it in. + */ + REQUIRE(msg != NULL); + REQUIRE(VALID_SECTION(section)); + REQUIRE(target != NULL); + REQUIRE(name == NULL || *name == NULL); + + if (type == dns_rdatatype_any) { + REQUIRE(rdataset == NULL); + } else { + REQUIRE(rdataset == NULL || *rdataset == NULL); + } + + result = findname(&foundname, target, &msg->sections[section]); + + if (result == ISC_R_NOTFOUND) { + return (DNS_R_NXDOMAIN); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + if (name != NULL) { + *name = foundname; + } + + /* + * And now look for the type. + */ + if (type == dns_rdatatype_any) { + return (ISC_R_SUCCESS); + } + + result = dns_message_findtype(foundname, type, covers, rdataset); + if (result == ISC_R_NOTFOUND) { + return (DNS_R_NXRRSET); + } + + return (result); +} + +void +dns_message_movename(dns_message_t *msg, dns_name_t *name, + dns_section_t fromsection, dns_section_t tosection) { + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(fromsection)); + REQUIRE(VALID_NAMED_SECTION(tosection)); + + /* + * Unlink the name from the old section + */ + ISC_LIST_UNLINK(msg->sections[fromsection], name, link); + ISC_LIST_APPEND(msg->sections[tosection], name, link); +} + +void +dns_message_addname(dns_message_t *msg, dns_name_t *name, + dns_section_t section) { + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(section)); + + ISC_LIST_APPEND(msg->sections[section], name, link); +} + +void +dns_message_removename(dns_message_t *msg, dns_name_t *name, + dns_section_t section) { + REQUIRE(msg != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(name != NULL); + REQUIRE(VALID_NAMED_SECTION(section)); + + ISC_LIST_UNLINK(msg->sections[section], name, link); +} + +isc_result_t +dns_message_gettempname(dns_message_t *msg, dns_name_t **item) { + dns_fixedname_t *fn = NULL; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + fn = isc_mempool_get(msg->namepool); + *item = dns_fixedname_initname(fn); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdata(dns_message_t *msg, dns_rdata_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newrdata(msg); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdataset(dns_message_t *msg, dns_rdataset_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = isc_mempool_get(msg->rdspool); + dns_rdataset_init(*item); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newrdatalist(msg); + return (ISC_R_SUCCESS); +} + +void +dns_message_puttempname(dns_message_t *msg, dns_name_t **itemp) { + dns_name_t *item = NULL; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(itemp != NULL && *itemp != NULL); + + item = *itemp; + *itemp = NULL; + + REQUIRE(!ISC_LINK_LINKED(item, link)); + REQUIRE(ISC_LIST_HEAD(item->list) == NULL); + + /* + * we need to check this in case dns_name_dup() was used. + */ + if (dns_name_dynamic(item)) { + dns_name_free(item, msg->mctx); + } + + /* + * 'name' is the first field in dns_fixedname_t, so putting + * back the address of name is the same as putting back + * the fixedname. + */ + isc_mempool_put(msg->namepool, item); +} + +void +dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdata(msg, *item); + *item = NULL; +} + +void +dns_message_puttemprdataset(dns_message_t *msg, dns_rdataset_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + REQUIRE(!dns_rdataset_isassociated(*item)); + isc_mempool_put(msg->rdspool, *item); + *item = NULL; +} + +void +dns_message_puttemprdatalist(dns_message_t *msg, dns_rdatalist_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + releaserdatalist(msg, *item); + *item = NULL; +} + +isc_result_t +dns_message_peekheader(isc_buffer_t *source, dns_messageid_t *idp, + unsigned int *flagsp) { + isc_region_t r; + isc_buffer_t buffer; + dns_messageid_t id; + unsigned int flags; + + REQUIRE(source != NULL); + + buffer = *source; + + isc_buffer_remainingregion(&buffer, &r); + if (r.length < DNS_MESSAGE_HEADERLEN) { + return (ISC_R_UNEXPECTEDEND); + } + + id = isc_buffer_getuint16(&buffer); + flags = isc_buffer_getuint16(&buffer); + flags &= DNS_MESSAGE_FLAG_MASK; + + if (flagsp != NULL) { + *flagsp = flags; + } + if (idp != NULL) { + *idp = id; + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_reply(dns_message_t *msg, bool want_question_section) { + unsigned int clear_from; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE((msg->flags & DNS_MESSAGEFLAG_QR) == 0); + + if (!msg->header_ok) { + return (DNS_R_FORMERR); + } + if (msg->opcode != dns_opcode_query && msg->opcode != dns_opcode_notify) + { + want_question_section = false; + } + if (msg->opcode == dns_opcode_update) { + clear_from = DNS_SECTION_PREREQUISITE; + } else if (want_question_section) { + if (!msg->question_ok) { + return (DNS_R_FORMERR); + } + clear_from = DNS_SECTION_ANSWER; + } else { + clear_from = DNS_SECTION_QUESTION; + } + msg->from_to_wire = DNS_MESSAGE_INTENTRENDER; + msgresetnames(msg, clear_from); + msgresetopt(msg); + msgresetsigs(msg, true); + msginitprivate(msg); + /* + * We now clear most flags and then set QR, ensuring that the + * reply's flags will be in a reasonable state. + */ + if (msg->opcode == dns_opcode_query) { + msg->flags &= DNS_MESSAGE_REPLYPRESERVE; + } else { + msg->flags = 0; + } + msg->flags |= DNS_MESSAGEFLAG_QR; + + /* + * This saves the query TSIG status, if the query was signed, and + * reserves space in the reply for the TSIG. + */ + if (msg->tsigkey != NULL) { + unsigned int otherlen = 0; + msg->querytsigstatus = msg->tsigstatus; + msg->tsigstatus = dns_rcode_noerror; + if (msg->querytsigstatus == dns_tsigerror_badtime) { + otherlen = 6; + } + msg->sig_reserved = spacefortsig(msg->tsigkey, otherlen); + result = dns_message_renderreserve(msg, msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + } + if (msg->saved.base != NULL) { + msg->query.base = msg->saved.base; + msg->query.length = msg->saved.length; + msg->free_query = msg->free_saved; + msg->saved.base = NULL; + msg->saved.length = 0; + msg->free_saved = 0; + } + + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getopt(dns_message_t *msg) { + /* + * Get the OPT record for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->opt); +} + +isc_result_t +dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * Set the OPT record for 'msg'. + */ + + /* + * The space required for an OPT record is: + * + * 1 byte for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the rdata length + * --------------------------------- + * 11 bytes + * + * plus the length of the rdata. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(opt->type == dns_rdatatype_opt); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(msg->state == DNS_SECTION_ANY); + + msgresetopt(msg); + + result = dns_rdataset_first(opt); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + dns_rdataset_current(opt, &rdata); + msg->opt_reserved = 11 + rdata.length; + result = dns_message_renderreserve(msg, msg->opt_reserved); + if (result != ISC_R_SUCCESS) { + msg->opt_reserved = 0; + goto cleanup; + } + + msg->opt = opt; + + return (ISC_R_SUCCESS); + +cleanup: + dns_rdataset_disassociate(opt); + dns_message_puttemprdataset(msg, &opt); + return (result); +} + +dns_rdataset_t * +dns_message_gettsig(dns_message_t *msg, const dns_name_t **owner) { + /* + * Get the TSIG record and owner for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(owner == NULL || *owner == NULL); + + if (owner != NULL) { + *owner = msg->tsigname; + } + return (msg->tsig); +} + +isc_result_t +dns_message_settsigkey(dns_message_t *msg, dns_tsigkey_t *key) { + isc_result_t result; + + /* + * Set the TSIG key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (key == NULL && msg->tsigkey != NULL) { + if (msg->sig_reserved != 0) { + dns_message_renderrelease(msg, msg->sig_reserved); + msg->sig_reserved = 0; + } + dns_tsigkey_detach(&msg->tsigkey); + } + if (key != NULL) { + REQUIRE(msg->tsigkey == NULL && msg->sig0key == NULL); + dns_tsigkey_attach(key, &msg->tsigkey); + if (msg->from_to_wire == DNS_MESSAGE_INTENTRENDER) { + msg->sig_reserved = spacefortsig(msg->tsigkey, 0); + result = dns_message_renderreserve(msg, + msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + dns_tsigkey_detach(&msg->tsigkey); + msg->sig_reserved = 0; + return (result); + } + } + } + return (ISC_R_SUCCESS); +} + +dns_tsigkey_t * +dns_message_gettsigkey(dns_message_t *msg) { + /* + * Get the TSIG key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->tsigkey); +} + +isc_result_t +dns_message_setquerytsig(dns_message_t *msg, isc_buffer_t *querytsig) { + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *list = NULL; + dns_rdataset_t *set = NULL; + isc_buffer_t *buf = NULL; + isc_region_t r; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->querytsig == NULL); + + if (querytsig == NULL) { + return (ISC_R_SUCCESS); + } + + result = dns_message_gettemprdata(msg, &rdata); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_gettemprdatalist(msg, &list); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_gettemprdataset(msg, &set); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_buffer_usedregion(querytsig, &r); + isc_buffer_allocate(msg->mctx, &buf, r.length); + isc_buffer_putmem(buf, r.base, r.length); + isc_buffer_usedregion(buf, &r); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_tsig, &r); + dns_message_takebuffer(msg, &buf); + ISC_LIST_APPEND(list->rdata, rdata, link); + result = dns_rdatalist_tordataset(list, set); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + msg->querytsig = set; + + return (result); + +cleanup: + if (rdata != NULL) { + dns_message_puttemprdata(msg, &rdata); + } + if (list != NULL) { + dns_message_puttemprdatalist(msg, &list); + } + if (set != NULL) { + dns_message_puttemprdataset(msg, &set); + } + return (ISC_R_NOMEMORY); +} + +isc_result_t +dns_message_getquerytsig(dns_message_t *msg, isc_mem_t *mctx, + isc_buffer_t **querytsig) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(mctx != NULL); + REQUIRE(querytsig != NULL && *querytsig == NULL); + + if (msg->tsig == NULL) { + return (ISC_R_SUCCESS); + } + + result = dns_rdataset_first(msg->tsig); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdataset_current(msg->tsig, &rdata); + dns_rdata_toregion(&rdata, &r); + + isc_buffer_allocate(mctx, querytsig, r.length); + isc_buffer_putmem(*querytsig, r.base, r.length); + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getsig0(dns_message_t *msg, const dns_name_t **owner) { + /* + * Get the SIG(0) record for 'msg'. + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(owner == NULL || *owner == NULL); + + if (msg->sig0 != NULL && owner != NULL) { + /* If dns_message_getsig0 is called on a rendered message + * after the SIG(0) has been applied, we need to return the + * root name, not NULL. + */ + if (msg->sig0name == NULL) { + *owner = dns_rootname; + } else { + *owner = msg->sig0name; + } + } + return (msg->sig0); +} + +isc_result_t +dns_message_setsig0key(dns_message_t *msg, dst_key_t *key) { + isc_region_t r; + unsigned int x; + isc_result_t result; + + /* + * Set the SIG(0) key for 'msg' + */ + + /* + * The space required for an SIG(0) record is: + * + * 1 byte for the name + * 2 bytes for the type + * 2 bytes for the class + * 4 bytes for the ttl + * 2 bytes for the type covered + * 1 byte for the algorithm + * 1 bytes for the labels + * 4 bytes for the original ttl + * 4 bytes for the signature expiration + * 4 bytes for the signature inception + * 2 bytes for the key tag + * n bytes for the signer's name + * x bytes for the signature + * --------------------------------- + * 27 + n + x bytes + */ + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); + REQUIRE(msg->state == DNS_SECTION_ANY); + + if (key != NULL) { + REQUIRE(msg->sig0key == NULL && msg->tsigkey == NULL); + dns_name_toregion(dst_key_name(key), &r); + result = dst_key_sigsize(key, &x); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + msg->sig_reserved = 27 + r.length + x; + result = dns_message_renderreserve(msg, msg->sig_reserved); + if (result != ISC_R_SUCCESS) { + msg->sig_reserved = 0; + return (result); + } + msg->sig0key = key; + } + return (ISC_R_SUCCESS); +} + +dst_key_t * +dns_message_getsig0key(dns_message_t *msg) { + /* + * Get the SIG(0) key for 'msg' + */ + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + return (msg->sig0key); +} + +void +dns_message_takebuffer(dns_message_t *msg, isc_buffer_t **buffer) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(buffer != NULL); + REQUIRE(ISC_BUFFER_VALID(*buffer)); + + ISC_LIST_APPEND(msg->cleanup, *buffer, link); + *buffer = NULL; +} + +isc_result_t +dns_message_signer(dns_message_t *msg, dns_name_t *signer) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata = DNS_RDATA_INIT; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(signer != NULL); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + + if (msg->tsig == NULL && msg->sig0 == NULL) { + return (ISC_R_NOTFOUND); + } + + if (msg->verify_attempted == 0) { + return (DNS_R_NOTVERIFIEDYET); + } + + if (!dns_name_hasbuffer(signer)) { + isc_buffer_t *dynbuf = NULL; + isc_buffer_allocate(msg->mctx, &dynbuf, 512); + dns_name_setbuffer(signer, dynbuf); + dns_message_takebuffer(msg, &dynbuf); + } + + if (msg->sig0 != NULL) { + dns_rdata_sig_t sig; + + result = dns_rdataset_first(msg->sig0); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->sig0, &rdata); + + result = dns_rdata_tostruct(&rdata, &sig, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (msg->verified_sig && msg->sig0status == dns_rcode_noerror) { + result = ISC_R_SUCCESS; + } else { + result = DNS_R_SIGINVALID; + } + dns_name_clone(&sig.signer, signer); + dns_rdata_freestruct(&sig); + } else { + const dns_name_t *identity; + dns_rdata_any_tsig_t tsig; + + result = dns_rdataset_first(msg->tsig); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->tsig, &rdata); + + result = dns_rdata_tostruct(&rdata, &tsig, NULL); + INSIST(result == ISC_R_SUCCESS); + if (msg->verified_sig && msg->tsigstatus == dns_rcode_noerror && + tsig.error == dns_rcode_noerror) + { + result = ISC_R_SUCCESS; + } else if ((!msg->verified_sig) || + (msg->tsigstatus != dns_rcode_noerror)) + { + result = DNS_R_TSIGVERIFYFAILURE; + } else { + INSIST(tsig.error != dns_rcode_noerror); + result = DNS_R_TSIGERRORSET; + } + dns_rdata_freestruct(&tsig); + + if (msg->tsigkey == NULL) { + /* + * If msg->tsigstatus & tsig.error are both + * dns_rcode_noerror, the message must have been + * verified, which means msg->tsigkey will be + * non-NULL. + */ + INSIST(result != ISC_R_SUCCESS); + } else { + identity = dns_tsigkey_identity(msg->tsigkey); + if (identity == NULL) { + if (result == ISC_R_SUCCESS) { + result = DNS_R_NOIDENTITY; + } + identity = &msg->tsigkey->name; + } + dns_name_clone(identity, signer); + } + } + + return (result); +} + +void +dns_message_resetsig(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->verified_sig = 0; + msg->verify_attempted = 0; + msg->tsigstatus = dns_rcode_noerror; + msg->sig0status = dns_rcode_noerror; + msg->timeadjust = 0; + if (msg->tsigkey != NULL) { + dns_tsigkey_detach(&msg->tsigkey); + msg->tsigkey = NULL; + } +} + +isc_result_t +dns_message_rechecksig(dns_message_t *msg, dns_view_t *view) { + dns_message_resetsig(msg); + return (dns_message_checksig(msg, view)); +} + +#ifdef SKAN_MSG_DEBUG +void +dns_message_dumpsig(dns_message_t *msg, char *txt1) { + dns_rdata_t querytsigrdata = DNS_RDATA_INIT; + dns_rdata_any_tsig_t querytsig; + isc_result_t result; + + if (msg->tsig != NULL) { + result = dns_rdataset_first(msg->tsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->tsig, &querytsigrdata); + result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + hexdump(txt1, "TSIG", querytsig.signature, querytsig.siglen); + } + + if (msg->querytsig != NULL) { + result = dns_rdataset_first(msg->querytsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->querytsig, &querytsigrdata); + result = dns_rdata_tostruct(&querytsigrdata, &querytsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + hexdump(txt1, "QUERYTSIG", querytsig.signature, + querytsig.siglen); + } +} +#endif /* ifdef SKAN_MSG_DEBUG */ + +isc_result_t +dns_message_checksig(dns_message_t *msg, dns_view_t *view) { + isc_buffer_t b, msgb; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->tsigkey == NULL && msg->tsig == NULL && msg->sig0 == NULL) { + return (ISC_R_SUCCESS); + } + + INSIST(msg->saved.base != NULL); + isc_buffer_init(&msgb, msg->saved.base, msg->saved.length); + isc_buffer_add(&msgb, msg->saved.length); + if (msg->tsigkey != NULL || msg->tsig != NULL) { +#ifdef SKAN_MSG_DEBUG + dns_message_dumpsig(msg, "dns_message_checksig#1"); +#endif /* ifdef SKAN_MSG_DEBUG */ + if (view != NULL) { + return (dns_view_checksig(view, &msgb, msg)); + } else { + return (dns_tsig_verify(&msgb, msg, NULL, NULL)); + } + } else { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_sig_t sig; + dns_rdataset_t keyset; + isc_result_t result; + + result = dns_rdataset_first(msg->sig0); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(msg->sig0, &rdata); + + /* + * This can occur when the message is a dynamic update, since + * the rdata length checking is relaxed. This should not + * happen in a well-formed message, since the SIG(0) is only + * looked for in the additional section, and the dynamic update + * meta-records are in the prerequisite and update sections. + */ + if (rdata.length == 0) { + return (ISC_R_UNEXPECTEDEND); + } + + result = dns_rdata_tostruct(&rdata, &sig, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&keyset); + if (view == NULL) { + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } + result = dns_view_simplefind(view, &sig.signer, + dns_rdatatype_key /* SIG(0) */, 0, + 0, false, &keyset, NULL); + + if (result != ISC_R_SUCCESS) { + /* XXXBEW Should possibly create a fetch here */ + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } else if (keyset.trust < dns_trust_secure) { + /* XXXBEW Should call a validator here */ + result = DNS_R_KEYUNAUTHORIZED; + goto freesig; + } + result = dns_rdataset_first(&keyset); + INSIST(result == ISC_R_SUCCESS); + for (; result == ISC_R_SUCCESS; + result = dns_rdataset_next(&keyset)) + { + dst_key_t *key = NULL; + + dns_rdata_reset(&rdata); + dns_rdataset_current(&keyset, &rdata); + isc_buffer_init(&b, rdata.data, rdata.length); + isc_buffer_add(&b, rdata.length); + + result = dst_key_fromdns(&sig.signer, rdata.rdclass, &b, + view->mctx, &key); + if (result != ISC_R_SUCCESS) { + continue; + } + if (dst_key_alg(key) != sig.algorithm || + dst_key_id(key) != sig.keyid || + !(dst_key_proto(key) == DNS_KEYPROTO_DNSSEC || + dst_key_proto(key) == DNS_KEYPROTO_ANY)) + { + dst_key_free(&key); + continue; + } + result = dns_dnssec_verifymessage(&msgb, msg, key); + dst_key_free(&key); + if (result == ISC_R_SUCCESS) { + break; + } + } + if (result == ISC_R_NOMORE) { + result = DNS_R_KEYUNAUTHORIZED; + } + + freesig: + if (dns_rdataset_isassociated(&keyset)) { + dns_rdataset_disassociate(&keyset); + } + dns_rdata_freestruct(&sig); + return (result); + } +} + +#define INDENT(sp) \ + do { \ + unsigned int __i; \ + dns_masterstyle_flags_t __flags = dns_master_styleflags(sp); \ + if ((__flags & DNS_STYLEFLAG_INDENT) == 0ULL && \ + (__flags & DNS_STYLEFLAG_YAML) == 0ULL) \ + break; \ + for (__i = 0; __i < msg->indent.count; __i++) { \ + ADD_STRING(target, msg->indent.string); \ + } \ + } while (0) + +isc_result_t +dns_message_sectiontotext(dns_message_t *msg, dns_section_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target) { + dns_name_t *name, empty_name; + dns_rdataset_t *rdataset; + isc_result_t result = ISC_R_SUCCESS; + bool seensoa = false; + size_t saved_count; + dns_masterstyle_flags_t sflags; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_SECTION(section)); + + saved_count = msg->indent.count; + + if (ISC_LIST_EMPTY(msg->sections[section])) { + goto cleanup; + } + + sflags = dns_master_styleflags(style); + + INDENT(style); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, sectiontext[section]); + } else { + ADD_STRING(target, updsectiontext[section]); + } + ADD_STRING(target, "_SECTION:\n"); + } else if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) { + ADD_STRING(target, ";; "); + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, sectiontext[section]); + } else { + ADD_STRING(target, updsectiontext[section]); + } + ADD_STRING(target, " SECTION:\n"); + } + + dns_name_init(&empty_name, NULL); + result = dns_message_firstname(msg, section); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + msg->indent.count++; + } + do { + name = NULL; + dns_message_currentname(msg, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (section == DNS_SECTION_ANSWER && + rdataset->type == dns_rdatatype_soa) + { + if ((flags & DNS_MESSAGETEXTFLAG_OMITSOA) != 0) + { + continue; + } + if (seensoa && + (flags & DNS_MESSAGETEXTFLAG_ONESOA) != 0) + { + continue; + } + seensoa = true; + } + if (section == DNS_SECTION_QUESTION) { + INDENT(style); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + ADD_STRING(target, "- "); + } else { + ADD_STRING(target, ";"); + } + result = dns_master_questiontotext( + name, rdataset, style, target); + } else { + result = dns_master_rdatasettotext( + name, rdataset, style, &msg->indent, + target); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = dns_message_nextname(msg, section); + } while (result == ISC_R_SUCCESS); + if ((sflags & DNS_STYLEFLAG_YAML) != 0) { + msg->indent.count--; + } + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0 && + (sflags & DNS_STYLEFLAG_YAML) == 0) + { + INDENT(style); + ADD_STRING(target, "\n"); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + msg->indent.count = saved_count; + return (result); +} + +static isc_result_t +render_ecs(isc_buffer_t *ecsbuf, isc_buffer_t *target) { + int i; + char addr[16], addr_text[64]; + uint16_t family; + uint8_t addrlen, addrbytes, scopelen; + isc_result_t result; + + /* + * Note: This routine needs to handle malformed ECS options. + */ + + if (isc_buffer_remaininglength(ecsbuf) < 4) { + return (DNS_R_OPTERR); + } + family = isc_buffer_getuint16(ecsbuf); + addrlen = isc_buffer_getuint8(ecsbuf); + scopelen = isc_buffer_getuint8(ecsbuf); + + addrbytes = (addrlen + 7) / 8; + if (isc_buffer_remaininglength(ecsbuf) < addrbytes) { + return (DNS_R_OPTERR); + } + + if (addrbytes > sizeof(addr)) { + return (DNS_R_OPTERR); + } + + memset(addr, 0, sizeof(addr)); + for (i = 0; i < addrbytes; i++) { + addr[i] = isc_buffer_getuint8(ecsbuf); + } + + switch (family) { + case 0: + if (addrlen != 0U || scopelen != 0U) { + return (DNS_R_OPTERR); + } + strlcpy(addr_text, "0", sizeof(addr_text)); + break; + case 1: + if (addrlen > 32 || scopelen > 32) { + return (DNS_R_OPTERR); + } + inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text)); + break; + case 2: + if (addrlen > 128 || scopelen > 128) { + return (DNS_R_OPTERR); + } + inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text)); + break; + default: + return (DNS_R_OPTERR); + } + + ADD_STRING(target, " "); + ADD_STRING(target, addr_text); + snprintf(addr_text, sizeof(addr_text), "/%d/%d", addrlen, scopelen); + ADD_STRING(target, addr_text); + + result = ISC_R_SUCCESS; + +cleanup: + return (result); +} + +static isc_result_t +render_llq(isc_buffer_t *optbuf, isc_buffer_t *target) { + char buf[sizeof("18446744073709551615")]; /* 2^64-1 */ + isc_result_t result = ISC_R_SUCCESS; + uint32_t u; + uint64_t q; + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, " Version: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, ", Opcode: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + u = isc_buffer_getuint16(optbuf); + ADD_STRING(target, ", Error: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); + + q = isc_buffer_getuint32(optbuf); + q <<= 32; + q |= isc_buffer_getuint32(optbuf); + ADD_STRING(target, ", Identifier: "); + snprintf(buf, sizeof(buf), "%" PRIu64, q); + ADD_STRING(target, buf); + + u = isc_buffer_getuint32(optbuf); + ADD_STRING(target, ", Lifetime: "); + snprintf(buf, sizeof(buf), "%u", u); + ADD_STRING(target, buf); +cleanup: + return (result); +} + +static isc_result_t +dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, + isc_buffer_t *target) { + dns_rdataset_t *ps = NULL; + const dns_name_t *name = NULL; + isc_result_t result = ISC_R_SUCCESS; + char buf[sizeof("1234567890")]; + uint32_t mbz; + dns_rdata_t rdata; + isc_buffer_t optbuf; + uint16_t optcode, optlen; + size_t saved_count; + unsigned char *optdata; + unsigned int indent; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_PSEUDOSECTION(section)); + + saved_count = msg->indent.count; + + switch (section) { + case DNS_PSEUDOSECTION_OPT: + ps = dns_message_getopt(msg); + if (ps == NULL) { + goto cleanup; + } + + INDENT(style); + ADD_STRING(target, "OPT_PSEUDOSECTION:\n"); + msg->indent.count++; + + INDENT(style); + ADD_STRING(target, "EDNS:\n"); + indent = ++msg->indent.count; + + INDENT(style); + ADD_STRING(target, "version: "); + snprintf(buf, sizeof(buf), "%u", + (unsigned int)((ps->ttl & 0x00ff0000) >> 16)); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "flags:"); + if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) { + ADD_STRING(target, " do"); + } + ADD_STRING(target, "\n"); + mbz = ps->ttl & 0xffff; + mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */ + if (mbz != 0) { + INDENT(style); + ADD_STRING(target, "MBZ: "); + snprintf(buf, sizeof(buf), "0x%.4x", mbz); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } + INDENT(style); + ADD_STRING(target, "udp: "); + snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass); + ADD_STRING(target, buf); + result = dns_rdataset_first(ps); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* + * Print EDNS info, if any. + * + * WARNING: The option contents may be malformed as + * dig +ednsopt=value: does not perform validity + * checking. + */ + dns_rdata_init(&rdata); + dns_rdataset_current(ps, &rdata); + + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) != 0) { + bool extra_text = false; + msg->indent.count = indent; + INSIST(isc_buffer_remaininglength(&optbuf) >= 4U); + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(isc_buffer_remaininglength(&optbuf) >= optlen); + + if (optcode == DNS_OPT_LLQ) { + INDENT(style); + ADD_STRING(target, "LLQ:"); + if (optlen == 18U) { + result = render_llq(&optbuf, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_NSID) { + INDENT(style); + ADD_STRING(target, "NSID:"); + } else if (optcode == DNS_OPT_COOKIE) { + INDENT(style); + ADD_STRING(target, "COOKIE:"); + } else if (optcode == DNS_OPT_CLIENT_SUBNET) { + isc_buffer_t ecsbuf; + INDENT(style); + ADD_STRING(target, "CLIENT-SUBNET:"); + isc_buffer_init(&ecsbuf, + isc_buffer_current(&optbuf), + optlen); + isc_buffer_add(&ecsbuf, optlen); + result = render_ecs(&ecsbuf, target); + if (result == ISC_R_NOSPACE) { + goto cleanup; + } + if (result == ISC_R_SUCCESS) { + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, "\n"); + continue; + } + ADD_STRING(target, "\n"); + } else if (optcode == DNS_OPT_EXPIRE) { + INDENT(style); + ADD_STRING(target, "EXPIRE:"); + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ADD_STRING(target, ")\n"); + continue; + } + } else if (optcode == DNS_OPT_TCP_KEEPALIVE) { + if (optlen == 2) { + unsigned int dsecs; + dsecs = isc_buffer_getuint16(&optbuf); + INDENT(style); + ADD_STRING(target, "TCP-KEEPALIVE: "); + snprintf(buf, sizeof(buf), "%u.%u", + dsecs / 10U, dsecs % 10U); + ADD_STRING(target, buf); + ADD_STRING(target, " secs\n"); + continue; + } + INDENT(style); + ADD_STRING(target, "TCP-KEEPALIVE:"); + } else if (optcode == DNS_OPT_PAD) { + INDENT(style); + ADD_STRING(target, "PAD:"); + } else if (optcode == DNS_OPT_KEY_TAG) { + INDENT(style); + ADD_STRING(target, "KEY-TAG:"); + if (optlen > 0U && (optlen % 2U) == 0U) { + const char *sep = ""; + uint16_t id; + while (optlen > 0U) { + id = isc_buffer_getuint16( + &optbuf); + snprintf(buf, sizeof(buf), + "%s %u", sep, id); + ADD_STRING(target, buf); + sep = ","; + optlen -= 2; + } + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_EDE) { + INDENT(style); + ADD_STRING(target, "EDE:"); + if (optlen >= 2U) { + uint16_t ede; + ADD_STRING(target, "\n"); + msg->indent.count++; + INDENT(style); + ADD_STRING(target, "INFO-CODE:"); + ede = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u", ede); + ADD_STRING(target, buf); + if (ede < ARRAY_SIZE(edetext)) { + ADD_STRING(target, " ("); + ADD_STRING(target, + edetext[ede]); + ADD_STRING(target, ")"); + } + ADD_STRING(target, "\n"); + optlen -= 2; + if (optlen != 0) { + INDENT(style); + ADD_STRING(target, + "EXTRA-TEXT:"); + extra_text = true; + } + } + } else if (optcode == DNS_OPT_CLIENT_TAG) { + uint16_t id; + INDENT(style); + ADD_STRING(target, "CLIENT-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else if (optcode == DNS_OPT_SERVER_TAG) { + uint16_t id; + INDENT(style); + ADD_STRING(target, "SERVER-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else { + INDENT(style); + ADD_STRING(target, "OPT="); + snprintf(buf, sizeof(buf), "%u:", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + bool utf8ok = false; + + ADD_STRING(target, " "); + + optdata = isc_buffer_current(&optbuf); + if (extra_text) { + utf8ok = isc_utf8_valid(optdata, + optlen); + } + if (!utf8ok) { + for (i = 0; i < optlen; i++) { + const char *sep; + switch (optcode) { + case DNS_OPT_COOKIE: + sep = ""; + break; + default: + sep = " "; + break; + } + snprintf(buf, sizeof(buf), + "%02x%s", optdata[i], + sep); + ADD_STRING(target, buf); + } + } + + isc_buffer_forward(&optbuf, optlen); + + if (optcode == DNS_OPT_COOKIE) { + /* + * Valid server cookie? + */ + if (msg->cc_ok && optlen >= 16) { + ADD_STRING(target, " (good)"); + } + /* + * Server cookie is not valid but + * we had our cookie echoed back. + */ + if (msg->cc_ok && optlen < 16) { + ADD_STRING(target, " (echoed)"); + } + /* + * We didn't get our cookie echoed + * back. + */ + if (msg->cc_bad) { + ADD_STRING(target, " (bad)"); + } + ADD_STRING(target, "\n"); + continue; + } + + if (optcode == DNS_OPT_CLIENT_SUBNET) { + ADD_STRING(target, "\n"); + continue; + } + + /* + * For non-COOKIE options, add a printable + * version + */ + if (!extra_text) { + ADD_STRING(target, "(\""); + } else { + ADD_STRING(target, "\""); + } + if (isc_buffer_availablelength(target) < optlen) + { + result = ISC_R_NOSPACE; + goto cleanup; + } + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i]) || + (utf8ok && optdata[i] > 127)) + { + isc_buffer_putmem( + target, &optdata[i], 1); + } else { + isc_buffer_putstr(target, "."); + } + } + if (!extra_text) { + ADD_STRING(target, "\")"); + } else { + ADD_STRING(target, "\""); + } + } + ADD_STRING(target, "\n"); + } + msg->indent.count = indent; + result = ISC_R_SUCCESS; + goto cleanup; + case DNS_PSEUDOSECTION_TSIG: + ps = dns_message_gettsig(msg, &name); + if (ps == NULL) { + result = ISC_R_SUCCESS; + goto cleanup; + } + INDENT(style); + ADD_STRING(target, "TSIG_PSEUDOSECTION:\n"); + result = dns_master_rdatasettotext(name, ps, style, + &msg->indent, target); + ADD_STRING(target, "\n"); + goto cleanup; + case DNS_PSEUDOSECTION_SIG0: + ps = dns_message_getsig0(msg, &name); + if (ps == NULL) { + result = ISC_R_SUCCESS; + goto cleanup; + } + INDENT(style); + ADD_STRING(target, "SIG0_PSEUDOSECTION:\n"); + result = dns_master_rdatasettotext(name, ps, style, + &msg->indent, target); + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) + { + ADD_STRING(target, "\n"); + } + goto cleanup; + } + + result = ISC_R_UNEXPECTED; + +cleanup: + msg->indent.count = saved_count; + return (result); +} + +isc_result_t +dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, + const dns_master_style_t *style, + dns_messagetextflag_t flags, + isc_buffer_t *target) { + dns_rdataset_t *ps = NULL; + const dns_name_t *name = NULL; + isc_result_t result; + char buf[sizeof(" (65000 bytes)")]; + uint32_t mbz; + dns_rdata_t rdata; + isc_buffer_t optbuf; + uint16_t optcode, optlen; + unsigned char *optdata; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_PSEUDOSECTION(section)); + + if ((dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) != 0) { + return (dns_message_pseudosectiontoyaml(msg, section, style, + flags, target)); + } + + switch (section) { + case DNS_PSEUDOSECTION_OPT: + ps = dns_message_getopt(msg); + if (ps == NULL) { + return (ISC_R_SUCCESS); + } + if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) { + INDENT(style); + ADD_STRING(target, ";; OPT PSEUDOSECTION:\n"); + } + + INDENT(style); + ADD_STRING(target, "; EDNS: version: "); + snprintf(buf, sizeof(buf), "%u", + (unsigned int)((ps->ttl & 0x00ff0000) >> 16)); + ADD_STRING(target, buf); + ADD_STRING(target, ", flags:"); + if ((ps->ttl & DNS_MESSAGEEXTFLAG_DO) != 0) { + ADD_STRING(target, " do"); + } + mbz = ps->ttl & 0xffff; + mbz &= ~DNS_MESSAGEEXTFLAG_DO; /* Known Flags. */ + if (mbz != 0) { + ADD_STRING(target, "; MBZ: "); + snprintf(buf, sizeof(buf), "0x%.4x", mbz); + ADD_STRING(target, buf); + ADD_STRING(target, ", udp: "); + } else { + ADD_STRING(target, "; udp: "); + } + snprintf(buf, sizeof(buf), "%u\n", (unsigned int)ps->rdclass); + ADD_STRING(target, buf); + + result = dns_rdataset_first(ps); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + /* + * Print EDNS info, if any. + * + * WARNING: The option contents may be malformed as + * dig +ednsopt=value: does no validity + * checking. + */ + dns_rdata_init(&rdata); + dns_rdataset_current(ps, &rdata); + + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) != 0) { + INSIST(isc_buffer_remaininglength(&optbuf) >= 4U); + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + + INSIST(isc_buffer_remaininglength(&optbuf) >= optlen); + + INDENT(style); + + if (optcode == DNS_OPT_LLQ) { + ADD_STRING(target, "; LLQ:"); + if (optlen == 18U) { + result = render_llq(&optbuf, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_NSID) { + ADD_STRING(target, "; NSID:"); + } else if (optcode == DNS_OPT_COOKIE) { + ADD_STRING(target, "; COOKIE:"); + } else if (optcode == DNS_OPT_CLIENT_SUBNET) { + isc_buffer_t ecsbuf; + + ADD_STRING(target, "; CLIENT-SUBNET:"); + isc_buffer_init(&ecsbuf, + isc_buffer_current(&optbuf), + optlen); + isc_buffer_add(&ecsbuf, optlen); + result = render_ecs(&ecsbuf, target); + if (result == ISC_R_NOSPACE) { + return (result); + } + if (result == ISC_R_SUCCESS) { + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_EXPIRE) { + ADD_STRING(target, "; EXPIRE:"); + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, ")\n"); + continue; + } + } else if (optcode == DNS_OPT_TCP_KEEPALIVE) { + ADD_STRING(target, "; TCP KEEPALIVE:"); + if (optlen == 2) { + unsigned int dsecs; + dsecs = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u.%u", + dsecs / 10U, dsecs % 10U); + ADD_STRING(target, buf); + ADD_STRING(target, " secs\n"); + continue; + } + } else if (optcode == DNS_OPT_PAD) { + ADD_STRING(target, "; PAD:"); + if (optlen > 0U) { + snprintf(buf, sizeof(buf), + " (%u bytes)", optlen); + ADD_STRING(target, buf); + isc_buffer_forward(&optbuf, optlen); + } + ADD_STRING(target, "\n"); + continue; + } else if (optcode == DNS_OPT_KEY_TAG) { + ADD_STRING(target, "; KEY-TAG:"); + if (optlen > 0U && (optlen % 2U) == 0U) { + const char *sep = ""; + uint16_t id; + while (optlen > 0U) { + id = isc_buffer_getuint16( + &optbuf); + snprintf(buf, sizeof(buf), + "%s %u", sep, id); + ADD_STRING(target, buf); + sep = ","; + optlen -= 2; + } + ADD_STRING(target, "\n"); + continue; + } + } else if (optcode == DNS_OPT_EDE) { + ADD_STRING(target, "; EDE:"); + if (optlen >= 2U) { + uint16_t ede; + ede = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u", ede); + ADD_STRING(target, buf); + if (ede < ARRAY_SIZE(edetext)) { + ADD_STRING(target, " ("); + ADD_STRING(target, + edetext[ede]); + ADD_STRING(target, ")"); + } + optlen -= 2; + if (optlen != 0) { + ADD_STRING(target, ":"); + } + } else if (optlen == 1U) { + /* Malformed */ + optdata = isc_buffer_current(&optbuf); + snprintf(buf, sizeof(buf), + " %02x (\"%c\")\n", optdata[0], + isprint(optdata[0]) + ? optdata[0] + : '.'); + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, buf); + continue; + } + } else if (optcode == DNS_OPT_CLIENT_TAG) { + uint16_t id; + ADD_STRING(target, "; CLIENT-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else if (optcode == DNS_OPT_SERVER_TAG) { + uint16_t id; + ADD_STRING(target, "; SERVER-TAG:"); + if (optlen == 2U) { + id = isc_buffer_getuint16(&optbuf); + snprintf(buf, sizeof(buf), " %u\n", id); + ADD_STRING(target, buf); + optlen -= 2; + POST(optlen); + continue; + } + } else { + ADD_STRING(target, "; OPT="); + snprintf(buf, sizeof(buf), "%u:", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + bool utf8ok = false; + + ADD_STRING(target, " "); + + optdata = isc_buffer_current(&optbuf); + if (optcode == DNS_OPT_EDE) { + utf8ok = isc_utf8_valid(optdata, + optlen); + } + if (!utf8ok) { + for (i = 0; i < optlen; i++) { + const char *sep; + switch (optcode) { + case DNS_OPT_COOKIE: + sep = ""; + break; + default: + sep = " "; + break; + } + snprintf(buf, sizeof(buf), + "%02x%s", optdata[i], + sep); + ADD_STRING(target, buf); + } + } + + isc_buffer_forward(&optbuf, optlen); + + if (optcode == DNS_OPT_COOKIE) { + /* + * Valid server cookie? + */ + if (msg->cc_ok && optlen >= 16) { + ADD_STRING(target, " (good)"); + } + /* + * Server cookie is not valid but + * we had our cookie echoed back. + */ + if (msg->cc_ok && optlen < 16) { + ADD_STRING(target, " (echoed)"); + } + /* + * We didn't get our cookie echoed + * back. + */ + if (msg->cc_bad) { + ADD_STRING(target, " (bad)"); + } + ADD_STRING(target, "\n"); + continue; + } + + if (optcode == DNS_OPT_CLIENT_SUBNET) { + ADD_STRING(target, "\n"); + continue; + } + + /* + * For non-COOKIE options, add a printable + * version. + */ + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "(\""); + } else { + ADD_STRING(target, "("); + } + if (isc_buffer_availablelength(target) < optlen) + { + return (ISC_R_NOSPACE); + } + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i]) || + (utf8ok && optdata[i] > 127)) + { + isc_buffer_putmem( + target, &optdata[i], 1); + } else { + isc_buffer_putstr(target, "."); + } + } + if (optcode != DNS_OPT_EDE) { + ADD_STRING(target, "\")"); + } else { + ADD_STRING(target, ")"); + } + } + ADD_STRING(target, "\n"); + } + return (ISC_R_SUCCESS); + case DNS_PSEUDOSECTION_TSIG: + ps = dns_message_gettsig(msg, &name); + if (ps == NULL) { + return (ISC_R_SUCCESS); + } + INDENT(style); + if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) { + ADD_STRING(target, ";; TSIG PSEUDOSECTION:\n"); + } + result = dns_master_rdatasettotext(name, ps, style, + &msg->indent, target); + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) + { + ADD_STRING(target, "\n"); + } + return (result); + case DNS_PSEUDOSECTION_SIG0: + ps = dns_message_getsig0(msg, &name); + if (ps == NULL) { + return (ISC_R_SUCCESS); + } + INDENT(style); + if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) { + ADD_STRING(target, ";; SIG0 PSEUDOSECTION:\n"); + } + result = dns_master_rdatasettotext(name, ps, style, + &msg->indent, target); + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) + { + ADD_STRING(target, "\n"); + } + return (result); + } + result = ISC_R_UNEXPECTED; +cleanup: + return (result); +} + +isc_result_t +dns_message_headertotext(dns_message_t *msg, const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target) { + char buf[sizeof("1234567890")]; + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) != 0) { + return (ISC_R_SUCCESS); + } + + if (dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) { + INDENT(style); + ADD_STRING(target, "opcode: "); + ADD_STRING(target, opcodetext[msg->opcode]); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "status: "); + result = dns_rcode_totext(msg->rcode, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "id: "); + snprintf(buf, sizeof(buf), "%u", msg->id); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "flags:"); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) { + ADD_STRING(target, " qr"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) { + ADD_STRING(target, " aa"); + } + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + ADD_STRING(target, " tc"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) { + ADD_STRING(target, " rd"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) { + ADD_STRING(target, " ra"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) { + ADD_STRING(target, " ad"); + } + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) { + ADD_STRING(target, " cd"); + } + ADD_STRING(target, "\n"); + /* + * The final unnamed flag must be zero. + */ + if ((msg->flags & 0x0040U) != 0) { + INDENT(style); + ADD_STRING(target, "MBZ: 0x4"); + ADD_STRING(target, "\n"); + } + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "QUESTION: "); + } else { + INDENT(style); + ADD_STRING(target, "ZONE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_QUESTION]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "ANSWER: "); + } else { + INDENT(style); + ADD_STRING(target, "PREREQ: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ANSWER]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "AUTHORITY: "); + } else { + INDENT(style); + ADD_STRING(target, "UPDATE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_AUTHORITY]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, "ADDITIONAL: "); + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ADDITIONAL]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } else { + INDENT(style); + ADD_STRING(target, ";; ->>HEADER<<- opcode: "); + ADD_STRING(target, opcodetext[msg->opcode]); + ADD_STRING(target, ", status: "); + result = dns_rcode_totext(msg->rcode, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + ADD_STRING(target, ", id: "); + snprintf(buf, sizeof(buf), "%6u", msg->id); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + INDENT(style); + ADD_STRING(target, ";; flags:"); + if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) { + ADD_STRING(target, " qr"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) { + ADD_STRING(target, " aa"); + } + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + ADD_STRING(target, " tc"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) { + ADD_STRING(target, " rd"); + } + if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) { + ADD_STRING(target, " ra"); + } + if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) { + ADD_STRING(target, " ad"); + } + if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) { + ADD_STRING(target, " cd"); + } + /* + * The final unnamed flag must be zero. + */ + if ((msg->flags & 0x0040U) != 0) { + INDENT(style); + ADD_STRING(target, "; MBZ: 0x4"); + } + if (msg->opcode != dns_opcode_update) { + INDENT(style); + ADD_STRING(target, "; QUESTION: "); + } else { + INDENT(style); + ADD_STRING(target, "; ZONE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_QUESTION]); + ADD_STRING(target, buf); + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, ", ANSWER: "); + } else { + ADD_STRING(target, ", PREREQ: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ANSWER]); + ADD_STRING(target, buf); + if (msg->opcode != dns_opcode_update) { + ADD_STRING(target, ", AUTHORITY: "); + } else { + ADD_STRING(target, ", UPDATE: "); + } + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_AUTHORITY]); + ADD_STRING(target, buf); + ADD_STRING(target, ", ADDITIONAL: "); + snprintf(buf, sizeof(buf), "%1u", + msg->counts[DNS_SECTION_ADDITIONAL]); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } + +cleanup: + return (result); +} + +isc_result_t +dns_message_totext(dns_message_t *msg, const dns_master_style_t *style, + dns_messagetextflag_t flags, isc_buffer_t *target) { + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + + result = dns_message_headertotext(msg, style, flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_OPT, + style, flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_sectiontotext(msg, DNS_SECTION_QUESTION, style, + flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_sectiontotext(msg, DNS_SECTION_ANSWER, style, + flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_sectiontotext(msg, DNS_SECTION_AUTHORITY, style, + flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_sectiontotext(msg, DNS_SECTION_ADDITIONAL, style, + flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_TSIG, + style, flags, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_message_pseudosectiontotext(msg, DNS_PSEUDOSECTION_SIG0, + style, flags, target); + return (result); +} + +isc_region_t * +dns_message_getrawmessage(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + return (&msg->saved); +} + +void +dns_message_setsortorder(dns_message_t *msg, dns_rdatasetorderfunc_t order, + dns_aclenv_t *env, dns_acl_t *acl, + const dns_aclelement_t *elem) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE((order == NULL) == (env == NULL)); + REQUIRE(env == NULL || (acl != NULL || elem != NULL)); + + msg->order = order; + if (env != NULL) { + dns_aclenv_attach(env, &msg->order_arg.env); + } + if (acl != NULL) { + dns_acl_attach(acl, &msg->order_arg.acl); + } + msg->order_arg.element = elem; +} + +void +dns_message_settimeadjust(dns_message_t *msg, int timeadjust) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->timeadjust = timeadjust; +} + +int +dns_message_gettimeadjust(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + return (msg->timeadjust); +} + +isc_result_t +dns_opcode_totext(dns_opcode_t opcode, isc_buffer_t *target) { + REQUIRE(opcode < 16); + + if (isc_buffer_availablelength(target) < strlen(opcodetext[opcode])) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(target, opcodetext[opcode]); + return (ISC_R_SUCCESS); +} + +void +dns_message_logpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx) { + REQUIRE(address != NULL); + + logfmtpacket(message, description, address, category, module, + &dns_master_style_debug, level, mctx); +} + +void +dns_message_logfmtpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx) { + REQUIRE(address != NULL); + + logfmtpacket(message, description, address, category, module, style, + level, mctx); +} + +static void +logfmtpacket(dns_message_t *message, const char *description, + const isc_sockaddr_t *address, isc_logcategory_t *category, + isc_logmodule_t *module, const dns_master_style_t *style, + int level, isc_mem_t *mctx) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + const char *newline = "\n"; + const char *space = " "; + isc_buffer_t buffer; + char *buf = NULL; + int len = 1024; + isc_result_t result; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + /* + * Note that these are multiline debug messages. We want a newline + * to appear in the log after each message. + */ + + if (address != NULL) { + isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); + } else { + newline = space = ""; + } + + do { + buf = isc_mem_get(mctx, len); + isc_buffer_init(&buffer, buf, len); + result = dns_message_totext(message, style, 0, &buffer); + if (result == ISC_R_NOSPACE) { + isc_mem_put(mctx, buf, len); + len += 1024; + } else if (result == ISC_R_SUCCESS) { + isc_log_write(dns_lctx, category, module, level, + "%s%s%s%s%.*s", description, space, + addrbuf, newline, + (int)isc_buffer_usedlength(&buffer), buf); + } + } while (result == ISC_R_NOSPACE); + + if (buf != NULL) { + isc_mem_put(mctx, buf, len); + } +} + +isc_result_t +dns_message_buildopt(dns_message_t *message, dns_rdataset_t **rdatasetp, + unsigned int version, uint16_t udpsize, unsigned int flags, + dns_ednsopt_t *ednsopts, size_t count) { + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; + dns_rdata_t *rdata = NULL; + isc_result_t result; + unsigned int len = 0, i; + + REQUIRE(DNS_MESSAGE_VALID(message)); + REQUIRE(rdatasetp != NULL && *rdatasetp == NULL); + + result = dns_message_gettemprdatalist(message, &rdatalist); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_message_gettemprdata(message, &rdata); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_gettemprdataset(message, &rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + rdatalist->type = dns_rdatatype_opt; + + /* + * Set Maximum UDP buffer size. + */ + rdatalist->rdclass = udpsize; + + /* + * Set EXTENDED-RCODE and Z to 0. + */ + rdatalist->ttl = (version << 16); + rdatalist->ttl |= (flags & 0xffff); + + /* + * Set EDNS options if applicable + */ + if (count != 0U) { + isc_buffer_t *buf = NULL; + bool seenpad = false; + for (i = 0; i < count; i++) { + len += ednsopts[i].length + 4; + } + + if (len > 0xffffU) { + result = ISC_R_NOSPACE; + goto cleanup; + } + + isc_buffer_allocate(message->mctx, &buf, len); + + for (i = 0; i < count; i++) { + if (ednsopts[i].code == DNS_OPT_PAD && + ednsopts[i].length == 0U && !seenpad) + { + seenpad = true; + continue; + } + isc_buffer_putuint16(buf, ednsopts[i].code); + isc_buffer_putuint16(buf, ednsopts[i].length); + if (ednsopts[i].length != 0) { + isc_buffer_putmem(buf, ednsopts[i].value, + ednsopts[i].length); + } + } + + /* Padding must be the final option */ + if (seenpad) { + isc_buffer_putuint16(buf, DNS_OPT_PAD); + isc_buffer_putuint16(buf, 0); + } + rdata->data = isc_buffer_base(buf); + rdata->length = len; + dns_message_takebuffer(message, &buf); + if (seenpad) { + message->padding_off = len; + } + } else { + rdata->data = NULL; + rdata->length = 0; + } + + rdata->rdclass = rdatalist->rdclass; + rdata->type = rdatalist->type; + rdata->flags = 0; + + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + result = dns_rdatalist_tordataset(rdatalist, rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + *rdatasetp = rdataset; + return (ISC_R_SUCCESS); + +cleanup: + if (rdata != NULL) { + dns_message_puttemprdata(message, &rdata); + } + if (rdataset != NULL) { + dns_message_puttemprdataset(message, &rdataset); + } + if (rdatalist != NULL) { + dns_message_puttemprdatalist(message, &rdatalist); + } + return (result); +} + +void +dns_message_setclass(dns_message_t *msg, dns_rdataclass_t rdclass) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTPARSE); + REQUIRE(msg->state == DNS_SECTION_ANY); + REQUIRE(msg->rdclass_set == 0); + + msg->rdclass = rdclass; + msg->rdclass_set = 1; +} + +void +dns_message_setpadding(dns_message_t *msg, uint16_t padding) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + + /* Avoid silly large padding */ + if (padding > 512) { + padding = 512; + } + msg->padding = padding; +} + +void +dns_message_clonebuffer(dns_message_t *msg) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + + if (msg->free_saved == 0 && msg->saved.base != NULL) { + msg->saved.base = + memmove(isc_mem_get(msg->mctx, msg->saved.length), + msg->saved.base, msg->saved.length); + msg->free_saved = 1; + } + if (msg->free_query == 0 && msg->query.base != NULL) { + msg->query.base = + memmove(isc_mem_get(msg->mctx, msg->query.length), + msg->query.base, msg->query.length); + msg->free_query = 1; + } +} + +static isc_result_t +message_authority_soa_min(dns_message_t *msg, dns_ttl_t *pttl) { + isc_result_t result; + dns_rdataset_t *rdataset = NULL; + dns_name_t *name = NULL; + + if (msg->counts[DNS_SECTION_AUTHORITY] == 0) { + return (ISC_R_NOTFOUND); + } + + for (result = dns_message_firstname(msg, DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(msg, DNS_SECTION_AUTHORITY)) + { + name = NULL; + dns_message_currentname(msg, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + isc_result_t tresult; + + if ((rdataset->attributes & + DNS_RDATASETATTR_RENDERED) == 0) + { + continue; + } + + /* loop over the rdatas */ + for (tresult = dns_rdataset_first(rdataset); + tresult == ISC_R_SUCCESS; + tresult = dns_rdataset_next(rdataset)) + { + dns_name_t tmp; + isc_region_t r = { 0 }; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(rdataset, &rdata); + + switch (rdata.type) { + case dns_rdatatype_soa: + /* SOA rdataset */ + break; + case dns_rdatatype_none: + /* + * Negative cache rdataset: we need + * to inspect the rdata to determine + * whether it's an SOA. + */ + dns_rdata_toregion(&rdata, &r); + dns_name_init(&tmp, NULL); + dns_name_fromregion(&tmp, &r); + isc_region_consume(&r, tmp.length); + if (r.length < 2) { + continue; + } + rdata.type = r.base[0] << 8 | r.base[1]; + if (rdata.type != dns_rdatatype_soa) { + continue; + } + break; + default: + continue; + } + + if (rdata.type == dns_rdatatype_soa) { + *pttl = ISC_MIN( + rdataset->ttl, + dns_soa_getminimum(&rdata)); + return (ISC_R_SUCCESS); + } + } + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid, + dns_ttl_t *pttl) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(pttl != NULL); + + if (!msg->minttl[sectionid].is_set) { + return (ISC_R_NOTFOUND); + } + + *pttl = msg->minttl[sectionid].ttl; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) { + isc_result_t result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(pttl != NULL); + + result = dns_message_minttl(msg, DNS_SECTION_ANSWER, pttl); + if (result != ISC_R_SUCCESS) { + return (message_authority_soa_min(msg, pttl)); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/name.c b/lib/dns/name.c new file mode 100644 index 0000000..8a258a2 --- /dev/null +++ b/lib/dns/name.c @@ -0,0 +1,2652 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define VALID_NAME(n) ISC_MAGIC_VALID(n, DNS_NAME_MAGIC) + +typedef enum { + ft_init = 0, + ft_start, + ft_ordinary, + ft_initialescape, + ft_escape, + ft_escdecimal, + ft_at +} ft_state; + +typedef enum { fw_start = 0, fw_ordinary, fw_newcurrent } fw_state; + +static char digitvalue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*16*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*32*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*48*/ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /*64*/ + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*80*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*96*/ + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*112*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*128*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*256*/ +}; + +static unsigned char maptolower[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff +}; + +#define CONVERTTOASCII(c) +#define CONVERTFROMASCII(c) + +#define INIT_OFFSETS(name, var, default_offsets) \ + if ((name)->offsets != NULL) \ + var = (name)->offsets; \ + else \ + var = (default_offsets); + +#define SETUP_OFFSETS(name, var, default_offsets) \ + if ((name)->offsets != NULL) { \ + var = (name)->offsets; \ + } else { \ + var = (default_offsets); \ + set_offsets(name, var, NULL); \ + } + +/*% + * Note: If additional attributes are added that should not be set for + * empty names, MAKE_EMPTY() must be changed so it clears them. + */ +#define MAKE_EMPTY(name) \ + do { \ + name->ndata = NULL; \ + name->length = 0; \ + name->labels = 0; \ + name->attributes &= ~DNS_NAMEATTR_ABSOLUTE; \ + } while (0); + +/*% + * A name is "bindable" if it can be set to point to a new value, i.e. + * name->ndata and name->length may be changed. + */ +#define BINDABLE(name) \ + ((name->attributes & \ + (DNS_NAMEATTR_READONLY | DNS_NAMEATTR_DYNAMIC)) == 0) + +/*% + * Note that the name data must be a char array, not a string + * literal, to avoid compiler warnings about discarding + * the const attribute of a string. + */ +static unsigned char root_ndata[] = { "" }; +static unsigned char root_offsets[] = { 0 }; + +static dns_name_t root = DNS_NAME_INITABSOLUTE(root_ndata, root_offsets); +const dns_name_t *dns_rootname = &root; + +static unsigned char wild_ndata[] = { "\001*" }; +static unsigned char wild_offsets[] = { 0 }; + +static dns_name_t const wild = DNS_NAME_INITNONABSOLUTE(wild_ndata, + wild_offsets); + +const dns_name_t *dns_wildcardname = &wild; + +/* + * dns_name_t to text post-conversion procedure. + */ +static thread_local dns_name_totextfilter_t *totext_filter_proc = NULL; + +static void +set_offsets(const dns_name_t *name, unsigned char *offsets, + dns_name_t *set_name); + +void +dns_name_init(dns_name_t *name, unsigned char *offsets) { + /* + * Initialize 'name'. + */ + DNS_NAME_INIT(name, offsets); +} + +void +dns_name_reset(dns_name_t *name) { + REQUIRE(VALID_NAME(name)); + REQUIRE(BINDABLE(name)); + + DNS_NAME_RESET(name); +} + +void +dns_name_invalidate(dns_name_t *name) { + /* + * Make 'name' invalid. + */ + + REQUIRE(VALID_NAME(name)); + + name->magic = 0; + name->ndata = NULL; + name->length = 0; + name->labels = 0; + name->attributes = 0; + name->offsets = NULL; + name->buffer = NULL; + ISC_LINK_INIT(name, link); +} + +bool +dns_name_isvalid(const dns_name_t *name) { + unsigned char *ndata, *offsets; + unsigned int offset, count, length, nlabels; + + if (!VALID_NAME(name)) { + return (false); + } + + if (name->length > 255U || name->labels > 127U) { + return (false); + } + + ndata = name->ndata; + length = name->length; + offsets = name->offsets; + offset = 0; + nlabels = 0; + + while (offset != length) { + count = *ndata; + if (count > 63U) { + return (false); + } + if (offsets != NULL && offsets[nlabels] != offset) { + return (false); + } + + nlabels++; + offset += count + 1; + ndata += count + 1; + if (offset > length) { + return (false); + } + + if (count == 0) { + break; + } + } + + if (nlabels != name->labels || offset != name->length) { + return (false); + } + + return (true); +} + +void +dns_name_setbuffer(dns_name_t *name, isc_buffer_t *buffer) { + /* + * Dedicate a buffer for use with 'name'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE((buffer != NULL && name->buffer == NULL) || (buffer == NULL)); + + name->buffer = buffer; +} + +bool +dns_name_hasbuffer(const dns_name_t *name) { + /* + * Does 'name' have a dedicated buffer? + */ + + REQUIRE(VALID_NAME(name)); + + if (name->buffer != NULL) { + return (true); + } + + return (false); +} + +bool +dns_name_isabsolute(const dns_name_t *name) { + /* + * Does 'name' end in the root label? + */ + + REQUIRE(VALID_NAME(name)); + + if ((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + return (true); + } + return (false); +} + +#define hyphenchar(c) ((c) == 0x2d) +#define asterchar(c) ((c) == 0x2a) +#define alphachar(c) \ + (((c) >= 0x41 && (c) <= 0x5a) || ((c) >= 0x61 && (c) <= 0x7a)) +#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39) +#define borderchar(c) (alphachar(c) || digitchar(c)) +#define middlechar(c) (borderchar(c) || hyphenchar(c)) +#define domainchar(c) ((c) > 0x20 && (c) < 0x7f) + +bool +dns_name_ismailbox(const dns_name_t *name) { + unsigned char *ndata, ch; + unsigned int n; + bool first; + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE); + + /* + * Root label. + */ + if (name->length == 1) { + return (true); + } + + ndata = name->ndata; + n = *ndata++; + INSIST(n <= 63); + while (n--) { + ch = *ndata++; + if (!domainchar(ch)) { + return (false); + } + } + + if (ndata == name->ndata + name->length) { + return (false); + } + + /* + * RFC952/RFC1123 hostname. + */ + while (ndata < (name->ndata + name->length)) { + n = *ndata++; + INSIST(n <= 63); + first = true; + while (n--) { + ch = *ndata++; + if (first || n == 0) { + if (!borderchar(ch)) { + return (false); + } + } else { + if (!middlechar(ch)) { + return (false); + } + } + first = false; + } + } + return (true); +} + +bool +dns_name_ishostname(const dns_name_t *name, bool wildcard) { + unsigned char *ndata, ch; + unsigned int n; + bool first; + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + REQUIRE(name->attributes & DNS_NAMEATTR_ABSOLUTE); + + /* + * Root label. + */ + if (name->length == 1) { + return (true); + } + + /* + * Skip wildcard if this is a ownername. + */ + ndata = name->ndata; + if (wildcard && ndata[0] == 1 && ndata[1] == '*') { + ndata += 2; + } + + /* + * RFC952/RFC1123 hostname. + */ + while (ndata < (name->ndata + name->length)) { + n = *ndata++; + INSIST(n <= 63); + first = true; + while (n--) { + ch = *ndata++; + if (first || n == 0) { + if (!borderchar(ch)) { + return (false); + } + } else { + if (!middlechar(ch)) { + return (false); + } + } + first = false; + } + } + return (true); +} + +bool +dns_name_iswildcard(const dns_name_t *name) { + unsigned char *ndata; + + /* + * Is 'name' a wildcard name? + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + + if (name->length >= 2) { + ndata = name->ndata; + if (ndata[0] == 1 && ndata[1] == '*') { + return (true); + } + } + + return (false); +} + +bool +dns_name_internalwildcard(const dns_name_t *name) { + unsigned char *ndata; + unsigned int count; + unsigned int label; + + /* + * Does 'name' contain a internal wildcard? + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + + /* + * Skip first label. + */ + ndata = name->ndata; + count = *ndata++; + INSIST(count <= 63); + ndata += count; + label = 1; + /* + * Check all but the last of the remaining labels. + */ + while (label + 1 < name->labels) { + count = *ndata++; + INSIST(count <= 63); + if (count == 1 && *ndata == '*') { + return (true); + } + ndata += count; + label++; + } + return (false); +} + +unsigned int +dns_name_hash(const dns_name_t *name, bool case_sensitive) { + unsigned int length; + + /* + * Provide a hash value for 'name'. + */ + REQUIRE(VALID_NAME(name)); + + if (name->labels == 0) { + return (0); + } + + length = name->length; + if (length > 16) { + length = 16; + } + + /* High bits are more random. */ + return (isc_hash32(name->ndata, length, case_sensitive)); +} + +unsigned int +dns_name_fullhash(const dns_name_t *name, bool case_sensitive) { + /* + * Provide a hash value for 'name'. + */ + REQUIRE(VALID_NAME(name)); + + if (name->labels == 0) { + return (0); + } + + /* High bits are more random. */ + return (isc_hash32(name->ndata, name->length, case_sensitive)); +} + +dns_namereln_t +dns_name_fullcompare(const dns_name_t *name1, const dns_name_t *name2, + int *orderp, unsigned int *nlabelsp) { + unsigned int l1, l2, l, count1, count2, count, nlabels; + int cdiff, ldiff, chdiff; + unsigned char *label1, *label2; + unsigned char *offsets1, *offsets2; + dns_offsets_t odata1, odata2; + dns_namereln_t namereln = dns_namereln_none; + + /* + * Determine the relative ordering under the DNSSEC order relation of + * 'name1' and 'name2', and also determine the hierarchical + * relationship of the names. + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + */ + + REQUIRE(VALID_NAME(name1)); + REQUIRE(VALID_NAME(name2)); + REQUIRE(orderp != NULL); + REQUIRE(nlabelsp != NULL); + /* + * Either name1 is absolute and name2 is absolute, or neither is. + */ + REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) == + (name2->attributes & DNS_NAMEATTR_ABSOLUTE)); + + if (name1 == name2) { + *orderp = 0; + *nlabelsp = name1->labels; + return (dns_namereln_equal); + } + + SETUP_OFFSETS(name1, offsets1, odata1); + SETUP_OFFSETS(name2, offsets2, odata2); + + nlabels = 0; + l1 = name1->labels; + l2 = name2->labels; + if (l2 > l1) { + l = l1; + ldiff = 0 - (l2 - l1); + } else { + l = l2; + ldiff = l1 - l2; + } + + offsets1 += l1; + offsets2 += l2; + + while (l > 0) { + l--; + offsets1--; + offsets2--; + label1 = &name1->ndata[*offsets1]; + label2 = &name2->ndata[*offsets2]; + count1 = *label1++; + count2 = *label2++; + + /* + * We dropped bitstring labels, and we don't support any + * other extended label types. + */ + INSIST(count1 <= 63 && count2 <= 63); + + cdiff = (int)count1 - (int)count2; + if (cdiff < 0) { + count = count1; + } else { + count = count2; + } + + /* Loop unrolled for performance */ + while (count > 3) { + chdiff = (int)maptolower[label1[0]] - + (int)maptolower[label2[0]]; + if (chdiff != 0) { + *orderp = chdiff; + goto done; + } + chdiff = (int)maptolower[label1[1]] - + (int)maptolower[label2[1]]; + if (chdiff != 0) { + *orderp = chdiff; + goto done; + } + chdiff = (int)maptolower[label1[2]] - + (int)maptolower[label2[2]]; + if (chdiff != 0) { + *orderp = chdiff; + goto done; + } + chdiff = (int)maptolower[label1[3]] - + (int)maptolower[label2[3]]; + if (chdiff != 0) { + *orderp = chdiff; + goto done; + } + count -= 4; + label1 += 4; + label2 += 4; + } + while (count-- > 0) { + chdiff = (int)maptolower[*label1++] - + (int)maptolower[*label2++]; + if (chdiff != 0) { + *orderp = chdiff; + goto done; + } + } + if (cdiff != 0) { + *orderp = cdiff; + goto done; + } + nlabels++; + } + + *orderp = ldiff; + if (ldiff < 0) { + namereln = dns_namereln_contains; + } else if (ldiff > 0) { + namereln = dns_namereln_subdomain; + } else { + namereln = dns_namereln_equal; + } + *nlabelsp = nlabels; + return (namereln); + +done: + *nlabelsp = nlabels; + if (nlabels > 0) { + namereln = dns_namereln_commonancestor; + } + + return (namereln); +} + +int +dns_name_compare(const dns_name_t *name1, const dns_name_t *name2) { + int order; + unsigned int nlabels; + + /* + * Determine the relative ordering under the DNSSEC order relation of + * 'name1' and 'name2'. + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + */ + + (void)dns_name_fullcompare(name1, name2, &order, &nlabels); + + return (order); +} + +bool +dns_name_equal(const dns_name_t *name1, const dns_name_t *name2) { + unsigned int l, count; + unsigned char c; + unsigned char *label1, *label2; + + /* + * Are 'name1' and 'name2' equal? + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + */ + + REQUIRE(VALID_NAME(name1)); + REQUIRE(VALID_NAME(name2)); + /* + * Either name1 is absolute and name2 is absolute, or neither is. + */ + REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) == + (name2->attributes & DNS_NAMEATTR_ABSOLUTE)); + + if (name1 == name2) { + return (true); + } + + if (name1->length != name2->length) { + return (false); + } + + l = name1->labels; + + if (l != name2->labels) { + return (false); + } + + label1 = name1->ndata; + label2 = name2->ndata; + while (l-- > 0) { + count = *label1++; + if (count != *label2++) { + return (false); + } + + INSIST(count <= 63); /* no bitstring support */ + + /* Loop unrolled for performance */ + while (count > 3) { + c = maptolower[label1[0]]; + if (c != maptolower[label2[0]]) { + return (false); + } + c = maptolower[label1[1]]; + if (c != maptolower[label2[1]]) { + return (false); + } + c = maptolower[label1[2]]; + if (c != maptolower[label2[2]]) { + return (false); + } + c = maptolower[label1[3]]; + if (c != maptolower[label2[3]]) { + return (false); + } + count -= 4; + label1 += 4; + label2 += 4; + } + while (count-- > 0) { + c = maptolower[*label1++]; + if (c != maptolower[*label2++]) { + return (false); + } + } + } + + return (true); +} + +bool +dns_name_caseequal(const dns_name_t *name1, const dns_name_t *name2) { + /* + * Are 'name1' and 'name2' equal? + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + */ + + REQUIRE(VALID_NAME(name1)); + REQUIRE(VALID_NAME(name2)); + /* + * Either name1 is absolute and name2 is absolute, or neither is. + */ + REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) == + (name2->attributes & DNS_NAMEATTR_ABSOLUTE)); + + if (name1->length != name2->length) { + return (false); + } + + if (memcmp(name1->ndata, name2->ndata, name1->length) != 0) { + return (false); + } + + return (true); +} + +int +dns_name_rdatacompare(const dns_name_t *name1, const dns_name_t *name2) { + unsigned int l1, l2, l, count1, count2, count; + unsigned char c1, c2; + unsigned char *label1, *label2; + + /* + * Compare two absolute names as rdata. + */ + + REQUIRE(VALID_NAME(name1)); + REQUIRE(name1->labels > 0); + REQUIRE((name1->attributes & DNS_NAMEATTR_ABSOLUTE) != 0); + REQUIRE(VALID_NAME(name2)); + REQUIRE(name2->labels > 0); + REQUIRE((name2->attributes & DNS_NAMEATTR_ABSOLUTE) != 0); + + l1 = name1->labels; + l2 = name2->labels; + + l = (l1 < l2) ? l1 : l2; + + label1 = name1->ndata; + label2 = name2->ndata; + while (l > 0) { + l--; + count1 = *label1++; + count2 = *label2++; + + /* no bitstring support */ + INSIST(count1 <= 63 && count2 <= 63); + + if (count1 != count2) { + return ((count1 < count2) ? -1 : 1); + } + count = count1; + while (count > 0) { + count--; + c1 = maptolower[*label1++]; + c2 = maptolower[*label2++]; + if (c1 < c2) { + return (-1); + } else if (c1 > c2) { + return (1); + } + } + } + + /* + * If one name had more labels than the other, their common + * prefix must have been different because the shorter name + * ended with the root label and the longer one can't have + * a root label in the middle of it. Therefore, if we get + * to this point, the lengths must be equal. + */ + INSIST(l1 == l2); + + return (0); +} + +bool +dns_name_issubdomain(const dns_name_t *name1, const dns_name_t *name2) { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + + /* + * Is 'name1' a subdomain of 'name2'? + * + * Note: It makes no sense for one of the names to be relative and the + * other absolute. If both names are relative, then to be meaningfully + * compared the caller must ensure that they are both relative to the + * same domain. + */ + + namereln = dns_name_fullcompare(name1, name2, &order, &nlabels); + if (namereln == dns_namereln_subdomain || + namereln == dns_namereln_equal) + { + return (true); + } + + return (false); +} + +bool +dns_name_matcheswildcard(const dns_name_t *name, const dns_name_t *wname) { + int order; + unsigned int nlabels, labels; + dns_name_t tname; + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + REQUIRE(VALID_NAME(wname)); + labels = wname->labels; + REQUIRE(labels > 0); + REQUIRE(dns_name_iswildcard(wname)); + + DNS_NAME_INIT(&tname, NULL); + dns_name_getlabelsequence(wname, 1, labels - 1, &tname); + if (dns_name_fullcompare(name, &tname, &order, &nlabels) == + dns_namereln_subdomain) + { + return (true); + } + return (false); +} + +unsigned int +dns_name_countlabels(const dns_name_t *name) { + /* + * How many labels does 'name' have? + */ + + REQUIRE(VALID_NAME(name)); + + ENSURE(name->labels <= 128); + + return (name->labels); +} + +void +dns_name_getlabel(const dns_name_t *name, unsigned int n, dns_label_t *label) { + unsigned char *offsets; + dns_offsets_t odata; + + /* + * Make 'label' refer to the 'n'th least significant label of 'name'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(name->labels > 0); + REQUIRE(n < name->labels); + REQUIRE(label != NULL); + + SETUP_OFFSETS(name, offsets, odata); + + label->base = &name->ndata[offsets[n]]; + if (n == name->labels - 1) { + label->length = name->length - offsets[n]; + } else { + label->length = offsets[n + 1] - offsets[n]; + } +} + +void +dns_name_getlabelsequence(const dns_name_t *source, unsigned int first, + unsigned int n, dns_name_t *target) { + unsigned char *p, l; + unsigned int firstoffset, endoffset; + unsigned int i; + + /* + * Make 'target' refer to the 'n' labels including and following + * 'first' in 'source'. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(target)); + REQUIRE(first <= source->labels); + REQUIRE(n <= source->labels - first); /* note first+n could overflow */ + REQUIRE(BINDABLE(target)); + + p = source->ndata; + if (first == source->labels) { + firstoffset = source->length; + } else { + for (i = 0; i < first; i++) { + l = *p; + p += l + 1; + } + firstoffset = (unsigned int)(p - source->ndata); + } + + if (first + n == source->labels) { + endoffset = source->length; + } else { + for (i = 0; i < n; i++) { + l = *p; + p += l + 1; + } + endoffset = (unsigned int)(p - source->ndata); + } + + target->ndata = &source->ndata[firstoffset]; + target->length = endoffset - firstoffset; + + if (first + n == source->labels && n > 0 && + (source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) + { + target->attributes |= DNS_NAMEATTR_ABSOLUTE; + } else { + target->attributes &= ~DNS_NAMEATTR_ABSOLUTE; + } + + target->labels = n; + + /* + * If source and target are the same, and we're making target + * a prefix of source, the offsets table is correct already + * so we don't need to call set_offsets(). + */ + if (target->offsets != NULL && (target != source || first != 0)) { + set_offsets(target, target->offsets, NULL); + } +} + +void +dns_name_clone(const dns_name_t *source, dns_name_t *target) { + /* + * Make 'target' refer to the same name as 'source'. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(target)); + REQUIRE(BINDABLE(target)); + + target->ndata = source->ndata; + target->length = source->length; + target->labels = source->labels; + target->attributes = source->attributes & + (unsigned int)~(DNS_NAMEATTR_READONLY | + DNS_NAMEATTR_DYNAMIC | + DNS_NAMEATTR_DYNOFFSETS); + if (target->offsets != NULL && source->labels > 0) { + if (source->offsets != NULL) { + memmove(target->offsets, source->offsets, + source->labels); + } else { + set_offsets(target, target->offsets, NULL); + } + } +} + +void +dns_name_fromregion(dns_name_t *name, const isc_region_t *r) { + unsigned char *offsets; + dns_offsets_t odata; + unsigned int len; + isc_region_t r2; + + /* + * Make 'name' refer to region 'r'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(r != NULL); + REQUIRE(BINDABLE(name)); + + INIT_OFFSETS(name, offsets, odata); + + if (name->buffer != NULL) { + isc_buffer_clear(name->buffer); + isc_buffer_availableregion(name->buffer, &r2); + len = (r->length < r2.length) ? r->length : r2.length; + if (len > DNS_NAME_MAXWIRE) { + len = DNS_NAME_MAXWIRE; + } + if (len != 0) { + memmove(r2.base, r->base, len); + } + name->ndata = r2.base; + name->length = len; + } else { + name->ndata = r->base; + name->length = (r->length <= DNS_NAME_MAXWIRE) + ? r->length + : DNS_NAME_MAXWIRE; + } + + if (r->length > 0) { + set_offsets(name, offsets, name); + } else { + name->labels = 0; + name->attributes &= ~DNS_NAMEATTR_ABSOLUTE; + } + + if (name->buffer != NULL) { + isc_buffer_add(name->buffer, name->length); + } +} + +void +dns_name_toregion(const dns_name_t *name, isc_region_t *r) { + /* + * Make 'r' refer to 'name'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(r != NULL); + + DNS_NAME_TOREGION(name, r); +} + +isc_result_t +dns_name_fromtext(dns_name_t *name, isc_buffer_t *source, + const dns_name_t *origin, unsigned int options, + isc_buffer_t *target) { + unsigned char *ndata, *label = NULL; + char *tdata; + char c; + ft_state state; + unsigned int value = 0, count = 0; + unsigned int n1 = 0, n2 = 0; + unsigned int tlen, nrem, nused, digits = 0, labels, tused; + bool done; + unsigned char *offsets; + dns_offsets_t odata; + bool downcase; + + /* + * Convert the textual representation of a DNS name at source + * into uncompressed wire form stored in target. + * + * Notes: + * Relative domain names will have 'origin' appended to them + * unless 'origin' is NULL, in which case relative domain names + * will remain relative. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(ISC_BUFFER_VALID(source)); + REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) || + (target == NULL && ISC_BUFFER_VALID(name->buffer))); + + downcase = ((options & DNS_NAME_DOWNCASE) != 0); + + if (target == NULL && name->buffer != NULL) { + target = name->buffer; + isc_buffer_clear(target); + } + + REQUIRE(BINDABLE(name)); + + INIT_OFFSETS(name, offsets, odata); + offsets[0] = 0; + + /* + * Make 'name' empty in case of failure. + */ + MAKE_EMPTY(name); + + /* + * Set up the state machine. + */ + tdata = (char *)source->base + source->current; + tlen = isc_buffer_remaininglength(source); + tused = 0; + ndata = isc_buffer_used(target); + nrem = isc_buffer_availablelength(target); + if (nrem > 255) { + nrem = 255; + } + nused = 0; + labels = 0; + done = false; + state = ft_init; + + while (nrem > 0 && tlen > 0 && !done) { + c = *tdata++; + tlen--; + tused++; + + switch (state) { + case ft_init: + /* + * Is this the root name? + */ + if (c == '.') { + if (tlen != 0) { + return (DNS_R_EMPTYLABEL); + } + labels++; + *ndata++ = 0; + nrem--; + nused++; + done = true; + break; + } + if (c == '@' && tlen == 0) { + state = ft_at; + break; + } + + FALLTHROUGH; + case ft_start: + label = ndata; + ndata++; + nrem--; + nused++; + count = 0; + if (c == '\\') { + state = ft_initialescape; + break; + } + state = ft_ordinary; + if (nrem == 0) { + return (ISC_R_NOSPACE); + } + FALLTHROUGH; + case ft_ordinary: + if (c == '.') { + if (count == 0) { + return (DNS_R_EMPTYLABEL); + } + *label = count; + labels++; + INSIST(labels <= 127); + offsets[labels] = nused; + if (tlen == 0) { + labels++; + *ndata++ = 0; + nrem--; + nused++; + done = true; + } + state = ft_start; + } else if (c == '\\') { + state = ft_escape; + } else { + if (count >= 63) { + return (DNS_R_LABELTOOLONG); + } + count++; + CONVERTTOASCII(c); + if (downcase) { + c = maptolower[c & 0xff]; + } + *ndata++ = c; + nrem--; + nused++; + } + break; + case ft_initialescape: + if (c == '[') { + /* + * This looks like a bitstring label, which + * was deprecated. Intentionally drop it. + */ + return (DNS_R_BADLABELTYPE); + } + state = ft_escape; + POST(state); + FALLTHROUGH; + case ft_escape: + if (!isdigit((unsigned char)c)) { + if (count >= 63) { + return (DNS_R_LABELTOOLONG); + } + count++; + CONVERTTOASCII(c); + if (downcase) { + c = maptolower[c & 0xff]; + } + *ndata++ = c; + nrem--; + nused++; + state = ft_ordinary; + break; + } + digits = 0; + value = 0; + state = ft_escdecimal; + FALLTHROUGH; + case ft_escdecimal: + if (!isdigit((unsigned char)c)) { + return (DNS_R_BADESCAPE); + } + value *= 10; + value += digitvalue[c & 0xff]; + digits++; + if (digits == 3) { + if (value > 255) { + return (DNS_R_BADESCAPE); + } + if (count >= 63) { + return (DNS_R_LABELTOOLONG); + } + count++; + if (downcase) { + value = maptolower[value]; + } + *ndata++ = value; + nrem--; + nused++; + state = ft_ordinary; + } + break; + default: + FATAL_ERROR("Unexpected state %d", state); + /* Does not return. */ + } + } + + if (!done) { + if (nrem == 0) { + return (ISC_R_NOSPACE); + } + INSIST(tlen == 0); + if (state != ft_ordinary && state != ft_at) { + return (ISC_R_UNEXPECTEDEND); + } + if (state == ft_ordinary) { + INSIST(count != 0); + INSIST(label != NULL); + *label = count; + labels++; + INSIST(labels <= 127); + offsets[labels] = nused; + } + if (origin != NULL) { + if (nrem < origin->length) { + return (ISC_R_NOSPACE); + } + label = origin->ndata; + n1 = origin->length; + nrem -= n1; + POST(nrem); + while (n1 > 0) { + n2 = *label++; + INSIST(n2 <= 63); /* no bitstring support */ + *ndata++ = n2; + n1 -= n2 + 1; + nused += n2 + 1; + while (n2 > 0) { + c = *label++; + if (downcase) { + c = maptolower[c & 0xff]; + } + *ndata++ = c; + n2--; + } + labels++; + if (n1 > 0) { + INSIST(labels <= 127); + offsets[labels] = nused; + } + } + if ((origin->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + name->attributes |= DNS_NAMEATTR_ABSOLUTE; + } + } + } else { + name->attributes |= DNS_NAMEATTR_ABSOLUTE; + } + + name->ndata = (unsigned char *)target->base + target->used; + name->labels = labels; + name->length = nused; + + isc_buffer_forward(source, tused); + isc_buffer_add(target, name->length); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_totext(const dns_name_t *name, bool omit_final_dot, + isc_buffer_t *target) { + unsigned int options = DNS_NAME_MASTERFILE; + + if (omit_final_dot) { + options |= DNS_NAME_OMITFINALDOT; + } + return (dns_name_totext2(name, options, target)); +} + +isc_result_t +dns_name_toprincipal(const dns_name_t *name, isc_buffer_t *target) { + return (dns_name_totext2(name, DNS_NAME_OMITFINALDOT, target)); +} + +isc_result_t +dns_name_totext2(const dns_name_t *name, unsigned int options, + isc_buffer_t *target) { + unsigned char *ndata; + char *tdata; + unsigned int nlen, tlen; + unsigned char c; + unsigned int trem, count; + unsigned int labels; + bool saw_root = false; + unsigned int oused; + bool omit_final_dot = ((options & DNS_NAME_OMITFINALDOT) != 0); + + /* + * This function assumes the name is in proper uncompressed + * wire format. + */ + REQUIRE(VALID_NAME(name)); + REQUIRE(ISC_BUFFER_VALID(target)); + + oused = target->used; + + ndata = name->ndata; + nlen = name->length; + labels = name->labels; + tdata = isc_buffer_used(target); + tlen = isc_buffer_availablelength(target); + + trem = tlen; + + if (labels == 0 && nlen == 0) { + /* + * Special handling for an empty name. + */ + if (trem == 0) { + return (ISC_R_NOSPACE); + } + + /* + * The names of these booleans are misleading in this case. + * This empty name is not necessarily from the root node of + * the DNS root zone, nor is a final dot going to be included. + * They need to be set this way, though, to keep the "@" + * from being trounced. + */ + saw_root = true; + omit_final_dot = false; + *tdata++ = '@'; + trem--; + + /* + * Skip the while() loop. + */ + nlen = 0; + } else if (nlen == 1 && labels == 1 && *ndata == '\0') { + /* + * Special handling for the root label. + */ + if (trem == 0) { + return (ISC_R_NOSPACE); + } + + saw_root = true; + omit_final_dot = false; + *tdata++ = '.'; + trem--; + + /* + * Skip the while() loop. + */ + nlen = 0; + } + + while (labels > 0 && nlen > 0 && trem > 0) { + labels--; + count = *ndata++; + nlen--; + if (count == 0) { + saw_root = true; + break; + } + if (count < 64) { + INSIST(nlen >= count); + while (count > 0) { + c = *ndata; + switch (c) { + /* Special modifiers in zone files. */ + case 0x40: /* '@' */ + case 0x24: /* '$' */ + if ((options & DNS_NAME_MASTERFILE) == + 0) + { + goto no_escape; + } + FALLTHROUGH; + case 0x22: /* '"' */ + case 0x28: /* '(' */ + case 0x29: /* ')' */ + case 0x2E: /* '.' */ + case 0x3B: /* ';' */ + case 0x5C: /* '\\' */ + if (trem < 2) { + return (ISC_R_NOSPACE); + } + *tdata++ = '\\'; + CONVERTFROMASCII(c); + *tdata++ = c; + ndata++; + trem -= 2; + nlen--; + break; + no_escape: + default: + if (c > 0x20 && c < 0x7f) { + if (trem == 0) { + return (ISC_R_NOSPACE); + } + CONVERTFROMASCII(c); + *tdata++ = c; + ndata++; + trem--; + nlen--; + } else { + if (trem < 4) { + return (ISC_R_NOSPACE); + } + *tdata++ = 0x5c; + *tdata++ = 0x30 + + ((c / 100) % 10); + *tdata++ = 0x30 + + ((c / 10) % 10); + *tdata++ = 0x30 + (c % 10); + trem -= 4; + ndata++; + nlen--; + } + } + count--; + } + } else { + FATAL_ERROR("Unexpected label type %02x", count); + UNREACHABLE(); + } + + /* + * The following assumes names are absolute. If not, we + * fix things up later. Note that this means that in some + * cases one more byte of text buffer is required than is + * needed in the final output. + */ + if (trem == 0) { + return (ISC_R_NOSPACE); + } + *tdata++ = '.'; + trem--; + } + + if (nlen != 0 && trem == 0) { + return (ISC_R_NOSPACE); + } + + if (!saw_root || omit_final_dot) { + trem++; + tdata--; + } + if (trem > 0) { + *tdata = 0; + } + isc_buffer_add(target, tlen - trem); + + if (totext_filter_proc != NULL) { + return ((totext_filter_proc)(target, oused)); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_tofilenametext(const dns_name_t *name, bool omit_final_dot, + isc_buffer_t *target) { + unsigned char *ndata; + char *tdata; + unsigned int nlen, tlen; + unsigned char c; + unsigned int trem, count; + unsigned int labels; + + /* + * This function assumes the name is in proper uncompressed + * wire format. + */ + REQUIRE(VALID_NAME(name)); + REQUIRE((name->attributes & DNS_NAMEATTR_ABSOLUTE) != 0); + REQUIRE(ISC_BUFFER_VALID(target)); + + ndata = name->ndata; + nlen = name->length; + labels = name->labels; + tdata = isc_buffer_used(target); + tlen = isc_buffer_availablelength(target); + + trem = tlen; + + if (nlen == 1 && labels == 1 && *ndata == '\0') { + /* + * Special handling for the root label. + */ + if (trem == 0) { + return (ISC_R_NOSPACE); + } + + omit_final_dot = false; + *tdata++ = '.'; + trem--; + + /* + * Skip the while() loop. + */ + nlen = 0; + } + + while (labels > 0 && nlen > 0 && trem > 0) { + labels--; + count = *ndata++; + nlen--; + if (count == 0) { + break; + } + if (count < 64) { + INSIST(nlen >= count); + while (count > 0) { + c = *ndata; + if ((c >= 0x30 && c <= 0x39) || /* digit */ + (c >= 0x41 && c <= 0x5A) || /* uppercase */ + (c >= 0x61 && c <= 0x7A) || /* lowercase */ + c == 0x2D || /* hyphen */ + c == 0x5F) /* underscore */ + { + if (trem == 0) { + return (ISC_R_NOSPACE); + } + /* downcase */ + if (c >= 0x41 && c <= 0x5A) { + c += 0x20; + } + CONVERTFROMASCII(c); + *tdata++ = c; + ndata++; + trem--; + nlen--; + } else { + if (trem < 4) { + return (ISC_R_NOSPACE); + } + snprintf(tdata, trem, "%%%02X", c); + tdata += 3; + trem -= 3; + ndata++; + nlen--; + } + count--; + } + } else { + FATAL_ERROR("Unexpected label type %02x", count); + UNREACHABLE(); + } + + /* + * The following assumes names are absolute. If not, we + * fix things up later. Note that this means that in some + * cases one more byte of text buffer is required than is + * needed in the final output. + */ + if (trem == 0) { + return (ISC_R_NOSPACE); + } + *tdata++ = '.'; + trem--; + } + + if (nlen != 0 && trem == 0) { + return (ISC_R_NOSPACE); + } + + if (omit_final_dot) { + trem++; + } + + isc_buffer_add(target, tlen - trem); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_downcase(const dns_name_t *source, dns_name_t *name, + isc_buffer_t *target) { + unsigned char *sndata, *ndata; + unsigned int nlen, count, labels; + isc_buffer_t buffer; + + /* + * Downcase 'source'. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(name)); + if (source == name) { + REQUIRE((name->attributes & DNS_NAMEATTR_READONLY) == 0); + isc_buffer_init(&buffer, source->ndata, source->length); + target = &buffer; + ndata = source->ndata; + } else { + REQUIRE(BINDABLE(name)); + REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) || + (target == NULL && ISC_BUFFER_VALID(name->buffer))); + if (target == NULL) { + target = name->buffer; + isc_buffer_clear(name->buffer); + } + ndata = (unsigned char *)target->base + target->used; + name->ndata = ndata; + } + + sndata = source->ndata; + nlen = source->length; + labels = source->labels; + + if (nlen > (target->length - target->used)) { + MAKE_EMPTY(name); + return (ISC_R_NOSPACE); + } + + while (labels > 0 && nlen > 0) { + labels--; + count = *sndata++; + *ndata++ = count; + nlen--; + if (count < 64) { + INSIST(nlen >= count); + while (count > 0) { + *ndata++ = maptolower[(*sndata++)]; + nlen--; + count--; + } + } else { + FATAL_ERROR("Unexpected label type %02x", count); + /* Does not return. */ + } + } + + if (source != name) { + name->labels = source->labels; + name->length = source->length; + if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + name->attributes = DNS_NAMEATTR_ABSOLUTE; + } else { + name->attributes = 0; + } + if (name->labels > 0 && name->offsets != NULL) { + set_offsets(name, name->offsets, NULL); + } + } + + isc_buffer_add(target, name->length); + + return (ISC_R_SUCCESS); +} + +static void +set_offsets(const dns_name_t *name, unsigned char *offsets, + dns_name_t *set_name) { + unsigned int offset, count, length, nlabels; + unsigned char *ndata; + bool absolute; + + ndata = name->ndata; + length = name->length; + offset = 0; + nlabels = 0; + absolute = false; + while (offset != length) { + INSIST(nlabels < 128); + offsets[nlabels++] = offset; + count = *ndata; + INSIST(count <= 63); + offset += count + 1; + ndata += count + 1; + INSIST(offset <= length); + if (count == 0) { + absolute = true; + break; + } + } + if (set_name != NULL) { + INSIST(set_name == name); + + set_name->labels = nlabels; + set_name->length = offset; + if (absolute) { + set_name->attributes |= DNS_NAMEATTR_ABSOLUTE; + } else { + set_name->attributes &= ~DNS_NAMEATTR_ABSOLUTE; + } + } + INSIST(nlabels == name->labels); + INSIST(offset == name->length); +} + +isc_result_t +dns_name_fromwire(dns_name_t *const name, isc_buffer_t *const source, + dns_decompress_t *const dctx, unsigned int options, + isc_buffer_t *target) { + /* + * Copy the name at source into target, decompressing it. + * + * *** WARNING *** + * + * dns_name_fromwire() deals with raw network data. An error in this + * routine could result in the failure or hijacking of the server. + * + * The description of name compression in RFC 1035 section 4.1.4 is + * subtle wrt certain edge cases. The first important sentence is: + * + * > In this scheme, an entire domain name or a list of labels at the + * > end of a domain name is replaced with a pointer to a prior + * > occurance of the same name. + * + * The key word is "prior". This says that compression pointers must + * point strictly earlier in the message (before our "marker" variable), + * which is enough to prevent DoS attacks due to compression loops. + * + * The next important sentence is: + * + * > If a domain name is contained in a part of the message subject to a + * > length field (such as the RDATA section of an RR), and compression + * > is used, the length of the compressed name is used in the length + * > calculation, rather than the length of the expanded name. + * + * When decompressing, this means that the amount of the source buffer + * that we consumed (which is checked wrt the container's length field) + * is the length of the compressed name. A compressed name is defined as + * a sequence of labels ending with the root label or a compression + * pointer, that is, the segment of the name that dns_name_fromwire() + * examines first. + * + * This matters when handling names that play dirty tricks, like: + * + * +---+---+---+---+---+---+ + * | 4 | 1 |'a'|192| 0 | 0 | + * +---+---+---+---+---+---+ + * + * We start at octet 1. There is an ordinary single character label "a", + * followed by a compression pointer that refers back to octet zero. + * Here there is a label of length 4, which weirdly re-uses the octets + * we already examined as the data for the label. It is followed by the + * root label, + * + * The specification says that the compressed name ends after the first + * zero octet (after the compression pointer) not the second zero octet, + * even though the second octet is later in the message. This shows the + * correct way to set our "consumed" variable. + */ + + REQUIRE((options & DNS_NAME_DOWNCASE) == 0); + REQUIRE(VALID_NAME(name)); + REQUIRE(BINDABLE(name)); + REQUIRE(dctx != NULL); + REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) || + (target == NULL && ISC_BUFFER_VALID(name->buffer))); + + if (target == NULL && name->buffer != NULL) { + target = name->buffer; + isc_buffer_clear(target); + } + + uint8_t *const name_buf = isc_buffer_used(target); + const uint32_t name_max = ISC_MIN(DNS_NAME_MAXWIRE, + isc_buffer_availablelength(target)); + uint32_t name_len = 0; + MAKE_EMPTY(name); /* in case of failure */ + + dns_offsets_t odata; + uint8_t *offsets = NULL; + uint32_t labels = 0; + INIT_OFFSETS(name, offsets, odata); + + /* + * After chasing a compression pointer, these variables refer to the + * source buffer as follows: + * + * sb --- mr --- cr --- st --- cd --- sm + * + * sb = source_buf (const) + * mr = marker + * cr = cursor + * st = start (const) + * cd = consumed + * sm = source_max (const) + * + * The marker hops backwards for each pointer. + * The cursor steps forwards for each label. + * The amount of the source we consumed is set once. + */ + const uint8_t *const source_buf = isc_buffer_base(source); + const uint8_t *const source_max = isc_buffer_used(source); + const uint8_t *const start = isc_buffer_current(source); + const uint8_t *marker = start; + const uint8_t *cursor = start; + const uint8_t *consumed = NULL; + + /* + * One iteration per label. + */ + while (cursor < source_max) { + const uint8_t label_len = *cursor++; + if (label_len < 64) { + /* + * Normal label: record its offset, and check bounds on + * the name length, which also ensures we don't overrun + * the offsets array. Don't touch any source bytes yet! + * The source bounds check will happen when we loop. + */ + offsets[labels++] = name_len; + /* and then a step to the ri-i-i-i-i-ight */ + cursor += label_len; + name_len += label_len + 1; + if (name_len > name_max) { + return (name_max == DNS_NAME_MAXWIRE + ? DNS_R_NAMETOOLONG + : ISC_R_NOSPACE); + } else if (label_len == 0) { + goto root_label; + } + } else if (label_len < 192) { + return (DNS_R_BADLABELTYPE); + } else if ((dctx->allowed & DNS_COMPRESS_GLOBAL14) == 0) { + return (DNS_R_DISALLOWED); + } else if (cursor < source_max) { + /* + * Compression pointer. Ensure it does not loop. + * + * Copy multiple labels in one go, to make the most of + * memmove() performance. Start at the marker and finish + * just before the pointer's hi+lo bytes, before the + * cursor. Bounds were already checked. + */ + const uint32_t hi = label_len & 0x3F; + const uint32_t lo = *cursor++; + const uint8_t *pointer = source_buf + (256 * hi + lo); + if (pointer >= marker) { + return (DNS_R_BADPOINTER); + } + const uint32_t copy_len = (cursor - 2) - marker; + uint8_t *const dest = name_buf + name_len - copy_len; + memmove(dest, marker, copy_len); + consumed = consumed != NULL ? consumed : cursor; + /* it's just a jump to the left */ + cursor = marker = pointer; + } + } + return (ISC_R_UNEXPECTEDEND); +root_label:; + /* + * Copy labels almost like we do for compression pointers, + * from the marker up to and including the root label. + */ + const uint32_t copy_len = cursor - marker; + memmove(name_buf + name_len - copy_len, marker, copy_len); + consumed = consumed != NULL ? consumed : cursor; + isc_buffer_forward(source, consumed - start); + + name->attributes |= DNS_NAMEATTR_ABSOLUTE; + name->ndata = name_buf; + name->labels = labels; + name->length = name_len; + isc_buffer_add(target, name_len); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_towire(const dns_name_t *name, dns_compress_t *cctx, + isc_buffer_t *target) { + return (dns_name_towire2(name, cctx, target, NULL)); +} + +isc_result_t +dns_name_towire2(const dns_name_t *name, dns_compress_t *cctx, + isc_buffer_t *target, uint16_t *comp_offsetp) { + unsigned int methods; + uint16_t offset; + dns_name_t gp; /* Global compression prefix */ + bool gf; /* Global compression target found */ + uint16_t go; /* Global compression offset */ + dns_offsets_t clo; + dns_name_t clname; + + /* + * Convert 'name' into wire format, compressing it as specified by the + * compression context 'cctx', and storing the result in 'target'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(cctx != NULL); + REQUIRE(ISC_BUFFER_VALID(target)); + + /* + * If this exact name was already rendered before, and the + * offset of the previously rendered name is passed to us, write + * a compression pointer directly. + */ + methods = dns_compress_getmethods(cctx); + if (comp_offsetp != NULL && *comp_offsetp < 0x4000 && + (name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 && + (methods & DNS_COMPRESS_GLOBAL14) != 0) + { + if (target->length - target->used < 2) { + return (ISC_R_NOSPACE); + } + offset = *comp_offsetp; + offset |= 0xc000; + isc_buffer_putuint16(target, offset); + return (ISC_R_SUCCESS); + } + + /* + * If 'name' doesn't have an offsets table, make a clone which + * has one. + */ + if (name->offsets == NULL) { + DNS_NAME_INIT(&clname, clo); + dns_name_clone(name, &clname); + name = &clname; + } + DNS_NAME_INIT(&gp, NULL); + + offset = target->used; /*XXX*/ + + if ((name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 && + (methods & DNS_COMPRESS_GLOBAL14) != 0) + { + gf = dns_compress_findglobal(cctx, name, &gp, &go); + } else { + gf = false; + } + + /* + * If the offset is too high for 14 bit global compression, we're + * out of luck. + */ + if (gf && go >= 0x4000) { + gf = false; + } + + /* + * Will the compression pointer reduce the message size? + */ + if (gf && (gp.length + 2) >= name->length) { + gf = false; + } + + if (gf) { + if (target->length - target->used < gp.length) { + return (ISC_R_NOSPACE); + } + if (gp.length != 0) { + unsigned char *base = target->base; + (void)memmove(base + target->used, gp.ndata, + (size_t)gp.length); + } + isc_buffer_add(target, gp.length); + if (target->length - target->used < 2) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint16(target, go | 0xc000); + if (gp.length != 0) { + dns_compress_add(cctx, name, &gp, offset); + if (comp_offsetp != NULL) { + *comp_offsetp = offset; + } + } else if (comp_offsetp != NULL) { + *comp_offsetp = go; + } + } else { + if (target->length - target->used < name->length) { + return (ISC_R_NOSPACE); + } + if (name->length != 0) { + unsigned char *base = target->base; + (void)memmove(base + target->used, name->ndata, + (size_t)name->length); + } + isc_buffer_add(target, name->length); + dns_compress_add(cctx, name, name, offset); + if (comp_offsetp != NULL) { + *comp_offsetp = offset; + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_concatenate(const dns_name_t *prefix, const dns_name_t *suffix, + dns_name_t *name, isc_buffer_t *target) { + unsigned char *ndata, *offsets; + unsigned int nrem, labels, prefix_length, length; + bool copy_prefix = true; + bool copy_suffix = true; + bool absolute = false; + dns_name_t tmp_name; + dns_offsets_t odata; + + /* + * Concatenate 'prefix' and 'suffix'. + */ + + REQUIRE(prefix == NULL || VALID_NAME(prefix)); + REQUIRE(suffix == NULL || VALID_NAME(suffix)); + REQUIRE(name == NULL || VALID_NAME(name)); + REQUIRE((target != NULL && ISC_BUFFER_VALID(target)) || + (target == NULL && name != NULL && + ISC_BUFFER_VALID(name->buffer))); + if (prefix == NULL || prefix->labels == 0) { + copy_prefix = false; + } + if (suffix == NULL || suffix->labels == 0) { + copy_suffix = false; + } + if (copy_prefix && (prefix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + absolute = true; + REQUIRE(!copy_suffix); + } + if (name == NULL) { + DNS_NAME_INIT(&tmp_name, odata); + name = &tmp_name; + } + if (target == NULL) { + INSIST(name->buffer != NULL); + target = name->buffer; + isc_buffer_clear(name->buffer); + } + + REQUIRE(BINDABLE(name)); + + /* + * Set up. + */ + nrem = target->length - target->used; + ndata = (unsigned char *)target->base + target->used; + if (nrem > DNS_NAME_MAXWIRE) { + nrem = DNS_NAME_MAXWIRE; + } + length = 0; + prefix_length = 0; + labels = 0; + if (copy_prefix) { + prefix_length = prefix->length; + length += prefix_length; + labels += prefix->labels; + } + if (copy_suffix) { + length += suffix->length; + labels += suffix->labels; + } + if (length > DNS_NAME_MAXWIRE) { + MAKE_EMPTY(name); + return (DNS_R_NAMETOOLONG); + } + if (length > nrem) { + MAKE_EMPTY(name); + return (ISC_R_NOSPACE); + } + + if (copy_suffix) { + if ((suffix->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + absolute = true; + } + memmove(ndata + prefix_length, suffix->ndata, suffix->length); + } + + /* + * If 'prefix' and 'name' are the same object, and the object has + * a dedicated buffer, and we're using it, then we don't have to + * copy anything. + */ + if (copy_prefix && (prefix != name || prefix->buffer != target)) { + memmove(ndata, prefix->ndata, prefix_length); + } + + name->ndata = ndata; + name->labels = labels; + name->length = length; + if (absolute) { + name->attributes = DNS_NAMEATTR_ABSOLUTE; + } else { + name->attributes = 0; + } + + if (name->labels > 0 && name->offsets != NULL) { + INIT_OFFSETS(name, offsets, odata); + set_offsets(name, offsets, NULL); + } + + isc_buffer_add(target, name->length); + + return (ISC_R_SUCCESS); +} + +void +dns_name_split(const dns_name_t *name, unsigned int suffixlabels, + dns_name_t *prefix, dns_name_t *suffix) + +{ + unsigned int splitlabel; + + REQUIRE(VALID_NAME(name)); + REQUIRE(suffixlabels > 0); + REQUIRE(suffixlabels <= name->labels); + REQUIRE(prefix != NULL || suffix != NULL); + REQUIRE(prefix == NULL || (VALID_NAME(prefix) && BINDABLE(prefix))); + REQUIRE(suffix == NULL || (VALID_NAME(suffix) && BINDABLE(suffix))); + + splitlabel = name->labels - suffixlabels; + + if (prefix != NULL) { + dns_name_getlabelsequence(name, 0, splitlabel, prefix); + } + + if (suffix != NULL) { + dns_name_getlabelsequence(name, splitlabel, suffixlabels, + suffix); + } + + return; +} + +void +dns_name_dup(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) { + /* + * Make 'target' a dynamically allocated copy of 'source'. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(source->length > 0); + REQUIRE(VALID_NAME(target)); + REQUIRE(BINDABLE(target)); + + /* + * Make 'target' empty in case of failure. + */ + MAKE_EMPTY(target); + + target->ndata = isc_mem_get(mctx, source->length); + + memmove(target->ndata, source->ndata, source->length); + + target->length = source->length; + target->labels = source->labels; + target->attributes = DNS_NAMEATTR_DYNAMIC; + if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + target->attributes |= DNS_NAMEATTR_ABSOLUTE; + } + if (target->offsets != NULL) { + if (source->offsets != NULL) { + memmove(target->offsets, source->offsets, + source->labels); + } else { + set_offsets(target, target->offsets, NULL); + } + } +} + +isc_result_t +dns_name_dupwithoffsets(const dns_name_t *source, isc_mem_t *mctx, + dns_name_t *target) { + /* + * Make 'target' a read-only dynamically allocated copy of 'source'. + * 'target' will also have a dynamically allocated offsets table. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(source->length > 0); + REQUIRE(VALID_NAME(target)); + REQUIRE(BINDABLE(target)); + REQUIRE(target->offsets == NULL); + + /* + * Make 'target' empty in case of failure. + */ + MAKE_EMPTY(target); + + target->ndata = isc_mem_get(mctx, source->length + source->labels); + + memmove(target->ndata, source->ndata, source->length); + + target->length = source->length; + target->labels = source->labels; + target->attributes = DNS_NAMEATTR_DYNAMIC | DNS_NAMEATTR_DYNOFFSETS | + DNS_NAMEATTR_READONLY; + if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + target->attributes |= DNS_NAMEATTR_ABSOLUTE; + } + target->offsets = target->ndata + source->length; + if (source->offsets != NULL) { + memmove(target->offsets, source->offsets, source->labels); + } else { + set_offsets(target, target->offsets, NULL); + } + + return (ISC_R_SUCCESS); +} + +void +dns_name_free(dns_name_t *name, isc_mem_t *mctx) { + size_t size; + + /* + * Free 'name'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0); + + size = name->length; + if ((name->attributes & DNS_NAMEATTR_DYNOFFSETS) != 0) { + size += name->labels; + } + isc_mem_put(mctx, name->ndata, size); + dns_name_invalidate(name); +} + +isc_result_t +dns_name_digest(const dns_name_t *name, dns_digestfunc_t digest, void *arg) { + dns_name_t downname; + unsigned char data[256]; + isc_buffer_t buffer; + isc_result_t result; + isc_region_t r; + + /* + * Send 'name' in DNSSEC canonical form to 'digest'. + */ + + REQUIRE(VALID_NAME(name)); + REQUIRE(digest != NULL); + + DNS_NAME_INIT(&downname, NULL); + + isc_buffer_init(&buffer, data, sizeof(data)); + + result = dns_name_downcase(name, &downname, &buffer); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_usedregion(&buffer, &r); + + return ((digest)(arg, &r)); +} + +bool +dns_name_dynamic(const dns_name_t *name) { + REQUIRE(VALID_NAME(name)); + + /* + * Returns whether there is dynamic memory associated with this name. + */ + + return ((name->attributes & DNS_NAMEATTR_DYNAMIC) != 0 ? true : false); +} + +isc_result_t +dns_name_print(const dns_name_t *name, FILE *stream) { + isc_result_t result; + isc_buffer_t b; + isc_region_t r; + char t[1024]; + + /* + * Print 'name' on 'stream'. + */ + + REQUIRE(VALID_NAME(name)); + + isc_buffer_init(&b, t, sizeof(t)); + result = dns_name_totext(name, false, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_usedregion(&b, &r); + fprintf(stream, "%.*s", (int)r.length, (char *)r.base); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_settotextfilter(dns_name_totextfilter_t *proc) { + /* + * If we already have been here set / clear as appropriate. + */ + if (totext_filter_proc != NULL && proc != NULL) { + if (totext_filter_proc == proc) { + return (ISC_R_SUCCESS); + } + } + if (proc == NULL && totext_filter_proc != NULL) { + totext_filter_proc = NULL; + return (ISC_R_SUCCESS); + } + + totext_filter_proc = proc; + + return (ISC_R_SUCCESS); +} + +void +dns_name_format(const dns_name_t *name, char *cp, unsigned int size) { + isc_result_t result; + isc_buffer_t buf; + + REQUIRE(size > 0); + + /* + * Leave room for null termination after buffer. + */ + isc_buffer_init(&buf, cp, size - 1); + result = dns_name_totext(name, true, &buf); + if (result == ISC_R_SUCCESS) { + isc_buffer_putuint8(&buf, (uint8_t)'\0'); + } else { + snprintf(cp, size, ""); + } +} + +/* + * dns_name_tostring() -- similar to dns_name_format() but allocates its own + * memory. + */ +isc_result_t +dns_name_tostring(const dns_name_t *name, char **target, isc_mem_t *mctx) { + isc_result_t result; + isc_buffer_t buf; + isc_region_t reg; + char *p, txt[DNS_NAME_FORMATSIZE]; + + REQUIRE(VALID_NAME(name)); + REQUIRE(target != NULL && *target == NULL); + + isc_buffer_init(&buf, txt, sizeof(txt)); + result = dns_name_totext(name, false, &buf); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_usedregion(&buf, ®); + p = isc_mem_allocate(mctx, reg.length + 1); + memmove(p, (char *)reg.base, (int)reg.length); + p[reg.length] = '\0'; + + *target = p; + return (ISC_R_SUCCESS); +} + +/* + * dns_name_fromstring() -- convert directly from a string to a name, + * allocating memory as needed + */ +isc_result_t +dns_name_fromstring(dns_name_t *target, const char *src, unsigned int options, + isc_mem_t *mctx) { + return (dns_name_fromstring2(target, src, dns_rootname, options, mctx)); +} + +isc_result_t +dns_name_fromstring2(dns_name_t *target, const char *src, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx) { + isc_result_t result; + isc_buffer_t buf; + dns_fixedname_t fn; + dns_name_t *name; + + REQUIRE(src != NULL); + + isc_buffer_constinit(&buf, src, strlen(src)); + isc_buffer_add(&buf, strlen(src)); + if (BINDABLE(target) && target->buffer != NULL) { + name = target; + } else { + name = dns_fixedname_initname(&fn); + } + + result = dns_name_fromtext(name, &buf, origin, options, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (name != target) { + result = dns_name_dupwithoffsets(name, mctx, target); + } + return (result); +} + +void +dns_name_copy(const dns_name_t *source, dns_name_t *dest) { + isc_buffer_t *target = NULL; + unsigned char *ndata = NULL; + + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(dest)); + REQUIRE(BINDABLE(dest)); + + target = dest->buffer; + + REQUIRE(target != NULL); + REQUIRE(target->length >= source->length); + + isc_buffer_clear(target); + + ndata = (unsigned char *)target->base; + dest->ndata = target->base; + + if (source->length != 0) { + memmove(ndata, source->ndata, source->length); + } + + dest->ndata = ndata; + dest->labels = source->labels; + dest->length = source->length; + if ((source->attributes & DNS_NAMEATTR_ABSOLUTE) != 0) { + dest->attributes = DNS_NAMEATTR_ABSOLUTE; + } else { + dest->attributes = 0; + } + + if (dest->labels > 0 && dest->offsets != NULL) { + if (source->offsets != NULL && source->labels != 0) { + memmove(dest->offsets, source->offsets, source->labels); + } else { + set_offsets(dest, dest->offsets, NULL); + } + } + + isc_buffer_add(target, dest->length); +} + +/* + * Service Discovery Prefixes RFC 6763. + */ +static unsigned char b_dns_sd_udp_data[] = "\001b\007_dns-sd\004_udp"; +static unsigned char b_dns_sd_udp_offsets[] = { 0, 2, 10 }; +static unsigned char db_dns_sd_udp_data[] = "\002db\007_dns-sd\004_udp"; +static unsigned char db_dns_sd_udp_offsets[] = { 0, 3, 11 }; +static unsigned char r_dns_sd_udp_data[] = "\001r\007_dns-sd\004_udp"; +static unsigned char r_dns_sd_udp_offsets[] = { 0, 2, 10 }; +static unsigned char dr_dns_sd_udp_data[] = "\002dr\007_dns-sd\004_udp"; +static unsigned char dr_dns_sd_udp_offsets[] = { 0, 3, 11 }; +static unsigned char lb_dns_sd_udp_data[] = "\002lb\007_dns-sd\004_udp"; +static unsigned char lb_dns_sd_udp_offsets[] = { 0, 3, 11 }; + +static dns_name_t const dns_sd[] = { + DNS_NAME_INITNONABSOLUTE(b_dns_sd_udp_data, b_dns_sd_udp_offsets), + DNS_NAME_INITNONABSOLUTE(db_dns_sd_udp_data, db_dns_sd_udp_offsets), + DNS_NAME_INITNONABSOLUTE(r_dns_sd_udp_data, r_dns_sd_udp_offsets), + DNS_NAME_INITNONABSOLUTE(dr_dns_sd_udp_data, dr_dns_sd_udp_offsets), + DNS_NAME_INITNONABSOLUTE(lb_dns_sd_udp_data, lb_dns_sd_udp_offsets) +}; + +bool +dns_name_isdnssd(const dns_name_t *name) { + size_t i; + dns_name_t prefix; + + if (dns_name_countlabels(name) > 3U) { + dns_name_init(&prefix, NULL); + dns_name_getlabelsequence(name, 0, 3, &prefix); + for (i = 0; i < (sizeof(dns_sd) / sizeof(dns_sd[0])); i++) { + if (dns_name_equal(&prefix, &dns_sd[i])) { + return (true); + } + } + } + + return (false); +} + +static unsigned char inaddr10_offsets[] = { 0, 3, 11, 16 }; +static unsigned char inaddr172_offsets[] = { 0, 3, 7, 15, 20 }; +static unsigned char inaddr192_offsets[] = { 0, 4, 8, 16, 21 }; + +static unsigned char inaddr10[] = "\00210\007IN-ADDR\004ARPA"; + +static unsigned char inaddr16172[] = "\00216\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr17172[] = "\00217\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr18172[] = "\00218\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr19172[] = "\00219\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr20172[] = "\00220\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr21172[] = "\00221\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr22172[] = "\00222\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr23172[] = "\00223\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr24172[] = "\00224\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr25172[] = "\00225\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr26172[] = "\00226\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr27172[] = "\00227\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr28172[] = "\00228\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr29172[] = "\00229\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr30172[] = "\00230\003172\007IN-ADDR\004ARPA"; +static unsigned char inaddr31172[] = "\00231\003172\007IN-ADDR\004ARPA"; + +static unsigned char inaddr168192[] = "\003168\003192\007IN-ADDR\004ARPA"; + +static dns_name_t const rfc1918names[] = { + DNS_NAME_INITABSOLUTE(inaddr10, inaddr10_offsets), + DNS_NAME_INITABSOLUTE(inaddr16172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr17172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr18172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr19172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr20172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr21172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr22172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr23172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr24172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr25172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr26172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr27172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr28172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr29172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr30172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr31172, inaddr172_offsets), + DNS_NAME_INITABSOLUTE(inaddr168192, inaddr192_offsets) +}; + +bool +dns_name_isrfc1918(const dns_name_t *name) { + size_t i; + + for (i = 0; i < (sizeof(rfc1918names) / sizeof(*rfc1918names)); i++) { + if (dns_name_issubdomain(name, &rfc1918names[i])) { + return (true); + } + } + return (false); +} + +static unsigned char ulaoffsets[] = { 0, 2, 4, 8, 13 }; +static unsigned char ip6fc[] = "\001c\001f\003ip6\004ARPA"; +static unsigned char ip6fd[] = "\001d\001f\003ip6\004ARPA"; + +static dns_name_t const ulanames[] = { DNS_NAME_INITABSOLUTE(ip6fc, ulaoffsets), + DNS_NAME_INITABSOLUTE(ip6fd, + ulaoffsets) }; + +bool +dns_name_isula(const dns_name_t *name) { + size_t i; + + for (i = 0; i < (sizeof(ulanames) / sizeof(*ulanames)); i++) { + if (dns_name_issubdomain(name, &ulanames[i])) { + return (true); + } + } + return (false); +} + +/* + * Use a simple table as we don't want all the locale stuff + * associated with ishexdigit(). + */ +const char ishex[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +bool +dns_name_istat(const dns_name_t *name) { + unsigned char len; + const unsigned char *ndata; + + REQUIRE(VALID_NAME(name)); + + if (name->labels < 1) { + return (false); + } + + ndata = name->ndata; + len = ndata[0]; + INSIST(len <= name->length); + ndata++; + + /* + * Is there at least one trust anchor reported and is the + * label length consistent with a trust-anchor-telemetry label. + */ + if ((len < 8) || (len - 3) % 5 != 0) { + return (false); + } + + if (ndata[0] != '_' || maptolower[ndata[1]] != 't' || + maptolower[ndata[2]] != 'a') + { + return (false); + } + ndata += 3; + len -= 3; + + while (len > 0) { + INSIST(len >= 5); + if (ndata[0] != '-' || !ishex[ndata[1]] || !ishex[ndata[2]] || + !ishex[ndata[3]] || !ishex[ndata[4]]) + { + return (false); + } + ndata += 5; + len -= 5; + } + return (true); +} diff --git a/lib/dns/ncache.c b/lib/dns/ncache.c new file mode 100644 index 0000000..9247ac1 --- /dev/null +++ b/lib/dns/ncache.c @@ -0,0 +1,777 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DNS_NCACHE_RDATA 100U + +/* + * The format of an ncache rdata is a sequence of zero or more records of + * the following format: + * + * owner name + * type + * trust + * rdata count + * rdata length These two occur 'rdata count' + * rdata times. + * + */ + +static isc_result_t +addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *addedrdataset); + +static isc_result_t +copy_rdataset(dns_rdataset_t *rdataset, isc_buffer_t *buffer) { + isc_result_t result; + unsigned int count; + isc_region_t ar, r; + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * Copy the rdataset count to the buffer. + */ + isc_buffer_availableregion(buffer, &ar); + if (ar.length < 2) { + return (ISC_R_NOSPACE); + } + count = dns_rdataset_count(rdataset); + INSIST(count <= 65535); + isc_buffer_putuint16(buffer, (uint16_t)count); + + result = dns_rdataset_first(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + dns_rdata_toregion(&rdata, &r); + INSIST(r.length <= 65535); + isc_buffer_availableregion(buffer, &ar); + if (ar.length < 2) { + return (ISC_R_NOSPACE); + } + /* + * Copy the rdata length to the buffer. + */ + isc_buffer_putuint16(buffer, (uint16_t)r.length); + /* + * Copy the rdata to the buffer. + */ + result = isc_buffer_copyregion(buffer, &r); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); + } + if (result != ISC_R_NOMORE) { + return (result); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ncache_add(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, dns_rdataset_t *addedrdataset) { + return (addoptout(message, cache, node, covers, now, minttl, maxttl, + false, false, addedrdataset)); +} + +isc_result_t +dns_ncache_addoptout(dns_message_t *message, dns_db_t *cache, + dns_dbnode_t *node, dns_rdatatype_t covers, + isc_stdtime_t now, dns_ttl_t minttl, dns_ttl_t maxttl, + bool optout, dns_rdataset_t *addedrdataset) { + return (addoptout(message, cache, node, covers, now, minttl, maxttl, + optout, true, addedrdataset)); +} + +static isc_result_t +addoptout(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *addedrdataset) { + isc_result_t result; + isc_buffer_t buffer; + isc_region_t r; + dns_rdataset_t *rdataset; + dns_rdatatype_t type; + dns_name_t *name; + dns_ttl_t ttl; + dns_trust_t trust; + dns_rdata_t rdata[DNS_NCACHE_RDATA]; + dns_rdataset_t ncrdataset; + dns_rdatalist_t ncrdatalist; + unsigned char data[65536]; + unsigned int next = 0; + + /* + * Convert the authority data from 'message' into a negative cache + * rdataset, and store it in 'cache' at 'node'. + */ + + REQUIRE(message != NULL); + + /* + * We assume that all data in the authority section has been + * validated by the caller. + */ + + /* + * Initialize the list. + */ + dns_rdatalist_init(&ncrdatalist); + ncrdatalist.rdclass = dns_db_class(cache); + ncrdatalist.covers = covers; + ncrdatalist.ttl = maxttl; + + /* + * Build an ncache rdatas into buffer. + */ + ttl = maxttl; + trust = 0xffff; + isc_buffer_init(&buffer, data, sizeof(data)); + if (message->counts[DNS_SECTION_AUTHORITY]) { + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + } else { + result = ISC_R_NOMORE; + } + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + if ((name->attributes & DNS_NAMEATTR_NCACHE) != 0) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if ((rdataset->attributes & + DNS_RDATASETATTR_NCACHE) == 0) + { + continue; + } + type = rdataset->type; + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + if (type == dns_rdatatype_soa || + type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3) + { + if (ttl > rdataset->ttl) { + ttl = rdataset->ttl; + } + if (ttl < minttl) { + ttl = minttl; + } + if (trust > rdataset->trust) { + trust = rdataset->trust; + } + /* + * Copy the owner name to the buffer. + */ + dns_name_toregion(name, &r); + result = isc_buffer_copyregion(&buffer, + &r); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* + * Copy the type to the buffer. + */ + isc_buffer_availableregion(&buffer, &r); + if (r.length < 3) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint16(&buffer, + rdataset->type); + isc_buffer_putuint8( + &buffer, + (unsigned char)rdataset->trust); + /* + * Copy the rdataset into the buffer. + */ + result = copy_rdataset(rdataset, + &buffer); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (next >= DNS_NCACHE_RDATA) { + return (ISC_R_NOSPACE); + } + dns_rdata_init(&rdata[next]); + isc_buffer_remainingregion(&buffer, &r); + rdata[next].data = r.base; + rdata[next].length = r.length; + rdata[next].rdclass = + ncrdatalist.rdclass; + rdata[next].type = 0; + rdata[next].flags = 0; + ISC_LIST_APPEND(ncrdatalist.rdata, + &rdata[next], link); + isc_buffer_forward(&buffer, r.length); + next++; + } + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + if (result != ISC_R_NOMORE) { + return (result); + } + + if (trust == 0xffff) { + if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 && + message->counts[DNS_SECTION_ANSWER] == 0) + { + /* + * The response has aa set and we haven't followed + * any CNAME or DNAME chains. + */ + trust = dns_trust_authauthority; + } else { + trust = dns_trust_additional; + } + ttl = 0; + } + + INSIST(trust != 0xffff); + + ncrdatalist.ttl = ttl; + + dns_rdataset_init(&ncrdataset); + RUNTIME_CHECK(dns_rdatalist_tordataset(&ncrdatalist, &ncrdataset) == + ISC_R_SUCCESS); + if (!secure && trust > dns_trust_answer) { + trust = dns_trust_answer; + } + ncrdataset.trust = trust; + ncrdataset.attributes |= DNS_RDATASETATTR_NEGATIVE; + if (message->rcode == dns_rcode_nxdomain) { + ncrdataset.attributes |= DNS_RDATASETATTR_NXDOMAIN; + } + if (optout) { + ncrdataset.attributes |= DNS_RDATASETATTR_OPTOUT; + } + + return (dns_db_addrdataset(cache, node, NULL, now, &ncrdataset, 0, + addedrdataset)); +} + +isc_result_t +dns_ncache_towire(dns_rdataset_t *rdataset, dns_compress_t *cctx, + isc_buffer_t *target, unsigned int options, + unsigned int *countp) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + isc_region_t remaining, tavailable; + isc_buffer_t source, savedbuffer, rdlen; + dns_name_t name; + dns_rdatatype_t type; + unsigned int i, rcount, count; + + /* + * Convert the negative caching rdataset 'rdataset' to wire format, + * compressing names as specified in 'cctx', and storing the result in + * 'target'. + */ + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == 0); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); + + savedbuffer = *target; + count = 0; + + result = dns_rdataset_first(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + isc_buffer_init(&source, rdata.data, rdata.length); + isc_buffer_add(&source, rdata.length); + dns_name_init(&name, NULL); + isc_buffer_remainingregion(&source, &remaining); + dns_name_fromregion(&name, &remaining); + INSIST(remaining.length >= name.length); + isc_buffer_forward(&source, name.length); + remaining.length -= name.length; + + INSIST(remaining.length >= 5); + type = isc_buffer_getuint16(&source); + isc_buffer_forward(&source, 1); + rcount = isc_buffer_getuint16(&source); + + for (i = 0; i < rcount; i++) { + /* + * Get the length of this rdata and set up an + * rdata structure for it. + */ + isc_buffer_remainingregion(&source, &remaining); + INSIST(remaining.length >= 2); + dns_rdata_reset(&rdata); + rdata.length = isc_buffer_getuint16(&source); + isc_buffer_remainingregion(&source, &remaining); + rdata.data = remaining.base; + rdata.type = type; + rdata.rdclass = rdataset->rdclass; + INSIST(remaining.length >= rdata.length); + isc_buffer_forward(&source, rdata.length); + + if ((options & DNS_NCACHETOWIRE_OMITDNSSEC) != 0 && + dns_rdatatype_isdnssec(type)) + { + continue; + } + + /* + * Write the name. + */ + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + result = dns_name_towire(&name, cctx, target); + if (result != ISC_R_SUCCESS) { + goto rollback; + } + + /* + * See if we have space for type, class, ttl, and + * rdata length. Write the type, class, and ttl. + */ + isc_buffer_availableregion(target, &tavailable); + if (tavailable.length < 10) { + result = ISC_R_NOSPACE; + goto rollback; + } + isc_buffer_putuint16(target, type); + isc_buffer_putuint16(target, rdataset->rdclass); + isc_buffer_putuint32(target, rdataset->ttl); + + /* + * Save space for rdata length. + */ + rdlen = *target; + isc_buffer_add(target, 2); + + /* + * Write the rdata. + */ + result = dns_rdata_towire(&rdata, cctx, target); + if (result != ISC_R_SUCCESS) { + goto rollback; + } + + /* + * Set the rdata length field to the compressed + * length. + */ + INSIST((target->used >= rdlen.used + 2) && + (target->used - rdlen.used - 2 < 65536)); + isc_buffer_putuint16( + &rdlen, + (uint16_t)(target->used - rdlen.used - 2)); + + count++; + } + INSIST(isc_buffer_remaininglength(&source) == 0); + result = dns_rdataset_next(rdataset); + dns_rdata_reset(&rdata); + } + if (result != ISC_R_NOMORE) { + goto rollback; + } + + *countp = count; + + return (ISC_R_SUCCESS); + +rollback: + INSIST(savedbuffer.used < 65536); + dns_compress_rollback(cctx, (uint16_t)savedbuffer.used); + *countp = 0; + *target = savedbuffer; + + return (result); +} + +static void +rdataset_disassociate(dns_rdataset_t *rdataset) { + UNUSED(rdataset); +} + +static isc_result_t +rdataset_first(dns_rdataset_t *rdataset) { + unsigned char *raw = rdataset->private3; + unsigned int count; + + count = raw[0] * 256 + raw[1]; + if (count == 0) { + rdataset->private5 = NULL; + return (ISC_R_NOMORE); + } + raw += 2; + /* + * The privateuint4 field is the number of rdata beyond the cursor + * position, so we decrement the total count by one before storing + * it. + */ + count--; + rdataset->privateuint4 = count; + rdataset->private5 = raw; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdataset_next(dns_rdataset_t *rdataset) { + unsigned int count; + unsigned int length; + unsigned char *raw; + + count = rdataset->privateuint4; + if (count == 0) { + return (ISC_R_NOMORE); + } + count--; + rdataset->privateuint4 = count; + raw = rdataset->private5; + length = raw[0] * 256 + raw[1]; + raw += length + 2; + rdataset->private5 = raw; + + return (ISC_R_SUCCESS); +} + +static void +rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + unsigned char *raw = rdataset->private5; + isc_region_t r; + + REQUIRE(raw != NULL); + + r.length = raw[0] * 256 + raw[1]; + raw += 2; + r.base = raw; + dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r); +} + +static void +rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + *target = *source; + + /* + * Reset iterator state. + */ + target->privateuint4 = 0; + target->private5 = NULL; +} + +static unsigned int +rdataset_count(dns_rdataset_t *rdataset) { + unsigned char *raw = rdataset->private3; + unsigned int count; + + count = raw[0] * 256 + raw[1]; + + return (count); +} + +static void +rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) { + unsigned char *raw = rdataset->private3; + + raw[-1] = (unsigned char)trust; + rdataset->trust = trust; +} + +static dns_rdatasetmethods_t rdataset_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, /* addnoqname */ + NULL, /* getnoqname */ + NULL, /* addclosest */ + NULL, /* getclosest */ + rdataset_settrust, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL, /* getownercase */ + NULL /* addglue */ +}; + +isc_result_t +dns_ncache_getrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, + dns_rdatatype_t type, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t remaining; + isc_buffer_t source; + dns_name_t tname; + dns_rdatatype_t ttype; + dns_trust_t trust = dns_trust_none; + dns_rdataset_t rclone; + + REQUIRE(ncacherdataset != NULL); + REQUIRE(ncacherdataset->type == 0); + REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); + REQUIRE(name != NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + REQUIRE(type != dns_rdatatype_rrsig); + + dns_rdataset_init(&rclone); + dns_rdataset_clone(ncacherdataset, &rclone); + result = dns_rdataset_first(&rclone); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rclone, &rdata); + isc_buffer_init(&source, rdata.data, rdata.length); + isc_buffer_add(&source, rdata.length); + dns_name_init(&tname, NULL); + isc_buffer_remainingregion(&source, &remaining); + dns_name_fromregion(&tname, &remaining); + INSIST(remaining.length >= tname.length); + isc_buffer_forward(&source, tname.length); + remaining.length -= tname.length; + + INSIST(remaining.length >= 3); + ttype = isc_buffer_getuint16(&source); + + if (ttype == type && dns_name_equal(&tname, name)) { + trust = isc_buffer_getuint8(&source); + INSIST(trust <= dns_trust_ultimate); + isc_buffer_remainingregion(&source, &remaining); + break; + } + result = dns_rdataset_next(&rclone); + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&rclone); + if (result == ISC_R_NOMORE) { + return (ISC_R_NOTFOUND); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(remaining.length != 0); + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = ncacherdataset->rdclass; + rdataset->type = type; + rdataset->covers = 0; + rdataset->ttl = ncacherdataset->ttl; + rdataset->trust = trust; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + + rdataset->private3 = remaining.base; + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + rdataset->private6 = NULL; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ncache_getsigrdataset(dns_rdataset_t *ncacherdataset, dns_name_t *name, + dns_rdatatype_t covers, dns_rdataset_t *rdataset) { + dns_name_t tname; + dns_rdata_rrsig_t rrsig; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rclone; + dns_rdatatype_t type; + dns_trust_t trust = dns_trust_none; + isc_buffer_t source; + isc_region_t remaining, sigregion; + isc_result_t result; + unsigned char *raw; + unsigned int count; + + REQUIRE(ncacherdataset != NULL); + REQUIRE(ncacherdataset->type == 0); + REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); + REQUIRE(name != NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + dns_rdataset_init(&rclone); + dns_rdataset_clone(ncacherdataset, &rclone); + result = dns_rdataset_first(&rclone); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rclone, &rdata); + isc_buffer_init(&source, rdata.data, rdata.length); + isc_buffer_add(&source, rdata.length); + dns_name_init(&tname, NULL); + isc_buffer_remainingregion(&source, &remaining); + dns_name_fromregion(&tname, &remaining); + INSIST(remaining.length >= tname.length); + isc_buffer_forward(&source, tname.length); + isc_region_consume(&remaining, tname.length); + + INSIST(remaining.length >= 2); + type = isc_buffer_getuint16(&source); + isc_region_consume(&remaining, 2); + + if (type != dns_rdatatype_rrsig || + !dns_name_equal(&tname, name)) + { + result = dns_rdataset_next(&rclone); + dns_rdata_reset(&rdata); + continue; + } + + INSIST(remaining.length >= 1); + trust = isc_buffer_getuint8(&source); + INSIST(trust <= dns_trust_ultimate); + isc_region_consume(&remaining, 1); + + raw = remaining.base; + count = raw[0] * 256 + raw[1]; + INSIST(count > 0); + raw += 2; + sigregion.length = raw[0] * 256 + raw[1]; + raw += 2; + sigregion.base = raw; + dns_rdata_reset(&rdata); + dns_rdata_fromregion(&rdata, rdataset->rdclass, + dns_rdatatype_rrsig, &sigregion); + (void)dns_rdata_tostruct(&rdata, &rrsig, NULL); + if (rrsig.covered == covers) { + isc_buffer_remainingregion(&source, &remaining); + break; + } + + result = dns_rdataset_next(&rclone); + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&rclone); + if (result == ISC_R_NOMORE) { + return (ISC_R_NOTFOUND); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(remaining.length != 0); + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = ncacherdataset->rdclass; + rdataset->type = dns_rdatatype_rrsig; + rdataset->covers = covers; + rdataset->ttl = ncacherdataset->ttl; + rdataset->trust = trust; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + + rdataset->private3 = remaining.base; + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + rdataset->private6 = NULL; + return (ISC_R_SUCCESS); +} + +void +dns_ncache_current(dns_rdataset_t *ncacherdataset, dns_name_t *found, + dns_rdataset_t *rdataset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_trust_t trust; + isc_region_t remaining, sigregion; + isc_buffer_t source; + dns_name_t tname; + dns_rdatatype_t type; + unsigned int count; + dns_rdata_rrsig_t rrsig; + unsigned char *raw; + + REQUIRE(ncacherdataset != NULL); + REQUIRE(ncacherdataset->type == 0); + REQUIRE((ncacherdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0); + REQUIRE(found != NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + dns_rdataset_current(ncacherdataset, &rdata); + isc_buffer_init(&source, rdata.data, rdata.length); + isc_buffer_add(&source, rdata.length); + + dns_name_init(&tname, NULL); + isc_buffer_remainingregion(&source, &remaining); + dns_name_fromregion(found, &remaining); + INSIST(remaining.length >= found->length); + isc_buffer_forward(&source, found->length); + remaining.length -= found->length; + + INSIST(remaining.length >= 5); + type = isc_buffer_getuint16(&source); + trust = isc_buffer_getuint8(&source); + INSIST(trust <= dns_trust_ultimate); + isc_buffer_remainingregion(&source, &remaining); + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = ncacherdataset->rdclass; + rdataset->type = type; + if (type == dns_rdatatype_rrsig) { + /* + * Extract covers from RRSIG. + */ + raw = remaining.base; + count = raw[0] * 256 + raw[1]; + INSIST(count > 0); + raw += 2; + sigregion.length = raw[0] * 256 + raw[1]; + raw += 2; + sigregion.base = raw; + dns_rdata_reset(&rdata); + dns_rdata_fromregion(&rdata, rdataset->rdclass, rdataset->type, + &sigregion); + (void)dns_rdata_tostruct(&rdata, &rrsig, NULL); + rdataset->covers = rrsig.covered; + } else { + rdataset->covers = 0; + } + rdataset->ttl = ncacherdataset->ttl; + rdataset->trust = trust; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + + rdataset->private3 = remaining.base; + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + rdataset->private6 = NULL; +} diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c new file mode 100644 index 0000000..7cbf636 --- /dev/null +++ b/lib/dns/nsec.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +void +dns_nsec_setbit(unsigned char *array, unsigned int type, unsigned int bit) { + unsigned int shift, mask; + + shift = 7 - (type % 8); + mask = 1 << shift; + + if (bit != 0) { + array[type / 8] |= mask; + } else { + array[type / 8] &= (~mask & 0xFF); + } +} + +bool +dns_nsec_isset(const unsigned char *array, unsigned int type) { + unsigned int byte, shift, mask; + + byte = array[type / 8]; + shift = 7 - (type % 8); + mask = 1 << shift; + + return ((byte & mask) != 0); +} + +unsigned int +dns_nsec_compressbitmap(unsigned char *map, const unsigned char *raw, + unsigned int max_type) { + unsigned char *start = map; + unsigned int window; + int octet; + + if (raw == NULL) { + return (0); + } + + for (window = 0; window < 256; window++) { + if (window * 256 > max_type) { + break; + } + for (octet = 31; octet >= 0; octet--) { + if (*(raw + octet) != 0) { + break; + } + } + if (octet < 0) { + raw += 32; + continue; + } + *map++ = window; + *map++ = octet + 1; + /* + * Note: potential overlapping move. + */ + memmove(map, raw, octet + 1); + map += octet + 1; + raw += 32; + } + return ((unsigned int)(map - start)); +} + +isc_result_t +dns_nsec_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *target, unsigned char *buffer, + dns_rdata_t *rdata) { + isc_result_t result; + dns_rdataset_t rdataset; + isc_region_t r; + unsigned int i; + + unsigned char *nsec_bits, *bm; + unsigned int max_type; + dns_rdatasetiter_t *rdsiter; + + REQUIRE(target != NULL); + + memset(buffer, 0, DNS_NSEC_BUFFERSIZE); + dns_name_toregion(target, &r); + memmove(buffer, r.base, r.length); + r.base = buffer; + /* + * Use the end of the space for a raw bitmap leaving enough + * space for the window identifiers and length octets. + */ + bm = r.base + r.length + 512; + nsec_bits = r.base + r.length; + dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1); + dns_nsec_setbit(bm, dns_rdatatype_nsec, 1); + max_type = dns_rdatatype_nsec; + dns_rdataset_init(&rdataset); + rdsiter = NULL; + result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + return (result); + } + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + if (rdataset.type != dns_rdatatype_nsec && + rdataset.type != dns_rdatatype_nsec3 && + rdataset.type != dns_rdatatype_rrsig) + { + if (rdataset.type > max_type) { + max_type = rdataset.type; + } + dns_nsec_setbit(bm, rdataset.type, 1); + } + dns_rdataset_disassociate(&rdataset); + } + + /* + * At zone cuts, deny the existence of glue in the parent zone. + */ + if (dns_nsec_isset(bm, dns_rdatatype_ns) && + !dns_nsec_isset(bm, dns_rdatatype_soa)) + { + for (i = 0; i <= max_type; i++) { + if (dns_nsec_isset(bm, i) && + !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i)) + { + dns_nsec_setbit(bm, i, 0); + } + } + } + + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_NOMORE) { + return (result); + } + + nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type); + + r.length = (unsigned int)(nsec_bits - r.base); + INSIST(r.length <= DNS_NSEC_BUFFERSIZE); + dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec, &r); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_nsec_build(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + const dns_name_t *target, dns_ttl_t ttl) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[DNS_NSEC_BUFFERSIZE]; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdata_init(&rdata); + + RETERR(dns_nsec_buildrdata(db, version, node, target, data, &rdata)); + + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = dns_db_class(db); + rdatalist.type = dns_rdatatype_nsec; + rdatalist.ttl = ttl; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + RETERR(dns_rdatalist_tordataset(&rdatalist, &rdataset)); + result = dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + return (result); +} + +bool +dns_nsec_typepresent(dns_rdata_t *nsec, dns_rdatatype_t type) { + dns_rdata_nsec_t nsecstruct; + isc_result_t result; + bool present; + unsigned int i, len, window; + + REQUIRE(nsec != NULL); + REQUIRE(nsec->type == dns_rdatatype_nsec); + + /* This should never fail */ + result = dns_rdata_tostruct(nsec, &nsecstruct, NULL); + INSIST(result == ISC_R_SUCCESS); + + present = false; + for (i = 0; i < nsecstruct.len; i += len) { + INSIST(i + 2 <= nsecstruct.len); + window = nsecstruct.typebits[i]; + len = nsecstruct.typebits[i + 1]; + INSIST(len > 0 && len <= 32); + i += 2; + INSIST(i + len <= nsecstruct.len); + if (window * 256 > type) { + break; + } + if ((window + 1) * 256 <= type) { + continue; + } + if (type < (window * 256) + len * 8) { + present = dns_nsec_isset(&nsecstruct.typebits[i], + type % 256); + } + break; + } + dns_rdata_freestruct(&nsecstruct); + return (present); +} + +isc_result_t +dns_nsec_nseconly(dns_db_t *db, dns_dbversion_t *version, dns_diff_t *diff, + bool *answer) { + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_dnskey_t dnskey; + isc_result_t result; + + REQUIRE(answer != NULL); + + dns_rdataset_init(&rdataset); + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, 0, + 0, &rdataset, NULL); + dns_db_detachnode(db, &node); + + if (result == ISC_R_NOTFOUND) { + *answer = false; + } + if (result != ISC_R_SUCCESS) { + return (result); + } + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (dnskey.algorithm == DST_ALG_RSAMD5 || + dnskey.algorithm == DST_ALG_DH || + dnskey.algorithm == DST_ALG_DSA || + dnskey.algorithm == DST_ALG_RSASHA1) + { + bool deleted = false; + if (diff != NULL) { + for (dns_difftuple_t *tuple = + ISC_LIST_HEAD(diff->tuples); + tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + if (tuple->rdata.type != + dns_rdatatype_dnskey || + tuple->op != DNS_DIFFOP_DEL) + { + continue; + } + + if (dns_rdata_compare( + &rdata, &tuple->rdata) == 0) + { + deleted = true; + break; + } + } + } + + if (!deleted) { + break; + } + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + *answer = true; + } + if (result == ISC_R_NOMORE) { + *answer = false; + result = ISC_R_SUCCESS; + } + return (result); +} + +/*% + * Return ISC_R_SUCCESS if we can determine that the name doesn't exist + * or we can determine whether there is data or not at the name. + * If the name does not exist return the wildcard name. + * + * Return ISC_R_IGNORE when the NSEC is not the appropriate one. + */ +isc_result_t +dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + const dns_name_t *nsecname, dns_rdataset_t *nsecset, + bool *exists, bool *data, dns_name_t *wild, + dns_nseclog_t logit, void *arg) { + int order; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_namereln_t relation; + unsigned int olabels, nlabels, labels; + dns_rdata_nsec_t nsec; + bool atparent; + bool ns; + bool soa; + + REQUIRE(exists != NULL); + REQUIRE(data != NULL); + REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec); + + result = dns_rdataset_first(nsecset); + if (result != ISC_R_SUCCESS) { + (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC set"); + return (result); + } + dns_rdataset_current(nsecset, &rdata); + +#ifdef notyet + if (!dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) || + !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec)) + { + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC missing RRSIG and/or NSEC from type map"); + return (ISC_R_IGNORE); + } +#endif + + (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC"); + relation = dns_name_fullcompare(name, nsecname, &order, &olabels); + + if (order < 0) { + /* + * The name is not within the NSEC range. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC does not cover name, before NSEC"); + return (ISC_R_IGNORE); + } + + if (order == 0) { + /* + * The names are the same. If we are validating "." + * then atparent should not be set as there is no parent. + */ + atparent = (olabels != 1) && dns_rdatatype_atparent(type); + ns = dns_nsec_typepresent(&rdata, dns_rdatatype_ns); + soa = dns_nsec_typepresent(&rdata, dns_rdatatype_soa); + if (ns && !soa) { + if (!atparent) { + /* + * This NSEC record is from somewhere higher in + * the DNS, and at the parent of a delegation. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring parent nsec"); + return (ISC_R_IGNORE); + } + } else if (atparent && ns && soa) { + /* + * This NSEC record is from the child. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child nsec"); + return (ISC_R_IGNORE); + } + if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt || + type == dns_rdatatype_nsec || type == dns_rdatatype_key || + !dns_nsec_typepresent(&rdata, dns_rdatatype_cname)) + { + *exists = true; + *data = dns_nsec_typepresent(&rdata, type); + (*logit)(arg, ISC_LOG_DEBUG(3), + "nsec proves name exists (owner) data=%d", + *data); + return (ISC_R_SUCCESS); + } + (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC proves CNAME exists"); + return (ISC_R_IGNORE); + } + + if (relation == dns_namereln_subdomain && + dns_nsec_typepresent(&rdata, dns_rdatatype_ns) && + !dns_nsec_typepresent(&rdata, dns_rdatatype_soa)) + { + /* + * This NSEC record is from somewhere higher in + * the DNS, and at the parent of a delegation or + * at a DNAME. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent nsec"); + return (ISC_R_IGNORE); + } + + if (relation == dns_namereln_subdomain && + dns_nsec_typepresent(&rdata, dns_rdatatype_dname)) + { + (*logit)(arg, ISC_LOG_DEBUG(3), "nsec proves covered by dname"); + *exists = false; + return (DNS_R_DNAME); + } + + result = dns_rdata_tostruct(&rdata, &nsec, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + relation = dns_name_fullcompare(&nsec.next, name, &order, &nlabels); + if (order == 0) { + dns_rdata_freestruct(&nsec); + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring nsec matches next name"); + return (ISC_R_IGNORE); + } + + if (order < 0 && !dns_name_issubdomain(nsecname, &nsec.next)) { + /* + * The name is not within the NSEC range. + */ + dns_rdata_freestruct(&nsec); + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring nsec because name is past end of range"); + return (ISC_R_IGNORE); + } + + if (order > 0 && relation == dns_namereln_subdomain) { + (*logit)(arg, ISC_LOG_DEBUG(3), + "nsec proves name exist (empty)"); + dns_rdata_freestruct(&nsec); + *exists = true; + *data = false; + return (ISC_R_SUCCESS); + } + if (wild != NULL) { + dns_name_t common; + dns_name_init(&common, NULL); + if (olabels > nlabels) { + labels = dns_name_countlabels(nsecname); + dns_name_getlabelsequence(nsecname, labels - olabels, + olabels, &common); + } else { + labels = dns_name_countlabels(&nsec.next); + dns_name_getlabelsequence(&nsec.next, labels - nlabels, + nlabels, &common); + } + result = dns_name_concatenate(dns_wildcardname, &common, wild, + NULL); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&nsec); + (*logit)(arg, ISC_LOG_DEBUG(3), + "failure generating wildcard name"); + return (result); + } + } + dns_rdata_freestruct(&nsec); + (*logit)(arg, ISC_LOG_DEBUG(3), "nsec range ok"); + *exists = false; + return (ISC_R_SUCCESS); +} + +bool +dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) { + dns_rdataset_t rdataset; + isc_result_t result; + bool found = false; + + REQUIRE(DNS_RDATASET_VALID(nsecset)); + REQUIRE(nsecset->type == dns_rdatatype_nsec); + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsecset, &rdataset); + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) || + !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig)) + { + dns_rdataset_disassociate(&rdataset); + return (false); + } + found = true; + } + dns_rdataset_disassociate(&rdataset); + return (found); +} diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c new file mode 100644 index 0000000..b9fc699 --- /dev/null +++ b/lib/dns/nsec3.c @@ -0,0 +1,2195 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define OPTOUT(x) (((x)&DNS_NSEC3FLAG_OPTOUT) != 0) +#define CREATE(x) (((x)&DNS_NSEC3FLAG_CREATE) != 0) +#define INITIAL(x) (((x)&DNS_NSEC3FLAG_INITIAL) != 0) +#define REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0) + +isc_result_t +dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, + unsigned int hashalg, unsigned int flags, + unsigned int iterations, const unsigned char *salt, + size_t salt_length, const unsigned char *nexthash, + size_t hash_length, unsigned char *buffer, + dns_rdata_t *rdata) { + isc_result_t result; + dns_rdataset_t rdataset; + isc_region_t r; + unsigned int i; + bool found; + bool found_ns; + bool need_rrsig; + + unsigned char *nsec_bits, *bm; + unsigned int max_type; + dns_rdatasetiter_t *rdsiter; + unsigned char *p; + + REQUIRE(salt_length < 256U); + REQUIRE(hash_length < 256U); + REQUIRE(flags <= 0xffU); + REQUIRE(hashalg <= 0xffU); + REQUIRE(iterations <= 0xffffU); + + switch (hashalg) { + case dns_hash_sha1: + REQUIRE(hash_length == ISC_SHA1_DIGESTLENGTH); + break; + } + + memset(buffer, 0, DNS_NSEC3_BUFFERSIZE); + + p = buffer; + + *p++ = hashalg; + *p++ = flags; + + *p++ = iterations >> 8; + *p++ = iterations; + + *p++ = (unsigned char)salt_length; + memmove(p, salt, salt_length); + p += salt_length; + + *p++ = (unsigned char)hash_length; + memmove(p, nexthash, hash_length); + p += hash_length; + + r.length = (unsigned int)(p - buffer); + r.base = buffer; + + /* + * Use the end of the space for a raw bitmap leaving enough + * space for the window identifiers and length octets. + */ + bm = r.base + r.length + 512; + nsec_bits = r.base + r.length; + max_type = 0; + if (node == NULL) { + goto collapse_bitmap; + } + dns_rdataset_init(&rdataset); + rdsiter = NULL; + result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + return (result); + } + found = found_ns = need_rrsig = false; + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + if (rdataset.type != dns_rdatatype_nsec && + rdataset.type != dns_rdatatype_nsec3 && + rdataset.type != dns_rdatatype_rrsig) + { + if (rdataset.type > max_type) { + max_type = rdataset.type; + } + dns_nsec_setbit(bm, rdataset.type, 1); + /* + * Work out if we need to set the RRSIG bit for + * this node. We set the RRSIG bit if either of + * the following conditions are met: + * 1) We have a SOA or DS then we need to set + * the RRSIG bit as both always will be signed. + * 2) We set the RRSIG bit if we don't have + * a NS record but do have other data. + */ + if (rdataset.type == dns_rdatatype_soa || + rdataset.type == dns_rdatatype_ds) + { + need_rrsig = true; + } else if (rdataset.type == dns_rdatatype_ns) { + found_ns = true; + } else { + found = true; + } + } + dns_rdataset_disassociate(&rdataset); + } + if ((found && !found_ns) || need_rrsig) { + if (dns_rdatatype_rrsig > max_type) { + max_type = dns_rdatatype_rrsig; + } + dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1); + } + + /* + * At zone cuts, deny the existence of glue in the parent zone. + */ + if (dns_nsec_isset(bm, dns_rdatatype_ns) && + !dns_nsec_isset(bm, dns_rdatatype_soa)) + { + for (i = 0; i <= max_type; i++) { + if (dns_nsec_isset(bm, i) && + !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i)) + { + dns_nsec_setbit(bm, i, 0); + } + } + } + + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_NOMORE) { + return (result); + } + +collapse_bitmap: + nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type); + r.length = (unsigned int)(nsec_bits - r.base); + INSIST(r.length <= DNS_NSEC3_BUFFERSIZE); + dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec3, &r); + + return (ISC_R_SUCCESS); +} + +bool +dns_nsec3_typepresent(dns_rdata_t *rdata, dns_rdatatype_t type) { + dns_rdata_nsec3_t nsec3; + isc_result_t result; + bool present; + unsigned int i, len, window; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + /* This should never fail */ + result = dns_rdata_tostruct(rdata, &nsec3, NULL); + INSIST(result == ISC_R_SUCCESS); + + present = false; + for (i = 0; i < nsec3.len; i += len) { + INSIST(i + 2 <= nsec3.len); + window = nsec3.typebits[i]; + len = nsec3.typebits[i + 1]; + INSIST(len > 0 && len <= 32); + i += 2; + INSIST(i + len <= nsec3.len); + if (window * 256 > type) { + break; + } + if ((window + 1) * 256 <= type) { + continue; + } + if (type < (window * 256) + len * 8) { + present = dns_nsec_isset(&nsec3.typebits[i], + type % 256); + } + break; + } + dns_rdata_freestruct(&nsec3); + return (present); +} + +isc_result_t +dns_nsec3_generate_salt(unsigned char *salt, size_t saltlen) { + if (saltlen > 255U) { + return (ISC_R_RANGE); + } + isc_nonce_buf(salt, saltlen); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_nsec3_hashname(dns_fixedname_t *result, + unsigned char rethash[NSEC3_MAX_HASH_LENGTH], + size_t *hash_length, const dns_name_t *name, + const dns_name_t *origin, dns_hash_t hashalg, + unsigned int iterations, const unsigned char *salt, + size_t saltlength) { + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char nametext[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed; + dns_name_t *downcased; + isc_buffer_t namebuffer; + isc_region_t region; + size_t len; + + if (rethash == NULL) { + rethash = hash; + } + + memset(rethash, 0, NSEC3_MAX_HASH_LENGTH); + + downcased = dns_fixedname_initname(&fixed); + dns_name_downcase(name, downcased, NULL); + + /* hash the node name */ + len = isc_iterated_hash(rethash, hashalg, iterations, salt, + (int)saltlength, downcased->ndata, + downcased->length); + if (len == 0U) { + return (DNS_R_BADALG); + } + + if (hash_length != NULL) { + *hash_length = len; + } + + /* convert the hash to base32hex non-padded */ + region.base = rethash; + region.length = (unsigned int)len; + isc_buffer_init(&namebuffer, nametext, sizeof nametext); + isc_base32hexnp_totext(®ion, 1, "", &namebuffer); + + /* convert the hex to a domain name */ + dns_fixedname_init(result); + return (dns_name_fromtext(dns_fixedname_name(result), &namebuffer, + origin, 0, NULL)); +} + +unsigned int +dns_nsec3_hashlength(dns_hash_t hash) { + switch (hash) { + case dns_hash_sha1: + return (ISC_SHA1_DIGESTLENGTH); + } + return (0); +} + +bool +dns_nsec3_supportedhash(dns_hash_t hash) { + switch (hash) { + case dns_hash_sha1: + return (true); + } + return (false); +} + +/*% + * Update a single RR in version 'ver' of 'db' and log the + * update in 'diff'. + * + * Ensures: + * \li '*tuple' == NULL. Either the tuple is freed, or its + * ownership has been transferred to the diff. + */ +static isc_result_t +do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff) { + dns_diff_t temp_diff; + isc_result_t result; + + /* + * Create a singleton diff. + */ + dns_diff_init(diff->mctx, &temp_diff); + ISC_LIST_APPEND(temp_diff.tuples, *tuple, link); + + /* + * Apply it to the database. + */ + result = dns_diff_apply(&temp_diff, db, ver); + ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link); + if (result != ISC_R_SUCCESS) { + dns_difftuple_free(tuple); + return (result); + } + + /* + * Merge it into the current pending journal entry. + */ + dns_diff_appendminimal(diff, tuple); + + /* + * Do not clear temp_diff. + */ + return (ISC_R_SUCCESS); +} + +/*% + * Set '*exists' to true iff the given name exists, to false otherwise. + */ +static isc_result_t +name_exists(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + bool *exists) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *iter = NULL; + + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + *exists = false; + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_allrdatasets(db, node, version, 0, (isc_stdtime_t)0, + &iter); + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + result = dns_rdatasetiter_first(iter); + if (result == ISC_R_SUCCESS) { + *exists = true; + } else if (result == ISC_R_NOMORE) { + *exists = false; + result = ISC_R_SUCCESS; + } else { + *exists = false; + } + dns_rdatasetiter_destroy(&iter); + +cleanup_node: + dns_db_detachnode(db, &node); + return (result); +} + +static bool +match_nsec3param(const dns_rdata_nsec3_t *nsec3, + const dns_rdata_nsec3param_t *nsec3param) { + if (nsec3->hash == nsec3param->hash && + nsec3->iterations == nsec3param->iterations && + nsec3->salt_length == nsec3param->salt_length && + !memcmp(nsec3->salt, nsec3param->salt, nsec3->salt_length)) + { + return (true); + } + return (false); +} + +/*% + * Delete NSEC3 records at "name" which match "param", recording the + * change in "diff". + */ +static isc_result_t +delnsec3(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_difftuple_t *tuple = NULL; + dns_rdata_nsec3_t nsec3; + dns_rdataset_t rdataset; + isc_result_t result; + + result = dns_db_findnsec3node(db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + goto cleanup_node; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL)); + + if (!match_nsec3param(&nsec3, nsec3param)) { + continue; + } + + result = dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata, &tuple); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = do_one_tuple(&tuple, db, version, diff); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } + result = ISC_R_SUCCESS; + +failure: + dns_rdataset_disassociate(&rdataset); +cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +static bool +better_param(dns_rdataset_t *nsec3paramset, dns_rdata_t *param) { + dns_rdataset_t rdataset; + isc_result_t result; + + if (REMOVE(param->data[1])) { + return (true); + } + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsec3paramset, &rdataset); + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + if (rdataset.type != dns_rdatatype_nsec3param) { + dns_rdata_t tmprdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &tmprdata); + if (!dns_nsec3param_fromprivate(&tmprdata, &rdata, buf, + sizeof(buf))) + { + continue; + } + } else { + dns_rdataset_current(&rdataset, &rdata); + } + + if (rdata.length != param->length) { + continue; + } + if (rdata.data[0] != param->data[0] || REMOVE(rdata.data[1]) || + rdata.data[2] != param->data[2] || + rdata.data[3] != param->data[3] || + rdata.data[4] != param->data[4] || + memcmp(&rdata.data[5], ¶m->data[5], param->data[4])) + { + continue; + } + if (CREATE(rdata.data[1]) && !CREATE(param->data[1])) { + dns_rdataset_disassociate(&rdataset); + return (true); + } + } + dns_rdataset_disassociate(&rdataset); + return (false); +} + +static isc_result_t +find_nsec3(dns_rdata_nsec3_t *nsec3, dns_rdataset_t *rdataset, + const dns_rdata_nsec3param_t *nsec3param) { + isc_result_t result; + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, nsec3, NULL)); + dns_rdata_reset(&rdata); + if (match_nsec3param(nsec3, nsec3param)) { + break; + } + } +failure: + return (result); +} + +isc_result_t +dns_nsec3_addnsec3(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_ttl_t nsecttl, + bool unsecure, dns_diff_t *diff) { + dns_dbiterator_t *dbit = NULL; + dns_dbnode_t *node = NULL; + dns_dbnode_t *newnode = NULL; + dns_difftuple_t *tuple = NULL; + dns_fixedname_t fixed; + dns_fixedname_t fprev; + dns_hash_t hash; + dns_name_t *hashname; + dns_name_t *origin; + dns_name_t *prev; + dns_name_t empty; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + int pass; + bool exists = false; + bool maybe_remove_unsecure = false; + uint8_t flags; + isc_buffer_t buffer; + isc_result_t result; + unsigned char *old_next; + unsigned char *salt; + unsigned char nexthash[NSEC3_MAX_HASH_LENGTH]; + unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE]; + unsigned int iterations; + unsigned int labels; + size_t next_length; + unsigned int old_length; + unsigned int salt_length; + + hashname = dns_fixedname_initname(&fixed); + prev = dns_fixedname_initname(&fprev); + + dns_rdataset_init(&rdataset); + + origin = dns_db_origin(db); + + /* + * Chain parameters. + */ + hash = nsec3param->hash; + iterations = nsec3param->iterations; + salt_length = nsec3param->salt_length; + salt = nsec3param->salt; + + /* + * Default flags for a new chain. + */ + flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT; + + /* + * If this is the first NSEC3 in the chain nexthash will + * remain pointing to itself. + */ + next_length = sizeof(nexthash); + CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin, + hash, iterations, salt, salt_length)); + INSIST(next_length <= sizeof(nexthash)); + + /* + * Create the node if it doesn't exist and hold + * a reference to it until we have added the NSEC3. + */ + CHECK(dns_db_findnsec3node(db, hashname, true, &newnode)); + + /* + * Seek the iterator to the 'newnode'. + */ + CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit)); + CHECK(dns_dbiterator_seek(dbit, hashname)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset(db, newnode, version, dns_rdatatype_nsec3, + 0, (isc_stdtime_t)0, &rdataset, NULL); + /* + * If we updating a existing NSEC3 then find its + * next field. + */ + if (result == ISC_R_SUCCESS) { + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_SUCCESS) { + if (!CREATE(nsec3param->flags)) { + flags = nsec3.flags; + } + next_length = nsec3.next_length; + INSIST(next_length <= sizeof(nexthash)); + memmove(nexthash, nsec3.next, next_length); + dns_rdataset_disassociate(&rdataset); + /* + * If the NSEC3 is not for a unsecure delegation then + * we are just updating it. If it is for a unsecure + * delegation then we need find out if we need to + * remove the NSEC3 record or not by examining the + * previous NSEC3 record. + */ + if (!unsecure) { + goto addnsec3; + } else if (CREATE(nsec3param->flags) && OPTOUT(flags)) { + result = dns_nsec3_delnsec3(db, version, name, + nsec3param, diff); + goto failure; + } else { + maybe_remove_unsecure = true; + } + } else { + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + goto failure; + } + } + } + + /* + * Find the previous NSEC3 (if any) and update it if required. + */ + pass = 0; + do { + result = dns_dbiterator_prev(dbit); + if (result == ISC_R_NOMORE) { + pass++; + CHECK(dns_dbiterator_last(dbit)); + } + CHECK(dns_dbiterator_current(dbit, &node, prev)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + continue; + } + + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_NOMORE) { + dns_rdataset_disassociate(&rdataset); + continue; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + if (maybe_remove_unsecure) { + dns_rdataset_disassociate(&rdataset); + /* + * If we have OPTOUT set in the previous NSEC3 record + * we actually need to delete the NSEC3 record. + * Otherwise we just need to replace the NSEC3 record. + */ + if (OPTOUT(nsec3.flags)) { + result = dns_nsec3_delnsec3(db, version, name, + nsec3param, diff); + goto failure; + } + goto addnsec3; + } else { + /* + * Is this is a unsecure delegation we are adding? + * If so no change is required. + */ + if (OPTOUT(nsec3.flags) && unsecure) { + dns_rdataset_disassociate(&rdataset); + goto failure; + } + } + + old_next = nsec3.next; + old_length = nsec3.next_length; + + /* + * Delete the old previous NSEC3. + */ + CHECK(delnsec3(db, version, prev, nsec3param, diff)); + + /* + * Fixup the previous NSEC3. + */ + nsec3.next = nexthash; + nsec3.next_length = (unsigned char)next_length; + isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); + CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, + dns_rdatatype_nsec3, &nsec3, + &buffer)); + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, + rdataset.ttl, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + INSIST(old_length <= sizeof(nexthash)); + memmove(nexthash, old_next, old_length); + if (!CREATE(nsec3param->flags)) { + flags = nsec3.flags; + } + dns_rdata_reset(&rdata); + dns_rdataset_disassociate(&rdataset); + break; + } while (pass < 2); + +addnsec3: + /* + * Create the NSEC3 RDATA. + */ + CHECK(dns_db_findnode(db, name, false, &node)); + CHECK(dns_nsec3_buildrdata(db, version, node, hash, flags, iterations, + salt, salt_length, nexthash, next_length, + nsec3buf, &rdata)); + dns_db_detachnode(db, &node); + + /* + * Delete the old NSEC3 and record the change. + */ + CHECK(delnsec3(db, version, hashname, nsec3param, diff)); + /* + * Add the new NSEC3 and record the change. + */ + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname, + nsecttl, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + INSIST(tuple == NULL); + dns_rdata_reset(&rdata); + dns_db_detachnode(db, &newnode); + + /* + * Add missing NSEC3 records for empty nodes + */ + dns_name_init(&empty, NULL); + dns_name_clone(name, &empty); + do { + labels = dns_name_countlabels(&empty) - 1; + if (labels <= dns_name_countlabels(origin)) { + break; + } + dns_name_getlabelsequence(&empty, 1, labels, &empty); + CHECK(name_exists(db, version, &empty, &exists)); + if (exists) { + break; + } + CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty, + origin, hash, iterations, salt, + salt_length)); + + /* + * Create the node if it doesn't exist and hold + * a reference to it until we have added the NSEC3 + * or we discover we don't need to add make a change. + */ + CHECK(dns_db_findnsec3node(db, hashname, true, &newnode)); + result = dns_db_findrdataset(db, newnode, version, + dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + result = find_nsec3(&nsec3, &rdataset, nsec3param); + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + dns_db_detachnode(db, &newnode); + break; + } + if (result != ISC_R_NOMORE) { + goto failure; + } + } + + /* + * Find the previous NSEC3 and update it. + */ + CHECK(dns_dbiterator_seek(dbit, hashname)); + pass = 0; + do { + result = dns_dbiterator_prev(dbit); + if (result == ISC_R_NOMORE) { + pass++; + CHECK(dns_dbiterator_last(dbit)); + } + CHECK(dns_dbiterator_current(dbit, &node, prev)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset( + db, node, version, dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + continue; + } + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_NOMORE) { + dns_rdataset_disassociate(&rdataset); + continue; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + old_next = nsec3.next; + old_length = nsec3.next_length; + + /* + * Delete the old previous NSEC3. + */ + CHECK(delnsec3(db, version, prev, nsec3param, diff)); + + /* + * Fixup the previous NSEC3. + */ + nsec3.next = nexthash; + nsec3.next_length = (unsigned char)next_length; + isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); + CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, + dns_rdatatype_nsec3, &nsec3, + &buffer)); + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, + prev, rdataset.ttl, &rdata, + &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + INSIST(old_length <= sizeof(nexthash)); + memmove(nexthash, old_next, old_length); + if (!CREATE(nsec3param->flags)) { + flags = nsec3.flags; + } + dns_rdata_reset(&rdata); + dns_rdataset_disassociate(&rdataset); + break; + } while (pass < 2); + + INSIST(pass < 2); + + /* + * Create the NSEC3 RDATA for the empty node. + */ + CHECK(dns_nsec3_buildrdata( + db, version, NULL, hash, flags, iterations, salt, + salt_length, nexthash, next_length, nsec3buf, &rdata)); + /* + * Delete the old NSEC3 and record the change. + */ + CHECK(delnsec3(db, version, hashname, nsec3param, diff)); + + /* + * Add the new NSEC3 and record the change. + */ + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname, + nsecttl, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + INSIST(tuple == NULL); + dns_rdata_reset(&rdata); + dns_db_detachnode(db, &newnode); + } while (1); + + /* result cannot be ISC_R_NOMORE here */ + INSIST(result != ISC_R_NOMORE); + +failure: + if (dbit != NULL) { + dns_dbiterator_destroy(&dbit); + } + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (newnode != NULL) { + dns_db_detachnode(db, &newnode); + } + return (result); +} + +/*% + * Add NSEC3 records for "name", recording the change in "diff". + * The existing NSEC3 records are removed. + */ +isc_result_t +dns_nsec3_addnsec3s(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, + dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_rdata_nsec3param_t nsec3param; + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + + /* + * Find the NSEC3 parameters for this zone. + */ + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, 0, 0, &rdataset, + NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Update each active NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + if (nsec3param.flags != 0) { + continue; + } + /* + * We have a active chain. Update it. + */ + CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, + nsecttl, unsecure, diff)); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + return (result); +} + +bool +dns_nsec3param_fromprivate(dns_rdata_t *src, dns_rdata_t *target, + unsigned char *buf, size_t buflen) { + dns_decompress_t dctx; + isc_result_t result; + isc_buffer_t buf1; + isc_buffer_t buf2; + + /* + * Algorithm 0 (reserved by RFC 4034) is used to identify + * NSEC3PARAM records from DNSKEY pointers. + */ + if (src->length < 1 || src->data[0] != 0) { + return (false); + } + + isc_buffer_init(&buf1, src->data + 1, src->length - 1); + isc_buffer_add(&buf1, src->length - 1); + isc_buffer_setactive(&buf1, src->length - 1); + isc_buffer_init(&buf2, buf, (unsigned int)buflen); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); + result = dns_rdata_fromwire(target, src->rdclass, + dns_rdatatype_nsec3param, &buf1, &dctx, 0, + &buf2); + dns_decompress_invalidate(&dctx); + + return (result == ISC_R_SUCCESS); +} + +void +dns_nsec3param_toprivate(dns_rdata_t *src, dns_rdata_t *target, + dns_rdatatype_t privatetype, unsigned char *buf, + size_t buflen) { + REQUIRE(buflen >= src->length + 1); + + REQUIRE(DNS_RDATA_INITIALIZED(target)); + + memmove(buf + 1, src->data, src->length); + buf[0] = 0; + target->data = buf; + target->length = src->length + 1; + target->type = privatetype; + target->rdclass = src->rdclass; + target->flags = 0; + ISC_LINK_INIT(target, link); +} + +static isc_result_t +rr_exists(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name, + const dns_rdata_t *rdata, bool *flag) { + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + isc_result_t result; + + dns_rdataset_init(&rdataset); + if (rdata->type == dns_rdatatype_nsec3) { + CHECK(dns_db_findnsec3node(db, name, false, &node)); + } else { + CHECK(dns_db_findnode(db, name, false, &node)); + } + result = dns_db_findrdataset(db, node, ver, rdata->type, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + *flag = false; + result = ISC_R_SUCCESS; + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t myrdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &myrdata); + if (!dns_rdata_casecompare(&myrdata, rdata)) { + break; + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + *flag = true; + } else if (result == ISC_R_NOMORE) { + *flag = false; + result = ISC_R_SUCCESS; + } + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +isc_result_t +dns_nsec3param_salttotext(dns_rdata_nsec3param_t *nsec3param, char *dst, + size_t dstlen) { + isc_result_t result; + isc_region_t r; + isc_buffer_t b; + + REQUIRE(nsec3param != NULL); + REQUIRE(dst != NULL); + + if (nsec3param->salt_length == 0) { + if (dstlen < 2U) { + return (ISC_R_NOSPACE); + } + strlcpy(dst, "-", dstlen); + return (ISC_R_SUCCESS); + } + + r.base = nsec3param->salt; + r.length = nsec3param->salt_length; + isc_buffer_init(&b, dst, (unsigned int)dstlen); + + result = isc_hex_totext(&r, 2, "", &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (isc_buffer_availablelength(&b) < 1) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint8(&b, 0); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_nsec3param_deletechains(dns_db_t *db, dns_dbversion_t *ver, + dns_zone_t *zone, bool nonsec, dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_difftuple_t *tuple = NULL; + dns_name_t next; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + bool flag; + isc_result_t result = ISC_R_SUCCESS; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE + 1]; + dns_name_t *origin = dns_zone_getorigin(zone); + dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone); + + dns_name_init(&next, NULL); + dns_rdataset_init(&rdataset); + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Cause all NSEC3 chains to be deleted. + */ + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + goto try_private; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t private = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin, + rdataset.ttl, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); + INSIST(tuple == NULL); + + dns_nsec3param_toprivate(&rdata, &private, privatetype, buf, + sizeof(buf)); + buf[2] = DNS_NSEC3FLAG_REMOVE; + if (nonsec) { + buf[2] |= DNS_NSEC3FLAG_NONSEC; + } + + CHECK(rr_exists(db, ver, origin, &private, &flag)); + + if (!flag) { + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, + origin, 0, &private, + &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); + INSIST(tuple == NULL); + } + dns_rdata_reset(&rdata); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + + dns_rdataset_disassociate(&rdataset); + +try_private: + if (privatetype == 0) { + goto success; + } + result = dns_db_findrdataset(db, node, ver, privatetype, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rdataset, &rdata); + INSIST(rdata.length <= sizeof(buf)); + memmove(buf, rdata.data, rdata.length); + + /* + * Private NSEC3 record length >= 6. + * <0(1), hash(1), flags(1), iterations(2), saltlen(1)> + */ + if (rdata.length < 6 || buf[0] != 0 || + (buf[2] & DNS_NSEC3FLAG_REMOVE) != 0 || + (nonsec && (buf[2] & DNS_NSEC3FLAG_NONSEC) != 0)) + { + continue; + } + + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin, + 0, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); + INSIST(tuple == NULL); + + rdata.data = buf; + buf[2] = DNS_NSEC3FLAG_REMOVE; + if (nonsec) { + buf[2] |= DNS_NSEC3FLAG_NONSEC; + } + + CHECK(rr_exists(db, ver, origin, &rdata, &flag)); + + if (!flag) { + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, + origin, 0, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); + INSIST(tuple == NULL); + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } +success: + result = ISC_R_SUCCESS; + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + dns_db_detachnode(db, &node); + return (result); +} + +isc_result_t +dns_nsec3_addnsec3sx(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, + dns_rdatatype_t type, dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_rdata_nsec3param_t nsec3param; + dns_rdataset_t rdataset; + dns_rdataset_t prdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&prdataset); + + /* + * Find the NSEC3 parameters for this zone. + */ + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_findrdataset(db, node, version, type, 0, 0, &prdataset, + NULL); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, 0, 0, &rdataset, + NULL); + if (result == ISC_R_NOTFOUND) { + goto try_private; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Update each active NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + if (nsec3param.flags != 0) { + continue; + } + + /* + * We have a active chain. Update it. + */ + CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, + nsecttl, unsecure, diff)); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + + dns_rdataset_disassociate(&rdataset); + +try_private: + if (!dns_rdataset_isassociated(&prdataset)) { + goto success; + } + /* + * Update each active NSEC3 chain. + */ + for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&prdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + dns_rdataset_current(&prdataset, &rdata1); + if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, + sizeof(buf))) + { + continue; + } + CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL)); + + if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { + continue; + } + if (better_param(&prdataset, &rdata2)) { + continue; + } + + /* + * We have a active chain. Update it. + */ + CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, + nsecttl, unsecure, diff)); + } + if (result == ISC_R_NOMORE) { + success: + result = ISC_R_SUCCESS; + } +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&prdataset)) { + dns_rdataset_disassociate(&prdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + return (result); +} + +/*% + * Determine whether any NSEC3 records that were associated with + * 'name' should be deleted or if they should continue to exist. + * true indicates they should be deleted. + * false indicates they should be retained. + */ +static isc_result_t +deleteit(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name, + bool *yesno) { + isc_result_t result; + dns_fixedname_t foundname; + dns_fixedname_init(&foundname); + + result = dns_db_find(db, name, ver, dns_rdatatype_any, + DNS_DBFIND_GLUEOK | DNS_DBFIND_NOWILD, + (isc_stdtime_t)0, NULL, + dns_fixedname_name(&foundname), NULL, NULL); + if (result == DNS_R_EMPTYNAME || result == ISC_R_SUCCESS || + result == DNS_R_ZONECUT) + { + *yesno = false; + return (ISC_R_SUCCESS); + } + if (result == DNS_R_GLUE || result == DNS_R_DNAME || + result == DNS_R_DELEGATION || result == DNS_R_NXDOMAIN) + { + *yesno = true; + return (ISC_R_SUCCESS); + } + /* + * Silence compiler. + */ + *yesno = true; + return (result); +} + +isc_result_t +dns_nsec3_delnsec3(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) { + dns_dbiterator_t *dbit = NULL; + dns_dbnode_t *node = NULL; + dns_difftuple_t *tuple = NULL; + dns_fixedname_t fixed; + dns_fixedname_t fprev; + dns_hash_t hash; + dns_name_t *hashname; + dns_name_t *origin; + dns_name_t *prev; + dns_name_t empty; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + int pass; + bool yesno; + isc_buffer_t buffer; + isc_result_t result; + unsigned char *salt; + unsigned char nexthash[NSEC3_MAX_HASH_LENGTH]; + unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE]; + unsigned int iterations; + unsigned int labels; + size_t next_length; + unsigned int salt_length; + + hashname = dns_fixedname_initname(&fixed); + prev = dns_fixedname_initname(&fprev); + + dns_rdataset_init(&rdataset); + + origin = dns_db_origin(db); + + /* + * Chain parameters. + */ + hash = nsec3param->hash; + iterations = nsec3param->iterations; + salt_length = nsec3param->salt_length; + salt = nsec3param->salt; + + /* + * If this is the first NSEC3 in the chain nexthash will + * remain pointing to itself. + */ + next_length = sizeof(nexthash); + CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin, + hash, iterations, salt, salt_length)); + + CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit)); + + result = dns_dbiterator_seek(dbit, hashname); + if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) { + goto cleanup_orphaned_ents; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + CHECK(dns_dbiterator_current(dbit, &node, NULL)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + goto cleanup_orphaned_ents; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * If we find a existing NSEC3 for this chain then save the + * next field. + */ + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_SUCCESS) { + next_length = nsec3.next_length; + INSIST(next_length <= sizeof(nexthash)); + memmove(nexthash, nsec3.next, next_length); + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_NOMORE) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Find the previous NSEC3 and update it. + */ + pass = 0; + do { + result = dns_dbiterator_prev(dbit); + if (result == ISC_R_NOMORE) { + pass++; + CHECK(dns_dbiterator_last(dbit)); + } + CHECK(dns_dbiterator_current(dbit, &node, prev)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + continue; + } + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_NOMORE) { + dns_rdataset_disassociate(&rdataset); + continue; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Delete the old previous NSEC3. + */ + CHECK(delnsec3(db, version, prev, nsec3param, diff)); + + /* + * Fixup the previous NSEC3. + */ + nsec3.next = nexthash; + nsec3.next_length = (unsigned char)next_length; + if (CREATE(nsec3param->flags)) { + nsec3.flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT; + } + isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); + CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, + dns_rdatatype_nsec3, &nsec3, + &buffer)); + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, + rdataset.ttl, &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + dns_rdata_reset(&rdata); + dns_rdataset_disassociate(&rdataset); + break; + } while (pass < 2); + + /* + * Delete the old NSEC3 and record the change. + */ + CHECK(delnsec3(db, version, hashname, nsec3param, diff)); + + /* + * Delete NSEC3 records for now non active nodes. + */ +cleanup_orphaned_ents: + dns_name_init(&empty, NULL); + dns_name_clone(name, &empty); + do { + labels = dns_name_countlabels(&empty) - 1; + if (labels <= dns_name_countlabels(origin)) { + break; + } + dns_name_getlabelsequence(&empty, 1, labels, &empty); + CHECK(deleteit(db, version, &empty, &yesno)); + if (!yesno) { + break; + } + + CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty, + origin, hash, iterations, salt, + salt_length)); + result = dns_dbiterator_seek(dbit, hashname); + if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + CHECK(dns_dbiterator_current(dbit, &node, NULL)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_SUCCESS) { + next_length = nsec3.next_length; + INSIST(next_length <= sizeof(nexthash)); + memmove(nexthash, nsec3.next, next_length); + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_NOMORE) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + pass = 0; + do { + result = dns_dbiterator_prev(dbit); + if (result == ISC_R_NOMORE) { + pass++; + CHECK(dns_dbiterator_last(dbit)); + } + CHECK(dns_dbiterator_current(dbit, &node, prev)); + CHECK(dns_dbiterator_pause(dbit)); + result = dns_db_findrdataset( + db, node, version, dns_rdatatype_nsec3, 0, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + continue; + } + result = find_nsec3(&nsec3, &rdataset, nsec3param); + if (result == ISC_R_NOMORE) { + dns_rdataset_disassociate(&rdataset); + continue; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Delete the old previous NSEC3. + */ + CHECK(delnsec3(db, version, prev, nsec3param, diff)); + + /* + * Fixup the previous NSEC3. + */ + nsec3.next = nexthash; + nsec3.next_length = (unsigned char)next_length; + isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); + CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, + dns_rdatatype_nsec3, &nsec3, + &buffer)); + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, + prev, rdataset.ttl, &rdata, + &tuple)); + CHECK(do_one_tuple(&tuple, db, version, diff)); + dns_rdata_reset(&rdata); + dns_rdataset_disassociate(&rdataset); + break; + } while (pass < 2); + + INSIST(pass < 2); + + /* + * Delete the old NSEC3 and record the change. + */ + CHECK(delnsec3(db, version, hashname, nsec3param, diff)); + } while (1); + +success: + result = ISC_R_SUCCESS; + +failure: + if (dbit != NULL) { + dns_dbiterator_destroy(&dbit); + } + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +isc_result_t +dns_nsec3_delnsec3s(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_diff_t *diff) { + return (dns_nsec3_delnsec3sx(db, version, name, 0, diff)); +} + +isc_result_t +dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version, + const dns_name_t *name, dns_rdatatype_t privatetype, + dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_rdata_nsec3param_t nsec3param; + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + + /* + * Find the NSEC3 parameters for this zone. + */ + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, 0, 0, &rdataset, + NULL); + if (result == ISC_R_NOTFOUND) { + goto try_private; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Update each active NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + if (nsec3param.flags != 0) { + continue; + } + /* + * We have a active chain. Update it. + */ + CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff)); + } + dns_rdataset_disassociate(&rdataset); + +try_private: + if (privatetype == 0) { + goto success; + } + result = dns_db_findrdataset(db, node, version, privatetype, 0, 0, + &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Update each NSEC3 chain being built. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + dns_rdataset_current(&rdataset, &rdata1); + if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, + sizeof(buf))) + { + continue; + } + CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL)); + + if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { + continue; + } + if (better_param(&rdataset, &rdata2)) { + continue; + } + + /* + * We have a active chain. Update it. + */ + CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff)); + } + if (result == ISC_R_NOMORE) { + success: + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + return (result); +} + +isc_result_t +dns_nsec3_active(dns_db_t *db, dns_dbversion_t *version, bool complete, + bool *answer) { + return (dns_nsec3_activex(db, version, complete, 0, answer)); +} + +isc_result_t +dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete, + dns_rdatatype_t privatetype, bool *answer) { + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_nsec3param_t nsec3param; + isc_result_t result; + + REQUIRE(answer != NULL); + + dns_rdataset_init(&rdataset); + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, 0, 0, &rdataset, + NULL); + + if (result == ISC_R_NOTFOUND) { + goto try_private; + } + + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(db, &node); + return (result); + } + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (nsec3param.flags == 0) { + break; + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + dns_db_detachnode(db, &node); + *answer = true; + return (ISC_R_SUCCESS); + } + if (result == ISC_R_NOMORE) { + *answer = false; + } + +try_private: + if (privatetype == 0 || complete) { + dns_db_detachnode(db, &node); + *answer = false; + return (ISC_R_SUCCESS); + } + result = dns_db_findrdataset(db, node, version, privatetype, 0, 0, + &rdataset, NULL); + + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + *answer = false; + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + dns_rdataset_current(&rdataset, &rdata1); + if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, + sizeof(buf))) + { + continue; + } + result = dns_rdata_tostruct(&rdata2, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (!complete && CREATE(nsec3param.flags)) { + break; + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + *answer = true; + result = ISC_R_SUCCESS; + } + if (result == ISC_R_NOMORE) { + *answer = false; + result = ISC_R_SUCCESS; + } + + return (result); +} + +unsigned int +dns_nsec3_maxiterations(void) { + return (DNS_NSEC3_MAXITERATIONS); +} + +isc_result_t +dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + const dns_name_t *nsec3name, dns_rdataset_t *nsec3set, + dns_name_t *zonename, bool *exists, bool *data, + bool *optout, bool *unknown, bool *setclosest, + bool *setnearest, dns_name_t *closest, + dns_name_t *nearest, dns_nseclog_t logit, void *arg) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fzone; + dns_fixedname_t qfixed; + dns_label_t hashlabel; + dns_name_t *qname; + dns_name_t *zone; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + int order; + int scope; + bool atparent; + bool first; + bool ns; + bool soa; + isc_buffer_t buffer; + isc_result_t answer = ISC_R_IGNORE; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + unsigned int length; + unsigned int qlabels; + unsigned int zlabels; + + REQUIRE((exists == NULL && data == NULL) || + (exists != NULL && data != NULL)); + REQUIRE(nsec3set != NULL && nsec3set->type == dns_rdatatype_nsec3); + REQUIRE((setclosest == NULL && closest == NULL) || + (setclosest != NULL && closest != NULL)); + REQUIRE((setnearest == NULL && nearest == NULL) || + (setnearest != NULL && nearest != NULL)); + + result = dns_rdataset_first(nsec3set); + if (result != ISC_R_SUCCESS) { + (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC3 set"); + return (result); + } + + dns_rdataset_current(nsec3set, &rdata); + + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC3"); + + zone = dns_fixedname_initname(&fzone); + zlabels = dns_name_countlabels(nsec3name); + + /* + * NSEC3 records must have two or more labels to be valid. + */ + if (zlabels < 2) { + return (ISC_R_IGNORE); + } + + /* + * Strip off the NSEC3 hash to get the zone. + */ + zlabels--; + dns_name_split(nsec3name, zlabels, NULL, zone); + + /* + * If not below the zone name we can ignore this record. + */ + if (!dns_name_issubdomain(name, zone)) { + return (ISC_R_IGNORE); + } + + /* + * Is this zone the same or deeper than the current zone? + */ + if (dns_name_countlabels(zonename) == 0 || + dns_name_issubdomain(zone, zonename)) + { + dns_name_copy(zone, zonename); + } + + if (!dns_name_equal(zone, zonename)) { + return (ISC_R_IGNORE); + } + + /* + * Are we only looking for the most enclosing zone? + */ + if (exists == NULL || data == NULL) { + return (ISC_R_SUCCESS); + } + + /* + * Only set unknown once we are sure that this NSEC3 is from + * the deepest covering zone. + */ + if (!dns_nsec3_supportedhash(nsec3.hash)) { + if (unknown != NULL) { + *unknown = true; + } + return (ISC_R_IGNORE); + } + + /* + * Recover the hash from the first label. + */ + dns_name_getlabel(nsec3name, 0, &hashlabel); + isc_region_consume(&hashlabel, 1); + isc_buffer_init(&buffer, owner, sizeof(owner)); + result = isc_base32hex_decoderegion(&hashlabel, &buffer); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * The hash lengths should match. If not ignore the record. + */ + if (isc_buffer_usedlength(&buffer) != nsec3.next_length) { + return (ISC_R_IGNORE); + } + + /* + * Work out what this NSEC3 covers. + * Inside (<0) or outside (>=0). + */ + scope = memcmp(owner, nsec3.next, nsec3.next_length); + + /* + * Prepare to compute all the hashes. + */ + qname = dns_fixedname_initname(&qfixed); + dns_name_downcase(name, qname, NULL); + qlabels = dns_name_countlabels(qname); + first = true; + + while (qlabels >= zlabels) { + /* + * If there are too many iterations reject the NSEC3 record. + */ + if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) { + return (DNS_R_NSEC3ITERRANGE); + } + + length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations, + nsec3.salt, nsec3.salt_length, + qname->ndata, qname->length); + /* + * The computed hash length should match. + */ + if (length != nsec3.next_length) { + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring NSEC bad length %u vs %u", length, + nsec3.next_length); + return (ISC_R_IGNORE); + } + + order = memcmp(hash, owner, length); + if (first && order == 0) { + /* + * The hashes are the same. + */ + atparent = dns_rdatatype_atparent(type); + ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns); + soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa); + if (ns && !soa) { + if (!atparent) { + /* + * This NSEC3 record is from somewhere + * higher in the DNS, and at the + * parent of a delegation. It can not + * be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + } else if (atparent && ns && soa) { + /* + * This NSEC3 record is from the child. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring child NSEC3"); + return (ISC_R_IGNORE); + } + if (type == dns_rdatatype_cname || + type == dns_rdatatype_nxt || + type == dns_rdatatype_nsec || + type == dns_rdatatype_key || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) + { + *exists = true; + *data = dns_nsec3_typepresent(&rdata, type); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 proves name exists (owner) " + "data=%d", + *data); + return (ISC_R_SUCCESS); + } + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 proves CNAME exists"); + return (ISC_R_IGNORE); + } + + if (order == 0 && + dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa)) + { + /* + * This NSEC3 record is from somewhere higher in + * the DNS, and at the parent of a delegation. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + + /* + * Potential closest encloser. + */ + if (order == 0) { + if (closest != NULL && + (dns_name_countlabels(closest) == 0 || + dns_name_issubdomain(qname, closest)) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) && + !dns_nsec3_typepresent(&rdata, + dns_rdatatype_dname) && + (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns))) + { + dns_name_format(qname, namebuf, + sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 indicates potential closest " + "encloser: '%s'", + namebuf); + dns_name_copy(qname, closest); + *setclosest = true; + } + dns_name_format(qname, namebuf, sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 at super-domain %s", namebuf); + return (answer); + } + + /* + * Find if the name does not exist. + * + * We continue as we need to find the name closest to the + * closest encloser that doesn't exist. + * + * We also need to continue to ensure that we are not + * proving the non-existence of a record in a sub-zone. + * If that would be the case we will return ISC_R_IGNORE + * above. + */ + if ((scope < 0 && order > 0 && + memcmp(hash, nsec3.next, length) < 0) || + (scope >= 0 && + (order > 0 || memcmp(hash, nsec3.next, length) < 0))) + { + dns_name_format(qname, namebuf, sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 proves " + "name does not exist: '%s'", + namebuf); + if (nearest != NULL && + (dns_name_countlabels(nearest) == 0 || + dns_name_issubdomain(nearest, qname))) + { + dns_name_copy(qname, nearest); + *setnearest = true; + } + + *exists = false; + *data = false; + if (optout != NULL) { + *optout = ((nsec3.flags & + DNS_NSEC3FLAG_OPTOUT) != 0); + (*logit)(arg, ISC_LOG_DEBUG(3), + (*optout ? "NSEC3 indicates optout" + : "NSEC3 indicates secure " + "range")); + } + answer = ISC_R_SUCCESS; + } + + qlabels--; + if (qlabels > 0) { + dns_name_split(qname, qlabels, NULL, qname); + } + first = false; + } + return (answer); +} diff --git a/lib/dns/nta.c b/lib/dns/nta.c new file mode 100644 index 0000000..5614eb8 --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,703 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dns_nta { + unsigned int magic; + isc_refcount_t refcount; + dns_ntatable_t *ntatable; + bool forced; + isc_timer_t *timer; + dns_fetch_t *fetch; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; + dns_fixedname_t fn; + dns_name_t *name; + isc_stdtime_t expiry; +}; + +#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n') +#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC) + +/* + * Obtain a reference to the nta object. Released by + * nta_detach. + */ +static void +nta_ref(dns_nta_t *nta) { + isc_refcount_increment(&nta->refcount); +} + +static void +nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { + REQUIRE(ntap != NULL && VALID_NTA(*ntap)); + dns_nta_t *nta = *ntap; + *ntap = NULL; + + if (isc_refcount_decrement(&nta->refcount) == 1) { + isc_refcount_destroy(&nta->refcount); + nta->magic = 0; + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, NULL, + NULL, true); + isc_timer_destroy(&nta->timer); + } + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + dns_resolver_destroyfetch(&nta->fetch); + } + isc_mem_put(mctx, nta, sizeof(dns_nta_t)); + } +} + +static void +free_nta(void *data, void *arg) { + dns_nta_t *nta = (dns_nta_t *)data; + isc_mem_t *mctx = (isc_mem_t *)arg; + + nta_detach(mctx, &nta); +} + +isc_result_t +dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep) { + dns_ntatable_t *ntatable; + isc_result_t result; + + REQUIRE(ntatablep != NULL && *ntatablep == NULL); + + ntatable = isc_mem_get(view->mctx, sizeof(*ntatable)); + + ntatable->task = NULL; + result = isc_task_create(taskmgr, 0, &ntatable->task); + if (result != ISC_R_SUCCESS) { + goto cleanup_ntatable; + } + isc_task_setname(ntatable->task, "ntatable", ntatable); + + ntatable->table = NULL; + result = dns_rbt_create(view->mctx, free_nta, view->mctx, + &ntatable->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + + isc_rwlock_init(&ntatable->rwlock, 0, 0); + + ntatable->shuttingdown = false; + ntatable->timermgr = timermgr; + ntatable->taskmgr = taskmgr; + + ntatable->view = view; + isc_refcount_init(&ntatable->references, 1); + + ntatable->magic = NTATABLE_MAGIC; + *ntatablep = ntatable; + + return (ISC_R_SUCCESS); + +cleanup_task: + isc_task_detach(&ntatable->task); + +cleanup_ntatable: + isc_mem_put(view->mctx, ntatable, sizeof(*ntatable)); + + return (result); +} + +void +dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) { + REQUIRE(VALID_NTATABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep) { + dns_ntatable_t *ntatable; + + REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep)); + + ntatable = *ntatablep; + *ntatablep = NULL; + + if (isc_refcount_decrement(&ntatable->references) == 1) { + dns_rbt_destroy(&ntatable->table); + isc_rwlock_destroy(&ntatable->rwlock); + isc_refcount_destroy(&ntatable->references); + if (ntatable->task != NULL) { + isc_task_detach(&ntatable->task); + } + ntatable->timermgr = NULL; + ntatable->taskmgr = NULL; + ntatable->magic = 0; + isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); + } +} + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; + dns_nta_t *nta = devent->ev_arg; + isc_result_t eresult = devent->result; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = ntatable->view; + isc_stdtime_t now; + + UNUSED(task); + + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + if (nta->fetch == devent->fetch) { + nta->fetch = NULL; + } + dns_resolver_destroyfetch(&devent->fetch); + + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + + isc_event_free(&event); + isc_stdtime_get(&now); + + switch (eresult) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_NXRRSET: + if (nta->expiry > now) { + nta->expiry = now; + } + break; + default: + break; + } + + /* + * If we're expiring before the next recheck, we might + * as well stop the timer now. + */ + if (nta->timer != NULL && nta->expiry - now < view->nta_recheck) { + (void)isc_timer_reset(nta->timer, isc_timertype_inactive, NULL, + NULL, true); + } + nta_detach(view->mctx, &nta); + dns_view_weakdetach(&view); +} + +static void +checkbogus(isc_task_t *task, isc_event_t *event) { + dns_nta_t *nta = event->ev_arg; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = NULL; + isc_result_t result; + + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + nta->fetch = NULL; + } + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + + isc_event_free(&event); + + nta_ref(nta); + dns_view_weakattach(ntatable->view, &view); + result = dns_resolver_createfetch( + view->resolver, nta->name, dns_rdatatype_nsec, NULL, NULL, NULL, + NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, task, fetch_done, nta, + &nta->rdataset, &nta->sigrdataset, &nta->fetch); + if (result != ISC_R_SUCCESS) { + nta_detach(view->mctx, &nta); + dns_view_weakdetach(&view); + } +} + +static isc_result_t +settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, uint32_t lifetime) { + isc_result_t result; + isc_interval_t interval; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(VALID_NTA(nta)); + + if (ntatable->timermgr == NULL) { + return (ISC_R_SUCCESS); + } + + view = ntatable->view; + if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) { + return (ISC_R_SUCCESS); + } + + isc_interval_set(&interval, view->nta_recheck, 0); + result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker, + NULL, &interval, ntatable->task, checkbogus, + nta, &nta->timer); + if (result != ISC_R_SUCCESS) { + isc_timer_destroy(&nta->timer); + } + return (result); +} + +static isc_result_t +nta_create(dns_ntatable_t *ntatable, const dns_name_t *name, + dns_nta_t **target) { + dns_nta_t *nta = NULL; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(target != NULL && *target == NULL); + + view = ntatable->view; + + nta = isc_mem_get(view->mctx, sizeof(dns_nta_t)); + + nta->ntatable = ntatable; + nta->expiry = 0; + nta->timer = NULL; + nta->fetch = NULL; + dns_rdataset_init(&nta->rdataset); + dns_rdataset_init(&nta->sigrdataset); + + isc_refcount_init(&nta->refcount, 1); + + nta->name = dns_fixedname_initname(&nta->fn); + dns_name_copy(name, nta->name); + + nta->magic = NTA_MAGIC; + + *target = nta; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force, + isc_stdtime_t now, uint32_t lifetime) { + isc_result_t result = ISC_R_SUCCESS; + dns_nta_t *nta = NULL; + dns_rbtnode_t *node; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + + view = ntatable->view; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (ntatable->shuttingdown) { + goto unlock; + } + + result = nta_create(ntatable, name, &nta); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + nta->expiry = now + lifetime; + nta->forced = force; + + node = NULL; + result = dns_rbt_addnode(ntatable->table, name, &node); + if (result == ISC_R_SUCCESS) { + if (!force) { + (void)settimer(ntatable, nta, lifetime); + } + node->data = nta; + nta = NULL; + } else if (result == ISC_R_EXISTS) { + dns_nta_t *n = node->data; + if (n == NULL) { + if (!force) { + (void)settimer(ntatable, nta, lifetime); + } + node->data = nta; + nta = NULL; + } else { + n->expiry = nta->expiry; + nta_detach(view->mctx, &nta); + } + result = ISC_R_SUCCESS; + } + +unlock: + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (nta != NULL) { + nta_detach(view->mctx, &nta); + } + + return (result); +} + +/* + * Caller must hold a write lock on rwlock. + */ +static isc_result_t +deletenode(dns_ntatable_t *ntatable, const dns_name_t *name) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(name != NULL); + + result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (node->data != NULL) { + result = dns_rbt_deletenode(ntatable->table, node, + false); + } else { + result = ISC_R_NOTFOUND; + } + } else if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + + return (result); +} + +isc_result_t +dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) { + isc_result_t result; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + result = deletenode(ntatable, name); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + return (result); +} + +bool +dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, + const dns_name_t *name, const dns_name_t *anchor) { + isc_result_t result; + dns_fixedname_t fn; + dns_rbtnode_t *node; + dns_name_t *foundname; + dns_nta_t *nta = NULL; + bool answer = false; + isc_rwlocktype_t locktype = isc_rwlocktype_read; + + REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable)); + REQUIRE(dns_name_isabsolute(name)); + + if (ntatable == NULL) { + return (false); + } + + foundname = dns_fixedname_initname(&fn); + +relock: + RWLOCK(&ntatable->rwlock, locktype); +again: + node = NULL; + result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == DNS_R_PARTIALMATCH) { + if (dns_name_issubdomain(foundname, anchor)) { + result = ISC_R_SUCCESS; + } + } + if (result == ISC_R_SUCCESS) { + nta = (dns_nta_t *)node->data; + answer = (nta->expiry > now); + } + + /* Deal with expired NTA */ + if (result == ISC_R_SUCCESS && !answer) { + char nb[DNS_NAME_FORMATSIZE]; + + if (locktype == isc_rwlocktype_read) { + RWUNLOCK(&ntatable->rwlock, locktype); + locktype = isc_rwlocktype_write; + goto relock; + } + + dns_name_format(foundname, nb, sizeof(nb)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting expired NTA at %s", nb); + + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, NULL, + NULL, true); + isc_timer_destroy(&nta->timer); + } + + result = deletenode(ntatable, foundname); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting NTA failed: %s", + isc_result_totext(result)); + } + goto again; + } + RWUNLOCK(&ntatable->rwlock, locktype); + + return (answer); +} + +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + isc_result_t result; + + result = isc_buffer_reserve(b, strlen(str)); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_putstr(*b, str); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view, + isc_buffer_t **buf) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + bool first = true; + isc_stdtime_t now; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + goto cleanup; + } + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *)node->data; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char obuf[DNS_NAME_FORMATSIZE + + ISC_FORMATHTTPTIMESTAMP_SIZE + + sizeof("expired: \n")]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + + if (n->expiry != 0xffffffffU) { + /* Normal NTA entries */ + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, + sizeof(tbuf)); + + snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s", + first ? "" : "\n", nbuf, + view != NULL ? "/" : "", + view != NULL ? view : "", + n->expiry <= now ? "expired" + : "expiry", + tbuf); + } else { + /* "validate-except" entries */ + snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s", + first ? "" : "\n", nbuf, + view != NULL ? "/" : "", + view != NULL ? view : "", "permanent"); + } + + first = false; + result = putstr(buf, obuf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + return (result); +} + +isc_result_t +dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_stdtime_t now; + bool written = false; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + goto cleanup; + } + + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + isc_buffer_t b; + char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + dns_nta_t *n = (dns_nta_t *)node->data; + + /* + * Skip this node if the expiry is already in the + * past, or if this is a "validate-except" entry. + */ + if (n->expiry <= now || n->expiry == 0xffffffffU) { + goto skip; + } + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + + isc_buffer_init(&b, nbuf, sizeof(nbuf)); + result = dns_name_totext(name, false, &b); + if (result != ISC_R_SUCCESS) { + goto skip; + } + + /* Zero terminate. */ + isc_buffer_putuint8(&b, 0); + + isc_buffer_init(&b, tbuf, sizeof(tbuf)); + dns_time32_totext(n->expiry, &b); + + /* Zero terminate. */ + isc_buffer_putuint8(&b, 0); + + fprintf(fp, "%s %s %s\n", nbuf, + n->forced ? "forced" : "regular", tbuf); + written = true; + } + skip: + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + + if (result != ISC_R_SUCCESS) { + return (result); + } else { + return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND); + } +} + +void +dns_ntatable_shutdown(dns_ntatable_t *ntatable) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + + REQUIRE(VALID_NTATABLE(ntatable)); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + ntatable->shuttingdown = true; + + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *nta = (dns_nta_t *)node->data; + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, + NULL, NULL, true); + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); +} diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c new file mode 100644 index 0000000..addf16a --- /dev/null +++ b/lib/dns/openssl_link.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" + +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + +#include "openssl_shim.h" + +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +static ENGINE *e = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + +static void +enable_fips_mode(void) { +#ifdef HAVE_FIPS_MODE + if (FIPS_mode() != 0) { + /* + * FIPS mode is already enabled. + */ + return; + } + + if (FIPS_mode_set(1) == 0) { + dst__openssl_toresult2("FIPS_mode_set", DST_R_OPENSSLFAILURE); + exit(1); + } +#endif /* HAVE_FIPS_MODE */ +} + +isc_result_t +dst__openssl_init(const char *engine) { + isc_result_t result = ISC_R_SUCCESS; + + enable_fips_mode(); + +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + if (engine != NULL && *engine == '\0') { + engine = NULL; + } + + if (engine != NULL) { + e = ENGINE_by_id(engine); + if (e == NULL) { + result = DST_R_NOENGINE; + goto cleanup_rm; + } + if (!ENGINE_init(e)) { + result = DST_R_NOENGINE; + goto cleanup_rm; + } + /* This will init the engine. */ + if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { + result = DST_R_NOENGINE; + goto cleanup_init; + } + } + + return (ISC_R_SUCCESS); +cleanup_init: + ENGINE_finish(e); +cleanup_rm: + if (e != NULL) { + ENGINE_free(e); + } + e = NULL; + ERR_clear_error(); +#else + UNUSED(engine); +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + return (result); +} + +void +dst__openssl_destroy(void) { +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + if (e != NULL) { + ENGINE_finish(e); + ENGINE_free(e); + } + e = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ +} + +static isc_result_t +toresult(isc_result_t fallback) { + isc_result_t result = fallback; + unsigned long err = ERR_peek_error(); +#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) + int lib = ERR_GET_LIB(err); +#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */ + int reason = ERR_GET_REASON(err); + + switch (reason) { + /* + * ERR_* errors are globally unique; others + * are unique per sublibrary + */ + case ERR_R_MALLOC_FAILURE: + result = ISC_R_NOMEMORY; + break; + default: +#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) + if (lib == ERR_R_ECDSA_LIB && + reason == ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) + { + result = ISC_R_NOENTROPY; + break; + } +#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */ + break; + } + + return (result); +} + +isc_result_t +dst__openssl_toresult(isc_result_t fallback) { + isc_result_t result; + + result = toresult(fallback); + + ERR_clear_error(); + return (result); +} + +isc_result_t +dst__openssl_toresult2(const char *funcname, isc_result_t fallback) { + return (dst__openssl_toresult3(DNS_LOGCATEGORY_GENERAL, funcname, + fallback)); +} + +isc_result_t +dst__openssl_toresult3(isc_logcategory_t *category, const char *funcname, + isc_result_t fallback) { + isc_result_t result; + unsigned long err; + const char *file, *func, *data; + int line, flags; + char buf[256]; + + result = toresult(fallback); + + isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING, + "%s failed (%s)", funcname, isc_result_totext(result)); + + if (result == ISC_R_NOMEMORY) { + goto done; + } + + for (;;) { + err = ERR_get_error_all(&file, &line, &func, &data, &flags); + if (err == 0U) { + goto done; + } + ERR_error_string_n(err, buf, sizeof(buf)); + isc_log_write(dns_lctx, category, DNS_LOGMODULE_CRYPTO, + ISC_LOG_INFO, "%s:%s:%d:%s", buf, file, line, + ((flags & ERR_TXT_STRING) != 0) ? data : ""); + } + +done: + ERR_clear_error(); + return (result); +} + +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +ENGINE * +dst__openssl_getengine(const char *engine) { + if (engine == NULL) { + return (NULL); + } + if (e == NULL) { + return (NULL); + } + if (strcmp(engine, ENGINE_get_id(e)) == 0) { + return (e); + } + return (NULL); +} +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + +/*! \file */ diff --git a/lib/dns/openssl_shim.c b/lib/dns/openssl_shim.c new file mode 100644 index 0000000..816813a --- /dev/null +++ b/lib/dns/openssl_shim.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include "openssl_shim.h" + +#if !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L +/* From OpenSSL 1.1.0 */ +int +RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { + /* + * If the fields n and e in r are NULL, the corresponding input + * parameters MUST be non-NULL for n and e. d may be + * left NULL (in case only the public key is used). + */ + if ((r->n == NULL && n == NULL) || (r->e == NULL && e == NULL)) { + return (0); + } + + if (n != NULL) { + BN_free(r->n); + r->n = n; + } + if (e != NULL) { + BN_free(r->e); + r->e = e; + } + if (d != NULL) { + BN_clear_free(r->d); + r->d = d; + } + + return (1); +} + +int +RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) { + /* + * If the fields p and q in r are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((r->p == NULL && p == NULL) || (r->q == NULL && q == NULL)) { + return (0); + } + + if (p != NULL) { + BN_clear_free(r->p); + r->p = p; + } + if (q != NULL) { + BN_clear_free(r->q); + r->q = q; + } + + return (1); +} + +int +RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) { + /* + * If the fields dmp1, dmq1 and iqmp in r are NULL, the + * corresponding input parameters MUST be non-NULL. + */ + if ((r->dmp1 == NULL && dmp1 == NULL) || + (r->dmq1 == NULL && dmq1 == NULL) || + (r->iqmp == NULL && iqmp == NULL)) + { + return (0); + } + + if (dmp1 != NULL) { + BN_clear_free(r->dmp1); + r->dmp1 = dmp1; + } + if (dmq1 != NULL) { + BN_clear_free(r->dmq1); + r->dmq1 = dmq1; + } + if (iqmp != NULL) { + BN_clear_free(r->iqmp); + r->iqmp = iqmp; + } + + return (1); +} + +void +RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, + const BIGNUM **d) { + if (n != NULL) { + *n = r->n; + } + if (e != NULL) { + *e = r->e; + } + if (d != NULL) { + *d = r->d; + } +} + +void +RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) { + if (p != NULL) { + *p = r->p; + } + if (q != NULL) { + *q = r->q; + } +} + +void +RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, + const BIGNUM **iqmp) { + if (dmp1 != NULL) { + *dmp1 = r->dmp1; + } + if (dmq1 != NULL) { + *dmq1 = r->dmq1; + } + if (iqmp != NULL) { + *iqmp = r->iqmp; + } +} + +int +RSA_test_flags(const RSA *r, int flags) { + return (r->flags & flags); +} +#endif /* !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */ + +#if !HAVE_ECDSA_SIG_GET0 +/* From OpenSSL 1.1 */ +void +ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { + if (pr != NULL) { + *pr = sig->r; + } + if (ps != NULL) { + *ps = sig->s; + } +} + +int +ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) { + if (r == NULL || s == NULL) { + return (0); + } + + BN_clear_free(sig->r); + BN_clear_free(sig->s); + sig->r = r; + sig->s = s; + + return (1); +} +#endif /* !HAVE_ECDSA_SIG_GET0 */ + +#if !HAVE_DH_GET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L +/* + * DH_get0_key, DH_set0_key, DH_get0_pqg and DH_set0_pqg + * are from OpenSSL 1.1.0. + */ +void +DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) { + if (pub_key != NULL) { + *pub_key = dh->pub_key; + } + if (priv_key != NULL) { + *priv_key = dh->priv_key; + } +} + +int +DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) { + if (pub_key != NULL) { + BN_free(dh->pub_key); + dh->pub_key = pub_key; + } + + if (priv_key != NULL) { + BN_free(dh->priv_key); + dh->priv_key = priv_key; + } + + return (1); +} + +void +DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, + const BIGNUM **g) { + if (p != NULL) { + *p = dh->p; + } + if (q != NULL) { + *q = dh->q; + } + if (g != NULL) { + *g = dh->g; + } +} + +int +DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) { + /* If the fields p and g in d are NULL, the corresponding input + * parameters MUST be non-NULL. q may remain NULL. + */ + if ((dh->p == NULL && p == NULL) || (dh->g == NULL && g == NULL)) { + return (0); + } + + if (p != NULL) { + BN_free(dh->p); + dh->p = p; + } + if (q != NULL) { + BN_free(dh->q); + dh->q = q; + } + if (g != NULL) { + BN_free(dh->g); + dh->g = g; + } + + if (q != NULL) { + dh->length = BN_num_bits(q); + } + + return (1); +} +#endif /* !HAVE_DH_GET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */ + +#if !HAVE_ERR_GET_ERROR_ALL +static const char err_empty_string = '\0'; + +unsigned long +ERR_get_error_all(const char **file, int *line, const char **func, + const char **data, int *flags) { + if (func != NULL) { + *func = &err_empty_string; + } + return (ERR_get_error_line_data(file, line, data, flags)); +} +#endif /* if !HAVE_ERR_GET_ERROR_ALL */ diff --git a/lib/dns/openssl_shim.h b/lib/dns/openssl_shim.h new file mode 100644 index 0000000..439d9f9 --- /dev/null +++ b/lib/dns/openssl_shim.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +/* + * Limit the size of public exponents. + */ +#ifndef RSA_MAX_PUBEXP_BITS +#define RSA_MAX_PUBEXP_BITS 35 +#endif /* ifndef RSA_MAX_PUBEXP_BITS */ + +#if !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L +int +RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); + +int +RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); + +int +RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); + +void +RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, + const BIGNUM **d); + +void +RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); + +void +RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, + const BIGNUM **iqmp); + +int +RSA_test_flags(const RSA *r, int flags); +#endif /* !HAVE_RSA_SET0_KEY && OPENSSL_VERSION_NUMBER < 0x30000000L */ + +#if !HAVE_ECDSA_SIG_GET0 +void +ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); + +int +ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s); +#endif /* !HAVE_ECDSA_SIG_GET0 */ + +#if !HAVE_DH_GET0_KEY +void +DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key); + +int +DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); + +void +DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); + +int +DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); + +#define DH_clear_flags(d, f) ((d)->flags &= ~(f)) +#endif /* !HAVE_DH_GET0_KEY */ + +#if !HAVE_ERR_GET_ERROR_ALL +unsigned long +ERR_get_error_all(const char **file, int *line, const char **func, + const char **data, int *flags); +#endif /* if !HAVE_ERR_GET_ERROR_ALL */ + +#if !HAVE_EVP_PKEY_EQ +#define EVP_PKEY_eq EVP_PKEY_cmp +#endif diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c new file mode 100644 index 0000000..00950b7 --- /dev/null +++ b/lib/dns/openssldh_link.c @@ -0,0 +1,1336 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif +#include + +#include +#include +#include +#include +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#include "openssl_shim.h" + +#define PRIME2 "02" + +#define PRIME768 \ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" \ + "A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25" \ + "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFF" \ + "F" + +#define PRIME1024 \ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \ + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \ + "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \ + "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" + +#define PRIME1536 \ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +static BIGNUM *bn2 = NULL, *bn768 = NULL, *bn1024 = NULL, *bn1536 = NULL; + +static isc_result_t +openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv, + isc_buffer_t *secret) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dhpub, *dhpriv; + const BIGNUM *pub_key = NULL; + int secret_len = 0; +#else + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *dhpub, *dhpriv; + size_t secret_len = 0; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + isc_region_t r; + unsigned int len; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + REQUIRE(pub->keydata.dh != NULL); + REQUIRE(priv->keydata.dh != NULL); + + dhpub = pub->keydata.dh; + dhpriv = priv->keydata.dh; + + len = DH_size(dhpriv); +#else + REQUIRE(pub->keydata.pkey != NULL); + REQUIRE(priv->keydata.pkey != NULL); + + dhpub = pub->keydata.pkey; + dhpriv = priv->keydata.pkey; + + len = EVP_PKEY_get_size(dhpriv); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_availableregion(secret, &r); + if (r.length < len) { + return (ISC_R_NOSPACE); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH_get0_key(dhpub, &pub_key, NULL); + secret_len = DH_compute_key(r.base, pub_key, dhpriv); + if (secret_len <= 0) { + return (dst__openssl_toresult2("DH_compute_key", + DST_R_COMPUTESECRETFAILURE)); + } +#else + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dhpriv, NULL); + if (ctx == NULL) { + return (dst__openssl_toresult2("EVP_PKEY_CTX_new_from_pkey", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_derive_init(ctx) != 1) { + EVP_PKEY_CTX_free(ctx); + return (dst__openssl_toresult2("EVP_PKEY_derive_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_derive_set_peer(ctx, dhpub) != 1) { + EVP_PKEY_CTX_free(ctx); + return (dst__openssl_toresult2("EVP_PKEY_derive_set_peer", + DST_R_OPENSSLFAILURE)); + } + secret_len = r.length; + if (EVP_PKEY_derive(ctx, r.base, &secret_len) != 1 || secret_len == 0) { + EVP_PKEY_CTX_free(ctx); + return (dst__openssl_toresult2("EVP_PKEY_derive", + DST_R_COMPUTESECRETFAILURE)); + } + EVP_PKEY_CTX_free(ctx); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_add(secret, (unsigned int)secret_len); + + return (ISC_R_SUCCESS); +} + +static bool +openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) { + bool ret = true; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh1, *dh2; + const BIGNUM *pub_key1 = NULL, *pub_key2 = NULL; + const BIGNUM *priv_key1 = NULL, *priv_key2 = NULL; + const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL; +#else + EVP_PKEY *pkey1, *pkey2; + BIGNUM *pub_key1 = NULL, *pub_key2 = NULL; + BIGNUM *priv_key1 = NULL, *priv_key2 = NULL; + BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + dh1 = key1->keydata.dh; + dh2 = key2->keydata.dh; + + if (dh1 == NULL && dh2 == NULL) { + return (true); + } else if (dh1 == NULL || dh2 == NULL) { + return (false); + } + + DH_get0_key(dh1, &pub_key1, &priv_key1); + DH_get0_key(dh2, &pub_key2, &priv_key2); + DH_get0_pqg(dh1, &p1, NULL, &g1); + DH_get0_pqg(dh2, &p2, NULL, &g2); +#else + pkey1 = key1->keydata.pkey; + pkey2 = key2->keydata.pkey; + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_P, &p1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_P, &p2); + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_G, &g1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_G, &g2); + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PUB_KEY, &pub_key1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PUB_KEY, &pub_key2); + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key2); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000*/ + + if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0 || + BN_cmp(pub_key1, pub_key2) != 0) + { + DST_RET(false); + } + + if (priv_key1 != NULL || priv_key2 != NULL) { + if (priv_key1 == NULL || priv_key2 == NULL || + BN_cmp(priv_key1, priv_key2) != 0) + { + DST_RET(false); + } + } + +err: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 + if (p1 != NULL) { + BN_free(p1); + } + if (p2 != NULL) { + BN_free(p2); + } + if (g1 != NULL) { + BN_free(g1); + } + if (g2 != NULL) { + BN_free(g2); + } + if (pub_key1 != NULL) { + BN_free(pub_key1); + } + if (pub_key2 != NULL) { + BN_free(pub_key2); + } + if (priv_key1 != NULL) { + BN_clear_free(priv_key1); + } + if (priv_key2 != NULL) { + BN_clear_free(priv_key2); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \ + */ + + return (ret); +} + +static bool +openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { + bool ret = true; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh1, *dh2; + const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL; +#else + EVP_PKEY *pkey1, *pkey2; + BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + dh1 = key1->keydata.dh; + dh2 = key2->keydata.dh; + + if (dh1 == NULL && dh2 == NULL) { + return (true); + } else if (dh1 == NULL || dh2 == NULL) { + return (false); + } + + DH_get0_pqg(dh1, &p1, NULL, &g1); + DH_get0_pqg(dh2, &p2, NULL, &g2); +#else + pkey1 = key1->keydata.pkey; + pkey2 = key2->keydata.pkey; + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_P, &p1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_P, &p2); + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_FFC_G, &g1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_FFC_G, &g2); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0) { + DST_RET(false); + } + +err: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 + if (p1 != NULL) { + BN_free(p1); + } + if (p2 != NULL) { + BN_free(p2); + } + if (g1 != NULL) { + BN_free(g1); + } + if (g2 != NULL) { + BN_free(g2); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \ + */ + + return (ret); +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static int +progress_cb(int p, int n, BN_GENCB *cb) { + union { + void *dptr; + void (*fptr)(int); + } u; + + UNUSED(n); + + u.dptr = BN_GENCB_get_arg(cb); + if (u.fptr != NULL) { + u.fptr(p); + } + return (1); +} +#else +static int +progress_cb(EVP_PKEY_CTX *ctx) { + union { + void *dptr; + void (*fptr)(int); + } u; + + u.dptr = EVP_PKEY_CTX_get_app_data(ctx); + if (u.fptr != NULL) { + int p = EVP_PKEY_CTX_get_keygen_info(ctx, 0); + u.fptr(p); + } + return (1); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +static isc_result_t +openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) { + isc_result_t ret; + union { + void *dptr; + void (*fptr)(int); + } u; + BIGNUM *p = NULL, *g = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh = NULL; + BN_GENCB *cb = NULL; +#if !HAVE_BN_GENCB_NEW + BN_GENCB _cb; +#endif /* !HAVE_BN_GENCB_NEW */ +#else + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *param_ctx = NULL; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *param_pkey = NULL; + EVP_PKEY *pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + dh = DH_new(); + if (dh == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + param_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); + if (param_ctx == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (generator == 0) { + /* + * When `generator` is 0, we have three pre-computed `p` and `g` + * static parameters which we can use. + */ + if (key->key_size == 768 || key->key_size == 1024 || + key->key_size == 1536) + { + if (key->key_size == 768) { + p = BN_dup(bn768); + } else if (key->key_size == 1024) { + p = BN_dup(bn1024); + } else { + p = BN_dup(bn1536); + } + g = BN_dup(bn2); + if (p == NULL || g == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (DH_set0_pqg(dh, p, NULL, g) != 1) { + DST_RET(dst__openssl_toresult2( + "DH_set0_pqg", DST_R_OPENSSLFAILURE)); + } +#else + if (OSSL_PARAM_BLD_push_uint(bld, + OSSL_PKEY_PARAM_FFC_PBITS, + key->key_size) != 1) + { + DST_RET(dst__openssl_toresult2( + "OSSL_PARAM_BLD_push_uint", + DST_R_OPENSSLFAILURE)); + } + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, + p) != 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, + g) != 1) + { + DST_RET(dst__openssl_toresult2( + "OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + } else { + /* + * If the requested size is not present in our + * pre-computed set, we will use `generator` 2 to + * generate new parameters. + */ + generator = 2; + } + } + + if (generator != 0) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + cb = BN_GENCB_new(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + if (cb == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } +#endif /* if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + * !defined(LIBRESSL_VERSION_NUMBER) */ + if (callback == NULL) { + BN_GENCB_set_old(cb, NULL, NULL); + } else { + u.fptr = callback; + BN_GENCB_set(cb, progress_cb, u.dptr); + } + + if (!DH_generate_parameters_ex(dh, key->key_size, generator, + cb)) + { + DST_RET(dst__openssl_toresult2("DH_generate_parameters_" + "ex", + DST_R_OPENSSLFAILURE)); + } +#else + if (OSSL_PARAM_BLD_push_int(bld, OSSL_PKEY_PARAM_DH_GENERATOR, + generator) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_" + "int", + DST_R_OPENSSLFAILURE)); + } + if (OSSL_PARAM_BLD_push_utf8_string( + bld, OSSL_PKEY_PARAM_FFC_TYPE, "generator", 0) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_" + "utf8_string", + DST_R_OPENSSLFAILURE)); + } + if (OSSL_PARAM_BLD_push_uint(bld, OSSL_PKEY_PARAM_FFC_PBITS, + key->key_size) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_" + "uint", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (DH_generate_key(dh) == 0) { + DST_RET(dst__openssl_toresult2("DH_generate_key", + DST_R_OPENSSLFAILURE)); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); + key->keydata.dh = dh; + dh = NULL; +#else + if (params == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (generator == 0) { + if (EVP_PKEY_fromdata_init(param_ctx) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_fromdata(param_ctx, ¶m_pkey, + OSSL_KEYMGMT_SELECT_ALL, params) != 1 || + param_pkey == NULL) + { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } + } else { + if (EVP_PKEY_paramgen_init(param_ctx) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_CTX_set_params(param_ctx, params) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_" + "params", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_paramgen(param_ctx, ¶m_pkey) != 1 || + param_pkey == NULL) + { + DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen", + DST_R_OPENSSLFAILURE)); + } + } + + /* + * Now `param_pkey` holds the DH parameters (either pre-coumputed or + * newly generated) so we will generate a new public/private key-pair + * using those parameters and put it into `pkey`. + */ + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, param_pkey, NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_pkey", + DST_R_OPENSSLFAILURE)); + } + if (callback != NULL) { + u.fptr = callback; + EVP_PKEY_CTX_set_app_data(ctx, u.dptr); + EVP_PKEY_CTX_set_cb(ctx, progress_cb); + } + if (EVP_PKEY_keygen_init(ctx) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_keygen(ctx, &pkey) != 1 || pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", + DST_R_OPENSSLFAILURE)); + } + + key->keydata.pkey = pkey; + pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + ret = ISC_R_SUCCESS; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (dh != NULL) { + DH_free(dh); + } + if (cb != NULL) { + BN_GENCB_free(cb); + } +#else + if (param_pkey != NULL) { + EVP_PKEY_free(param_pkey); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (param_ctx != NULL) { + EVP_PKEY_CTX_free(param_ctx); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static bool +openssldh_isprivate(const dst_key_t *key) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh = key->keydata.dh; + const BIGNUM *priv_key = NULL; + + DH_get0_key(dh, NULL, &priv_key); + + return (dh != NULL && priv_key != NULL); +#else + bool ret; + EVP_PKEY *pkey; + BIGNUM *priv_key = NULL; + + pkey = key->keydata.pkey; + if (pkey == NULL) { + return (false); + } + + ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, + &priv_key) == 1 && + priv_key != NULL); + if (priv_key != NULL) { + BN_clear_free(priv_key); + } + + return (ret); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ +} + +static void +openssldh_destroy(dst_key_t *key) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh = key->keydata.dh; + + if (dh == NULL) { + return; + } + + DH_free(dh); + key->keydata.dh = NULL; +#else + EVP_PKEY *pkey = key->keydata.pkey; + + if (pkey == NULL) { + return; + } + + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ +} + +static void +uint16_toregion(uint16_t val, isc_region_t *region) { + *region->base = (val & 0xff00) >> 8; + isc_region_consume(region, 1); + *region->base = (val & 0x00ff); + isc_region_consume(region, 1); +} + +static uint16_t +uint16_fromregion(isc_region_t *region) { + uint16_t val; + unsigned char *cp = region->base; + + val = ((unsigned int)(cp[0])) << 8; + val |= ((unsigned int)(cp[1])); + + isc_region_consume(region, 2); + + return (val); +} + +static isc_result_t +openssldh_todns(const dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret = ISC_R_SUCCESS; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh; + const BIGNUM *pub_key = NULL, *p = NULL, *g = NULL; +#else + EVP_PKEY *pkey; + BIGNUM *pub_key = NULL, *p = NULL, *g = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + isc_region_t r; + uint16_t dnslen, plen, glen, publen; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + REQUIRE(key->keydata.dh != NULL); + + dh = key->keydata.dh; + DH_get0_pqg(dh, &p, NULL, &g); + DH_get0_key(dh, &pub_key, NULL); +#else + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_P, &p); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_G, &g); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_availableregion(data, &r); + + if (BN_cmp(g, bn2) == 0 && + (BN_cmp(p, bn768) == 0 || BN_cmp(p, bn1024) == 0 || + BN_cmp(p, bn1536) == 0)) + { + plen = 1; + glen = 0; + } else { + plen = BN_num_bytes(p); + glen = BN_num_bytes(g); + } + + publen = BN_num_bytes(pub_key); + dnslen = plen + glen + publen + 6; + if (r.length < (unsigned int)dnslen) { + DST_RET(ISC_R_NOSPACE); + } + + uint16_toregion(plen, &r); + if (plen == 1) { + if (BN_cmp(p, bn768) == 0) { + *r.base = 1; + } else if (BN_cmp(p, bn1024) == 0) { + *r.base = 2; + } else { + *r.base = 3; + } + } else { + BN_bn2bin(p, r.base); + } + isc_region_consume(&r, plen); + + uint16_toregion(glen, &r); + if (glen > 0) { + BN_bn2bin(g, r.base); + } + isc_region_consume(&r, glen); + + uint16_toregion(publen, &r); + BN_bn2bin(pub_key, r.base); + isc_region_consume(&r, publen); + + isc_buffer_add(data, dnslen); + +err: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + if (pub_key != NULL) { + BN_free(pub_key); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \ + */ + + return (ret); +} + +static isc_result_t +openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh; +#else + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + BIGNUM *pub_key = NULL, *p = NULL, *g = NULL; + int key_size; + isc_region_t r; + uint16_t plen, glen, publen; + int special = 0; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + dh = DH_new(); + if (dh == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + /* + * Read the prime length. 1 & 2 are table entries, > 16 means a + * prime follows, otherwise an error. + */ + if (r.length < 2) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + plen = uint16_fromregion(&r); + if (plen < 16 && plen != 1 && plen != 2) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + if (r.length < plen) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + if (plen == 1 || plen == 2) { + if (plen == 1) { + special = *r.base; + isc_region_consume(&r, 1); + } else { + special = uint16_fromregion(&r); + } + switch (special) { + case 1: + p = BN_dup(bn768); + break; + case 2: + p = BN_dup(bn1024); + break; + case 3: + p = BN_dup(bn1536); + break; + default: + DST_RET(DST_R_INVALIDPUBLICKEY); + } + } else { + p = BN_bin2bn(r.base, plen, NULL); + isc_region_consume(&r, plen); + } + + /* + * Read the generator length. This should be 0 if the prime was + * special, but it might not be. If it's 0 and the prime is not + * special, we have a problem. + */ + if (r.length < 2) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + glen = uint16_fromregion(&r); + if (r.length < glen) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + if (special != 0) { + if (glen == 0) { + g = BN_dup(bn2); + } else { + g = BN_bin2bn(r.base, glen, NULL); + if (g != NULL && BN_cmp(g, bn2) != 0) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + } + } else { + if (glen == 0) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + g = BN_bin2bn(r.base, glen, NULL); + } + isc_region_consume(&r, glen); + + if (p == NULL || g == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + + key_size = BN_num_bits(p); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (DH_set0_pqg(dh, p, NULL, g) != 1) { + DST_RET(dst__openssl_toresult2("DH_set0_pqg", + DST_R_OPENSSLFAILURE)); + } + + /* These are now managed by OpenSSL */ + p = NULL; + g = NULL; +#else + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (r.length < 2) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + publen = uint16_fromregion(&r); + if (r.length < publen) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + pub_key = BN_bin2bn(r.base, publen, NULL); + if (pub_key == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + + isc_region_consume(&r, publen); + + isc_buffer_forward(data, plen + glen + publen + 6); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +#if (LIBRESSL_VERSION_NUMBER >= 0x2070000fL) && \ + (LIBRESSL_VERSION_NUMBER <= 0x2070200fL) + /* + * LibreSSL << 2.7.3 DH_get0_key requires priv_key to be set when + * DH structure is empty, hence we cannot use DH_get0_key(). + */ + dh->pub_key = pub_key; +#else /* LIBRESSL_VERSION_NUMBER */ + if (DH_set0_key(dh, pub_key, NULL) != 1) { + DST_RET(dst__openssl_toresult2("DH_set0_key", + DST_R_OPENSSLFAILURE)); + } +#endif /* LIBRESSL_VERSION_NUMBER */ + + /* This is now managed by OpenSSL */ + pub_key = NULL; + + key->keydata.dh = dh; + dh = NULL; +#else + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_fromdata_init(ctx) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_fromdata(ctx, &pkey, OSSL_KEYMGMT_SELECT_ALL, params) != + 1 || + pkey == NULL) + { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } + + key->keydata.pkey = pkey; + pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + key->key_size = (unsigned int)key_size; + + ret = ISC_R_SUCCESS; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (dh != NULL) { + DH_free(dh); + } +#else + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + if (pub_key != NULL) { + BN_free(pub_key); + } + + return (ret); +} + +static isc_result_t +openssldh_tofile(const dst_key_t *key, const char *directory) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh; + const BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL; +#else + EVP_PKEY *pkey; + BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + dst_private_t priv; + unsigned char *bufs[4] = { NULL }; + unsigned short i = 0; + isc_result_t result; + + if (key->external) { + return (DST_R_EXTERNALKEY); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (key->keydata.dh == NULL) { + return (DST_R_NULLKEY); + } + + dh = key->keydata.dh; + DH_get0_key(dh, &pub_key, &priv_key); + DH_get0_pqg(dh, &p, NULL, &g); +#else + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + pkey = key->keydata.pkey; + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_P, &p); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_G, &g); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv_key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + priv.elements[i].tag = TAG_DH_PRIME; + priv.elements[i].length = BN_num_bytes(p); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(p, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_GENERATOR; + priv.elements[i].length = BN_num_bytes(g); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(g, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_PRIVATE; + priv.elements[i].length = BN_num_bytes(priv_key); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(priv_key, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_PUBLIC; + priv.elements[i].length = BN_num_bytes(pub_key); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(pub_key, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.nelements = i; + result = dst__privstruct_writefile(key, &priv, directory); + + while (i--) { + if (bufs[i] != NULL) { + isc_mem_put(key->mctx, bufs[i], + priv.elements[i].length); + } + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + if (pub_key != NULL) { + BN_free(pub_key); + } + if (priv_key != NULL) { + BN_clear_free(priv_key); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \ + */ + + return (result); +} + +static isc_result_t +openssldh_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + int i; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + DH *dh = NULL; +#else + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL; + int key_size = 0; + isc_mem_t *mctx; + + UNUSED(pub); + mctx = key->mctx; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + if (key->external) { + DST_RET(DST_R_EXTERNALKEY); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + dh = DH_new(); + if (dh == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + for (i = 0; i < priv.nelements; i++) { + BIGNUM *bn; + bn = BN_bin2bn(priv.elements[i].data, priv.elements[i].length, + NULL); + if (bn == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + + switch (priv.elements[i].tag) { + case TAG_DH_PRIME: + p = bn; + key_size = BN_num_bits(p); + break; + case TAG_DH_GENERATOR: + g = bn; + break; + case TAG_DH_PRIVATE: + priv_key = bn; + break; + case TAG_DH_PUBLIC: + pub_key = bn; + break; + } + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (DH_set0_key(dh, pub_key, priv_key) != 1) { + DST_RET(dst__openssl_toresult2("DH_set0_key", + DST_R_OPENSSLFAILURE)); + } + if (DH_set0_pqg(dh, p, NULL, g) != 1) { + DST_RET(dst__openssl_toresult2("DH_set0_pqg", + DST_R_OPENSSLFAILURE)); + } + + /* These are now managed by OpenSSL */ + pub_key = NULL; + priv_key = NULL; + p = NULL; + g = NULL; + + key->keydata.dh = dh; + dh = NULL; +#else + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key) != + 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv_key) != + 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_fromdata_init(ctx) != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + if (EVP_PKEY_fromdata(ctx, &pkey, OSSL_KEYMGMT_SELECT_ALL, params) != + 1 || + pkey == NULL) + { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } + + key->keydata.pkey = pkey; + pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + key->key_size = (unsigned int)key_size; + ret = ISC_R_SUCCESS; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (dh != NULL) { + DH_free(dh); + } +#else + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + if (pub_key != NULL) { + BN_free(pub_key); + } + if (priv_key != NULL) { + BN_clear_free(priv_key); + } + if (ret != ISC_R_SUCCESS) { + openssldh_destroy(key); + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ret); +} + +static void +openssldh_cleanup(void) { + BN_free(bn2); + bn2 = NULL; + + BN_free(bn768); + bn768 = NULL; + + BN_free(bn1024); + bn1024 = NULL; + + BN_free(bn1536); + bn1536 = NULL; +} + +static dst_func_t openssldh_functions = { + NULL, /*%< createctx */ + NULL, /*%< createctx2 */ + NULL, /*%< destroyctx */ + NULL, /*%< adddata */ + NULL, /*%< openssldh_sign */ + NULL, /*%< openssldh_verify */ + NULL, /*%< openssldh_verify2 */ + openssldh_computesecret, + openssldh_compare, + openssldh_paramcompare, + openssldh_generate, + openssldh_isprivate, + openssldh_destroy, + openssldh_todns, + openssldh_fromdns, + openssldh_tofile, + openssldh_parse, + openssldh_cleanup, + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__openssldh_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + if (BN_hex2bn(&bn2, PRIME2) == 0 || bn2 == NULL) { + goto cleanup; + } + if (BN_hex2bn(&bn768, PRIME768) == 0 || bn768 == NULL) { + goto cleanup; + } + if (BN_hex2bn(&bn1024, PRIME1024) == 0 || bn1024 == NULL) { + goto cleanup; + } + if (BN_hex2bn(&bn1536, PRIME1536) == 0 || bn1536 == NULL) { + goto cleanup; + } + *funcp = &openssldh_functions; + } + return (ISC_R_SUCCESS); + +cleanup: + if (bn2 != NULL) { + BN_free(bn2); + } + if (bn768 != NULL) { + BN_free(bn768); + } + if (bn1024 != NULL) { + BN_free(bn1024); + } + if (bn1536 != NULL) { + BN_free(bn1536); + } + return (ISC_R_NOMEMORY); +} diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c new file mode 100644 index 0000000..b80d637 --- /dev/null +++ b/lib/dns/opensslecdsa_link.c @@ -0,0 +1,1453 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 +#include +#endif +#include +#include +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 +#include +#endif +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#include "openssl_shim.h" + +#ifndef NID_X9_62_prime256v1 +#error "P-256 group is not known (NID_X9_62_prime256v1)" +#endif /* ifndef NID_X9_62_prime256v1 */ +#ifndef NID_secp384r1 +#error "P-384 group is not known (NID_secp384r1)" +#endif /* ifndef NID_secp384r1 */ + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 +static isc_result_t +raw_key_to_ossl(unsigned int key_alg, int private, const unsigned char *key, + size_t key_len, EVP_PKEY **pkey) { + isc_result_t ret; + int status; + const char *groupname; + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; + BIGNUM *priv = NULL; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + + if (key_alg == DST_ALG_ECDSA256) { + groupname = "P-256"; + } else if (key_alg == DST_ALG_ECDSA384) { + groupname = "P-384"; + } else { + DST_RET(ISC_R_NOTIMPLEMENTED); + } + + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new", + DST_R_OPENSSLFAILURE)); + } + status = OSSL_PARAM_BLD_push_utf8_string( + bld, OSSL_PKEY_PARAM_GROUP_NAME, groupname, 0); + if (status != 1) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_" + "utf8_string", + DST_R_OPENSSLFAILURE)); + } + + if (private) { + priv = BN_bin2bn(key, key_len, NULL); + if (priv == NULL) { + DST_RET(dst__openssl_toresult2("BN_bin2bn", + DST_R_OPENSSLFAILURE)); + } + + status = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (status != 1) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + } else { + INSIST(key_len < sizeof(buf)); + buf[0] = POINT_CONVERSION_UNCOMPRESSED; + memmove(buf + 1, key, key_len); + + status = OSSL_PARAM_BLD_push_octet_string( + bld, OSSL_PKEY_PARAM_PUB_KEY, buf, 1 + key_len); + if (status != 1) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_" + "octet_string", + DST_R_OPENSSLFAILURE)); + } + } + + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param", + DST_R_OPENSSLFAILURE)); + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata( + ctx, pkey, private ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY, + params); + if (status != 1 || *pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } + + ret = ISC_R_SUCCESS; + +err: + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (priv != NULL) { + BN_clear_free(priv); + } + + return (ret); +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 \ + */ + +static isc_result_t +opensslecdsa_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_result_t ret = ISC_R_SUCCESS; + EVP_MD_CTX *evp_md_ctx; + const EVP_MD *type = NULL; + + UNUSED(key); + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY); + + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + if (dctx->key->key_alg == DST_ALG_ECDSA256) { + type = EVP_sha256(); + } else { + type = EVP_sha384(); + } + + if (dctx->use == DO_SIGN) { + if (EVP_DigestSignInit(evp_md_ctx, NULL, type, NULL, + dctx->key->keydata.pkey) != 1) + { + EVP_MD_CTX_destroy(evp_md_ctx); + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestSignInit", + ISC_R_FAILURE)); + } + } else { + if (EVP_DigestVerifyInit(evp_md_ctx, NULL, type, NULL, + dctx->key->keydata.pkey) != 1) + { + EVP_MD_CTX_destroy(evp_md_ctx); + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestVerifyInit", + ISC_R_FAILURE)); + } + } + + dctx->ctxdata.evp_md_ctx = evp_md_ctx; + +err: + return (ret); +} + +static void +opensslecdsa_destroyctx(dst_context_t *dctx) { + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY); + + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + dctx->ctxdata.evp_md_ctx = NULL; + } +} + +static isc_result_t +opensslecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_result_t ret = ISC_R_SUCCESS; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + REQUIRE(dctx->use == DO_SIGN || dctx->use == DO_VERIFY); + + if (dctx->use == DO_SIGN) { + if (EVP_DigestSignUpdate(evp_md_ctx, data->base, + data->length) != 1) + { + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestSignUpdate", + ISC_R_FAILURE)); + } + } else { + if (EVP_DigestVerifyUpdate(evp_md_ctx, data->base, + data->length) != 1) + { + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestVerifyUpdate", + ISC_R_FAILURE)); + } + } + +err: + return (ret); +} + +static int +BN_bn2bin_fixed(const BIGNUM *bn, unsigned char *buf, int size) { + int bytes = size - BN_num_bytes(bn); + + INSIST(bytes >= 0); + + while (bytes-- > 0) { + *buf++ = 0; + } + BN_bn2bin(bn, buf); + return (size); +} + +static isc_result_t +opensslecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_result_t ret; + dst_key_t *key = dctx->key; + isc_region_t region; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + ECDSA_SIG *ecdsasig = NULL; + size_t siglen, sigder_len = 0, sigder_alloced = 0; + unsigned char *sigder = NULL; + const unsigned char *sigder_copy; + const BIGNUM *r, *s; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + REQUIRE(dctx->use == DO_SIGN); + + if (key->key_alg == DST_ALG_ECDSA256) { + siglen = DNS_SIG_ECDSA256SIZE; + } else { + siglen = DNS_SIG_ECDSA384SIZE; + } + + isc_buffer_availableregion(sig, ®ion); + if (region.length < siglen) { + DST_RET(ISC_R_NOSPACE); + } + + if (EVP_DigestSignFinal(evp_md_ctx, NULL, &sigder_len) != 1) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestSignFinal", ISC_R_FAILURE)); + } + if (sigder_len == 0) { + DST_RET(ISC_R_FAILURE); + } + sigder = isc_mem_get(dctx->mctx, sigder_len); + sigder_alloced = sigder_len; + if (EVP_DigestSignFinal(evp_md_ctx, sigder, &sigder_len) != 1) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestSignFinal", ISC_R_FAILURE)); + } + sigder_copy = sigder; + if (d2i_ECDSA_SIG(&ecdsasig, &sigder_copy, sigder_len) == NULL) { + DST_RET(dst__openssl_toresult3(dctx->category, "d2i_ECDSA_SIG", + ISC_R_FAILURE)); + } + + ECDSA_SIG_get0(ecdsasig, &r, &s); + BN_bn2bin_fixed(r, region.base, siglen / 2); + isc_region_consume(®ion, siglen / 2); + BN_bn2bin_fixed(s, region.base, siglen / 2); + isc_region_consume(®ion, siglen / 2); + ECDSA_SIG_free(ecdsasig); + isc_buffer_add(sig, siglen); + ret = ISC_R_SUCCESS; + +err: + if (sigder != NULL && sigder_alloced != 0) { + isc_mem_put(dctx->mctx, sigder, sigder_alloced); + } + + return (ret); +} + +static isc_result_t +opensslecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_result_t ret; + dst_key_t *key = dctx->key; + int status; + unsigned char *cp = sig->base; + ECDSA_SIG *ecdsasig = NULL; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + size_t siglen, sigder_len = 0, sigder_alloced = 0; + unsigned char *sigder = NULL; + unsigned char *sigder_copy; + BIGNUM *r = NULL, *s = NULL; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + REQUIRE(dctx->use == DO_VERIFY); + + if (key->key_alg == DST_ALG_ECDSA256) { + siglen = DNS_SIG_ECDSA256SIZE; + } else { + siglen = DNS_SIG_ECDSA384SIZE; + } + + if (sig->length != siglen) { + DST_RET(DST_R_VERIFYFAILURE); + } + + ecdsasig = ECDSA_SIG_new(); + if (ecdsasig == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + r = BN_bin2bn(cp, siglen / 2, NULL); + cp += siglen / 2; + s = BN_bin2bn(cp, siglen / 2, NULL); + /* cp += siglen / 2; */ + ECDSA_SIG_set0(ecdsasig, r, s); + + status = i2d_ECDSA_SIG(ecdsasig, NULL); + if (status < 0) { + DST_RET(dst__openssl_toresult3(dctx->category, "i2d_ECDSA_SIG", + DST_R_VERIFYFAILURE)); + } + + sigder_len = (size_t)status; + sigder = isc_mem_get(dctx->mctx, sigder_len); + sigder_alloced = sigder_len; + + sigder_copy = sigder; + status = i2d_ECDSA_SIG(ecdsasig, &sigder_copy); + if (status < 0) { + DST_RET(dst__openssl_toresult3(dctx->category, "i2d_ECDSA_SIG", + DST_R_VERIFYFAILURE)); + } + + status = EVP_DigestVerifyFinal(evp_md_ctx, sigder, sigder_len); + + switch (status) { + case 1: + ret = ISC_R_SUCCESS; + break; + case 0: + ret = dst__openssl_toresult(DST_R_VERIFYFAILURE); + break; + default: + ret = dst__openssl_toresult3(dctx->category, + "EVP_DigestVerifyFinal", + DST_R_VERIFYFAILURE); + break; + } + +err: + if (ecdsasig != NULL) { + ECDSA_SIG_free(ecdsasig); + } + if (sigder != NULL && sigder_alloced != 0) { + isc_mem_put(dctx->mctx, sigder, sigder_alloced); + } + + return (ret); +} + +static bool +opensslecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + bool ret; + EVP_PKEY *pkey1 = key1->keydata.pkey; + EVP_PKEY *pkey2 = key2->keydata.pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey1 = NULL; + EC_KEY *eckey2 = NULL; + const BIGNUM *priv1; + const BIGNUM *priv2; +#else + BIGNUM *priv1 = NULL; + BIGNUM *priv2 = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + /* `EVP_PKEY_eq` checks only the public key components and paramters. */ + if (EVP_PKEY_eq(pkey1, pkey2) != 1) { + DST_RET(false); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey1 = EVP_PKEY_get1_EC_KEY(pkey1); + eckey2 = EVP_PKEY_get1_EC_KEY(pkey2); + if (eckey1 == NULL && eckey2 == NULL) { + ERR_clear_error(); + DST_RET(true); + } else if (eckey1 == NULL || eckey2 == NULL) { + ERR_clear_error(); + DST_RET(false); + } + priv1 = EC_KEY_get0_private_key(eckey1); + priv2 = EC_KEY_get0_private_key(eckey2); +#else + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_PRIV_KEY, &priv1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_PRIV_KEY, &priv2); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (priv1 != NULL || priv2 != NULL) { + if (priv1 == NULL || priv2 == NULL || BN_cmp(priv1, priv2) != 0) + { + ERR_clear_error(); + DST_RET(false); + } + } else { + ERR_clear_error(); + } + + ret = true; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (eckey1 != NULL) { + EC_KEY_free(eckey1); + } + if (eckey2 != NULL) { + EC_KEY_free(eckey2); + } +#else + if (priv1 != NULL) { + BN_clear_free(priv1); + } + if (priv2 != NULL) { + BN_clear_free(priv2); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static isc_result_t +opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + isc_result_t ret; + int status; + EVP_PKEY *pkey = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey = NULL; +#else + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *params_pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + int group_nid; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + UNUSED(unused); + UNUSED(callback); + + if (key->key_alg == DST_ALG_ECDSA256) { + group_nid = NID_X9_62_prime256v1; + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + } else { + group_nid = NID_secp384r1; + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult2("EC_KEY_new_by_curve_name", + DST_R_OPENSSLFAILURE)); + } + + status = EC_KEY_generate_key(eckey); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EC_KEY_generate_key", + DST_R_OPENSSLFAILURE)); + } + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { + DST_RET(ISC_R_FAILURE); + } +#else + /* Generate the key's parameters. */ + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_paramgen_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen_init", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, group_nid); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_ec_paramgen_" + "curve_nid", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_paramgen(ctx, ¶ms_pkey); + if (status != 1 || params_pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_paramgen", + DST_R_OPENSSLFAILURE)); + } + EVP_PKEY_CTX_free(ctx); + + /* Generate the key. */ + ctx = EVP_PKEY_CTX_new(params_pkey, NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_keygen_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_keygen(ctx, &pkey); + if (status != 1 || pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", + DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + key->keydata.pkey = pkey; + pkey = NULL; + ret = ISC_R_SUCCESS; + +err: + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#else + if (params_pkey != NULL) { + EVP_PKEY_free(params_pkey); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static bool +opensslecdsa_isprivate(const dst_key_t *key) { + bool ret; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey; +#else + BIGNUM *priv = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + pkey = key->keydata.pkey; + if (pkey == NULL) { + return (false); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey = EVP_PKEY_get1_EC_KEY(pkey); + + ret = (eckey != NULL && EC_KEY_get0_private_key(eckey) != NULL); + if (eckey != NULL) { + EC_KEY_free(eckey); + } else { + ERR_clear_error(); + } +#else + ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv) == + 1 && + priv != NULL); + if (priv != NULL) { + BN_clear_free(priv); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static void +opensslecdsa_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; + } +} + +static isc_result_t +opensslecdsa_todns(const dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey = NULL; + int len; + unsigned char *cp; +#else + int status; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + size_t keysize = 0; + size_t len = 0; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + isc_region_t r; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey = EVP_PKEY_get1_EC_KEY(pkey); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } + len = i2o_ECPublicKey(eckey, NULL); + + /* skip form */ + len--; +#else + if (key->key_alg == DST_ALG_ECDSA256) { + keysize = DNS_KEY_ECDSA256SIZE; + } else if (key->key_alg == DST_ALG_ECDSA384) { + keysize = DNS_KEY_ECDSA384SIZE; + } else { + DST_RET(ISC_R_NOTIMPLEMENTED); + } + + len = keysize; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_availableregion(data, &r); + if (r.length < (unsigned int)len) { + DST_RET(ISC_R_NOSPACE); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + cp = buf; + if (!i2o_ECPublicKey(eckey, &cp)) { + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } + memmove(r.base, buf + 1, len); +#else + status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x); + if (status != 1 || x == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y); + if (status != 1 || y == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param", + DST_R_OPENSSLFAILURE)); + } + BN_bn2bin_fixed(x, &buf[0], keysize / 2); + BN_bn2bin_fixed(y, &buf[keysize / 2], keysize / 2); + memmove(r.base, buf, len); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_add(data, len); + ret = ISC_R_SUCCESS; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#else + if (x != NULL) { + BN_clear_free(x); + } + if (y != NULL) { + BN_clear_free(y); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static isc_result_t +opensslecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; + EVP_PKEY *pkey = NULL; + isc_region_t r; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey = NULL; + const unsigned char *cp; + unsigned int len; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + int group_nid; +#else + size_t len; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + if (key->key_alg == DST_ALG_ECDSA256) { + len = DNS_KEY_ECDSA256SIZE; + } else { + len = DNS_KEY_ECDSA384SIZE; + } + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + DST_RET(ISC_R_SUCCESS); + } + if (r.length != len) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (key->key_alg == DST_ALG_ECDSA256) { + group_nid = NID_X9_62_prime256v1; + } else { + group_nid = NID_secp384r1; + } + + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + buf[0] = POINT_CONVERSION_UNCOMPRESSED; + memmove(buf + 1, r.base, len); + cp = buf; + if (o2i_ECPublicKey(&eckey, (const unsigned char **)&cp, + (long)len + 1) == NULL) + { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY)); + } + if (EC_KEY_check_key(eckey) != 1) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPUBLICKEY)); + } + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult(ISC_R_NOMEMORY)); + } + if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { + EVP_PKEY_free(pkey); + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } +#else + ret = raw_key_to_ossl(key->key_alg, 0, r.base, len, &pkey); + if (ret != ISC_R_SUCCESS) { + DST_RET(ret); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + isc_buffer_forward(data, len); + key->keydata.pkey = pkey; + key->key_size = len * 4; + ret = ISC_R_SUCCESS; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + return (ret); +} + +static isc_result_t +opensslecdsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey = NULL; + const BIGNUM *privkey = NULL; +#else + int status; + BIGNUM *privkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + dst_private_t priv; + unsigned char *buf = NULL; + unsigned short i; + + if (key->keydata.pkey == NULL) { + DST_RET(DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + DST_RET(dst__privstruct_writefile(key, &priv, directory)); + } + + pkey = key->keydata.pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey = EVP_PKEY_get1_EC_KEY(pkey); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_get1_EC_KEY", + DST_R_OPENSSLFAILURE)); + } + privkey = EC_KEY_get0_private_key(eckey); + if (privkey == NULL) { + DST_RET(dst__openssl_toresult2("EC_KEY_get0_private_key", + DST_R_OPENSSLFAILURE)); + } +#else + status = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, + &privkey); + if (status != 1 || privkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_get_bn_param", + DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + buf = isc_mem_get(key->mctx, BN_num_bytes(privkey)); + + i = 0; + + priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY; + priv.elements[i].length = BN_num_bytes(privkey); + BN_bn2bin(privkey, buf); + priv.elements[i].data = buf; + i++; + + if (key->engine != NULL) { + priv.elements[i].tag = TAG_ECDSA_ENGINE; + priv.elements[i].length = (unsigned short)strlen(key->engine) + + 1; + priv.elements[i].data = (unsigned char *)key->engine; + i++; + } + + if (key->label != NULL) { + priv.elements[i].tag = TAG_ECDSA_LABEL; + priv.elements[i].length = (unsigned short)strlen(key->label) + + 1; + priv.elements[i].data = (unsigned char *)key->label; + i++; + } + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + +err: + if (buf != NULL && privkey != NULL) { + isc_mem_put(key->mctx, buf, BN_num_bytes(privkey)); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#else + if (privkey != NULL) { + BN_clear_free(privkey); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static isc_result_t +ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) { + const EC_POINT *pubkey; + + pubkey = EC_KEY_get0_public_key(eckey); + if (pubkey != NULL) { + return (ISC_R_SUCCESS); + } else if (pubeckey != NULL) { + pubkey = EC_KEY_get0_public_key(pubeckey); + if (pubkey == NULL) { + return (ISC_R_SUCCESS); + } + if (EC_KEY_set_public_key(eckey, pubkey) != 1) { + return (ISC_R_SUCCESS); + } + } + if (EC_KEY_check_key(eckey) == 1) { + return (ISC_R_SUCCESS); + } + + return (ISC_R_FAILURE); +} +#else +static isc_result_t +ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) { + isc_result_t ret = ISC_R_FAILURE; + int status; + size_t pkey_len = 0; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + BIGNUM *priv = NULL; + char groupname[80]; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + size_t keysize; + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey_new = NULL; + + /* Check if `pkey` has a public key. */ + status = EVP_PKEY_get_octet_string_param(*pkey, OSSL_PKEY_PARAM_PUB_KEY, + NULL, 0, &pkey_len); + + /* Check if `pubpkey` exists and that we can extract its public key. */ + if (pubpkey == NULL || + EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_EC_PUB_X, &x) != 1 || + x == NULL || + EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y) != 1 || + y == NULL) + { + if (status != 1 || pkey_len == 0) { + /* No public key both in `pkey` and in `pubpkey` */ + DST_RET(DST_R_INVALIDPRIVATEKEY); + } else { + /* + * `pkey` has a public key, but there is no public key + * in `pubpkey` to check against. + */ + DST_RET(ISC_R_SUCCESS); + } + } + + /* + * If `pkey` doesn't have a public key then we will copy it from + * `pubpkey`. + */ + if (status != 1 || pkey_len == 0) { + /* + * We can't (?) add a public key to an existing PKEY, so we + * have to create a new PKEY. + */ + + keysize = (EVP_PKEY_bits(*pkey) + 7) / 8; + /* + * The "raw" public key is created by combining the "x" and "y" + * parts. + */ + keysize *= 2; + buf[0] = POINT_CONVERSION_UNCOMPRESSED; + BN_bn2bin_fixed(x, &buf[1], keysize / 2); + BN_bn2bin_fixed(y, &buf[1 + keysize / 2], keysize / 2); + + groupname[0] = '\0'; + status = EVP_PKEY_get_utf8_string_param( + *pkey, OSSL_PKEY_PARAM_GROUP_NAME, groupname, + sizeof groupname, NULL); + if (status != 1 || strlen(groupname) == 0) { + DST_RET(ISC_R_FAILURE); + } + status = EVP_PKEY_get_bn_param(*pkey, OSSL_PKEY_PARAM_PRIV_KEY, + &priv); + if (status != 1) { + DST_RET(ISC_R_FAILURE); + } + + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(ISC_R_FAILURE); + } + if (OSSL_PARAM_BLD_push_utf8_string( + bld, OSSL_PKEY_PARAM_GROUP_NAME, groupname, 0) != 1) + { + DST_RET(ISC_R_FAILURE); + } + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, + priv) != 1) + { + DST_RET(ISC_R_FAILURE); + } + if (OSSL_PARAM_BLD_push_octet_string(bld, + OSSL_PKEY_PARAM_PUB_KEY, + buf, 1 + keysize) != 1) + { + DST_RET(ISC_R_FAILURE); + } + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(ISC_R_FAILURE); + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (ctx == NULL) { + DST_RET(ISC_R_FAILURE); + } + if (EVP_PKEY_fromdata_init(ctx) != 1) { + DST_RET(ISC_R_FAILURE); + } + status = EVP_PKEY_fromdata(ctx, &pkey_new, EVP_PKEY_KEYPAIR, + params); + if (status != 1 || pkey_new == NULL) { + DST_RET(ISC_R_FAILURE); + } + + /* Replace the old key with the new one. */ + EVP_PKEY_free(*pkey); + *pkey = pkey_new; + } + + if (EVP_PKEY_eq(*pkey, pubpkey) == 1) { + DST_RET(ISC_R_SUCCESS); + } + +err: + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } + if (priv != NULL) { + BN_clear_free(priv); + } + if (x != NULL) { + BN_clear_free(x); + } + if (y != NULL) { + BN_clear_free(y); + } + + return (ret); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static isc_result_t +load_privkey_from_privstruct(EC_KEY *eckey, dst_private_t *priv, + int privkey_index) { + BIGNUM *privkey = BN_bin2bn(priv->elements[privkey_index].data, + priv->elements[privkey_index].length, NULL); + isc_result_t result = ISC_R_SUCCESS; + + if (privkey == NULL) { + return (ISC_R_NOMEMORY); + } + + if (!EC_KEY_set_private_key(eckey, privkey)) { + result = ISC_R_NOMEMORY; + } + + BN_clear_free(privkey); + return (result); +} + +static isc_result_t +eckey_to_pkey(EC_KEY *eckey, EVP_PKEY **pkey) { + REQUIRE(pkey != NULL && *pkey == NULL); + + *pkey = EVP_PKEY_new(); + if (*pkey == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + if (!EVP_PKEY_set1_EC_KEY(*pkey, eckey)) { + EVP_PKEY_free(*pkey); + *pkey = NULL; + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + return (ISC_R_SUCCESS); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +static isc_result_t +finalize_eckey(dst_key_t *key, +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey, +#endif + const char *engine, const char *label) { + isc_result_t result = ISC_R_SUCCESS; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EVP_PKEY *pkey = NULL; + + REQUIRE(eckey != NULL); + + result = eckey_to_pkey(eckey, &pkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + + key->keydata.pkey = pkey; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (label != NULL) { + key->label = isc_mem_strdup(key->mctx, label); + key->engine = isc_mem_strdup(key->mctx, engine); + } + + if (key->key_alg == DST_ALG_ECDSA256) { + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + } else { + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + } + + return (result); +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static isc_result_t +dst__key_to_eckey(dst_key_t *key, EC_KEY **eckey) { + int group_nid; + + REQUIRE(eckey != NULL && *eckey == NULL); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + group_nid = NID_X9_62_prime256v1; + break; + case DST_ALG_ECDSA384: + group_nid = NID_secp384r1; + break; + default: + UNREACHABLE(); + } + + *eckey = EC_KEY_new_by_curve_name(group_nid); + if (*eckey == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + return (ISC_R_SUCCESS); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +static isc_result_t +opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin); + +static isc_result_t +opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + const char *engine = NULL; + const char *label = NULL; + int i, privkey_index = -1; + bool finalize_key = false; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, key->mctx, + &priv); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + if (key->external) { + if (priv.nelements != 0 || pub == NULL) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + DST_RET(ISC_R_SUCCESS); + } + + for (i = 0; i < priv.nelements; i++) { + switch (priv.elements[i].tag) { + case TAG_ECDSA_ENGINE: + engine = (char *)priv.elements[i].data; + break; + case TAG_ECDSA_LABEL: + label = (char *)priv.elements[i].data; + break; + case TAG_ECDSA_PRIVATEKEY: + privkey_index = i; + break; + default: + break; + } + } + + if (privkey_index < 0) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + + if (label != NULL) { + ret = opensslecdsa_fromlabel(key, engine, label, NULL); + if (ret != ISC_R_SUCCESS) { + goto err; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + eckey = EVP_PKEY_get1_EC_KEY(key->keydata.pkey); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + } else { +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + ret = dst__key_to_eckey(key, &eckey); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + ret = load_privkey_from_privstruct(eckey, &priv, privkey_index); +#else + if (key->keydata.pkey != NULL) { + EVP_PKEY_free(key->keydata.pkey); + key->keydata.pkey = NULL; + } + + ret = raw_key_to_ossl(key->key_alg, 1, + priv.elements[privkey_index].data, + priv.elements[privkey_index].length, + &key->keydata.pkey); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (ret != ISC_R_SUCCESS) { + goto err; + } + + finalize_key = true; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (pub != NULL && pub->keydata.pkey != NULL) { + pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey); + } + + if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + + if (finalize_key) { + ret = finalize_eckey(key, eckey, engine, label); + } +#else + if (ecdsa_check(&key->keydata.pkey, + pub == NULL ? NULL : pub->keydata.pkey) != + ISC_R_SUCCESS) + { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + + if (finalize_key) { + ret = finalize_eckey(key, engine, label); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (pubeckey != NULL) { + EC_KEY_free(pubeckey); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (ret != ISC_R_SUCCESS) { + key->keydata.generic = NULL; + } + + dst__privstruct_free(&priv, key->mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ret); +} + +static isc_result_t +opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + isc_result_t ret = ISC_R_SUCCESS; + ENGINE *e; + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; + int group_nid; + EVP_PKEY *pkey = NULL; + EVP_PKEY *pubpkey = NULL; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + UNUSED(pin); + + if (engine == NULL || label == NULL) { + return (DST_R_NOENGINE); + } + e = dst__openssl_getengine(engine); + if (e == NULL) { + DST_RET(DST_R_NOENGINE); + } + + if (key->key_alg == DST_ALG_ECDSA256) { + group_nid = NID_X9_62_prime256v1; + } else { + group_nid = NID_secp384r1; + } + + /* Load private key. */ + pkey = ENGINE_load_private_key(e, label, NULL, NULL); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_private_key", + DST_R_OPENSSLFAILURE)); + } + /* Check base id, group nid */ + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + eckey = EVP_PKEY_get1_EC_KEY(pkey); + if (eckey == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + /* Load public key. */ + pubpkey = ENGINE_load_public_key(e, label, NULL, NULL); + if (pubpkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_public_key", + DST_R_OPENSSLFAILURE)); + } + /* Check base id, group nid */ + if (EVP_PKEY_base_id(pubpkey) != EVP_PKEY_EC) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey); + if (pubeckey == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + + if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + + key->label = isc_mem_strdup(key->mctx, label); + key->engine = isc_mem_strdup(key->mctx, engine); + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + pkey = NULL; + +err: + if (pubpkey != NULL) { + EVP_PKEY_free(pubpkey); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (pubeckey != NULL) { + EC_KEY_free(pubeckey); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } + + return (ret); +#else + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif /* !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ +} + +static dst_func_t opensslecdsa_functions = { + opensslecdsa_createctx, + NULL, /*%< createctx2 */ + opensslecdsa_destroyctx, + opensslecdsa_adddata, + opensslecdsa_sign, + opensslecdsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + opensslecdsa_compare, + NULL, /*%< paramcompare */ + opensslecdsa_generate, + opensslecdsa_isprivate, + opensslecdsa_destroy, + opensslecdsa_todns, + opensslecdsa_fromdns, + opensslecdsa_tofile, + opensslecdsa_parse, + NULL, /*%< cleanup */ + opensslecdsa_fromlabel, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__opensslecdsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + *funcp = &opensslecdsa_functions; + } + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c new file mode 100644 index 0000000..0fddfd4 --- /dev/null +++ b/lib/dns/openssleddsa_link.c @@ -0,0 +1,702 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 + +#include + +#include +#include +#include +#include +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#include "openssl_shim.h" + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +#if HAVE_OPENSSL_ED25519 +#ifndef NID_ED25519 +#error "Ed25519 group is not known (NID_ED25519)" +#endif /* ifndef NID_ED25519 */ +#endif /* HAVE_OPENSSL_ED25519 */ + +#if HAVE_OPENSSL_ED448 +#ifndef NID_ED448 +#error "Ed448 group is not known (NID_ED448)" +#endif /* ifndef NID_ED448 */ +#endif /* HAVE_OPENSSL_ED448 */ + +static isc_result_t +raw_key_to_ossl(unsigned int key_alg, int private, const unsigned char *key, + size_t *key_len, EVP_PKEY **pkey) { + isc_result_t ret; + int pkey_type = EVP_PKEY_NONE; + size_t len = 0; + +#if HAVE_OPENSSL_ED25519 + if (key_alg == DST_ALG_ED25519) { + pkey_type = EVP_PKEY_ED25519; + len = DNS_KEY_ED25519SIZE; + } +#endif /* HAVE_OPENSSL_ED25519 */ +#if HAVE_OPENSSL_ED448 + if (key_alg == DST_ALG_ED448) { + pkey_type = EVP_PKEY_ED448; + len = DNS_KEY_ED448SIZE; + } +#endif /* HAVE_OPENSSL_ED448 */ + if (pkey_type == EVP_PKEY_NONE) { + return (ISC_R_NOTIMPLEMENTED); + } + + ret = (private ? DST_R_INVALIDPRIVATEKEY : DST_R_INVALIDPUBLICKEY); + if (*key_len < len) { + return (ret); + } + + if (private) { + *pkey = EVP_PKEY_new_raw_private_key(pkey_type, NULL, key, len); + } else { + *pkey = EVP_PKEY_new_raw_public_key(pkey_type, NULL, key, len); + } + if (*pkey == NULL) { + return (dst__openssl_toresult(ret)); + } + + *key_len = len; + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin); + +static isc_result_t +openssleddsa_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_buffer_t *buf = NULL; + + UNUSED(key); + REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || + dctx->key->key_alg == DST_ALG_ED448); + + isc_buffer_allocate(dctx->mctx, &buf, 64); + dctx->ctxdata.generic = buf; + + return (ISC_R_SUCCESS); +} + +static void +openssleddsa_destroyctx(dst_context_t *dctx) { + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + + REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || + dctx->key->key_alg == DST_ALG_ED448); + if (buf != NULL) { + isc_buffer_free(&buf); + } + dctx->ctxdata.generic = NULL; +} + +static isc_result_t +openssleddsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + isc_buffer_t *nbuf = NULL; + isc_region_t r; + unsigned int length; + isc_result_t result; + + REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || + dctx->key->key_alg == DST_ALG_ED448); + + result = isc_buffer_copyregion(buf, data); + if (result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + length = isc_buffer_length(buf) + data->length + 64; + isc_buffer_allocate(dctx->mctx, &nbuf, length); + isc_buffer_usedregion(buf, &r); + (void)isc_buffer_copyregion(nbuf, &r); + (void)isc_buffer_copyregion(nbuf, data); + isc_buffer_free(&buf); + dctx->ctxdata.generic = nbuf; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssleddsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_result_t ret; + dst_key_t *key = dctx->key; + isc_region_t tbsreg; + isc_region_t sigreg; + EVP_PKEY *pkey = key->keydata.pkey; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + size_t siglen; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + if (ctx == NULL) { + return (ISC_R_NOMEMORY); + } + + if (key->key_alg == DST_ALG_ED25519) { + siglen = DNS_SIG_ED25519SIZE; + } else { + siglen = DNS_SIG_ED448SIZE; + } + + isc_buffer_availableregion(sig, &sigreg); + if (sigreg.length < (unsigned int)siglen) { + DST_RET(ISC_R_NOSPACE); + } + + isc_buffer_usedregion(buf, &tbsreg); + + if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) != 1) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestSignInit", ISC_R_FAILURE)); + } + if (EVP_DigestSign(ctx, sigreg.base, &siglen, tbsreg.base, + tbsreg.length) != 1) + { + DST_RET(dst__openssl_toresult3(dctx->category, "EVP_DigestSign", + DST_R_SIGNFAILURE)); + } + isc_buffer_add(sig, (unsigned int)siglen); + ret = ISC_R_SUCCESS; + +err: + EVP_MD_CTX_free(ctx); + isc_buffer_free(&buf); + dctx->ctxdata.generic = NULL; + + return (ret); +} + +static isc_result_t +openssleddsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_result_t ret; + dst_key_t *key = dctx->key; + int status; + isc_region_t tbsreg; + EVP_PKEY *pkey = key->keydata.pkey; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + unsigned int siglen = 0; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + if (ctx == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + +#if HAVE_OPENSSL_ED25519 + if (key->key_alg == DST_ALG_ED25519) { + siglen = DNS_SIG_ED25519SIZE; + } +#endif /* if HAVE_OPENSSL_ED25519 */ +#if HAVE_OPENSSL_ED448 + if (key->key_alg == DST_ALG_ED448) { + siglen = DNS_SIG_ED448SIZE; + } +#endif /* if HAVE_OPENSSL_ED448 */ + if (siglen == 0) { + DST_RET(ISC_R_NOTIMPLEMENTED); + } + + if (sig->length != siglen) { + DST_RET(DST_R_VERIFYFAILURE); + } + + isc_buffer_usedregion(buf, &tbsreg); + + if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) != 1) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestVerifyInit", ISC_R_FAILURE)); + } + + status = EVP_DigestVerify(ctx, sig->base, siglen, tbsreg.base, + tbsreg.length); + + switch (status) { + case 1: + ret = ISC_R_SUCCESS; + break; + case 0: + ret = dst__openssl_toresult(DST_R_VERIFYFAILURE); + break; + default: + ret = dst__openssl_toresult3(dctx->category, "EVP_DigestVerify", + DST_R_VERIFYFAILURE); + break; + } + +err: + EVP_MD_CTX_free(ctx); + isc_buffer_free(&buf); + dctx->ctxdata.generic = NULL; + + return (ret); +} + +static bool +openssleddsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + int status; + EVP_PKEY *pkey1 = key1->keydata.pkey; + EVP_PKEY *pkey2 = key2->keydata.pkey; + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + status = EVP_PKEY_eq(pkey1, pkey2); + if (status == 1) { + return (true); + } + return (false); +} + +static isc_result_t +openssleddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + isc_result_t ret; + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int nid = 0, status; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + UNUSED(unused); + UNUSED(callback); + +#if HAVE_OPENSSL_ED25519 + if (key->key_alg == DST_ALG_ED25519) { + nid = NID_ED25519; + key->key_size = DNS_KEY_ED25519SIZE * 8; + } +#endif /* if HAVE_OPENSSL_ED25519 */ +#if HAVE_OPENSSL_ED448 + if (key->key_alg == DST_ALG_ED448) { + nid = NID_ED448; + key->key_size = DNS_KEY_ED448SIZE * 8; + } +#endif /* if HAVE_OPENSSL_ED448 */ + if (nid == 0) { + return (ISC_R_NOTIMPLEMENTED); + } + + ctx = EVP_PKEY_CTX_new_id(nid, NULL); + if (ctx == NULL) { + return (dst__openssl_toresult2("EVP_PKEY_CTX_new_id", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_keygen_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_keygen(ctx, &pkey); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", + DST_R_OPENSSLFAILURE)); + } + + key->keydata.pkey = pkey; + ret = ISC_R_SUCCESS; + +err: + EVP_PKEY_CTX_free(ctx); + return (ret); +} + +static bool +openssleddsa_isprivate(const dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + size_t len; + + if (pkey == NULL) { + return (false); + } + + if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) == 1 && len > 0) { + return (true); + } + /* can check if first error is EC_R_INVALID_PRIVATE_KEY */ + while (ERR_get_error() != 0) { + /**/ + } + + return (false); +} + +static void +openssleddsa_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; +} + +static isc_result_t +openssleddsa_todns(const dst_key_t *key, isc_buffer_t *data) { + EVP_PKEY *pkey = key->keydata.pkey; + isc_region_t r; + size_t len; + + REQUIRE(pkey != NULL); + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + if (key->key_alg == DST_ALG_ED25519) { + len = DNS_KEY_ED25519SIZE; + } else { + len = DNS_KEY_ED448SIZE; + } + + isc_buffer_availableregion(data, &r); + if (r.length < len) { + return (ISC_R_NOSPACE); + } + + if (EVP_PKEY_get_raw_public_key(pkey, r.base, &len) != 1) { + return (dst__openssl_toresult(ISC_R_FAILURE)); + } + + isc_buffer_add(data, len); + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssleddsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; + isc_region_t r; + size_t len; + EVP_PKEY *pkey; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + + len = r.length; + ret = raw_key_to_ossl(key->key_alg, 0, r.base, &len, &pkey); + if (ret != ISC_R_SUCCESS) { + return ret; + } + + isc_buffer_forward(data, len); + key->keydata.pkey = pkey; + key->key_size = len * 8; + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssleddsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + dst_private_t priv; + unsigned char *buf = NULL; + size_t len; + int i; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + i = 0; + + if (openssleddsa_isprivate(key)) { + if (key->key_alg == DST_ALG_ED25519) { + len = DNS_KEY_ED25519SIZE; + } else { + len = DNS_KEY_ED448SIZE; + } + buf = isc_mem_get(key->mctx, len); + if (EVP_PKEY_get_raw_private_key(key->keydata.pkey, buf, + &len) != 1) + { + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } + priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY; + priv.elements[i].length = len; + priv.elements[i].data = buf; + i++; + } + if (key->engine != NULL) { + priv.elements[i].tag = TAG_EDDSA_ENGINE; + priv.elements[i].length = (unsigned short)strlen(key->engine) + + 1; + priv.elements[i].data = (unsigned char *)key->engine; + i++; + } + if (key->label != NULL) { + priv.elements[i].tag = TAG_EDDSA_LABEL; + priv.elements[i].length = (unsigned short)strlen(key->label) + + 1; + priv.elements[i].data = (unsigned char *)key->label; + i++; + } + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + +err: + if (buf != NULL) { + isc_mem_put(key->mctx, buf, len); + } + return (ret); +} + +static isc_result_t +eddsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) { + if (pubpkey == NULL) { + return (ISC_R_SUCCESS); + } + if (EVP_PKEY_eq(pkey, pubpkey) == 1) { + return (ISC_R_SUCCESS); + } + return (ISC_R_FAILURE); +} + +static isc_result_t +openssleddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + int i, privkey_index = -1; + const char *engine = NULL, *label = NULL; + EVP_PKEY *pkey = NULL, *pubpkey = NULL; + size_t len; + isc_mem_t *mctx = key->mctx; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + if (key->external) { + if (priv.nelements != 0) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + if (pub == NULL) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ISC_R_SUCCESS); + } + + if (pub != NULL) { + pubpkey = pub->keydata.pkey; + } + + for (i = 0; i < priv.nelements; i++) { + switch (priv.elements[i].tag) { + case TAG_EDDSA_ENGINE: + engine = (char *)priv.elements[i].data; + break; + case TAG_EDDSA_LABEL: + label = (char *)priv.elements[i].data; + break; + case TAG_EDDSA_PRIVATEKEY: + privkey_index = i; + break; + default: + break; + } + } + + if (label != NULL) { + ret = openssleddsa_fromlabel(key, engine, label, NULL); + if (ret != ISC_R_SUCCESS) { + goto err; + } + if (eddsa_check(key->keydata.pkey, pubpkey) != ISC_R_SUCCESS) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + DST_RET(ISC_R_SUCCESS); + } + + if (privkey_index < 0) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + len = priv.elements[privkey_index].length; + ret = raw_key_to_ossl(key->key_alg, 1, + priv.elements[privkey_index].data, &len, &pkey); + if (ret != ISC_R_SUCCESS) { + goto err; + } + if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) { + EVP_PKEY_free(pkey); + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + key->keydata.pkey = pkey; + key->key_size = len * 8; + ret = ISC_R_SUCCESS; + +err: + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static isc_result_t +openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + isc_result_t ret; + ENGINE *e; + EVP_PKEY *pkey = NULL, *pubpkey = NULL; + int baseid = EVP_PKEY_NONE; + + UNUSED(pin); + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + +#if HAVE_OPENSSL_ED25519 + if (key->key_alg == DST_ALG_ED25519) { + baseid = EVP_PKEY_ED25519; + } +#endif /* if HAVE_OPENSSL_ED25519 */ +#if HAVE_OPENSSL_ED448 + if (key->key_alg == DST_ALG_ED448) { + baseid = EVP_PKEY_ED448; + } +#endif /* if HAVE_OPENSSL_ED448 */ + if (baseid == EVP_PKEY_NONE) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (engine == NULL) { + return (DST_R_NOENGINE); + } + e = dst__openssl_getengine(engine); + if (e == NULL) { + return (DST_R_NOENGINE); + } + pkey = ENGINE_load_private_key(e, label, NULL, NULL); + if (pkey == NULL) { + return (dst__openssl_toresult2("ENGINE_load_private_key", + ISC_R_NOTFOUND)); + } + if (EVP_PKEY_base_id(pkey) != baseid) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + pubpkey = ENGINE_load_public_key(e, label, NULL, NULL); + if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + key->engine = isc_mem_strdup(key->mctx, engine); + key->label = isc_mem_strdup(key->mctx, label); + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + pkey = NULL; + ret = ISC_R_SUCCESS; + +err: + if (pubpkey != NULL) { + EVP_PKEY_free(pubpkey); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + return (ret); +#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ +} + +static dst_func_t openssleddsa_functions = { + openssleddsa_createctx, + NULL, /*%< createctx2 */ + openssleddsa_destroyctx, + openssleddsa_adddata, + openssleddsa_sign, + openssleddsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + openssleddsa_compare, + NULL, /*%< paramcompare */ + openssleddsa_generate, + openssleddsa_isprivate, + openssleddsa_destroy, + openssleddsa_todns, + openssleddsa_fromdns, + openssleddsa_tofile, + openssleddsa_parse, + NULL, /*%< cleanup */ + openssleddsa_fromlabel, + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__openssleddsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + *funcp = &openssleddsa_functions; + } + return (ISC_R_SUCCESS); +} + +#endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */ diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c new file mode 100644 index 0000000..dc7382c --- /dev/null +++ b/lib/dns/opensslrsa_link.c @@ -0,0 +1,1816 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 +#include +#endif +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000 +#include +#endif +#include + +#include +#include +#include +#include +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#include "openssl_shim.h" + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +static isc_result_t +opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) { + EVP_MD_CTX *evp_md_ctx; + const EVP_MD *type = NULL; + + UNUSED(key); + REQUIRE(dctx != NULL && dctx->key != NULL); + REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 || + dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || + dctx->key->key_alg == DST_ALG_RSASHA256 || + dctx->key->key_alg == DST_ALG_RSASHA512); + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + /* From RFC 3110 */ + if (dctx->key->key_size > 4096) { + return (ISC_R_FAILURE); + } + break; + case DST_ALG_RSASHA256: + /* From RFC 5702 */ + if (dctx->key->key_size < 512 || dctx->key->key_size > 4096) { + return (ISC_R_FAILURE); + } + break; + case DST_ALG_RSASHA512: + /* From RFC 5702 */ + if (dctx->key->key_size < 1024 || dctx->key->key_size > 4096) { + return (ISC_R_FAILURE); + } + break; + default: + UNREACHABLE(); + } + + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + + switch (dctx->key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + type = EVP_sha1(); /* SHA1 + RSA */ + break; + case DST_ALG_RSASHA256: + type = EVP_sha256(); /* SHA256 + RSA */ + break; + case DST_ALG_RSASHA512: + type = EVP_sha512(); + break; + default: + UNREACHABLE(); + } + + if (!EVP_DigestInit_ex(evp_md_ctx, type, NULL)) { + EVP_MD_CTX_destroy(evp_md_ctx); + return (dst__openssl_toresult3( + dctx->category, "EVP_DigestInit_ex", ISC_R_FAILURE)); + } + dctx->ctxdata.evp_md_ctx = evp_md_ctx; + + return (ISC_R_SUCCESS); +} + +static void +opensslrsa_destroyctx(dst_context_t *dctx) { + EVP_MD_CTX *evp_md_ctx = NULL; + + REQUIRE(dctx != NULL && dctx->key != NULL); + REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 || + dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || + dctx->key->key_alg == DST_ALG_RSASHA256 || + dctx->key->key_alg == DST_ALG_RSASHA512); + + evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + dctx->ctxdata.evp_md_ctx = NULL; + } +} + +static isc_result_t +opensslrsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + EVP_MD_CTX *evp_md_ctx = NULL; + + REQUIRE(dctx != NULL && dctx->key != NULL); + REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 || + dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || + dctx->key->key_alg == DST_ALG_RSASHA256 || + dctx->key->key_alg == DST_ALG_RSASHA512); + + evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) { + return (dst__openssl_toresult3( + dctx->category, "EVP_DigestUpdate", ISC_R_FAILURE)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + dst_key_t *key = NULL; + isc_region_t r; + unsigned int siglen = 0; + EVP_MD_CTX *evp_md_ctx = NULL; + EVP_PKEY *pkey = NULL; + + REQUIRE(dctx != NULL && dctx->key != NULL); + REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 || + dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || + dctx->key->key_alg == DST_ALG_RSASHA256 || + dctx->key->key_alg == DST_ALG_RSASHA512); + + key = dctx->key; + evp_md_ctx = dctx->ctxdata.evp_md_ctx; + pkey = key->keydata.pkey; + + isc_buffer_availableregion(sig, &r); + + if (r.length < (unsigned int)EVP_PKEY_size(pkey)) { + return (ISC_R_NOSPACE); + } + + if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) { + return (dst__openssl_toresult3(dctx->category, "EVP_SignFinal", + ISC_R_FAILURE)); + } + + isc_buffer_add(sig, siglen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslrsa_verify2(dst_context_t *dctx, int maxbits, const isc_region_t *sig) { + dst_key_t *key = NULL; + int status = 0; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa; + const BIGNUM *e = NULL; +#else + BIGNUM *e = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + EVP_MD_CTX *evp_md_ctx = NULL; + EVP_PKEY *pkey = NULL; + int bits; + + REQUIRE(dctx != NULL && dctx->key != NULL); + REQUIRE(dctx->key->key_alg == DST_ALG_RSASHA1 || + dctx->key->key_alg == DST_ALG_NSEC3RSASHA1 || + dctx->key->key_alg == DST_ALG_RSASHA256 || + dctx->key->key_alg == DST_ALG_RSASHA512); + + key = dctx->key; + evp_md_ctx = dctx->ctxdata.evp_md_ctx; + pkey = key->keydata.pkey; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + RSA_get0_key(rsa, NULL, &e, NULL); + if (e == NULL) { + RSA_free(rsa); + return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); + } + bits = BN_num_bits(e); + RSA_free(rsa); +#else + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e); + if (e == NULL) { + return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); + } + bits = BN_num_bits(e); + BN_free(e); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (bits > maxbits && maxbits != 0) { + return (DST_R_VERIFYFAILURE); + } + + status = EVP_VerifyFinal(evp_md_ctx, sig->base, sig->length, pkey); + switch (status) { + case 1: + return (ISC_R_SUCCESS); + case 0: + return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); + default: + return (dst__openssl_toresult3(dctx->category, + "EVP_VerifyFinal", + DST_R_VERIFYFAILURE)); + } +} + +static isc_result_t +opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + return (opensslrsa_verify2(dctx, 0, sig)); +} + +static bool +opensslrsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + bool ret; + int status; + EVP_PKEY *pkey1 = key1->keydata.pkey; + EVP_PKEY *pkey2 = key2->keydata.pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa1 = NULL; + RSA *rsa2 = NULL; + const BIGNUM *d1 = NULL, *d2 = NULL; + const BIGNUM *p1 = NULL, *p2 = NULL; + const BIGNUM *q1 = NULL, *q2 = NULL; +#else + BIGNUM *d1 = NULL, *d2 = NULL; + BIGNUM *p1 = NULL, *p2 = NULL; + BIGNUM *q1 = NULL, *q2 = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + /* `EVP_PKEY_eq` checks only the public key components and paramters. */ + status = EVP_PKEY_eq(pkey1, pkey2); + if (status != 1) { + DST_RET(false); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa1 = EVP_PKEY_get1_RSA(pkey1); + rsa2 = EVP_PKEY_get1_RSA(pkey2); + if (rsa1 == NULL && rsa2 == NULL) { + DST_RET(true); + } else if (rsa1 == NULL || rsa2 == NULL) { + DST_RET(false); + } + RSA_get0_key(rsa1, NULL, NULL, &d1); + RSA_get0_key(rsa2, NULL, NULL, &d2); +#else + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_D, &d1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_D, &d2); + ERR_clear_error(); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (d1 != NULL || d2 != NULL) { + if (d1 == NULL || d2 == NULL) { + DST_RET(false); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA_get0_factors(rsa1, &p1, &q1); + RSA_get0_factors(rsa2, &p2, &q2); +#else + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_FACTOR1, &p1); + EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_RSA_FACTOR2, &q1); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_FACTOR1, &p2); + EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_RSA_FACTOR2, &q2); + ERR_clear_error(); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (BN_cmp(d1, d2) != 0 || BN_cmp(p1, p2) != 0 || + BN_cmp(q1, q2) != 0) + { + DST_RET(false); + } + } + + ret = true; + +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa1 != NULL) { + RSA_free(rsa1); + } + if (rsa2 != NULL) { + RSA_free(rsa2); + } +#else + if (d1 != NULL) { + BN_clear_free(d1); + } + if (d2 != NULL) { + BN_clear_free(d2); + } + if (p1 != NULL) { + BN_clear_free(p1); + } + if (p2 != NULL) { + BN_clear_free(p2); + } + if (q1 != NULL) { + BN_clear_free(q1); + } + if (q2 != NULL) { + BN_clear_free(q2); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static int +progress_cb(int p, int n, BN_GENCB *cb) { + union { + void *dptr; + void (*fptr)(int); + } u; + + UNUSED(n); + + u.dptr = BN_GENCB_get_arg(cb); + if (u.fptr != NULL) { + u.fptr(p); + } + return (1); +} +#else +static int +progress_cb(EVP_PKEY_CTX *ctx) { + union { + void *dptr; + void (*fptr)(int); + } u; + + u.dptr = EVP_PKEY_CTX_get_app_data(ctx); + if (u.fptr != NULL) { + int p = EVP_PKEY_CTX_get_keygen_info(ctx, 0); + u.fptr(p); + } + return (1); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +static isc_result_t +opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { + isc_result_t ret; + union { + void *dptr; + void (*fptr)(int); + } u; + BIGNUM *e = BN_new(); +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa = RSA_new(); + EVP_PKEY *pkey = EVP_PKEY_new(); +#if !HAVE_BN_GENCB_NEW + BN_GENCB _cb; +#endif /* !HAVE_BN_GENCB_NEW */ + BN_GENCB *cb = BN_GENCB_new(); +#else + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + EVP_PKEY *pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (e == NULL || rsa == NULL || pkey == NULL || cb == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#else + if (e == NULL || ctx == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + /* + * Reject incorrect RSA key lengths. + */ + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + /* From RFC 3110 */ + if (key->key_size > 4096) { + DST_RET(DST_R_INVALIDPARAM); + } + break; + case DST_ALG_RSASHA256: + /* From RFC 5702 */ + if (key->key_size < 512 || key->key_size > 4096) { + DST_RET(DST_R_INVALIDPARAM); + } + break; + case DST_ALG_RSASHA512: + /* From RFC 5702 */ + if (key->key_size < 1024 || key->key_size > 4096) { + DST_RET(DST_R_INVALIDPARAM); + } + break; + default: + UNREACHABLE(); + } + + if (exp == 0) { + /* RSA_F4 0x10001 */ + BN_set_bit(e, 0); + BN_set_bit(e, 16); + } else { + /* (phased-out) F5 0x100000001 */ + BN_set_bit(e, 0); + BN_set_bit(e, 32); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (EVP_PKEY_set1_RSA(pkey, rsa) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (callback == NULL) { + BN_GENCB_set_old(cb, NULL, NULL); + } else { + u.fptr = callback; + BN_GENCB_set(cb, progress_cb, u.dptr); + } + + if (RSA_generate_key_ex(rsa, key->key_size, e, cb) != 1) { + DST_RET(dst__openssl_toresult2("RSA_generate_key_ex", + DST_R_OPENSSLFAILURE)); + } +#else + if (EVP_PKEY_keygen_init(ctx) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)key->key_size) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx, e) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (callback != NULL) { + u.fptr = callback; + EVP_PKEY_CTX_set_app_data(ctx, u.dptr); + EVP_PKEY_CTX_set_cb(ctx, progress_cb); + } + + if (EVP_PKEY_keygen(ctx, &pkey) != 1 || pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", + DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + key->keydata.pkey = pkey; + pkey = NULL; + ret = ISC_R_SUCCESS; + +err: + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa != NULL) { + RSA_free(rsa); + } + if (cb != NULL) { + BN_GENCB_free(cb); + } +#else + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (e != NULL) { + BN_free(e); + } + return (ret); +} + +static bool +opensslrsa_isprivate(const dst_key_t *key) { + bool ret; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa; + const BIGNUM *d = NULL; +#else + BIGNUM *d = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + REQUIRE(key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); + + pkey = key->keydata.pkey; + if (pkey == NULL) { + return (false); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = EVP_PKEY_get1_RSA(pkey); + INSIST(rsa != NULL); + + if (RSA_test_flags(rsa, RSA_FLAG_EXT_PKEY) != 0) { + ret = true; + } else { + RSA_get0_key(rsa, NULL, NULL, &d); + ret = (d != NULL); + } + RSA_free(rsa); +#else + ret = (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d) == 1 && + d != NULL); + if (d != NULL) { + BN_clear_free(d); + } else { + ERR_clear_error(); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +static void +opensslrsa_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; + } +} + +static isc_result_t +opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) { + isc_region_t r; + unsigned int e_bytes; + unsigned int mod_bytes; + isc_result_t ret; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa; + const BIGNUM *e = NULL, *n = NULL; +#else + BIGNUM *e = NULL, *n = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + isc_buffer_availableregion(data, &r); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + RSA_get0_key(rsa, &n, &e, NULL); +#else + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (e == NULL || n == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + mod_bytes = BN_num_bytes(n); + e_bytes = BN_num_bytes(e); + + if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */ + if (r.length < 1) { + DST_RET(ISC_R_NOSPACE); + } + isc_buffer_putuint8(data, (uint8_t)e_bytes); + isc_region_consume(&r, 1); + } else { + if (r.length < 3) { + DST_RET(ISC_R_NOSPACE); + } + isc_buffer_putuint8(data, 0); + isc_buffer_putuint16(data, (uint16_t)e_bytes); + isc_region_consume(&r, 3); + } + + if (r.length < e_bytes + mod_bytes) { + DST_RET(ISC_R_NOSPACE); + } + + BN_bn2bin(e, r.base); + isc_region_consume(&r, e_bytes); + BN_bn2bin(n, r.base); + isc_region_consume(&r, mod_bytes); + + isc_buffer_add(data, e_bytes + mod_bytes); + + ret = ISC_R_SUCCESS; +err: +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa != NULL) { + RSA_free(rsa); + } +#else + if (e != NULL) { + BN_free(e); + } + if (n != NULL) { + BN_free(n); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + return (ret); +} + +static isc_result_t +opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; + int status; + isc_region_t r; + unsigned int e_bytes; + unsigned int length; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa = NULL; +#else + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + EVP_PKEY *pkey = NULL; + BIGNUM *e = NULL, *n = NULL; + + REQUIRE(key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + DST_RET(ISC_R_SUCCESS); + } + length = r.length; + if (r.length < 1) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + + e_bytes = *r.base; + isc_region_consume(&r, 1); + + if (e_bytes == 0) { + if (r.length < 2) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + e_bytes = (*r.base) << 8; + isc_region_consume(&r, 1); + e_bytes += *r.base; + isc_region_consume(&r, 1); + } + + if (r.length < e_bytes) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + e = BN_bin2bn(r.base, e_bytes, NULL); + isc_region_consume(&r, e_bytes); + n = BN_bin2bn(r.base, r.length, NULL); + if (e == NULL || n == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + + key->key_size = BN_num_bits(n); + + isc_buffer_forward(data, length); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = RSA_new(); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult2("RSA_new", + DST_R_OPENSSLFAILURE)); + } + status = RSA_set0_key(rsa, n, e, NULL); + if (status != 1) { + DST_RET(dst__openssl_toresult2("RSA_set0_key", + DST_R_OPENSSLFAILURE)); + } + + /* These are now managed by OpenSSL. */ + n = NULL; + e = NULL; + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_new", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_set1_RSA(pkey, rsa); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_set1_RSA", + DST_R_OPENSSLFAILURE)); + } +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new", + DST_R_OPENSSLFAILURE)); + } + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param", + DST_R_OPENSSLFAILURE)); + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params); + if (status != 1 || pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + key->keydata.pkey = pkey; + pkey = NULL; + ret = ISC_R_SUCCESS; + +err: + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa != NULL) { + RSA_free(rsa); + } +#else + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (n != NULL) { + BN_free(n); + } + if (e != NULL) { + BN_free(e); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ret); +} + +static isc_result_t +opensslrsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + dst_private_t priv = { 0 }; + unsigned char *bufs[8] = { NULL }; + unsigned short i = 0; + EVP_PKEY *pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa = NULL; + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + const BIGNUM *p = NULL, *q = NULL; + const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; +#else + BIGNUM *n = NULL, *e = NULL, *d = NULL; + BIGNUM *p = NULL, *q = NULL; + BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (key->keydata.pkey == NULL) { + DST_RET(DST_R_NULLKEY); + } + + if (key->external) { + return (dst__privstruct_writefile(key, &priv, directory)); + } + + pkey = key->keydata.pkey; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); +#else + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp); + ERR_clear_error(); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (n == NULL || e == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + priv.elements[i].tag = TAG_RSA_MODULUS; + priv.elements[i].length = BN_num_bytes(n); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(n, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT; + priv.elements[i].length = BN_num_bytes(e); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(e, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + if (d != NULL) { + priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT; + priv.elements[i].length = BN_num_bytes(d); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(d, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (p != NULL) { + priv.elements[i].tag = TAG_RSA_PRIME1; + priv.elements[i].length = BN_num_bytes(p); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(p, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (q != NULL) { + priv.elements[i].tag = TAG_RSA_PRIME2; + priv.elements[i].length = BN_num_bytes(q); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(q, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (dmp1 != NULL) { + priv.elements[i].tag = TAG_RSA_EXPONENT1; + priv.elements[i].length = BN_num_bytes(dmp1); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(dmp1, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (dmq1 != NULL) { + priv.elements[i].tag = TAG_RSA_EXPONENT2; + priv.elements[i].length = BN_num_bytes(dmq1); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(dmq1, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (iqmp != NULL) { + priv.elements[i].tag = TAG_RSA_COEFFICIENT; + priv.elements[i].length = BN_num_bytes(iqmp); + INSIST(i < ARRAY_SIZE(bufs)); + bufs[i] = isc_mem_get(key->mctx, priv.elements[i].length); + BN_bn2bin(iqmp, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + } + + if (key->engine != NULL) { + priv.elements[i].tag = TAG_RSA_ENGINE; + priv.elements[i].length = (unsigned short)strlen(key->engine) + + 1; + priv.elements[i].data = (unsigned char *)key->engine; + i++; + } + + if (key->label != NULL) { + priv.elements[i].tag = TAG_RSA_LABEL; + priv.elements[i].length = (unsigned short)strlen(key->label) + + 1; + priv.elements[i].data = (unsigned char *)key->label; + i++; + } + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + +err: + for (i = 0; i < ARRAY_SIZE(bufs); i++) { + if (bufs[i] != NULL) { + isc_mem_put(key->mctx, bufs[i], + priv.elements[i].length); + } + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA_free(rsa); +#else + if (n != NULL) { + BN_free(n); + } + if (e != NULL) { + BN_free(e); + } + if (d != NULL) { + BN_clear_free(d); + } + if (p != NULL) { + BN_clear_free(p); + } + if (q != NULL) { + BN_clear_free(q); + } + if (dmp1 != NULL) { + BN_clear_free(dmp1); + } + if (dmq1 != NULL) { + BN_clear_free(dmq1); + } + if (iqmp != NULL) { + BN_clear_free(iqmp); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + return (ret); +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 +static isc_result_t +rsa_check(RSA *rsa, RSA *pub) { + const BIGNUM *n1 = NULL, *n2 = NULL; + const BIGNUM *e1 = NULL, *e2 = NULL; + BIGNUM *n = NULL, *e = NULL; + + /* + * Public parameters should be the same but if they are not set + * copy them from the public key. + */ + if (pub != NULL) { + RSA_get0_key(rsa, &n1, &e1, NULL); + RSA_get0_key(pub, &n2, &e2, NULL); + if (n1 != NULL) { + if (BN_cmp(n1, n2) != 0) { + return (DST_R_INVALIDPRIVATEKEY); + } + } else { + n = BN_dup(n2); + if (n == NULL) { + return (ISC_R_NOMEMORY); + } + } + if (e1 != NULL) { + if (BN_cmp(e1, e2) != 0) { + if (n != NULL) { + BN_free(n); + } + return (DST_R_INVALIDPRIVATEKEY); + } + } else { + e = BN_dup(e2); + if (e == NULL) { + if (n != NULL) { + BN_free(n); + } + return (ISC_R_NOMEMORY); + } + } + if (RSA_set0_key(rsa, n, e, NULL) == 0) { + if (n != NULL) { + BN_free(n); + } + if (e != NULL) { + BN_free(e); + } + } + } + + RSA_get0_key(rsa, &n1, &e1, NULL); + if (n1 == NULL || e1 == NULL) { + return (DST_R_INVALIDPRIVATEKEY); + } + + return (ISC_R_SUCCESS); +} +#else +static isc_result_t +rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) { + isc_result_t ret = ISC_R_FAILURE; + int status; + BIGNUM *n1 = NULL, *n2 = NULL; + BIGNUM *e1 = NULL, *e2 = NULL; + + /* Try to get the public key from pkey. */ + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n1); + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e1); + + /* Check if `pubpkey` exists and that we can extract its public key. */ + if (pubpkey == NULL || + EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_RSA_N, &n2) != 1 || + n2 == NULL || + EVP_PKEY_get_bn_param(pubpkey, OSSL_PKEY_PARAM_RSA_E, &e2) != 1 || + e2 == NULL) + { + if (n1 == NULL || e1 == NULL) { + /* No public key both in `pkey` and in `pubpkey`. */ + DST_RET(DST_R_INVALIDPRIVATEKEY); + } else { + /* + * `pkey` has a public key, but there is no public key + * in `pubpkey` to check against. + */ + DST_RET(ISC_R_SUCCESS); + } + } + + /* + * If `pkey` doesn't have a public key then we will copy it from + * `pubpkey`. + */ + if (n1 == NULL || e1 == NULL) { + status = EVP_PKEY_set_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, n2); + if (status != 1) { + DST_RET(ISC_R_FAILURE); + } + + status = EVP_PKEY_set_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, e2); + if (status != 1) { + DST_RET(ISC_R_FAILURE); + } + } + + if (EVP_PKEY_eq(pkey, pubpkey) == 1) { + DST_RET(ISC_R_SUCCESS); + } + +err: + if (n1 != NULL) { + BN_free(n1); + } + if (n2 != NULL) { + BN_free(n2); + } + if (e1 != NULL) { + BN_free(e1); + } + if (e2 != NULL) { + BN_free(e2); + } + + return (ret); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + +static isc_result_t +opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + int i; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa = NULL, *pubrsa = NULL; +#else + OSSL_PARAM_BLD *bld = NULL; + OSSL_PARAM *params = NULL; + EVP_PKEY_CTX *ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + const BIGNUM *ex = NULL; + ENGINE *ep = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + isc_mem_t *mctx = NULL; + const char *engine = NULL, *label = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *n = NULL, *e = NULL, *d = NULL; + BIGNUM *p = NULL, *q = NULL; + BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; + + REQUIRE(key != NULL); + REQUIRE(key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); + + mctx = key->mctx; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + if (key->external) { + if (priv.nelements != 0 || pub == NULL) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + key->key_size = pub->key_size; + DST_RET(ISC_R_SUCCESS); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (pub != NULL && pub->keydata.pkey != NULL) { + pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + for (i = 0; i < priv.nelements; i++) { + switch (priv.elements[i].tag) { + case TAG_RSA_ENGINE: + engine = (char *)priv.elements[i].data; + break; + case TAG_RSA_LABEL: + label = (char *)priv.elements[i].data; + break; + default: + break; + } + } + + /* + * Is this key stored in a HSM? + * See if we can fetch it. + */ + if (label != NULL) { +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + if (engine == NULL) { + DST_RET(DST_R_NOENGINE); + } + ep = dst__openssl_getengine(engine); + if (ep == NULL) { + DST_RET(dst__openssl_toresult(DST_R_NOENGINE)); + } + pkey = ENGINE_load_private_key(ep, label, NULL, NULL); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_private_" + "key", + ISC_R_NOTFOUND)); + } + key->engine = isc_mem_strdup(key->mctx, engine); + key->label = isc_mem_strdup(key->mctx, label); + + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + RSA_get0_key(rsa, NULL, &ex, NULL); + + if (ex == NULL) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + pkey = NULL; + DST_RET(ISC_R_SUCCESS); +#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + UNUSED(engine); + DST_RET(DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + } + + for (i = 0; i < priv.nelements; i++) { + BIGNUM *bn; + switch (priv.elements[i].tag) { + case TAG_RSA_ENGINE: + continue; + case TAG_RSA_LABEL: + continue; + default: + bn = BN_bin2bn(priv.elements[i].data, + priv.elements[i].length, NULL); + if (bn == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + switch (priv.elements[i].tag) { + case TAG_RSA_MODULUS: + n = bn; + break; + case TAG_RSA_PUBLICEXPONENT: + e = bn; + break; + case TAG_RSA_PRIVATEEXPONENT: + d = bn; + break; + case TAG_RSA_PRIME1: + p = bn; + break; + case TAG_RSA_PRIME2: + q = bn; + break; + case TAG_RSA_EXPONENT1: + dmp1 = bn; + break; + case TAG_RSA_EXPONENT2: + dmq1 = bn; + break; + case TAG_RSA_COEFFICIENT: + iqmp = bn; + break; + default: + BN_clear_free(bn); + } + } + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = RSA_new(); + if (rsa == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + if (EVP_PKEY_set1_RSA(pkey, rsa) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (RSA_set0_key(rsa, n, e, d) == 0) { + if (n != NULL) { + BN_free(n); + } + if (e != NULL) { + BN_free(e); + } + if (d != NULL) { + BN_clear_free(d); + } + } + if (RSA_set0_factors(rsa, p, q) == 0) { + if (p != NULL) { + BN_clear_free(p); + } + if (q != NULL) { + BN_clear_free(q); + } + } + if (RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 0) { + if (dmp1 != NULL) { + BN_clear_free(dmp1); + } + if (dmq1 != NULL) { + BN_clear_free(dmq1); + } + if (iqmp != NULL) { + BN_clear_free(iqmp); + } + } + if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (n != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (e != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (d != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (p != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (q != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (dmp1 != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1) != + 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (dmq1 != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1) != + 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (iqmp != NULL && + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, + iqmp) != 1) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + if (ctx == NULL || EVP_PKEY_fromdata_init(ctx) != 1) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1 || + pkey == NULL) + { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + if (rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL) != + ISC_R_SUCCESS) + { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + + if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + + key->key_size = BN_num_bits(n); + key->keydata.pkey = pkey; + pkey = NULL; + +err: + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa != NULL) { + RSA_free(rsa); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } +#else + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } + if (e != NULL) { + BN_free(e); + } + if (n != NULL) { + BN_free(n); + } + if (d != NULL) { + BN_clear_free(d); + } + if (p != NULL) { + BN_clear_free(p); + } + if (q != NULL) { + BN_clear_free(q); + } + if (dmp1 != NULL) { + BN_clear_free(dmp1); + } + if (dmq1 != NULL) { + BN_clear_free(dmq1); + } + if (iqmp != NULL) { + BN_clear_free(iqmp); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */ + if (ret != ISC_R_SUCCESS) { + key->keydata.generic = NULL; + } + + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ret); +} + +static isc_result_t +opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 + ENGINE *e = NULL; + isc_result_t ret = ISC_R_SUCCESS; + EVP_PKEY *pkey = NULL, *pubpkey = NULL; + RSA *rsa = NULL, *pubrsa = NULL; + const BIGNUM *ex = NULL; + + UNUSED(pin); + + if (engine == NULL) { + DST_RET(DST_R_NOENGINE); + } + e = dst__openssl_getengine(engine); + if (e == NULL) { + DST_RET(dst__openssl_toresult(DST_R_NOENGINE)); + } + + pubpkey = ENGINE_load_public_key(e, label, NULL, NULL); + if (pubpkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_public_key", + DST_R_OPENSSLFAILURE)); + } + pubrsa = EVP_PKEY_get1_RSA(pubpkey); + if (pubrsa == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + pkey = ENGINE_load_private_key(e, label, NULL, NULL); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_private_key", + DST_R_OPENSSLFAILURE)); + } + + key->engine = isc_mem_strdup(key->mctx, engine); + key->label = isc_mem_strdup(key->mctx, label); + + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + RSA_get0_key(rsa, NULL, &ex, NULL); + + if (ex == NULL) { + DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY)); + } + if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + pkey = NULL; + +err: + if (rsa != NULL) { + RSA_free(rsa); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (pubpkey != NULL) { + EVP_PKEY_free(pubpkey); + } + return (ret); +#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ +} + +static dst_func_t opensslrsa_functions = { + opensslrsa_createctx, + NULL, /*%< createctx2 */ + opensslrsa_destroyctx, + opensslrsa_adddata, + opensslrsa_sign, + opensslrsa_verify, + opensslrsa_verify2, + NULL, /*%< computesecret */ + opensslrsa_compare, + NULL, /*%< paramcompare */ + opensslrsa_generate, + opensslrsa_isprivate, + opensslrsa_destroy, + opensslrsa_todns, + opensslrsa_fromdns, + opensslrsa_tofile, + opensslrsa_parse, + NULL, /*%< cleanup */ + opensslrsa_fromlabel, + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +/* + * An RSA public key with 2048 bits + */ +static const unsigned char e_bytes[] = "\x01\x00\x01"; +static const unsigned char n_bytes[] = + "\xc3\x90\x07\xbe\xf1\x85\xfc\x1a\x43\xb1\xa5\x15\xce\x71\x34\xfc\xc1" + "\x87\x27\x28\x38\xa4\xcf\x7c\x1a\x82\xa8\xdc\x04\x14\xd0\x3f\xb4\xfe" + "\x20\x4a\xdd\xd9\x0d\xd7\xcd\x61\x8c\xbd\x61\xa8\x10\xb5\x63\x1c\x29" + "\x15\xcb\x41\xee\x43\x91\x7f\xeb\xa5\x2c\xab\x81\x75\x0d\xa3\x3d\xe4" + "\xc8\x49\xb9\xca\x5a\x55\xa1\xbb\x09\xd1\xfb\xcd\xa2\xd2\x12\xa4\x85" + "\xdf\xa5\x65\xc9\x27\x2d\x8b\xd7\x8b\xfe\x6d\xc4\xd1\xd9\x83\x1c\x91" + "\x7d\x3d\xd0\xa4\xcd\xe1\xe7\xb9\x7a\x11\x38\xf9\x8b\x3c\xec\x30\xb6" + "\x36\xb9\x92\x64\x81\x56\x3c\xbc\xf9\x49\xfb\xba\x82\xb7\xa0\xfa\x65" + "\x79\x83\xb9\x4c\xa7\xfd\x53\x0b\x5a\xe4\xde\xf9\xfc\x38\x7e\xb5\x2c" + "\xa0\xc3\xb2\xfc\x7c\x38\xb0\x63\x50\xaf\x00\xaa\xb2\xad\x49\x54\x1e" + "\x8b\x11\x88\x9b\x6e\xae\x3b\x23\xa3\xdd\x53\x51\x80\x7a\x0b\x91\x4e" + "\x6d\x32\x01\xbd\x17\x81\x12\x64\x9f\x84\xae\x76\x53\x1a\x63\xa0\xda" + "\xcc\x45\x04\x72\xb0\xa7\xfb\xfa\x02\x39\x53\xc1\x83\x1f\x88\x54\x47" + "\x88\x63\x20\x71\x5d\xe2\xaa\x7c\x53\x39\x5e\x35\x25\xee\xe6\x5c\x15" + "\x5e\x14\xbe\x99\xde\x25\x19\xe7\x13\xdb\xce\xa3\xd3\x6c\x5c\xbb\x0e" + "\x6b"; + +static const unsigned char sha1_sig[] = + "\x69\x99\x89\x28\xe0\x38\x34\x91\x29\xb6\xac\x4b\xe9\x51\xbd\xbe\xc8" + "\x1a\x2d\xb6\xca\x99\xa3\x9f\x6a\x8b\x94\x5a\x51\x37\xd5\x8d\xae\x87" + "\xed\xbc\x8e\xb8\xa3\x60\x6b\xf6\xe6\x72\xfc\x26\x2a\x39\x2b\xfe\x88" + "\x1a\xa9\xd1\x93\xc7\xb9\xf8\xb6\x45\xa1\xf9\xa1\x56\x78\x7b\x00\xec" + "\x33\x83\xd4\x93\x25\x48\xb3\x50\x09\xd0\xbc\x7f\xac\x67\xc7\xa2\x7f" + "\xfc\xf6\x5a\xef\xf8\x5a\xad\x52\x74\xf5\x71\x34\xd9\x3d\x33\x8b\x4d" + "\x99\x64\x7e\x14\x59\xbe\xdf\x26\x8a\x67\x96\x6c\x1f\x79\x85\x10\x0d" + "\x7f\xd6\xa4\xba\x57\x41\x03\x71\x4e\x8c\x17\xd5\xc4\xfb\x4a\xbe\x66" + "\x45\x15\x45\x0c\x02\xe0\x10\xe1\xbb\x33\x8d\x90\x34\x3c\x94\xa4\x4c" + "\x7c\xd0\x5e\x90\x76\x80\x59\xb2\xfa\x54\xbf\xa9\x86\xb8\x84\x1e\x28" + "\x48\x60\x2f\x9e\xa4\xbc\xd4\x9c\x20\x27\x16\xac\x33\xcb\xcf\xab\x93" + "\x7a\x3b\x74\xa0\x18\x92\xa1\x4f\xfc\x52\x19\xee\x7a\x13\x73\xba\x36" + "\xaf\x78\x5d\xb6\x1f\x96\x76\x15\x73\xee\x04\xa8\x70\x27\xf7\xe7\xfa" + "\xe8\xf6\xc8\x5f\x4a\x81\x56\x0a\x94\xf3\xc6\x98\xd2\x93\xc4\x0b\x49" + "\x6b\x44\xd3\x73\xa2\xe3\xef\x5d\x9e\x68\xac\xa7\x42\xb1\xbb\x65\xbe" + "\x59"; + +static const unsigned char sha256_sig[] = + "\x0f\x8c\xdb\xe6\xb6\x21\xc8\xc5\x28\x76\x7d\xf6\xf2\x3b\x78\x47\x77" + "\x03\x34\xc5\x5e\xc0\xda\x42\x41\xc0\x0f\x97\xd3\xd0\x53\xa1\xd6\x87" + "\xe4\x16\x29\x9a\xa5\x59\xf4\x01\xad\xc9\x04\xe7\x61\xe2\xcb\x79\x73" + "\xce\xe0\xa6\x85\xe5\x10\x8c\x4b\xc5\x68\x3b\x96\x42\x3f\x56\xb3\x6d" + "\x89\xc4\xff\x72\x36\xf2\x3f\xed\xe9\xb8\xe3\xae\xab\x3c\xb7\xaa\xf7" + "\x1f\x8f\x26\x6b\xee\xc1\xac\x72\x89\x23\x8b\x7a\xd7\x8c\x84\xf3\xf5" + "\x97\xa8\x8d\xd3\xef\xb2\x5e\x06\x04\x21\xdd\x28\xa2\x28\x83\x68\x9b" + "\xac\x34\xdd\x36\x33\xda\xdd\xa4\x59\xc7\x5a\x4d\xf3\x83\x06\xd5\xc0" + "\x0d\x1f\x4f\x47\x2f\x9f\xcc\xc2\x0d\x21\x1e\x82\xb9\x3d\xf3\xa4\x1a" + "\xa6\xd8\x0e\x72\x1d\x71\x17\x1c\x54\xad\x37\x3e\xa4\x0e\x70\x86\x53" + "\xfb\x40\xad\xb9\x14\xf8\x8d\x93\xbb\xd7\xe7\x31\xce\xe0\x98\xda\x27" + "\x1c\x18\x8e\xd8\x85\xcb\xa7\xb1\x18\xac\x8c\xa8\x9d\xa9\xe2\xf6\x30" + "\x95\xa4\x81\xf4\x1c\xa0\x31\xd5\xc7\x9d\x28\x33\xee\x7f\x08\x4f\xcb" + "\xd1\x14\x17\xdf\xd0\x88\x78\x47\x29\xaf\x6c\xb2\x62\xa6\x30\x87\x29" + "\xaa\x80\x19\x7d\x2f\x05\xe3\x7e\x23\x73\x88\x08\xcc\xbd\x50\x46\x09" + "\x2a"; + +static const unsigned char sha512_sig[] = + "\x15\xda\x87\x87\x1f\x76\x08\xd3\x9d\x3a\xb9\xd2\x6a\x0e\x3b\x7d\xdd" + "\xec\x7d\xc4\x6d\x26\xf5\x04\xd3\x76\xc7\x83\xc4\x81\x69\x35\xe9\x47" + "\xbf\x49\xd1\xc0\xf9\x01\x4e\x0a\x34\x5b\xd0\xec\x6e\xe2\x2e\xe9\x2d" + "\x00\xfd\xe0\xa0\x28\x54\x53\x19\x49\x6d\xd2\x58\xb9\x47\xfa\x45\xad" + "\xd2\x1d\x52\xac\x80\xcb\xfc\x91\x97\x84\x58\x5f\xab\x21\x62\x60\x79" + "\xb8\x8a\x83\xe1\xf1\xcb\x05\x4c\x92\x56\x62\xd9\xbf\xa7\x81\x34\x23" + "\xdf\xd7\xa7\xc4\xdf\xde\x96\x00\x57\x4b\x78\x85\xb9\x3b\xdd\x3f\x98" + "\x88\x59\x1d\x48\xcf\x5a\xa8\xb7\x2a\x8b\x77\x93\x8e\x38\x3a\x0c\xa7" + "\x8a\x5f\xe6\x9f\xcb\xf0\x9a\x6b\xb6\x91\x04\x8b\x69\x6a\x37\xee\xa2" + "\xad\x5f\x31\x20\x96\xd6\x51\x80\xbf\x62\x48\xb8\xe4\x94\x10\x86\x4e" + "\xf2\x22\x1e\xa4\xd5\x54\xfe\xe1\x35\x49\xaf\xf8\x62\xfc\x11\xeb\xf7" + "\x3d\xd5\x5e\xaf\x11\xbd\x3d\xa9\x3a\x9f\x7f\xe8\xb4\x0d\xa2\xbb\x1c" + "\xbd\x4c\xed\x9e\x81\xb1\xec\xd3\xea\xaa\x03\xe3\x14\xdf\x8c\xb3\x78" + "\x85\x5e\x87\xad\xec\x41\x1a\xa9\x4f\xd2\xe6\xc6\xbe\xfa\xb8\x10\xea" + "\x74\x25\x36\x0c\x23\xe2\x24\xb7\x21\xb7\x0d\xaf\xf6\xb4\x31\xf5\x75" + "\xf1"; + +static isc_result_t +check_algorithm(unsigned char algorithm) { + BIGNUM *n = NULL, *e = NULL; + EVP_MD_CTX *evp_md_ctx = EVP_MD_CTX_create(); + EVP_PKEY *pkey = NULL; + const EVP_MD *type = NULL; + const unsigned char *sig = NULL; + int status; + isc_result_t ret = ISC_R_SUCCESS; + size_t len; +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + RSA *rsa = NULL; +#else + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + EVP_PKEY_CTX *ctx = NULL; +#endif + + if (evp_md_ctx == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + + switch (algorithm) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + type = EVP_sha1(); /* SHA1 + RSA */ + sig = sha1_sig; + len = sizeof(sha1_sig) - 1; + break; + case DST_ALG_RSASHA256: + type = EVP_sha256(); /* SHA256 + RSA */ + sig = sha256_sig; + len = sizeof(sha256_sig) - 1; + break; + case DST_ALG_RSASHA512: + type = EVP_sha512(); + sig = sha512_sig; + len = sizeof(sha512_sig) - 1; + break; + default: + DST_RET(ISC_R_NOTIMPLEMENTED); + } + + if (type == NULL) { + DST_RET(ISC_R_NOTIMPLEMENTED); + } + + /* + * Construct pkey. + */ + e = BN_bin2bn(e_bytes, sizeof(e_bytes) - 1, NULL); + n = BN_bin2bn(n_bytes, sizeof(n_bytes) - 1, NULL); + if (e == NULL || n == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + rsa = RSA_new(); + if (rsa == NULL) { + DST_RET(dst__openssl_toresult2("RSA_new", + DST_R_OPENSSLFAILURE)); + } + status = RSA_set0_key(rsa, n, e, NULL); + if (status != 1) { + DST_RET(dst__openssl_toresult2("RSA_set0_key", + DST_R_OPENSSLFAILURE)); + } + + /* These are now managed by OpenSSL. */ + n = NULL; + e = NULL; + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_new", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_set1_RSA(pkey, rsa); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_set1_RSA", + DST_R_OPENSSLFAILURE)); + } +#else + bld = OSSL_PARAM_BLD_new(); + if (bld == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_new", + DST_R_OPENSSLFAILURE)); + } + if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) != 1 || + OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) != 1) + { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_push_BN", + DST_R_OPENSSLFAILURE)); + } + params = OSSL_PARAM_BLD_to_param(bld); + if (params == NULL) { + DST_RET(dst__openssl_toresult2("OSSL_PARAM_BLD_to_param", + DST_R_OPENSSLFAILURE)); + } + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata_init", + DST_R_OPENSSLFAILURE)); + } + status = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params); + if (status != 1 || pkey == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_fromdata", + DST_R_OPENSSLFAILURE)); + } +#endif + + /* + * Check that we can verify the signature. + */ + if (EVP_DigestInit_ex(evp_md_ctx, type, NULL) != 1 || + EVP_DigestUpdate(evp_md_ctx, "test", 4) != 1 || + EVP_VerifyFinal(evp_md_ctx, sig, len, pkey) != 1) + { + DST_RET(ISC_R_NOTIMPLEMENTED); + } + +err: + BN_free(e); + BN_free(n); +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + if (rsa != NULL) { + RSA_free(rsa); + } +#else + if (bld != NULL) { + OSSL_PARAM_BLD_free(bld); + } + if (ctx != NULL) { + EVP_PKEY_CTX_free(ctx); + } + if (params != NULL) { + OSSL_PARAM_free(params); + } +#endif + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + } + ERR_clear_error(); + return (ret); +} + +isc_result_t +dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) { + isc_result_t result; + + REQUIRE(funcp != NULL); + + result = check_algorithm(algorithm); + + if (result == ISC_R_SUCCESS) { + if (*funcp == NULL) { + *funcp = &opensslrsa_functions; + } + } else if (result == ISC_R_NOTIMPLEMENTED) { + result = ISC_R_SUCCESS; + } + + return (result); +} diff --git a/lib/dns/order.c b/lib/dns/order.c new file mode 100644 index 0000000..477576c --- /dev/null +++ b/lib/dns/order.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct dns_order_ent dns_order_ent_t; +struct dns_order_ent { + dns_fixedname_t name; + dns_rdataclass_t rdclass; + dns_rdatatype_t rdtype; + unsigned int mode; + ISC_LINK(dns_order_ent_t) link; +}; + +struct dns_order { + unsigned int magic; + isc_refcount_t references; + ISC_LIST(dns_order_ent_t) ents; + isc_mem_t *mctx; +}; + +#define DNS_ORDER_MAGIC ISC_MAGIC('O', 'r', 'd', 'r') +#define DNS_ORDER_VALID(order) ISC_MAGIC_VALID(order, DNS_ORDER_MAGIC) + +isc_result_t +dns_order_create(isc_mem_t *mctx, dns_order_t **orderp) { + dns_order_t *order; + + REQUIRE(orderp != NULL && *orderp == NULL); + + order = isc_mem_get(mctx, sizeof(*order)); + + ISC_LIST_INIT(order->ents); + + /* Implicit attach. */ + isc_refcount_init(&order->references, 1); + + order->mctx = NULL; + isc_mem_attach(mctx, &order->mctx); + order->magic = DNS_ORDER_MAGIC; + *orderp = order; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_order_add(dns_order_t *order, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass, + unsigned int mode) { + dns_order_ent_t *ent; + + REQUIRE(DNS_ORDER_VALID(order)); + REQUIRE(mode == DNS_RDATASETATTR_RANDOMIZE || + mode == DNS_RDATASETATTR_FIXEDORDER || + mode == DNS_RDATASETATTR_CYCLIC || + mode == DNS_RDATASETATTR_NONE); + + ent = isc_mem_get(order->mctx, sizeof(*ent)); + + dns_fixedname_init(&ent->name); + dns_name_copy(name, dns_fixedname_name(&ent->name)); + ent->rdtype = rdtype; + ent->rdclass = rdclass; + ent->mode = mode; + ISC_LINK_INIT(ent, link); + ISC_LIST_INITANDAPPEND(order->ents, ent, link); + return (ISC_R_SUCCESS); +} + +static bool +match(const dns_name_t *name1, const dns_name_t *name2) { + if (dns_name_iswildcard(name2)) { + return (dns_name_matcheswildcard(name1, name2)); + } + return (dns_name_equal(name1, name2)); +} + +unsigned int +dns_order_find(dns_order_t *order, const dns_name_t *name, + dns_rdatatype_t rdtype, dns_rdataclass_t rdclass) { + dns_order_ent_t *ent; + REQUIRE(DNS_ORDER_VALID(order)); + + for (ent = ISC_LIST_HEAD(order->ents); ent != NULL; + ent = ISC_LIST_NEXT(ent, link)) + { + if (ent->rdtype != rdtype && ent->rdtype != dns_rdatatype_any) { + continue; + } + if (ent->rdclass != rdclass && + ent->rdclass != dns_rdataclass_any) + { + continue; + } + if (match(name, dns_fixedname_name(&ent->name))) { + return (ent->mode); + } + } + return (DNS_RDATASETATTR_NONE); +} + +void +dns_order_attach(dns_order_t *source, dns_order_t **target) { + REQUIRE(DNS_ORDER_VALID(source)); + REQUIRE(target != NULL && *target == NULL); + isc_refcount_increment(&source->references); + *target = source; +} + +void +dns_order_detach(dns_order_t **orderp) { + REQUIRE(orderp != NULL && DNS_ORDER_VALID(*orderp)); + dns_order_t *order; + order = *orderp; + *orderp = NULL; + + if (isc_refcount_decrement(&order->references) == 1) { + isc_refcount_destroy(&order->references); + order->magic = 0; + dns_order_ent_t *ent; + while ((ent = ISC_LIST_HEAD(order->ents)) != NULL) { + ISC_LIST_UNLINK(order->ents, ent, link); + isc_mem_put(order->mctx, ent, sizeof(*ent)); + } + isc_mem_putanddetach(&order->mctx, order, sizeof(*order)); + } +} diff --git a/lib/dns/peer.c b/lib/dns/peer.c new file mode 100644 index 0000000..e4d2fcb --- /dev/null +++ b/lib/dns/peer.c @@ -0,0 +1,898 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +/*** + *** Types + ***/ + +struct dns_peerlist { + unsigned int magic; + isc_refcount_t refs; + + isc_mem_t *mem; + + ISC_LIST(dns_peer_t) elements; +}; + +struct dns_peer { + unsigned int magic; + isc_refcount_t refs; + + isc_mem_t *mem; + + isc_netaddr_t address; + unsigned int prefixlen; + bool bogus; + dns_transfer_format_t transfer_format; + uint32_t transfers; + bool support_ixfr; + bool provide_ixfr; + bool request_ixfr; + bool support_edns; + bool request_nsid; + bool send_cookie; + bool request_expire; + bool force_tcp; + bool tcp_keepalive; + bool check_axfr_id; + dns_name_t *key; + isc_sockaddr_t *transfer_source; + isc_sockaddr_t *notify_source; + isc_sockaddr_t *query_source; + uint16_t udpsize; /* receive size */ + uint16_t maxudp; /* transmit size */ + uint16_t padding; /* pad block size */ + uint8_t ednsversion; /* edns version */ + + uint32_t bitflags; + + ISC_LINK(dns_peer_t) next; +}; + +/*% + * Bit positions in the dns_peer_t structure flags field + */ +#define BOGUS_BIT 0 +#define SERVER_TRANSFER_FORMAT_BIT 1 +#define TRANSFERS_BIT 2 +#define PROVIDE_IXFR_BIT 3 +#define REQUEST_IXFR_BIT 4 +#define SUPPORT_EDNS_BIT 5 +#define SERVER_UDPSIZE_BIT 6 +#define SERVER_MAXUDP_BIT 7 +#define REQUEST_NSID_BIT 8 +#define SEND_COOKIE_BIT 9 +#define REQUEST_EXPIRE_BIT 10 +#define EDNS_VERSION_BIT 11 +#define FORCE_TCP_BIT 12 +#define SERVER_PADDING_BIT 13 +#define REQUEST_TCP_KEEPALIVE_BIT 14 + +static void +peerlist_delete(dns_peerlist_t **list); + +static void +peer_delete(dns_peer_t **peer); + +isc_result_t +dns_peerlist_new(isc_mem_t *mem, dns_peerlist_t **list) { + dns_peerlist_t *l; + + REQUIRE(list != NULL); + + l = isc_mem_get(mem, sizeof(*l)); + + ISC_LIST_INIT(l->elements); + l->mem = mem; + isc_refcount_init(&l->refs, 1); + l->magic = DNS_PEERLIST_MAGIC; + + *list = l; + + return (ISC_R_SUCCESS); +} + +void +dns_peerlist_attach(dns_peerlist_t *source, dns_peerlist_t **target) { + REQUIRE(DNS_PEERLIST_VALID(source)); + REQUIRE(target != NULL); + REQUIRE(*target == NULL); + + isc_refcount_increment(&source->refs); + + *target = source; +} + +void +dns_peerlist_detach(dns_peerlist_t **list) { + dns_peerlist_t *plist; + + REQUIRE(list != NULL); + REQUIRE(*list != NULL); + REQUIRE(DNS_PEERLIST_VALID(*list)); + + plist = *list; + *list = NULL; + + if (isc_refcount_decrement(&plist->refs) == 1) { + peerlist_delete(&plist); + } +} + +static void +peerlist_delete(dns_peerlist_t **list) { + dns_peerlist_t *l; + dns_peer_t *server, *stmp; + + REQUIRE(list != NULL); + REQUIRE(DNS_PEERLIST_VALID(*list)); + + l = *list; + *list = NULL; + + isc_refcount_destroy(&l->refs); + + server = ISC_LIST_HEAD(l->elements); + while (server != NULL) { + stmp = ISC_LIST_NEXT(server, next); + ISC_LIST_UNLINK(l->elements, server, next); + dns_peer_detach(&server); + server = stmp; + } + + l->magic = 0; + isc_mem_put(l->mem, l, sizeof(*l)); +} + +void +dns_peerlist_addpeer(dns_peerlist_t *peers, dns_peer_t *peer) { + dns_peer_t *p = NULL; + + dns_peer_attach(peer, &p); + + /* + * More specifics to front of list. + */ + for (p = ISC_LIST_HEAD(peers->elements); p != NULL; + p = ISC_LIST_NEXT(p, next)) + { + if (p->prefixlen < peer->prefixlen) { + break; + } + } + + if (p != NULL) { + ISC_LIST_INSERTBEFORE(peers->elements, p, peer, next); + } else { + ISC_LIST_APPEND(peers->elements, peer, next); + } +} + +isc_result_t +dns_peerlist_peerbyaddr(dns_peerlist_t *servers, const isc_netaddr_t *addr, + dns_peer_t **retval) { + dns_peer_t *server; + isc_result_t res; + + REQUIRE(retval != NULL); + REQUIRE(DNS_PEERLIST_VALID(servers)); + + server = ISC_LIST_HEAD(servers->elements); + while (server != NULL) { + if (isc_netaddr_eqprefix(addr, &server->address, + server->prefixlen)) + { + break; + } + + server = ISC_LIST_NEXT(server, next); + } + + if (server != NULL) { + *retval = server; + res = ISC_R_SUCCESS; + } else { + res = ISC_R_NOTFOUND; + } + + return (res); +} + +isc_result_t +dns_peerlist_currpeer(dns_peerlist_t *peers, dns_peer_t **retval) { + dns_peer_t *p = NULL; + + p = ISC_LIST_TAIL(peers->elements); + + dns_peer_attach(p, retval); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_new(isc_mem_t *mem, const isc_netaddr_t *addr, dns_peer_t **peerptr) { + unsigned int prefixlen = 0; + + REQUIRE(peerptr != NULL); + switch (addr->family) { + case AF_INET: + prefixlen = 32; + break; + case AF_INET6: + prefixlen = 128; + break; + default: + UNREACHABLE(); + } + + return (dns_peer_newprefix(mem, addr, prefixlen, peerptr)); +} + +isc_result_t +dns_peer_newprefix(isc_mem_t *mem, const isc_netaddr_t *addr, + unsigned int prefixlen, dns_peer_t **peerptr) { + dns_peer_t *peer; + + REQUIRE(peerptr != NULL && *peerptr == NULL); + + peer = isc_mem_get(mem, sizeof(*peer)); + + *peer = (dns_peer_t){ + .magic = DNS_PEER_MAGIC, + .address = *addr, + .prefixlen = prefixlen, + .mem = mem, + .transfer_format = dns_one_answer, + }; + + isc_refcount_init(&peer->refs, 1); + + ISC_LINK_INIT(peer, next); + + *peerptr = peer; + + return (ISC_R_SUCCESS); +} + +void +dns_peer_attach(dns_peer_t *source, dns_peer_t **target) { + REQUIRE(DNS_PEER_VALID(source)); + REQUIRE(target != NULL); + REQUIRE(*target == NULL); + + isc_refcount_increment(&source->refs); + + *target = source; +} + +void +dns_peer_detach(dns_peer_t **peer) { + dns_peer_t *p; + + REQUIRE(peer != NULL); + REQUIRE(*peer != NULL); + REQUIRE(DNS_PEER_VALID(*peer)); + + p = *peer; + *peer = NULL; + + if (isc_refcount_decrement(&p->refs) == 1) { + peer_delete(&p); + } +} + +static void +peer_delete(dns_peer_t **peer) { + dns_peer_t *p; + isc_mem_t *mem; + + REQUIRE(peer != NULL); + REQUIRE(DNS_PEER_VALID(*peer)); + + p = *peer; + *peer = NULL; + + isc_refcount_destroy(&p->refs); + + mem = p->mem; + p->mem = NULL; + p->magic = 0; + + if (p->key != NULL) { + dns_name_free(p->key, mem); + isc_mem_put(mem, p->key, sizeof(dns_name_t)); + } + + if (p->query_source != NULL) { + isc_mem_put(mem, p->query_source, sizeof(*p->query_source)); + } + + if (p->notify_source != NULL) { + isc_mem_put(mem, p->notify_source, sizeof(*p->notify_source)); + } + + if (p->transfer_source != NULL) { + isc_mem_put(mem, p->transfer_source, + sizeof(*p->transfer_source)); + } + + isc_mem_put(mem, p, sizeof(*p)); +} + +isc_result_t +dns_peer_setbogus(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags); + + peer->bogus = newval; + DNS_BIT_SET(BOGUS_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getbogus(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(BOGUS_BIT, &peer->bitflags)) { + *retval = peer->bogus; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setprovideixfr(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags); + + peer->provide_ixfr = newval; + DNS_BIT_SET(PROVIDE_IXFR_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getprovideixfr(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(PROVIDE_IXFR_BIT, &peer->bitflags)) { + *retval = peer->provide_ixfr; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setrequestixfr(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags); + + peer->request_ixfr = newval; + DNS_BIT_SET(REQUEST_IXFR_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(REQUEST_IXFR_BIT, &peer->bitflags)) { + *retval = peer->request_ixfr; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setsupportedns(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags); + + peer->support_edns = newval; + DNS_BIT_SET(SUPPORT_EDNS_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getsupportedns(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(SUPPORT_EDNS_BIT, &peer->bitflags)) { + *retval = peer->support_edns; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setrequestnsid(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags); + + peer->request_nsid = newval; + DNS_BIT_SET(REQUEST_NSID_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(REQUEST_NSID_BIT, &peer->bitflags)) { + *retval = peer->request_nsid; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setsendcookie(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags); + + peer->send_cookie = newval; + DNS_BIT_SET(SEND_COOKIE_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getsendcookie(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(SEND_COOKIE_BIT, &peer->bitflags)) { + *retval = peer->send_cookie; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setrequestexpire(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags); + + peer->request_expire = newval; + DNS_BIT_SET(REQUEST_EXPIRE_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getrequestexpire(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(REQUEST_EXPIRE_BIT, &peer->bitflags)) { + *retval = peer->request_expire; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setforcetcp(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags); + + peer->force_tcp = newval; + DNS_BIT_SET(FORCE_TCP_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getforcetcp(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(FORCE_TCP_BIT, &peer->bitflags)) { + *retval = peer->force_tcp; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_settcpkeepalive(dns_peer_t *peer, bool newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags); + + peer->tcp_keepalive = newval; + DNS_BIT_SET(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_gettcpkeepalive(dns_peer_t *peer, bool *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(REQUEST_TCP_KEEPALIVE_BIT, &peer->bitflags)) { + *retval = peer->tcp_keepalive; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_settransfers(dns_peer_t *peer, uint32_t newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags); + + peer->transfers = newval; + DNS_BIT_SET(TRANSFERS_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_gettransfers(dns_peer_t *peer, uint32_t *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(TRANSFERS_BIT, &peer->bitflags)) { + *retval = peer->transfers; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_settransferformat(dns_peer_t *peer, dns_transfer_format_t newval) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags); + + peer->transfer_format = newval; + DNS_BIT_SET(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_gettransferformat(dns_peer_t *peer, dns_transfer_format_t *retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (DNS_BIT_CHECK(SERVER_TRANSFER_FORMAT_BIT, &peer->bitflags)) { + *retval = peer->transfer_format; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_getkey(dns_peer_t *peer, dns_name_t **retval) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(retval != NULL); + + if (peer->key != NULL) { + *retval = peer->key; + } + + return (peer->key == NULL ? ISC_R_NOTFOUND : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_setkey(dns_peer_t *peer, dns_name_t **keyval) { + bool exists = false; + + if (peer->key != NULL) { + dns_name_free(peer->key, peer->mem); + isc_mem_put(peer->mem, peer->key, sizeof(dns_name_t)); + exists = true; + } + + peer->key = *keyval; + *keyval = NULL; + + return (exists ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_setkeybycharp(dns_peer_t *peer, const char *keyval) { + isc_buffer_t b; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + dns_fixedname_init(&fname); + isc_buffer_constinit(&b, keyval, strlen(keyval)); + isc_buffer_add(&b, strlen(keyval)); + result = dns_name_fromtext(dns_fixedname_name(&fname), &b, dns_rootname, + 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + name = isc_mem_get(peer->mem, sizeof(dns_name_t)); + + dns_name_init(name, NULL); + dns_name_dup(dns_fixedname_name(&fname), peer->mem, name); + + result = dns_peer_setkey(peer, &name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(peer->mem, name, sizeof(dns_name_t)); + } + + return (result); +} + +isc_result_t +dns_peer_settransfersource(dns_peer_t *peer, + const isc_sockaddr_t *transfer_source) { + REQUIRE(DNS_PEER_VALID(peer)); + + if (peer->transfer_source != NULL) { + isc_mem_put(peer->mem, peer->transfer_source, + sizeof(*peer->transfer_source)); + peer->transfer_source = NULL; + } + if (transfer_source != NULL) { + peer->transfer_source = + isc_mem_get(peer->mem, sizeof(*peer->transfer_source)); + + *peer->transfer_source = *transfer_source; + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_gettransfersource(dns_peer_t *peer, isc_sockaddr_t *transfer_source) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(transfer_source != NULL); + + if (peer->transfer_source == NULL) { + return (ISC_R_NOTFOUND); + } + *transfer_source = *peer->transfer_source; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_setnotifysource(dns_peer_t *peer, + const isc_sockaddr_t *notify_source) { + REQUIRE(DNS_PEER_VALID(peer)); + + if (peer->notify_source != NULL) { + isc_mem_put(peer->mem, peer->notify_source, + sizeof(*peer->notify_source)); + peer->notify_source = NULL; + } + if (notify_source != NULL) { + peer->notify_source = isc_mem_get(peer->mem, + sizeof(*peer->notify_source)); + + *peer->notify_source = *notify_source; + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getnotifysource(dns_peer_t *peer, isc_sockaddr_t *notify_source) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(notify_source != NULL); + + if (peer->notify_source == NULL) { + return (ISC_R_NOTFOUND); + } + *notify_source = *peer->notify_source; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_setquerysource(dns_peer_t *peer, const isc_sockaddr_t *query_source) { + REQUIRE(DNS_PEER_VALID(peer)); + + if (peer->query_source != NULL) { + isc_mem_put(peer->mem, peer->query_source, + sizeof(*peer->query_source)); + peer->query_source = NULL; + } + if (query_source != NULL) { + peer->query_source = isc_mem_get(peer->mem, + sizeof(*peer->query_source)); + + *peer->query_source = *query_source; + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getquerysource(dns_peer_t *peer, isc_sockaddr_t *query_source) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(query_source != NULL); + + if (peer->query_source == NULL) { + return (ISC_R_NOTFOUND); + } + *query_source = *peer->query_source; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_setudpsize(dns_peer_t *peer, uint16_t udpsize) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags); + + peer->udpsize = udpsize; + DNS_BIT_SET(SERVER_UDPSIZE_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getudpsize(dns_peer_t *peer, uint16_t *udpsize) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(udpsize != NULL); + + if (DNS_BIT_CHECK(SERVER_UDPSIZE_BIT, &peer->bitflags)) { + *udpsize = peer->udpsize; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setmaxudp(dns_peer_t *peer, uint16_t maxudp) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags); + + peer->maxudp = maxudp; + DNS_BIT_SET(SERVER_MAXUDP_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getmaxudp(dns_peer_t *peer, uint16_t *maxudp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(maxudp != NULL); + + if (DNS_BIT_CHECK(SERVER_MAXUDP_BIT, &peer->bitflags)) { + *maxudp = peer->maxudp; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setpadding(dns_peer_t *peer, uint16_t padding) { + bool existed; + + REQUIRE(DNS_PEER_VALID(peer)); + + existed = DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags); + + if (padding > 512) { + padding = 512; + } + peer->padding = padding; + DNS_BIT_SET(SERVER_PADDING_BIT, &peer->bitflags); + + return (existed ? ISC_R_EXISTS : ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getpadding(dns_peer_t *peer, uint16_t *padding) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(padding != NULL); + + if (DNS_BIT_CHECK(SERVER_PADDING_BIT, &peer->bitflags)) { + *padding = peer->padding; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +isc_result_t +dns_peer_setednsversion(dns_peer_t *peer, uint8_t ednsversion) { + REQUIRE(DNS_PEER_VALID(peer)); + + peer->ednsversion = ednsversion; + DNS_BIT_SET(EDNS_VERSION_BIT, &peer->bitflags); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getednsversion(dns_peer_t *peer, uint8_t *ednsversion) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(ednsversion != NULL); + + if (DNS_BIT_CHECK(EDNS_VERSION_BIT, &peer->bitflags)) { + *ednsversion = peer->ednsversion; + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} diff --git a/lib/dns/private.c b/lib/dns/private.c new file mode 100644 index 0000000..56573b3 --- /dev/null +++ b/lib/dns/private.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * We need to build the relevant chain if there exists a NSEC/NSEC3PARAM + * at the apex; normally only one or the other of NSEC/NSEC3PARAM will exist. + * + * If a NSEC3PARAM RRset exists then we will need to build a NSEC chain + * if all the NSEC3PARAM records (and associated chains) are slated for + * destruction and we have not been told to NOT build the NSEC chain. + * + * If the NSEC set exist then check to see if there is a request to create + * a NSEC3 chain. + * + * If neither NSEC/NSEC3PARAM RRsets exist at the origin and the private + * type exists then we need to examine it to determine if NSEC3 chain has + * been requested to be built otherwise a NSEC chain needs to be built. + */ + +#define REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0) +#define CREATE(x) (((x)&DNS_NSEC3FLAG_CREATE) != 0) +#define INITIAL(x) (((x)&DNS_NSEC3FLAG_INITIAL) != 0) +#define NONSEC(x) (((x)&DNS_NSEC3FLAG_NONSEC) != 0) + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/* + * Work out if 'param' should be ignored or not (i.e. it is in the process + * of being removed). + * + * Note: we 'belt-and-braces' here by also checking for a CREATE private + * record and keep the param record in this case. + */ + +static bool +ignore(dns_rdata_t *param, dns_rdataset_t *privateset) { + isc_result_t result; + + for (result = dns_rdataset_first(privateset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(privateset)) + { + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + dns_rdata_t private = DNS_RDATA_INIT; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(privateset, &private); + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + /* + * We are going to create a new NSEC3 chain so it + * doesn't matter if we are removing this one. + */ + if (CREATE(rdata.data[1])) { + return (false); + } + if (rdata.data[0] != param->data[0] || + rdata.data[2] != param->data[2] || + rdata.data[3] != param->data[3] || + rdata.data[4] != param->data[4] || + memcmp(&rdata.data[5], ¶m->data[5], param->data[4])) + { + continue; + } + /* + * The removal of this NSEC3 chain does NOT cause a + * NSEC chain to be created so we don't need to tell + * the caller that it will be removed. + */ + if (NONSEC(rdata.data[1])) { + return (false); + } + return (true); + } + return (false); +} + +isc_result_t +dns_private_chains(dns_db_t *db, dns_dbversion_t *ver, + dns_rdatatype_t privatetype, bool *build_nsec, + bool *build_nsec3) { + dns_dbnode_t *node; + dns_rdataset_t nsecset, nsec3paramset, privateset; + bool nsec3chain; + bool signing; + isc_result_t result; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + unsigned int count; + + node = NULL; + dns_rdataset_init(&nsecset); + dns_rdataset_init(&nsec3paramset); + dns_rdataset_init(&privateset); + + CHECK(dns_db_getoriginnode(db, &node)); + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, + (isc_stdtime_t)0, &nsecset, NULL); + + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, + (isc_stdtime_t)0, &nsec3paramset, NULL); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + if (dns_rdataset_isassociated(&nsecset) && + dns_rdataset_isassociated(&nsec3paramset)) + { + if (build_nsec != NULL) { + *build_nsec = true; + } + if (build_nsec3 != NULL) { + *build_nsec3 = true; + } + goto success; + } + + if (privatetype != (dns_rdatatype_t)0) { + result = dns_db_findrdataset(db, node, ver, privatetype, 0, + (isc_stdtime_t)0, &privateset, + NULL); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + } + + /* + * Look to see if we also need to be creating a NSEC3 chain. + */ + if (dns_rdataset_isassociated(&nsecset)) { + if (build_nsec != NULL) { + *build_nsec = true; + } + if (build_nsec3 != NULL) { + *build_nsec3 = false; + } + if (!dns_rdataset_isassociated(&privateset)) { + goto success; + } + for (result = dns_rdataset_first(&privateset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&privateset)) + { + dns_rdata_t private = DNS_RDATA_INIT; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&privateset, &private); + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + if (REMOVE(rdata.data[1])) { + continue; + } + if (build_nsec3 != NULL) { + *build_nsec3 = true; + } + break; + } + goto success; + } + + if (dns_rdataset_isassociated(&nsec3paramset)) { + if (build_nsec3 != NULL) { + *build_nsec3 = true; + } + if (build_nsec != NULL) { + *build_nsec = false; + } + if (!dns_rdataset_isassociated(&privateset)) { + goto success; + } + /* + * If we are in the process of building a new NSEC3 chain + * then we don't need to build a NSEC chain. + */ + for (result = dns_rdataset_first(&privateset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&privateset)) + { + dns_rdata_t private = DNS_RDATA_INIT; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&privateset, &private); + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + if (CREATE(rdata.data[1])) { + goto success; + } + } + + /* + * Check to see if there will be a active NSEC3CHAIN once + * the changes queued complete. + */ + count = 0; + for (result = dns_rdataset_first(&nsec3paramset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&nsec3paramset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * If there is more that one NSEC3 chain present then + * we don't need to construct a NSEC chain. + */ + if (++count > 1) { + goto success; + } + dns_rdataset_current(&nsec3paramset, &rdata); + if (ignore(&rdata, &privateset)) { + continue; + } + /* + * We still have a good NSEC3 chain or we are + * not creating a NSEC chain as NONSEC is set. + */ + goto success; + } + + /* + * The last NSEC3 chain is being removed and does not have + * have NONSEC set. + */ + if (build_nsec != NULL) { + *build_nsec = true; + } + goto success; + } + + if (build_nsec != NULL) { + *build_nsec = false; + } + if (build_nsec3 != NULL) { + *build_nsec3 = false; + } + if (!dns_rdataset_isassociated(&privateset)) { + goto success; + } + + signing = false; + nsec3chain = false; + + for (result = dns_rdataset_first(&privateset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&privateset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_t private = DNS_RDATA_INIT; + + dns_rdataset_current(&privateset, &private); + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + /* + * Look for record that says we are signing the + * zone with a key. + */ + if (private.length == 5 && private.data[0] != 0 && + private.data[3] == 0 && private.data[4] == 0) + { + signing = true; + } + } else { + if (CREATE(rdata.data[1])) { + nsec3chain = true; + } + } + } + + if (signing) { + if (nsec3chain) { + if (build_nsec3 != NULL) { + *build_nsec3 = true; + } + } else { + if (build_nsec != NULL) { + *build_nsec = true; + } + } + } + +success: + result = ISC_R_SUCCESS; +failure: + if (dns_rdataset_isassociated(&nsecset)) { + dns_rdataset_disassociate(&nsecset); + } + if (dns_rdataset_isassociated(&nsec3paramset)) { + dns_rdataset_disassociate(&nsec3paramset); + } + if (dns_rdataset_isassociated(&privateset)) { + dns_rdataset_disassociate(&privateset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +isc_result_t +dns_private_totext(dns_rdata_t *private, isc_buffer_t *buf) { + isc_result_t result; + + if (private->length < 5) { + return (ISC_R_NOTFOUND); + } + + if (private->data[0] == 0) { + unsigned char nsec3buf[DNS_NSEC3PARAM_BUFFERSIZE]; + unsigned char newbuf[DNS_NSEC3PARAM_BUFFERSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t nsec3param; + bool del, init, nonsec; + isc_buffer_t b; + + if (!dns_nsec3param_fromprivate(private, &rdata, nsec3buf, + sizeof(nsec3buf))) + { + CHECK(ISC_R_FAILURE); + } + + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + del = ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0); + init = ((nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0); + nonsec = ((nsec3param.flags & DNS_NSEC3FLAG_NONSEC) != 0); + + nsec3param.flags &= + ~(DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_REMOVE | + DNS_NSEC3FLAG_INITIAL | DNS_NSEC3FLAG_NONSEC); + + if (init) { + isc_buffer_putstr(buf, "Pending NSEC3 chain "); + } else if (del) { + isc_buffer_putstr(buf, "Removing NSEC3 chain "); + } else { + isc_buffer_putstr(buf, "Creating NSEC3 chain "); + } + + dns_rdata_reset(&rdata); + isc_buffer_init(&b, newbuf, sizeof(newbuf)); + CHECK(dns_rdata_fromstruct(&rdata, dns_rdataclass_in, + dns_rdatatype_nsec3param, + &nsec3param, &b)); + + CHECK(dns_rdata_totext(&rdata, NULL, buf)); + + if (del && !nonsec) { + isc_buffer_putstr(buf, " / creating NSEC chain"); + } + } else if (private->length == 5) { + unsigned char alg = private->data[0]; + dns_keytag_t keyid = (private->data[2] | private->data[1] << 8); + char keybuf[DNS_SECALG_FORMATSIZE + BUFSIZ], + algbuf[DNS_SECALG_FORMATSIZE]; + bool del = private->data[3]; + bool complete = private->data[4]; + + if (del && complete) { + isc_buffer_putstr(buf, "Done removing signatures for "); + } else if (del) { + isc_buffer_putstr(buf, "Removing signatures for "); + } else if (complete) { + isc_buffer_putstr(buf, "Done signing with "); + } else { + isc_buffer_putstr(buf, "Signing with "); + } + + dns_secalg_format(alg, algbuf, sizeof(algbuf)); + snprintf(keybuf, sizeof(keybuf), "key %d/%s", keyid, algbuf); + isc_buffer_putstr(buf, keybuf); + } else { + return (ISC_R_NOTFOUND); + } + + isc_buffer_putuint8(buf, 0); + result = ISC_R_SUCCESS; +failure: + return (result); +} diff --git a/lib/dns/rbt.c b/lib/dns/rbt.c new file mode 100644 index 0000000..57dcd54 --- /dev/null +++ b/lib/dns/rbt.c @@ -0,0 +1,3124 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*% + * This define is so dns/name.h (included by dns/fixedname.h) uses more + * efficient macro calls instead of functions for a few operations. + */ +#define DNS_NAME_USEINLINE 1 + +#include + +#include + +#include +#include +#include + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +#define RBT_MAGIC ISC_MAGIC('R', 'B', 'T', '+') +#define VALID_RBT(rbt) ISC_MAGIC_VALID(rbt, RBT_MAGIC) + +/* + * XXXDCL Since parent pointers were added in again, I could remove all of the + * chain junk, and replace with dns_rbt_firstnode, _previousnode, _nextnode, + * _lastnode. This would involve pretty major change to the API. + */ +#define CHAIN_MAGIC ISC_MAGIC('0', '-', '0', '-') +#define VALID_CHAIN(chain) ISC_MAGIC_VALID(chain, CHAIN_MAGIC) + +#define RBT_HASH_NO_BITS 0 +#define RBT_HASH_MIN_BITS 4 +#define RBT_HASH_MAX_BITS 32 +#define RBT_HASH_OVERCOMMIT 3 + +#define RBT_HASH_NEXTTABLE(hindex) ((hindex == 0) ? 1 : 0) + +#define GOLDEN_RATIO_32 0x61C88647 + +#define HASHSIZE(bits) (UINT64_C(1) << (bits)) + +static uint32_t +hash_32(uint32_t val, unsigned int bits) { + REQUIRE(bits <= RBT_HASH_MAX_BITS); + /* High bits are more random. */ + return (val * GOLDEN_RATIO_32 >> (32 - bits)); +} + +struct dns_rbt { + unsigned int magic; + isc_mem_t *mctx; + dns_rbtnode_t *root; + void (*data_deleter)(void *, void *); + void *deleter_arg; + unsigned int nodecount; + uint8_t hashbits[2]; + dns_rbtnode_t **hashtable[2]; + uint8_t hindex; + uint32_t hiter; +}; + +#define RED 0 +#define BLACK 1 + +/*% + * Elements of the rbtnode structure. + */ +#define PARENT(node) ((node)->parent) +#define LEFT(node) ((node)->left) +#define RIGHT(node) ((node)->right) +#define DOWN(node) ((node)->down) +#define UPPERNODE(node) ((node)->uppernode) +#define DATA(node) ((node)->data) +#define IS_EMPTY(node) ((node)->data == NULL) +#define HASHNEXT(node) ((node)->hashnext) +#define HASHVAL(node) ((node)->hashval) +#define COLOR(node) ((node)->color) +#define NAMELEN(node) ((node)->namelen) +#define OLDNAMELEN(node) ((node)->oldnamelen) +#define OFFSETLEN(node) ((node)->offsetlen) +#define ATTRS(node) ((node)->attributes) +#define IS_ROOT(node) ((node)->is_root) +#define FINDCALLBACK(node) ((node)->find_callback) + +#define WANTEMPTYDATA_OR_DATA(options, node) \ + ((options & DNS_RBTFIND_EMPTYDATA) != 0 || DATA(node) != NULL) + +/*% + * Structure elements from the rbtdb.c, not + * used as part of the rbt.c algorithms. + */ +#define DIRTY(node) ((node)->dirty) +#define WILD(node) ((node)->wild) +#define LOCKNUM(node) ((node)->locknum) + +/*% + * The variable length stuff stored after the node has the following + * structure. + * + * <name_data>{1..255}<oldoffsetlen>{1}<offsets>{1..128} + * + * <name_data> contains the name of the node when it was created. + * <oldoffsetlen> contains the length of <offsets> when the node + * was created. + * <offsets> contains the offsets into name for each label when the node + * was created. + */ + +#define NAME(node) ((unsigned char *)((node) + 1)) +#define OFFSETS(node) (NAME(node) + OLDNAMELEN(node) + 1) +#define OLDOFFSETLEN(node) (OFFSETS(node)[-1]) + +#define NODE_SIZE(node) \ + (sizeof(*node) + OLDNAMELEN(node) + OLDOFFSETLEN(node) + 1) + +/*% + * Color management. + */ +#define IS_RED(node) ((node) != NULL && (node)->color == RED) +#define IS_BLACK(node) ((node) == NULL || (node)->color == BLACK) +#define MAKE_RED(node) ((node)->color = RED) +#define MAKE_BLACK(node) ((node)->color = BLACK) + +/*% + * Chain management. + * + * The "ancestors" member of chains were removed, with their job now + * being wholly handled by parent pointers (which didn't exist, because + * of memory concerns, when chains were first implemented). + */ +#define ADD_LEVEL(chain, node) \ + do { \ + INSIST((chain)->level_count < DNS_RBT_LEVELBLOCK); \ + (chain)->levels[(chain)->level_count++] = (node); \ + } while (0) + +/*% + * The following macros directly access normally private name variables. + * These macros are used to avoid a lot of function calls in the critical + * path of the tree traversal code. + */ + +static void +NODENAME(dns_rbtnode_t *node, dns_name_t *name) { + name->length = NAMELEN(node); + name->labels = OFFSETLEN(node); + name->ndata = NAME(node); + name->offsets = OFFSETS(node); + name->attributes = ATTRS(node); + name->attributes |= DNS_NAMEATTR_READONLY; +} + +#ifdef DEBUG +/* + * A little something to help out in GDB. + */ +dns_name_t +Name(dns_rbtnode_t *node); +dns_name_t +Name(dns_rbtnode_t *node) { + dns_name_t name; + + dns_name_init(&name, NULL); + if (node != NULL) { + NODENAME(node, &name); + } + + return (name); +} +#endif /* DEBUG */ + +/* + * Upper node is the parent of the root of the passed node's + * subtree. The passed node must not be NULL. + */ +static dns_rbtnode_t * +get_upper_node(dns_rbtnode_t *node) { + return (UPPERNODE(node)); +} + +size_t +dns__rbtnode_getdistance(dns_rbtnode_t *node) { + size_t nodes = 1; + + while (node != NULL) { + if (IS_ROOT(node)) { + break; + } + nodes++; + node = PARENT(node); + } + + return (nodes); +} + +/* + * Forward declarations. + */ +static isc_result_t +create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep); + +static void +hashtable_new(dns_rbt_t *rbt, uint8_t index, uint8_t bits); +static void +hashtable_free(dns_rbt_t *rbt, uint8_t index); + +static void +hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name); + +static void +unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node); + +static uint32_t +rehash_bits(dns_rbt_t *rbt, size_t newcount); +static void +hashtable_rehash(dns_rbt_t *rbt, uint32_t newbits); +static void +hashtable_rehash_one(dns_rbt_t *rbt); +static void +maybe_rehash(dns_rbt_t *rbt, size_t size); +static bool +rehashing_in_progress(dns_rbt_t *rbt); + +#define TRY_NEXTTABLE(hindex, rbt) \ + (hindex == rbt->hindex && rehashing_in_progress(rbt)) + +static void +rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp); +static void +rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp); + +static void +addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order, + dns_rbtnode_t **rootp); + +static void +deletefromlevel(dns_rbtnode_t *item, dns_rbtnode_t **rootp); + +static void +deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash, + dns_rbtnode_t **nodep); + +static void +printnodename(dns_rbtnode_t *node, bool quoted, FILE *f); + +static void +freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep); + +unsigned int +dns__rbtnode_namelen(dns_rbtnode_t *node) { + dns_name_t current; + unsigned int len = 0; + + REQUIRE(DNS_RBTNODE_VALID(node)); + + dns_name_init(¤t, NULL); + + do { + if (node != NULL) { + NODENAME(node, ¤t); + len += current.length; + } else { + len += 1; + break; + } + + node = get_upper_node(node); + } while (!dns_name_isabsolute(¤t)); + + return (len); +} + +/* + * Initialize a red/black tree of trees. + */ +isc_result_t +dns_rbt_create(isc_mem_t *mctx, dns_rbtdeleter_t deleter, void *deleter_arg, + dns_rbt_t **rbtp) { + dns_rbt_t *rbt; + + REQUIRE(mctx != NULL); + REQUIRE(rbtp != NULL && *rbtp == NULL); + REQUIRE(deleter == NULL ? deleter_arg == NULL : 1); + + rbt = isc_mem_get(mctx, sizeof(*rbt)); + *rbt = (dns_rbt_t){ + .data_deleter = deleter, + .deleter_arg = deleter_arg, + }; + + isc_mem_attach(mctx, &rbt->mctx); + + hashtable_new(rbt, 0, RBT_HASH_MIN_BITS); + + rbt->magic = RBT_MAGIC; + + *rbtp = rbt; + + return (ISC_R_SUCCESS); +} + +/* + * Deallocate a red/black tree of trees. + */ +void +dns_rbt_destroy(dns_rbt_t **rbtp) { + RUNTIME_CHECK(dns_rbt_destroy2(rbtp, 0) == ISC_R_SUCCESS); +} + +isc_result_t +dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum) { + dns_rbt_t *rbt; + + REQUIRE(rbtp != NULL && VALID_RBT(*rbtp)); + + rbt = *rbtp; + + deletetreeflat(rbt, quantum, false, &rbt->root); + if (rbt->root != NULL) { + return (ISC_R_QUOTA); + } + + *rbtp = NULL; + + INSIST(rbt->nodecount == 0); + + if (rbt->hashtable[0] != NULL) { + hashtable_free(rbt, 0); + } + if (rbt->hashtable[1] != NULL) { + hashtable_free(rbt, 1); + } + + rbt->magic = 0; + + isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt)); + return (ISC_R_SUCCESS); +} + +unsigned int +dns_rbt_nodecount(dns_rbt_t *rbt) { + REQUIRE(VALID_RBT(rbt)); + + return (rbt->nodecount); +} + +size_t +dns_rbt_hashsize(dns_rbt_t *rbt) { + REQUIRE(VALID_RBT(rbt)); + + uint8_t hashbits = (rbt->hashbits[0] > rbt->hashbits[1]) + ? rbt->hashbits[0] + : rbt->hashbits[1]; + + return (1 << hashbits); +} + +static isc_result_t +chain_name(dns_rbtnodechain_t *chain, dns_name_t *name, + bool include_chain_end) { + dns_name_t nodename; + isc_result_t result = ISC_R_SUCCESS; + int i; + + dns_name_init(&nodename, NULL); + + if (include_chain_end && chain->end != NULL) { + NODENAME(chain->end, &nodename); + dns_name_copy(&nodename, name); + } else { + dns_name_reset(name); + } + + for (i = (int)chain->level_count - 1; i >= 0; i--) { + NODENAME(chain->levels[i], &nodename); + result = dns_name_concatenate(name, &nodename, name, NULL); + + if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (result); +} + +static isc_result_t +move_chain_to_last(dns_rbtnodechain_t *chain, dns_rbtnode_t *node) { + do { + /* + * Go as far right and then down as much as possible, + * as long as the rightmost node has a down pointer. + */ + while (RIGHT(node) != NULL) { + node = RIGHT(node); + } + + if (DOWN(node) == NULL) { + break; + } + + ADD_LEVEL(chain, node); + node = DOWN(node); + } while (1); + + chain->end = node; + + return (ISC_R_SUCCESS); +} + +/* + * Add 'name' to tree, initializing its data pointer with 'data'. + */ + +isc_result_t +dns_rbt_addnode(dns_rbt_t *rbt, const dns_name_t *name, dns_rbtnode_t **nodep) { + /* + * Does this thing have too many variables or what? + */ + dns_rbtnode_t **root, *parent, *child, *current, *new_current; + dns_name_t *add_name, *new_name, current_name, *prefix, *suffix; + dns_fixedname_t fixedcopy, fixedprefix, fixedsuffix, fnewname; + dns_offsets_t current_offsets; + dns_namereln_t compared; + isc_result_t result = ISC_R_SUCCESS; + unsigned int level_count; + unsigned int common_labels; + unsigned int nlabels, hlabels; + int order; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(nodep != NULL && *nodep == NULL); + + /* + * Dear future BIND developer, + * + * After you have tried attempting to optimize this routine by + * using the hashtable and have realized your folly, please + * append another cross ("X") below as a warning to the next + * future BIND developer: + * + * Number of victim developers: X + * + * I wish the past developer had included such a notice. + * + * Long form: Unlike dns_rbt_findnode(), this function does not + * lend itself to be optimized using the hashtable: + * + * 1. In the subtree where the insertion occurs, this function + * needs to have the insertion point and the order where the + * lookup terminated (i.e., at the insertion point where left or + * right child is NULL). This cannot be determined from the + * hashtable, so at least in that subtree, a BST O(log N) lookup + * is necessary. + * + * 2. Our RBT nodes contain not only single labels but label + * sequences to optimize space usage. So at every level, we have + * to look for a match in the hashtable for all superdomains in + * the rest of the name we're searching. This is an O(N) + * operation at least, here N being the label size of name, each + * of which is a hashtable lookup involving dns_name_equal() + * comparisons. + */ + + /* + * Create a copy of the name so the original name structure is + * not modified. + */ + add_name = dns_fixedname_initname(&fixedcopy); + INSIST(add_name != NULL); + dns_name_clone(name, add_name); + + if (rbt->root == NULL) { + result = create_node(rbt->mctx, add_name, &new_current); + if (result == ISC_R_SUCCESS) { + rbt->nodecount++; + new_current->is_root = 1; + + UPPERNODE(new_current) = NULL; + + rbt->root = new_current; + *nodep = new_current; + hash_node(rbt, new_current, name); + } + return (result); + } + + level_count = 0; + + prefix = dns_fixedname_initname(&fixedprefix); + suffix = dns_fixedname_initname(&fixedsuffix); + + INSIST(prefix != NULL); + INSIST(suffix != NULL); + + root = &rbt->root; + INSIST(IS_ROOT(*root)); + parent = NULL; + current = NULL; + child = *root; + dns_name_init(¤t_name, current_offsets); + new_name = dns_fixedname_initname(&fnewname); + nlabels = dns_name_countlabels(name); + hlabels = 0; + + do { + current = child; + + NODENAME(current, ¤t_name); + compared = dns_name_fullcompare(add_name, ¤t_name, &order, + &common_labels); + + if (compared == dns_namereln_equal) { + *nodep = current; + result = ISC_R_EXISTS; + break; + } + + if (compared == dns_namereln_none) { + if (order < 0) { + parent = current; + child = LEFT(current); + } else if (order > 0) { + parent = current; + child = RIGHT(current); + } + } else { + /* + * This name has some suffix in common with the + * name at the current node. If the name at + * the current node is shorter, that means the + * new name should be in a subtree. If the + * name at the current node is longer, that means + * the down pointer to this tree should point + * to a new tree that has the common suffix, and + * the non-common parts of these two names should + * start a new tree. + */ + hlabels += common_labels; + if (compared == dns_namereln_subdomain) { + /* + * All of the existing labels are in common, + * so the new name is in a subtree. + * Whack off the common labels for the + * not-in-common part to be searched for + * in the next level. + */ + dns_name_split(add_name, common_labels, + add_name, NULL); + + /* + * Follow the down pointer (possibly NULL). + */ + root = &DOWN(current); + + INSIST(*root == NULL || + (IS_ROOT(*root) && + PARENT(*root) == current)); + + parent = NULL; + child = DOWN(current); + + INSIST(level_count < DNS_RBT_LEVELBLOCK); + level_count++; + } else { + /* + * The number of labels in common is fewer + * than the number of labels at the current + * node, so the current node must be adjusted + * to have just the common suffix, and a down + * pointer made to a new tree. + */ + + INSIST(compared == + dns_namereln_commonancestor || + compared == dns_namereln_contains); + + /* + * Ensure the number of levels in the tree + * does not exceed the number of logical + * levels allowed by DNSSEC. + * + * XXXDCL need a better error result? + */ + if (level_count >= DNS_RBT_LEVELBLOCK) { + result = ISC_R_NOSPACE; + break; + } + + /* + * Split the name into two parts, a prefix + * which is the not-in-common parts of the + * two names and a suffix that is the common + * parts of them. + */ + dns_name_split(¤t_name, common_labels, + prefix, suffix); + result = create_node(rbt->mctx, suffix, + &new_current); + + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Reproduce the tree attributes of the + * current node. + */ + new_current->is_root = current->is_root; + if (current->nsec == DNS_RBT_NSEC_HAS_NSEC) { + new_current->nsec = DNS_RBT_NSEC_NORMAL; + } else { + new_current->nsec = current->nsec; + } + PARENT(new_current) = PARENT(current); + LEFT(new_current) = LEFT(current); + RIGHT(new_current) = RIGHT(current); + COLOR(new_current) = COLOR(current); + + /* + * Fix pointers that were to the current node. + */ + if (parent != NULL) { + if (LEFT(parent) == current) { + LEFT(parent) = new_current; + } else { + RIGHT(parent) = new_current; + } + } + if (LEFT(new_current) != NULL) { + PARENT(LEFT(new_current)) = new_current; + } + if (RIGHT(new_current) != NULL) { + PARENT(RIGHT(new_current)) = + new_current; + } + if (*root == current) { + *root = new_current; + } + + NAMELEN(current) = prefix->length; + OFFSETLEN(current) = prefix->labels; + + /* + * Set up the new root of the next level. + * By definition it will not be the top + * level tree, so clear DNS_NAMEATTR_ABSOLUTE. + */ + current->is_root = 1; + PARENT(current) = new_current; + DOWN(new_current) = current; + root = &DOWN(new_current); + + UPPERNODE(new_current) = UPPERNODE(current); + UPPERNODE(current) = new_current; + + INSIST(level_count < DNS_RBT_LEVELBLOCK); + level_count++; + + LEFT(current) = NULL; + RIGHT(current) = NULL; + + MAKE_BLACK(current); + ATTRS(current) &= ~DNS_NAMEATTR_ABSOLUTE; + + rbt->nodecount++; + dns_name_getlabelsequence(name, + nlabels - hlabels, + hlabels, new_name); + hash_node(rbt, new_current, new_name); + + if (common_labels == + dns_name_countlabels(add_name)) + { + /* + * The name has been added by pushing + * the not-in-common parts down to + * a new level. + */ + *nodep = new_current; + return (ISC_R_SUCCESS); + } else { + /* + * The current node has no data, + * because it is just a placeholder. + * Its data pointer is already NULL + * from create_node()), so there's + * nothing more to do to it. + */ + + /* + * The not-in-common parts of the new + * name will be inserted into the new + * level following this loop (unless + * result != ISC_R_SUCCESS, which + * is tested after the loop ends). + */ + dns_name_split(add_name, common_labels, + add_name, NULL); + + break; + } + } + } + } while (child != NULL); + + if (result == ISC_R_SUCCESS) { + result = create_node(rbt->mctx, add_name, &new_current); + } + + if (result == ISC_R_SUCCESS) { + if (*root == NULL) { + UPPERNODE(new_current) = current; + } else { + UPPERNODE(new_current) = PARENT(*root); + } + + addonlevel(new_current, current, order, root); + rbt->nodecount++; + *nodep = new_current; + hash_node(rbt, new_current, name); + } + + return (result); +} + +/* + * Add a name to the tree of trees, associating it with some data. + */ +isc_result_t +dns_rbt_addname(dns_rbt_t *rbt, const dns_name_t *name, void *data) { + isc_result_t result; + dns_rbtnode_t *node; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(dns_name_isabsolute(name)); + + node = NULL; + + result = dns_rbt_addnode(rbt, name, &node); + + /* + * dns_rbt_addnode will report the node exists even when + * it does not have data associated with it, but the + * dns_rbt_*name functions all behave depending on whether + * there is data associated with a node. + */ + if (result == ISC_R_SUCCESS || + (result == ISC_R_EXISTS && DATA(node) == NULL)) + { + DATA(node) = data; + result = ISC_R_SUCCESS; + } + + return (result); +} + +/* + * Find the node for "name" in the tree of trees. + */ +isc_result_t +dns_rbt_findnode(dns_rbt_t *rbt, const dns_name_t *name, dns_name_t *foundname, + dns_rbtnode_t **node, dns_rbtnodechain_t *chain, + unsigned int options, dns_rbtfindcallback_t callback, + void *callback_arg) { + dns_rbtnode_t *current, *last_compared; + dns_rbtnodechain_t localchain; + dns_name_t *search_name, current_name, *callback_name; + dns_fixedname_t fixedcallbackname, fixedsearchname; + dns_namereln_t compared; + isc_result_t result, saved_result; + unsigned int common_labels; + unsigned int hlabels = 0; + int order; + uint8_t hindex; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(node != NULL && *node == NULL); + REQUIRE((options & (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR)) != + (DNS_RBTFIND_NOEXACT | DNS_RBTFIND_NOPREDECESSOR)); + + /* + * If there is a chain it needs to appear to be in a sane state, + * otherwise a chain is still needed to generate foundname and + * callback_name. + */ + if (chain == NULL) { + options |= DNS_RBTFIND_NOPREDECESSOR; + chain = &localchain; + dns_rbtnodechain_init(chain); + } else { + dns_rbtnodechain_reset(chain); + } + + if (rbt->root == NULL) { + return (ISC_R_NOTFOUND); + } + + /* + * Appease GCC about variables it incorrectly thinks are + * possibly used uninitialized. + */ + compared = dns_namereln_none; + last_compared = NULL; + order = 0; + + callback_name = dns_fixedname_initname(&fixedcallbackname); + + /* + * search_name is the name segment being sought in each tree level. + * By using a fixedname, the search_name will definitely have offsets + * for use by any splitting. + * By using dns_name_clone, no name data should be copied thanks to + * the lack of bitstring labels. + */ + search_name = dns_fixedname_initname(&fixedsearchname); + INSIST(search_name != NULL); + dns_name_clone(name, search_name); + + dns_name_init(¤t_name, NULL); + + saved_result = ISC_R_SUCCESS; + current = rbt->root; + + while (current != NULL) { + NODENAME(current, ¤t_name); + compared = dns_name_fullcompare(search_name, ¤t_name, + &order, &common_labels); + /* + * last_compared is used as a shortcut to start (or + * continue rather) finding the stop-node of the search + * when hashing was used (see much below in this + * function). + */ + last_compared = current; + + if (compared == dns_namereln_equal) { + break; + } + + if (compared == dns_namereln_none) { + /* + * Here, current is pointing at a subtree root + * node. We try to find a matching node using + * the hashtable. We can get one of 3 results + * here: (a) we locate the matching node, (b) we + * find a node to which the current node has a + * subdomain relation, (c) we fail to find (a) + * or (b). + */ + + dns_name_t hash_name; + dns_rbtnode_t *hnode; + dns_rbtnode_t *up_current; + unsigned int nlabels; + unsigned int tlabels = 1; + uint32_t hashval; + uint32_t hash; + + /* + * The case of current not being a subtree root, + * that means a left or right pointer was + * followed, only happens when the algorithm + * fell through to the traditional binary search + * because of a bitstring label. Since we + * dropped the bitstring support, this should + * not happen. + */ + INSIST(IS_ROOT(current)); + + nlabels = dns_name_countlabels(search_name); + + /* + * current is the root of the current level, so + * its parent is the same as its "up" pointer. + */ + up_current = PARENT(current); + dns_name_init(&hash_name, NULL); + + hashagain: + hindex = rbt->hindex; + /* + * Compute the hash over the full absolute + * name. Look for the smallest suffix match at + * this tree level (hlevel), and then at every + * iteration, look for the next smallest suffix + * match (add another subdomain label to the + * absolute name being hashed). + */ + dns_name_getlabelsequence(name, nlabels - tlabels, + hlabels + tlabels, + &hash_name); + hashval = dns_name_fullhash(&hash_name, false); + + dns_name_getlabelsequence(search_name, + nlabels - tlabels, tlabels, + &hash_name); + + nexttable: + /* + * Walk all the nodes in the hash bucket pointed + * by the computed hash value. + */ + + hash = hash_32(hashval, rbt->hashbits[hindex]); + + for (hnode = rbt->hashtable[hindex][hash]; + hnode != NULL; hnode = HASHNEXT(hnode)) + { + dns_name_t hnode_name; + + if (hashval != HASHVAL(hnode)) { + continue; + } + /* + * This checks that the hashed label sequence + * being looked up is at the same tree level, so + * that we don't match a labelsequence from some + * other subdomain. + */ + if (get_upper_node(hnode) != up_current) { + continue; + } + + dns_name_init(&hnode_name, NULL); + NODENAME(hnode, &hnode_name); + if (dns_name_equal(&hnode_name, &hash_name)) { + break; + } + } + + if (hnode != NULL) { + current = hnode; + /* + * This is an optimization. If hashing found + * the right node, the next call to + * dns_name_fullcompare() would obviously + * return _equal or _subdomain. Determine + * which of those would be the case by + * checking if the full name was hashed. Then + * make it look like dns_name_fullcompare + * was called and jump to the right place. + */ + if (tlabels == nlabels) { + compared = dns_namereln_equal; + break; + } else { + common_labels = tlabels; + compared = dns_namereln_subdomain; + goto subdomain; + } + } + + if (TRY_NEXTTABLE(hindex, rbt)) { + /* + * Rehashing in progress, check the other table + */ + hindex = RBT_HASH_NEXTTABLE(rbt->hindex); + goto nexttable; + } + + if (tlabels++ < nlabels) { + goto hashagain; + } + + /* + * All of the labels have been tried against the hash + * table. Since we dropped the support of bitstring + * labels, the name isn't in the table. + */ + current = NULL; + continue; + } else { + /* + * The names have some common suffix labels. + * + * If the number in common are equal in length to + * the current node's name length, then follow the + * down pointer and search in the new tree. + */ + if (compared == dns_namereln_subdomain) { + subdomain: + /* + * Whack off the current node's common parts + * for the name to search in the next level. + */ + dns_name_split(search_name, common_labels, + search_name, NULL); + hlabels += common_labels; + /* + * This might be the closest enclosing name. + */ + if (WANTEMPTYDATA_OR_DATA(options, current)) { + *node = current; + } + + /* + * Point the chain to the next level. This + * needs to be done before 'current' is pointed + * there because the callback in the next + * block of code needs the current 'current', + * but in the event the callback requests that + * the search be stopped then the + * DNS_R_PARTIALMATCH code at the end of this + * function needs the chain pointed to the + * next level. + */ + ADD_LEVEL(chain, current); + + /* + * The caller may want to interrupt the + * downward search when certain special nodes + * are traversed. If this is a special node, + * the callback is used to learn what the + * caller wants to do. + */ + if (callback != NULL && FINDCALLBACK(current)) { + result = chain_name( + chain, callback_name, false); + if (result != ISC_R_SUCCESS) { + dns_rbtnodechain_reset(chain); + return (result); + } + + result = (callback)(current, + callback_name, + callback_arg); + if (result != DNS_R_CONTINUE) { + saved_result = result; + /* + * Treat this node as if it + * had no down pointer. + */ + current = NULL; + break; + } + } + + /* + * Finally, head to the next tree level. + */ + current = DOWN(current); + } else { + /* + * Though there are labels in common, the + * entire name at this node is not common + * with the search name so the search + * name does not exist in the tree. + */ + INSIST(compared == + dns_namereln_commonancestor || + compared == dns_namereln_contains); + + current = NULL; + } + } + } + + /* + * If current is not NULL, NOEXACT is not disallowing exact matches, + * and either the node has data or an empty node is ok, return + * ISC_R_SUCCESS to indicate an exact match. + */ + if (current != NULL && (options & DNS_RBTFIND_NOEXACT) == 0 && + WANTEMPTYDATA_OR_DATA(options, current)) + { + /* + * Found an exact match. + */ + chain->end = current; + chain->level_matches = chain->level_count; + + if (foundname != NULL) { + result = chain_name(chain, foundname, true); + } else { + result = ISC_R_SUCCESS; + } + + if (result == ISC_R_SUCCESS) { + *node = current; + result = saved_result; + } else { + *node = NULL; + } + } else { + /* + * Did not find an exact match (or did not want one). + */ + if (*node != NULL) { + /* + * ... but found a partially matching superdomain. + * Unwind the chain to the partial match node + * to set level_matches to the level above the node, + * and then to derive the name. + * + * chain->level_count is guaranteed to be at least 1 + * here because by definition of finding a superdomain, + * the chain is pointed to at least the first subtree. + */ + chain->level_matches = chain->level_count - 1; + + while (chain->levels[chain->level_matches] != *node) { + INSIST(chain->level_matches > 0); + chain->level_matches--; + } + + if (foundname != NULL) { + unsigned int saved_count = chain->level_count; + + chain->level_count = chain->level_matches + 1; + + result = chain_name(chain, foundname, false); + + chain->level_count = saved_count; + } else { + result = ISC_R_SUCCESS; + } + + if (result == ISC_R_SUCCESS) { + result = DNS_R_PARTIALMATCH; + } + } else { + result = ISC_R_NOTFOUND; + } + + if (current != NULL) { + /* + * There was an exact match but either + * DNS_RBTFIND_NOEXACT was set, or + * DNS_RBTFIND_EMPTYDATA was set and the node had no + * data. A policy decision was made to set the + * chain to the exact match, but this is subject + * to change if it becomes apparent that something + * else would be more useful. It is important that + * this case is handled here, because the predecessor + * setting code below assumes the match was not exact. + */ + INSIST(((options & DNS_RBTFIND_NOEXACT) != 0) || + ((options & DNS_RBTFIND_EMPTYDATA) == 0 && + DATA(current) == NULL)); + chain->end = current; + } else if ((options & DNS_RBTFIND_NOPREDECESSOR) != 0) { + /* + * Ensure the chain points nowhere. + */ + chain->end = NULL; + } else { + /* + * Since there was no exact match, the chain argument + * needs to be pointed at the DNSSEC predecessor of + * the search name. + */ + if (compared == dns_namereln_subdomain) { + /* + * Attempted to follow a down pointer that was + * NULL, which means the searched for name was + * a subdomain of a terminal name in the tree. + * Since there are no existing subdomains to + * order against, the terminal name is the + * predecessor. + */ + INSIST(chain->level_count > 0); + INSIST(chain->level_matches < + chain->level_count); + chain->end = + chain->levels[--chain->level_count]; + } else { + isc_result_t result2; + + /* + * Point current to the node that stopped + * the search. + * + * With the hashing modification that has been + * added to the algorithm, the stop node of a + * standard binary search is not known. So it + * has to be found. There is probably a more + * clever way of doing this. + * + * The assignment of current to NULL when + * the relationship is *not* dns_namereln_none, + * even though it later gets set to the same + * last_compared anyway, is simply to not push + * the while loop in one more level of + * indentation. + */ + if (compared == dns_namereln_none) { + current = last_compared; + } else { + current = NULL; + } + + while (current != NULL) { + NODENAME(current, ¤t_name); + compared = dns_name_fullcompare( + search_name, ¤t_name, + &order, &common_labels); + POST(compared); + + last_compared = current; + + /* + * Standard binary search movement. + */ + if (order < 0) { + current = LEFT(current); + } else { + current = RIGHT(current); + } + } + + current = last_compared; + + /* + * Reached a point within a level tree that + * positively indicates the name is not + * present, but the stop node could be either + * less than the desired name (order > 0) or + * greater than the desired name (order < 0). + * + * If the stop node is less, it is not + * necessarily the predecessor. If the stop + * node has a down pointer, then the real + * predecessor is at the end of a level below + * (not necessarily the next level). + * Move down levels until the rightmost node + * does not have a down pointer. + * + * When the stop node is greater, it is + * the successor. All the logic for finding + * the predecessor is handily encapsulated + * in dns_rbtnodechain_prev. In the event + * that the search name is less than anything + * else in the tree, the chain is reset. + * XXX DCL What is the best way for the caller + * to know that the search name has + * no predecessor? + */ + + if (order > 0) { + if (DOWN(current) != NULL) { + ADD_LEVEL(chain, current); + + result2 = move_chain_to_last( + chain, DOWN(current)); + + if (result2 != ISC_R_SUCCESS) { + result = result2; + } + } else { + /* + * Ah, the pure and simple + * case. The stop node is the + * predecessor. + */ + chain->end = current; + } + } else { + INSIST(order < 0); + + chain->end = current; + + result2 = dns_rbtnodechain_prev( + chain, NULL, NULL); + if (result2 == ISC_R_SUCCESS || + result2 == DNS_R_NEWORIGIN) + { + /* Nothing. */ + } else if (result2 == ISC_R_NOMORE) { + /* + * There is no predecessor. + */ + dns_rbtnodechain_reset(chain); + } else { + result = result2; + } + } + } + } + } + + ENSURE(*node == NULL || DNS_RBTNODE_VALID(*node)); + + return (result); +} + +/* + * Get the data pointer associated with 'name'. + */ +isc_result_t +dns_rbt_findname(dns_rbt_t *rbt, const dns_name_t *name, unsigned int options, + dns_name_t *foundname, void **data) { + dns_rbtnode_t *node = NULL; + isc_result_t result; + + REQUIRE(data != NULL && *data == NULL); + + result = dns_rbt_findnode(rbt, name, foundname, &node, NULL, options, + NULL, NULL); + + if (node != NULL && WANTEMPTYDATA_OR_DATA(options, node)) { + *data = DATA(node); + } else { + result = ISC_R_NOTFOUND; + } + + return (result); +} + +/* + * Delete a name from the tree of trees. + */ +isc_result_t +dns_rbt_deletename(dns_rbt_t *rbt, const dns_name_t *name, bool recurse) { + dns_rbtnode_t *node = NULL; + isc_result_t result; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(dns_name_isabsolute(name)); + + /* + * First, find the node. + * + * When searching, the name might not have an exact match: + * consider a.b.a.com, b.b.a.com and c.b.a.com as the only + * elements of a tree, which would make layer 1 a single + * node tree of "b.a.com" and layer 2 a three node tree of + * a, b, and c. Deleting a.com would find only a partial depth + * match in the first layer. Should it be a requirement that + * that the name to be deleted have data? For now, it is. + * + * ->dirty, ->locknum and ->references are ignored; they are + * solely the province of rbtdb.c. + */ + result = dns_rbt_findnode(rbt, name, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + + if (result == ISC_R_SUCCESS) { + if (DATA(node) != NULL) { + result = dns_rbt_deletenode(rbt, node, recurse); + } else { + result = ISC_R_NOTFOUND; + } + } else if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + + return (result); +} + +/* + * Remove a node from the tree of trees. + * + * NOTE WELL: deletion is *not* symmetric with addition; that is, reversing + * a sequence of additions to be deletions will not generally get the + * tree back to the state it started in. For example, if the addition + * of "b.c" caused the node "a.b.c" to be split, pushing "a" to its own level, + * then the subsequent deletion of "b.c" will not cause "a" to be pulled up, + * restoring "a.b.c". The RBT *used* to do this kind of rejoining, but it + * turned out to be a bad idea because it could corrupt an active nodechain + * that had "b.c" as one of its levels -- and the RBT has no idea what + * nodechains are in use by callers, so it can't even *try* to helpfully + * fix them up (which would probably be doomed to failure anyway). + * + * Similarly, it is possible to leave the tree in a state where a supposedly + * deleted node still exists. The first case of this is obvious; take + * the tree which has "b.c" on one level, pointing to "a". Now deleted "b.c". + * It was just established in the previous paragraph why we can't pull "a" + * back up to its parent level. But what happens when "a" then gets deleted? + * "b.c" is left hanging around without data or children. This condition + * is actually pretty easy to detect, but ... should it really be removed? + * Is a chain pointing to it? An iterator? Who knows! (Note that the + * references structure member cannot be looked at because it is private to + * rbtdb.) This is ugly and makes me unhappy, but after hours of trying to + * make it more aesthetically proper and getting nowhere, this is the way it + * is going to stay until such time as it proves to be a *real* problem. + * + * Finally, for reference, note that the original routine that did node + * joining was called join_nodes(). It has been excised, living now only + * in the CVS history, but comments have been left behind that point to it just + * in case someone wants to muck with this some more. + * + * The one positive aspect of all of this is that joining used to have a + * case where it might fail. Without trying to join, now this function always + * succeeds. It still returns isc_result_t, though, so the API wouldn't change. + */ +isc_result_t +dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse) { + dns_rbtnode_t *parent; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(DNS_RBTNODE_VALID(node)); + INSIST(rbt->nodecount != 0); + + if (DOWN(node) != NULL) { + if (recurse) { + PARENT(DOWN(node)) = NULL; + deletetreeflat(rbt, 0, true, &DOWN(node)); + } else { + if (DATA(node) != NULL && rbt->data_deleter != NULL) { + rbt->data_deleter(DATA(node), rbt->deleter_arg); + } + DATA(node) = NULL; + + /* + * Since there is at least one node below this one and + * no recursion was requested, the deletion is + * complete. The down node from this node might be all + * by itself on a single level, so join_nodes() could + * be used to collapse the tree (with all the caveats + * of the comment at the start of this function). + * But join_nodes() function has now been removed. + */ + return (ISC_R_SUCCESS); + } + } + + /* + * Note the node that points to the level of the node + * that is being deleted. If the deleted node is the + * top level, parent will be set to NULL. + */ + parent = get_upper_node(node); + + /* + * This node now has no down pointer, so now it needs + * to be removed from this level. + */ + deletefromlevel(node, parent == NULL ? &rbt->root : &DOWN(parent)); + + if (DATA(node) != NULL && rbt->data_deleter != NULL) { + rbt->data_deleter(DATA(node), rbt->deleter_arg); + } + + unhash_node(rbt, node); +#if DNS_RBT_USEMAGIC + node->magic = 0; +#endif /* if DNS_RBT_USEMAGIC */ + isc_refcount_destroy(&node->references); + + freenode(rbt, &node); + + /* + * This function never fails. + */ + return (ISC_R_SUCCESS); +} + +void +dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name) { + REQUIRE(DNS_RBTNODE_VALID(node)); + REQUIRE(name != NULL); + REQUIRE(name->offsets == NULL); + + NODENAME(node, name); +} + +isc_result_t +dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name) { + dns_name_t current; + isc_result_t result; + + REQUIRE(DNS_RBTNODE_VALID(node)); + REQUIRE(name != NULL); + REQUIRE(name->buffer != NULL); + + dns_name_init(¤t, NULL); + dns_name_reset(name); + + do { + INSIST(node != NULL); + + NODENAME(node, ¤t); + + result = dns_name_concatenate(name, ¤t, name, NULL); + if (result != ISC_R_SUCCESS) { + break; + } + + node = get_upper_node(node); + } while (!dns_name_isabsolute(name)); + + return (result); +} + +char * +dns_rbt_formatnodename(dns_rbtnode_t *node, char *printname, + unsigned int size) { + dns_fixedname_t fixedname; + dns_name_t *name; + isc_result_t result; + + REQUIRE(DNS_RBTNODE_VALID(node)); + REQUIRE(printname != NULL); + + name = dns_fixedname_initname(&fixedname); + result = dns_rbt_fullnamefromnode(node, name); + if (result == ISC_R_SUCCESS) { + dns_name_format(name, printname, size); + } else { + snprintf(printname, size, "", + isc_result_totext(result)); + } + + return (printname); +} + +static isc_result_t +create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep) { + dns_rbtnode_t *node; + isc_region_t region; + unsigned int labels; + size_t nodelen; + + REQUIRE(name->offsets != NULL); + + dns_name_toregion(name, ®ion); + labels = dns_name_countlabels(name); + ENSURE(labels > 0); + + /* + * Allocate space for the node structure, the name, and the offsets. + */ + nodelen = sizeof(dns_rbtnode_t) + region.length + labels + 1; + node = isc_mem_get(mctx, nodelen); + memset(node, 0, nodelen); + + node->is_root = 0; + PARENT(node) = NULL; + RIGHT(node) = NULL; + LEFT(node) = NULL; + DOWN(node) = NULL; + DATA(node) = NULL; + node->rpz = 0; + + HASHNEXT(node) = NULL; + HASHVAL(node) = 0; + + ISC_LINK_INIT(node, deadlink); + + LOCKNUM(node) = 0; + WILD(node) = 0; + DIRTY(node) = 0; + isc_refcount_init(&node->references, 0); + node->find_callback = 0; + node->nsec = DNS_RBT_NSEC_NORMAL; + + MAKE_BLACK(node); + + /* + * The following is stored to make reconstructing a name from the + * stored value in the node easy: the length of the name, the number + * of labels, whether the name is absolute or not, the name itself, + * and the name's offsets table. + * + * XXX RTH + * The offsets table could be made smaller by eliminating the + * first offset, which is always 0. This requires changes to + * lib/dns/name.c. + * + * Note: OLDOFFSETLEN *must* be assigned *after* OLDNAMELEN is assigned + * as it uses OLDNAMELEN. + */ + OLDNAMELEN(node) = NAMELEN(node) = region.length; + OLDOFFSETLEN(node) = OFFSETLEN(node) = labels; + ATTRS(node) = name->attributes; + + memmove(NAME(node), region.base, region.length); + memmove(OFFSETS(node), name->offsets, labels); + +#if DNS_RBT_USEMAGIC + node->magic = DNS_RBTNODE_MAGIC; +#endif /* if DNS_RBT_USEMAGIC */ + *nodep = node; + + return (ISC_R_SUCCESS); +} + +/* + * Add a node to the hash table + */ +static void +hash_add_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) { + uint32_t hash; + + REQUIRE(name != NULL); + + HASHVAL(node) = dns_name_fullhash(name, false); + + hash = hash_32(HASHVAL(node), rbt->hashbits[rbt->hindex]); + HASHNEXT(node) = rbt->hashtable[rbt->hindex][hash]; + + rbt->hashtable[rbt->hindex][hash] = node; +} + +/* + * Initialize hash table + */ +static void +hashtable_new(dns_rbt_t *rbt, uint8_t index, uint8_t bits) { + size_t size; + + REQUIRE(rbt->hashbits[index] == RBT_HASH_NO_BITS); + REQUIRE(rbt->hashtable[index] == NULL); + REQUIRE(bits >= RBT_HASH_MIN_BITS); + REQUIRE(bits < RBT_HASH_MAX_BITS); + + rbt->hashbits[index] = bits; + + size = HASHSIZE(rbt->hashbits[index]) * sizeof(dns_rbtnode_t *); + rbt->hashtable[index] = isc_mem_get(rbt->mctx, size); + memset(rbt->hashtable[index], 0, size); +} + +static void +hashtable_free(dns_rbt_t *rbt, uint8_t index) { + size_t size = HASHSIZE(rbt->hashbits[index]) * sizeof(dns_rbtnode_t *); + isc_mem_put(rbt->mctx, rbt->hashtable[index], size); + + rbt->hashbits[index] = RBT_HASH_NO_BITS; + rbt->hashtable[index] = NULL; +} + +static uint32_t +rehash_bits(dns_rbt_t *rbt, size_t newcount) { + uint32_t newbits = rbt->hashbits[rbt->hindex]; + + while (newcount >= HASHSIZE(newbits) && newbits < RBT_HASH_MAX_BITS) { + newbits += 1; + } + + return (newbits); +} + +/* + * Rebuild the hashtable to reduce the load factor + */ +static void +hashtable_rehash(dns_rbt_t *rbt, uint32_t newbits) { + uint8_t oldindex = rbt->hindex; + uint32_t oldbits = rbt->hashbits[oldindex]; + uint8_t newindex = RBT_HASH_NEXTTABLE(oldindex); + + REQUIRE(rbt->hashbits[oldindex] >= RBT_HASH_MIN_BITS); + REQUIRE(rbt->hashbits[oldindex] <= RBT_HASH_MAX_BITS); + REQUIRE(rbt->hashtable[oldindex] != NULL); + + REQUIRE(newbits <= RBT_HASH_MAX_BITS); + REQUIRE(rbt->hashbits[newindex] == RBT_HASH_NO_BITS); + REQUIRE(rbt->hashtable[newindex] == NULL); + + REQUIRE(newbits > oldbits); + + hashtable_new(rbt, newindex, newbits); + + rbt->hindex = newindex; + + hashtable_rehash_one(rbt); +} + +static void +hashtable_rehash_one(dns_rbt_t *rbt) { + dns_rbtnode_t **newtable = rbt->hashtable[rbt->hindex]; + uint32_t oldsize = + HASHSIZE(rbt->hashbits[RBT_HASH_NEXTTABLE(rbt->hindex)]); + dns_rbtnode_t **oldtable = + rbt->hashtable[RBT_HASH_NEXTTABLE(rbt->hindex)]; + dns_rbtnode_t *node = NULL; + dns_rbtnode_t *nextnode; + + /* Find first non-empty node */ + while (rbt->hiter < oldsize && oldtable[rbt->hiter] == NULL) { + rbt->hiter++; + } + + /* Rehashing complete */ + if (rbt->hiter == oldsize) { + hashtable_free(rbt, RBT_HASH_NEXTTABLE(rbt->hindex)); + rbt->hiter = 0; + return; + } + + /* Move the first non-empty node from old hashtable to new hashtable */ + for (node = oldtable[rbt->hiter]; node != NULL; node = nextnode) { + uint32_t hash = hash_32(HASHVAL(node), + rbt->hashbits[rbt->hindex]); + nextnode = HASHNEXT(node); + HASHNEXT(node) = newtable[hash]; + newtable[hash] = node; + } + + oldtable[rbt->hiter] = NULL; + + rbt->hiter++; +} + +static void +maybe_rehash(dns_rbt_t *rbt, size_t newcount) { + uint32_t newbits = rehash_bits(rbt, newcount); + + if (rbt->hashbits[rbt->hindex] < newbits && + newbits <= RBT_HASH_MAX_BITS) + { + hashtable_rehash(rbt, newbits); + } +} + +static bool +rehashing_in_progress(dns_rbt_t *rbt) { + return (rbt->hashtable[RBT_HASH_NEXTTABLE(rbt->hindex)] != NULL); +} + +static bool +hashtable_is_overcommited(dns_rbt_t *rbt) { + return (rbt->nodecount >= + (HASHSIZE(rbt->hashbits[rbt->hindex]) * RBT_HASH_OVERCOMMIT)); +} + +/* + * Add a node to the hash table. Rehash the hashtable if the node count + * rises above a critical level. + */ +static void +hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, const dns_name_t *name) { + REQUIRE(DNS_RBTNODE_VALID(node)); + + if (rehashing_in_progress(rbt)) { + /* Rehash in progress */ + hashtable_rehash_one(rbt); + } else if (hashtable_is_overcommited(rbt)) { + /* Rehash requested */ + maybe_rehash(rbt, rbt->nodecount); + } + + hash_add_node(rbt, node, name); +} + +/* + * Remove a node from the hash table + */ +static void +unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *dnode) { + uint32_t hash; + uint8_t hindex = rbt->hindex; + dns_rbtnode_t *hnode; + + REQUIRE(DNS_RBTNODE_VALID(dnode)); + + /* + * The node could be either in: + * a) current table: no rehashing in progress, or + * b) current table: the node has been already moved, or + * c) other table: the node hasn't been moved yet. + */ +nexttable: + hash = hash_32(HASHVAL(dnode), rbt->hashbits[hindex]); + + hnode = rbt->hashtable[hindex][hash]; + + if (hnode == dnode) { + rbt->hashtable[hindex][hash] = HASHNEXT(hnode); + return; + } else { + for (; hnode != NULL; hnode = HASHNEXT(hnode)) { + if (HASHNEXT(hnode) == dnode) { + HASHNEXT(hnode) = HASHNEXT(dnode); + return; + } + } + } + + if (TRY_NEXTTABLE(hindex, rbt)) { + /* Rehashing in progress, delete from the other table */ + hindex = RBT_HASH_NEXTTABLE(hindex); + goto nexttable; + } + + /* We haven't found any matching node, this should not be possible. */ + UNREACHABLE(); +} + +static void +rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp) { + dns_rbtnode_t *child; + + REQUIRE(DNS_RBTNODE_VALID(node)); + REQUIRE(rootp != NULL); + + child = RIGHT(node); + INSIST(child != NULL); + + RIGHT(node) = LEFT(child); + if (LEFT(child) != NULL) { + PARENT(LEFT(child)) = node; + } + LEFT(child) = node; + + PARENT(child) = PARENT(node); + + if (IS_ROOT(node)) { + *rootp = child; + child->is_root = 1; + node->is_root = 0; + } else { + if (LEFT(PARENT(node)) == node) { + LEFT(PARENT(node)) = child; + } else { + RIGHT(PARENT(node)) = child; + } + } + + PARENT(node) = child; +} + +static void +rotate_right(dns_rbtnode_t *node, dns_rbtnode_t **rootp) { + dns_rbtnode_t *child; + + REQUIRE(DNS_RBTNODE_VALID(node)); + REQUIRE(rootp != NULL); + + child = LEFT(node); + INSIST(child != NULL); + + LEFT(node) = RIGHT(child); + if (RIGHT(child) != NULL) { + PARENT(RIGHT(child)) = node; + } + RIGHT(child) = node; + + PARENT(child) = PARENT(node); + + if (IS_ROOT(node)) { + *rootp = child; + child->is_root = 1; + node->is_root = 0; + } else { + if (LEFT(PARENT(node)) == node) { + LEFT(PARENT(node)) = child; + } else { + RIGHT(PARENT(node)) = child; + } + } + + PARENT(node) = child; +} + +/* + * This is the real workhorse of the insertion code, because it does the + * true red/black tree on a single level. + */ +static void +addonlevel(dns_rbtnode_t *node, dns_rbtnode_t *current, int order, + dns_rbtnode_t **rootp) { + dns_rbtnode_t *child, *root, *parent, *grandparent; + dns_name_t add_name, current_name; + dns_offsets_t add_offsets, current_offsets; + + REQUIRE(rootp != NULL); + REQUIRE(DNS_RBTNODE_VALID(node) && LEFT(node) == NULL && + RIGHT(node) == NULL); + REQUIRE(current != NULL); + + root = *rootp; + if (root == NULL) { + /* + * First node of a level. + */ + MAKE_BLACK(node); + node->is_root = 1; + PARENT(node) = current; + *rootp = node; + return; + } + + child = root; + POST(child); + + dns_name_init(&add_name, add_offsets); + NODENAME(node, &add_name); + + dns_name_init(¤t_name, current_offsets); + NODENAME(current, ¤t_name); + + if (order < 0) { + INSIST(LEFT(current) == NULL); + LEFT(current) = node; + } else { + INSIST(RIGHT(current) == NULL); + RIGHT(current) = node; + } + + INSIST(PARENT(node) == NULL); + PARENT(node) = current; + + MAKE_RED(node); + + while (node != root && IS_RED(PARENT(node))) { + /* + * XXXDCL could do away with separate parent and grandparent + * variables. They are vestiges of the days before parent + * pointers. However, they make the code a little clearer. + */ + + parent = PARENT(node); + grandparent = PARENT(parent); + + if (parent == LEFT(grandparent)) { + child = RIGHT(grandparent); + if (child != NULL && IS_RED(child)) { + MAKE_BLACK(parent); + MAKE_BLACK(child); + MAKE_RED(grandparent); + node = grandparent; + } else { + if (node == RIGHT(parent)) { + rotate_left(parent, &root); + node = parent; + parent = PARENT(node); + grandparent = PARENT(parent); + } + MAKE_BLACK(parent); + MAKE_RED(grandparent); + rotate_right(grandparent, &root); + } + } else { + child = LEFT(grandparent); + if (child != NULL && IS_RED(child)) { + MAKE_BLACK(parent); + MAKE_BLACK(child); + MAKE_RED(grandparent); + node = grandparent; + } else { + if (node == LEFT(parent)) { + rotate_right(parent, &root); + node = parent; + parent = PARENT(node); + grandparent = PARENT(parent); + } + MAKE_BLACK(parent); + MAKE_RED(grandparent); + rotate_left(grandparent, &root); + } + } + } + + MAKE_BLACK(root); + ENSURE(IS_ROOT(root)); + *rootp = root; + + return; +} + +/* + * This is the real workhorse of the deletion code, because it does the + * true red/black tree on a single level. + */ +static void +deletefromlevel(dns_rbtnode_t *item, dns_rbtnode_t **rootp) { + dns_rbtnode_t *child, *sibling, *parent; + dns_rbtnode_t *successor; + + REQUIRE(item != NULL); + + /* + * Verify that the parent history is (apparently) correct. + */ + INSIST((IS_ROOT(item) && *rootp == item) || + (!IS_ROOT(item) && + (LEFT(PARENT(item)) == item || RIGHT(PARENT(item)) == item))); + + child = NULL; + + if (LEFT(item) == NULL) { + if (RIGHT(item) == NULL) { + if (IS_ROOT(item)) { + /* + * This is the only item in the tree. + */ + *rootp = NULL; + return; + } + } else { + /* + * This node has one child, on the right. + */ + child = RIGHT(item); + } + } else if (RIGHT(item) == NULL) { + /* + * This node has one child, on the left. + */ + child = LEFT(item); + } else { + dns_rbtnode_t *saved_parent, *saved_right; + int saved_color; + + /* + * This node has two children, so it cannot be directly + * deleted. Find its immediate in-order successor and + * move it to this location, then do the deletion at the + * old site of the successor. + */ + successor = RIGHT(item); + while (LEFT(successor) != NULL) { + successor = LEFT(successor); + } + + /* + * The successor cannot possibly have a left child; + * if there is any child, it is on the right. + */ + if (RIGHT(successor) != NULL) { + child = RIGHT(successor); + } + + /* + * Swap the two nodes; it would be simpler to just replace + * the value being deleted with that of the successor, + * but this rigamarole is done so the caller has complete + * control over the pointers (and memory allocation) of + * all of nodes. If just the key value were removed from + * the tree, the pointer to the node would be unchanged. + */ + + /* + * First, put the successor in the tree location of the + * node to be deleted. Save its existing tree pointer + * information, which will be needed when linking up + * delete to the successor's old location. + */ + saved_parent = PARENT(successor); + saved_right = RIGHT(successor); + saved_color = COLOR(successor); + + if (IS_ROOT(item)) { + *rootp = successor; + successor->is_root = true; + item->is_root = false; + } else if (LEFT(PARENT(item)) == item) { + LEFT(PARENT(item)) = successor; + } else { + RIGHT(PARENT(item)) = successor; + } + + PARENT(successor) = PARENT(item); + LEFT(successor) = LEFT(item); + RIGHT(successor) = RIGHT(item); + COLOR(successor) = COLOR(item); + + if (LEFT(successor) != NULL) { + PARENT(LEFT(successor)) = successor; + } + if (RIGHT(successor) != successor) { + PARENT(RIGHT(successor)) = successor; + } + + /* + * Now relink the node to be deleted into the + * successor's previous tree location. + */ + INSIST(!IS_ROOT(item)); + + if (saved_parent == item) { + /* + * Node being deleted was successor's parent. + */ + RIGHT(successor) = item; + PARENT(item) = successor; + } else { + LEFT(saved_parent) = item; + PARENT(item) = saved_parent; + } + + /* + * Original location of successor node has no left. + */ + LEFT(item) = NULL; + RIGHT(item) = saved_right; + COLOR(item) = saved_color; + } + + /* + * Remove the node by removing the links from its parent. + */ + if (!IS_ROOT(item)) { + if (LEFT(PARENT(item)) == item) { + LEFT(PARENT(item)) = child; + } else { + RIGHT(PARENT(item)) = child; + } + + if (child != NULL) { + PARENT(child) = PARENT(item); + } + } else { + /* + * This is the root being deleted, and at this point + * it is known to have just one child. + */ + *rootp = child; + child->is_root = 1; + PARENT(child) = PARENT(item); + } + + /* + * Fix color violations. + */ + if (IS_BLACK(item)) { + parent = PARENT(item); + + while (child != *rootp && IS_BLACK(child)) { + INSIST(child == NULL || !IS_ROOT(child)); + + if (LEFT(parent) == child) { + sibling = RIGHT(parent); + + if (IS_RED(sibling)) { + MAKE_BLACK(sibling); + MAKE_RED(parent); + rotate_left(parent, rootp); + sibling = RIGHT(parent); + } + + INSIST(sibling != NULL); + + if (IS_BLACK(LEFT(sibling)) && + IS_BLACK(RIGHT(sibling))) + { + MAKE_RED(sibling); + child = parent; + } else { + if (IS_BLACK(RIGHT(sibling))) { + MAKE_BLACK(LEFT(sibling)); + MAKE_RED(sibling); + rotate_right(sibling, rootp); + sibling = RIGHT(parent); + } + + COLOR(sibling) = COLOR(parent); + MAKE_BLACK(parent); + INSIST(RIGHT(sibling) != NULL); + MAKE_BLACK(RIGHT(sibling)); + rotate_left(parent, rootp); + child = *rootp; + } + } else { + /* + * Child is parent's right child. + * Everything is done the same as above, + * except mirrored. + */ + sibling = LEFT(parent); + + if (IS_RED(sibling)) { + MAKE_BLACK(sibling); + MAKE_RED(parent); + rotate_right(parent, rootp); + sibling = LEFT(parent); + } + + INSIST(sibling != NULL); + + if (IS_BLACK(LEFT(sibling)) && + IS_BLACK(RIGHT(sibling))) + { + MAKE_RED(sibling); + child = parent; + } else { + if (IS_BLACK(LEFT(sibling))) { + MAKE_BLACK(RIGHT(sibling)); + MAKE_RED(sibling); + rotate_left(sibling, rootp); + sibling = LEFT(parent); + } + + COLOR(sibling) = COLOR(parent); + MAKE_BLACK(parent); + INSIST(LEFT(sibling) != NULL); + MAKE_BLACK(LEFT(sibling)); + rotate_right(parent, rootp); + child = *rootp; + } + } + + parent = PARENT(child); + } + + if (IS_RED(child)) { + MAKE_BLACK(child); + } + } +} + +static void +freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep) { + dns_rbtnode_t *node = *nodep; + *nodep = NULL; + + isc_mem_put(rbt->mctx, node, NODE_SIZE(node)); + + rbt->nodecount--; +} + +static void +deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, bool unhash, + dns_rbtnode_t **nodep) { + dns_rbtnode_t *root = *nodep; + + while (root != NULL) { + /* + * If there is a left, right or down node, walk into it + * and iterate. + */ + if (LEFT(root) != NULL) { + dns_rbtnode_t *node = root; + root = LEFT(root); + LEFT(node) = NULL; + } else if (RIGHT(root) != NULL) { + dns_rbtnode_t *node = root; + root = RIGHT(root); + RIGHT(node) = NULL; + } else if (DOWN(root) != NULL) { + dns_rbtnode_t *node = root; + root = DOWN(root); + DOWN(node) = NULL; + } else { + /* + * There are no left, right or down nodes, so we + * can free this one and go back to its parent. + */ + dns_rbtnode_t *node = root; + root = PARENT(root); + + if (rbt->data_deleter != NULL && DATA(node) != NULL) { + rbt->data_deleter(DATA(node), rbt->deleter_arg); + } + if (unhash) { + unhash_node(rbt, node); + } + /* + * Note: we don't call unhash_node() here as we + * are destroying the complete RBT tree. + */ +#if DNS_RBT_USEMAGIC + node->magic = 0; +#endif /* if DNS_RBT_USEMAGIC */ + freenode(rbt, &node); + if (quantum != 0 && --quantum == 0) { + break; + } + } + } + + *nodep = root; +} + +static size_t +getheight_helper(dns_rbtnode_t *node) { + size_t dl, dr; + size_t this_height, down_height; + + if (node == NULL) { + return (0); + } + + dl = getheight_helper(LEFT(node)); + dr = getheight_helper(RIGHT(node)); + + this_height = ISC_MAX(dl + 1, dr + 1); + down_height = getheight_helper(DOWN(node)); + + return (ISC_MAX(this_height, down_height)); +} + +size_t +dns__rbt_getheight(dns_rbt_t *rbt) { + return (getheight_helper(rbt->root)); +} + +static bool +check_properties_helper(dns_rbtnode_t *node) { + if (node == NULL) { + return (true); + } + + if (IS_RED(node)) { + /* Root nodes must be BLACK. */ + if (IS_ROOT(node)) { + return (false); + } + + /* Both children of RED nodes must be BLACK. */ + if (IS_RED(LEFT(node)) || IS_RED(RIGHT(node))) { + return (false); + } + } + + if ((DOWN(node) != NULL) && (!IS_ROOT(DOWN(node)))) { + return (false); + } + + if (IS_ROOT(node)) { + if ((PARENT(node) != NULL) && (DOWN(PARENT(node)) != node)) { + return (false); + } + + if (get_upper_node(node) != PARENT(node)) { + return (false); + } + } + + /* If node is assigned to the down_ pointer of its parent, it is + * a subtree root and must have the flag set. + */ + if (((!PARENT(node)) || (DOWN(PARENT(node)) == node)) && + (!IS_ROOT(node))) + { + return (false); + } + + /* Repeat tests with this node's children. */ + return (check_properties_helper(LEFT(node)) && + check_properties_helper(RIGHT(node)) && + check_properties_helper(DOWN(node))); +} + +static bool +check_black_distance_helper(dns_rbtnode_t *node, size_t *distance) { + size_t dl, dr, dd; + + if (node == NULL) { + *distance = 1; + return (true); + } + + if (!check_black_distance_helper(LEFT(node), &dl)) { + return (false); + } + + if (!check_black_distance_helper(RIGHT(node), &dr)) { + return (false); + } + + if (!check_black_distance_helper(DOWN(node), &dd)) { + return (false); + } + + /* Left and right side black node counts must match. */ + if (dl != dr) { + return (false); + } + + if (IS_BLACK(node)) { + dl++; + } + + *distance = dl; + + return (true); +} + +bool +dns__rbt_checkproperties(dns_rbt_t *rbt) { + size_t dd; + + if (!check_properties_helper(rbt->root)) { + return (false); + } + + /* Path from a given node to all its leaves must contain the + * same number of BLACK child nodes. This is done separately + * here instead of inside check_properties_helper() as + * it would take (n log n) complexity otherwise. + */ + return (check_black_distance_helper(rbt->root, &dd)); +} + +static void +dns_rbt_indent(FILE *f, int depth) { + int i; + + fprintf(f, "%4d ", depth); + + for (i = 0; i < depth; i++) { + fprintf(f, "- "); + } +} + +void +dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f) { + if (n == NULL) { + fprintf(f, "Null node\n"); + return; + } + + fprintf(f, "Node info for nodename: "); + printnodename(n, true, f); + fprintf(f, "\n"); + + fprintf(f, "n = %p\n", n); + + fprintf(f, "node lock address = %u\n", n->locknum); + + fprintf(f, "Parent: %p\n", n->parent); + fprintf(f, "Right: %p\n", n->right); + fprintf(f, "Left: %p\n", n->left); + fprintf(f, "Down: %p\n", n->down); + fprintf(f, "Data: %p\n", n->data); +} + +static void +printnodename(dns_rbtnode_t *node, bool quoted, FILE *f) { + isc_region_t r; + dns_name_t name; + char buffer[DNS_NAME_FORMATSIZE]; + dns_offsets_t offsets; + + r.length = NAMELEN(node); + r.base = NAME(node); + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &r); + + dns_name_format(&name, buffer, sizeof(buffer)); + + if (quoted) { + fprintf(f, "\"%s\"", buffer); + } else { + fprintf(f, "%s", buffer); + } +} + +static void +print_text_helper(dns_rbtnode_t *root, dns_rbtnode_t *parent, int depth, + const char *direction, void (*data_printer)(FILE *, void *), + FILE *f) { + dns_rbt_indent(f, depth); + + if (root != NULL) { + printnodename(root, true, f); + fprintf(f, " (%s, %s", direction, + COLOR(root) == RED ? "RED" : "BLACK"); + + if ((!IS_ROOT(root) && PARENT(root) != parent) || + (IS_ROOT(root) && depth > 0 && DOWN(PARENT(root)) != root)) + { + fprintf(f, " (BAD parent pointer! -> "); + if (PARENT(root) != NULL) { + printnodename(PARENT(root), true, f); + } else { + fprintf(f, "NULL"); + } + fprintf(f, ")"); + } + + fprintf(f, ")"); + + if (root->data != NULL && data_printer != NULL) { + fprintf(f, " data@%p: ", root->data); + data_printer(f, root->data); + } + fprintf(f, "\n"); + + depth++; + + if (COLOR(root) == RED && IS_RED(LEFT(root))) { + fprintf(f, "** Red/Red color violation on left\n"); + } + print_text_helper(LEFT(root), root, depth, "left", data_printer, + f); + + if (COLOR(root) == RED && IS_RED(RIGHT(root))) { + fprintf(f, "** Red/Red color violation on right\n"); + } + print_text_helper(RIGHT(root), root, depth, "right", + data_printer, f); + + print_text_helper(DOWN(root), NULL, depth, "down", data_printer, + f); + } else { + fprintf(f, "NULL (%s)\n", direction); + } +} + +void +dns_rbt_printtext(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *), + FILE *f) { + REQUIRE(VALID_RBT(rbt)); + + print_text_helper(rbt->root, NULL, 0, "root", data_printer, f); +} + +static int +print_dot_helper(dns_rbtnode_t *node, unsigned int *nodecount, + bool show_pointers, FILE *f) { + unsigned int l, r, d; + + if (node == NULL) { + return (0); + } + + l = print_dot_helper(LEFT(node), nodecount, show_pointers, f); + r = print_dot_helper(RIGHT(node), nodecount, show_pointers, f); + d = print_dot_helper(DOWN(node), nodecount, show_pointers, f); + + *nodecount += 1; + + fprintf(f, "node%u[label = \" | ", *nodecount); + printnodename(node, false, f); + fprintf(f, "|"); + + if (show_pointers) { + fprintf(f, "| n=%p| p=%p", node, PARENT(node)); + } + + fprintf(f, "\"] ["); + + if (IS_RED(node)) { + fprintf(f, "color=red"); + } else { + fprintf(f, "color=black"); + } + + /* XXXMUKS: verify that IS_ROOT() indicates subtree root and not + * forest root. + */ + if (IS_ROOT(node)) { + fprintf(f, ",penwidth=3"); + } + + if (IS_EMPTY(node)) { + fprintf(f, ",style=filled,fillcolor=lightgrey"); + } + + fprintf(f, "];\n"); + + if (LEFT(node) != NULL) { + fprintf(f, "\"node%u\":f0 -> \"node%u\":f1;\n", *nodecount, l); + } + + if (DOWN(node) != NULL) { + fprintf(f, "\"node%u\":f1 -> \"node%u\":f1 [penwidth=5];\n", + *nodecount, d); + } + if (RIGHT(node) != NULL) { + fprintf(f, "\"node%u\":f2 -> \"node%u\":f1;\n", *nodecount, r); + } + + return (*nodecount); +} + +void +dns_rbt_printdot(dns_rbt_t *rbt, bool show_pointers, FILE *f) { + unsigned int nodecount = 0; + + REQUIRE(VALID_RBT(rbt)); + + fprintf(f, "digraph g {\n"); + fprintf(f, "node [shape = record,height=.1];\n"); + print_dot_helper(rbt->root, &nodecount, show_pointers, f); + fprintf(f, "}\n"); +} + +/* + * Chain Functions + */ + +void +dns_rbtnodechain_init(dns_rbtnodechain_t *chain) { + REQUIRE(chain != NULL); + + /* + * Initialize 'chain'. + */ + chain->end = NULL; + chain->level_count = 0; + chain->level_matches = 0; + memset(chain->levels, 0, sizeof(chain->levels)); + + chain->magic = CHAIN_MAGIC; +} + +isc_result_t +dns_rbtnodechain_current(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin, dns_rbtnode_t **node) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_CHAIN(chain)); + + if (node != NULL) { + *node = chain->end; + } + + if (chain->end == NULL) { + return (ISC_R_NOTFOUND); + } + + if (name != NULL) { + NODENAME(chain->end, name); + + if (chain->level_count == 0) { + /* + * Names in the top level tree are all absolute. + * Always make 'name' relative. + */ + INSIST(dns_name_isabsolute(name)); + + /* + * This is cheaper than + * dns_name_getlabelsequence(). + */ + name->labels--; + name->length--; + name->attributes &= ~DNS_NAMEATTR_ABSOLUTE; + } + } + + if (origin != NULL) { + if (chain->level_count > 0) { + result = chain_name(chain, origin, false); + } else { + dns_name_copy(dns_rootname, origin); + } + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_prev(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin) { + dns_rbtnode_t *current, *previous, *predecessor; + isc_result_t result = ISC_R_SUCCESS; + bool new_origin = false; + + REQUIRE(VALID_CHAIN(chain) && chain->end != NULL); + + predecessor = NULL; + + current = chain->end; + + if (LEFT(current) != NULL) { + /* + * Moving left one then right as far as possible is the + * previous node, at least for this level. + */ + current = LEFT(current); + + while (RIGHT(current) != NULL) { + current = RIGHT(current); + } + + predecessor = current; + } else { + /* + * No left links, so move toward the root. If at any + * point on the way there the link from parent to child + * is a right link, then the parent is the previous + * node, at least for this level. + */ + while (!IS_ROOT(current)) { + previous = current; + current = PARENT(current); + + if (RIGHT(current) == previous) { + predecessor = current; + break; + } + } + } + + if (predecessor != NULL) { + /* + * Found a predecessor node in this level. It might not + * really be the predecessor, however. + */ + if (DOWN(predecessor) != NULL) { + /* + * The predecessor is really down at least one + * level. Go down and as far right as possible, + * and repeat as long as the rightmost node has + * a down pointer. + */ + do { + /* + * XXX DCL Need to do something about + * origins here. See whether to go down, + * and if so whether it is truly what + * Bob calls a new origin. + */ + ADD_LEVEL(chain, predecessor); + predecessor = DOWN(predecessor); + + /* XXX DCL duplicated from above; clever + * way to unduplicate? */ + + while (RIGHT(predecessor) != NULL) { + predecessor = RIGHT(predecessor); + } + } while (DOWN(predecessor) != NULL); + + /* XXX DCL probably needs work on the concept */ + if (origin != NULL) { + new_origin = true; + } + } + } else if (chain->level_count > 0) { + /* + * Dang, didn't find a predecessor in this level. + * Got to the root of this level without having + * traversed any right links. Ascend the tree one + * level; the node that points to this tree is the + * predecessor. + */ + INSIST(chain->level_count > 0 && IS_ROOT(current)); + predecessor = chain->levels[--chain->level_count]; + + /* XXX DCL probably needs work on the concept */ + /* + * Don't declare an origin change when the new origin is + * "." at the top level tree, because "." is declared as + * the origin for the second level tree. + */ + if (origin != NULL && + (chain->level_count > 0 || OFFSETLEN(predecessor) > 1)) + { + new_origin = true; + } + } + + if (predecessor != NULL) { + chain->end = predecessor; + + if (new_origin) { + result = dns_rbtnodechain_current(chain, name, origin, + NULL); + if (result == ISC_R_SUCCESS) { + result = DNS_R_NEWORIGIN; + } + } else { + result = dns_rbtnodechain_current(chain, name, NULL, + NULL); + } + } else { + result = ISC_R_NOMORE; + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_down(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin) { + dns_rbtnode_t *current, *successor; + isc_result_t result = ISC_R_SUCCESS; + bool new_origin = false; + + REQUIRE(VALID_CHAIN(chain) && chain->end != NULL); + + successor = NULL; + + current = chain->end; + + if (DOWN(current) != NULL) { + /* + * Don't declare an origin change when the new origin is + * "." at the second level tree, because "." is already + * declared as the origin for the top level tree. + */ + if (chain->level_count > 0 || OFFSETLEN(current) > 1) { + new_origin = true; + } + + ADD_LEVEL(chain, current); + current = DOWN(current); + + while (LEFT(current) != NULL) { + current = LEFT(current); + } + + successor = current; + } + + if (successor != NULL) { + chain->end = successor; + + /* + * It is not necessary to use dns_rbtnodechain_current + * like the other functions because this function will + * never find a node in the topmost level. This is + * because the root level will never be more than one + * name, and everything in the megatree is a successor + * to that node, down at the second level or below. + */ + + if (name != NULL) { + NODENAME(chain->end, name); + } + + if (new_origin) { + if (origin != NULL) { + result = chain_name(chain, origin, false); + } + + if (result == ISC_R_SUCCESS) { + result = DNS_R_NEWORIGIN; + } + } else { + result = ISC_R_SUCCESS; + } + } else { + result = ISC_R_NOMORE; + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_nextflat(dns_rbtnodechain_t *chain, dns_name_t *name) { + dns_rbtnode_t *current, *previous, *successor; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_CHAIN(chain) && chain->end != NULL); + + successor = NULL; + + current = chain->end; + + if (RIGHT(current) == NULL) { + while (!IS_ROOT(current)) { + previous = current; + current = PARENT(current); + + if (LEFT(current) == previous) { + successor = current; + break; + } + } + } else { + current = RIGHT(current); + + while (LEFT(current) != NULL) { + current = LEFT(current); + } + + successor = current; + } + + if (successor != NULL) { + chain->end = successor; + + if (name != NULL) { + NODENAME(chain->end, name); + } + + result = ISC_R_SUCCESS; + } else { + result = ISC_R_NOMORE; + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_next(dns_rbtnodechain_t *chain, dns_name_t *name, + dns_name_t *origin) { + dns_rbtnode_t *current, *previous, *successor; + isc_result_t result = ISC_R_SUCCESS; + bool new_origin = false; + + REQUIRE(VALID_CHAIN(chain) && chain->end != NULL); + + successor = NULL; + + current = chain->end; + + /* + * If there is a level below this node, the next node is the + * leftmost node of the next level. + */ + if (DOWN(current) != NULL) { + /* + * Don't declare an origin change when the new origin is + * "." at the second level tree, because "." is already + * declared as the origin for the top level tree. + */ + if (chain->level_count > 0 || OFFSETLEN(current) > 1) { + new_origin = true; + } + + ADD_LEVEL(chain, current); + current = DOWN(current); + + while (LEFT(current) != NULL) { + current = LEFT(current); + } + + successor = current; + } else if (RIGHT(current) == NULL) { + /* + * The successor is up, either in this level or a + * previous one. Head back toward the root of the tree, + * looking for any path that was via a left link; the + * successor is the node that has that left link. In + * the event the root of the level is reached without + * having traversed any left links, ascend one level and + * look for either a right link off the point of ascent, + * or search for a left link upward again, repeating + * ascends until either case is true. + */ + do { + while (!IS_ROOT(current)) { + previous = current; + current = PARENT(current); + + if (LEFT(current) == previous) { + successor = current; + break; + } + } + + if (successor == NULL) { + /* + * Reached the root without having + * traversed any left pointers, so this + * level is done. + */ + if (chain->level_count == 0) { + /* + * If the tree we are iterating + * over was modified since this + * chain was initialized in a + * way that caused node splits + * to occur, "current" may now + * be pointing to a root node + * which appears to be at level + * 0, but still has a parent. If + * that happens, abort. + * Otherwise, we are done + * looking for a successor as we + * really reached the root node + * on level 0. + */ + INSIST(PARENT(current) == NULL); + break; + } + + current = chain->levels[--chain->level_count]; + new_origin = true; + + if (RIGHT(current) != NULL) { + break; + } + } + } while (successor == NULL); + } + + if (successor == NULL && RIGHT(current) != NULL) { + current = RIGHT(current); + + while (LEFT(current) != NULL) { + current = LEFT(current); + } + + successor = current; + } + + if (successor != NULL) { + /* + * If we determine that the current node is the + * successor to itself, we will run into an infinite + * loop, so abort instead. + */ + INSIST(chain->end != successor); + + chain->end = successor; + + /* + * It is not necessary to use dns_rbtnodechain_current + * like the other functions because this function will + * never find a node in the topmost level. This is + * because the root level will never be more than one + * name, and everything in the megatree is a successor + * to that node, down at the second level or below. + */ + + if (name != NULL) { + NODENAME(chain->end, name); + } + + if (new_origin) { + if (origin != NULL) { + result = chain_name(chain, origin, false); + } + + if (result == ISC_R_SUCCESS) { + result = DNS_R_NEWORIGIN; + } + } else { + result = ISC_R_SUCCESS; + } + } else { + result = ISC_R_NOMORE; + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_first(dns_rbtnodechain_t *chain, dns_rbt_t *rbt, + dns_name_t *name, dns_name_t *origin) + +{ + isc_result_t result; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(VALID_CHAIN(chain)); + + dns_rbtnodechain_reset(chain); + + chain->end = rbt->root; + + result = dns_rbtnodechain_current(chain, name, origin, NULL); + + if (result == ISC_R_SUCCESS) { + result = DNS_R_NEWORIGIN; + } + + return (result); +} + +isc_result_t +dns_rbtnodechain_last(dns_rbtnodechain_t *chain, dns_rbt_t *rbt, + dns_name_t *name, dns_name_t *origin) + +{ + isc_result_t result; + + REQUIRE(VALID_RBT(rbt)); + REQUIRE(VALID_CHAIN(chain)); + + dns_rbtnodechain_reset(chain); + + result = move_chain_to_last(chain, rbt->root); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_rbtnodechain_current(chain, name, origin, NULL); + + if (result == ISC_R_SUCCESS) { + result = DNS_R_NEWORIGIN; + } + + return (result); +} + +void +dns_rbtnodechain_reset(dns_rbtnodechain_t *chain) { + REQUIRE(VALID_CHAIN(chain)); + + /* + * Free any dynamic storage associated with 'chain', and then + * reinitialize 'chain'. + */ + chain->end = NULL; + chain->level_count = 0; + chain->level_matches = 0; +} + +void +dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain) { + /* + * Free any dynamic storage associated with 'chain', and then + * invalidate 'chain'. + */ + + dns_rbtnodechain_reset(chain); + + chain->magic = 0; +} + +/* XXXMUKS: + * + * - worth removing inline as static functions are inlined automatically + * where suitable by modern compilers. + * - bump the size of dns_rbt.nodecount to size_t. + * - the dumpfile header also contains a nodecount that is unsigned + * int. If large files (> 2^32 nodes) are to be supported, the + * allocation for this field should be increased. + */ diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c new file mode 100644 index 0000000..5d36466 --- /dev/null +++ b/lib/dns/rbtdb.c @@ -0,0 +1,10241 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rbtdb.h" + +#define RBTDB_MAGIC ISC_MAGIC('R', 'B', 'D', '4') + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/*% + * Note that "impmagic" is not the first four bytes of the struct, so + * ISC_MAGIC_VALID cannot be used. + */ +#define VALID_RBTDB(rbtdb) \ + ((rbtdb) != NULL && (rbtdb)->common.impmagic == RBTDB_MAGIC) + +typedef uint32_t rbtdb_serial_t; +typedef uint32_t rbtdb_rdatatype_t; + +#define RBTDB_RDATATYPE_BASE(type) ((dns_rdatatype_t)((type)&0xFFFF)) +#define RBTDB_RDATATYPE_EXT(type) ((dns_rdatatype_t)((type) >> 16)) +#define RBTDB_RDATATYPE_VALUE(base, ext) \ + ((rbtdb_rdatatype_t)(((uint32_t)ext) << 16) | \ + (((uint32_t)base) & 0xffff)) + +#define RBTDB_RDATATYPE_SIGNSEC \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec) +#define RBTDB_RDATATYPE_SIGNSEC3 \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_nsec3) +#define RBTDB_RDATATYPE_SIGNS \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ns) +#define RBTDB_RDATATYPE_SIGCNAME \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_cname) +#define RBTDB_RDATATYPE_SIGDNAME \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_dname) +#define RBTDB_RDATATYPE_SIGDS \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ds) +#define RBTDB_RDATATYPE_SIGSOA \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_soa) +#define RBTDB_RDATATYPE_NCACHEANY RBTDB_RDATATYPE_VALUE(0, dns_rdatatype_any) + +#define RBTDB_INITLOCK(l) isc_rwlock_init((l), 0, 0) +#define RBTDB_DESTROYLOCK(l) isc_rwlock_destroy(l) +#define RBTDB_LOCK(l, t) RWLOCK((l), (t)) +#define RBTDB_UNLOCK(l, t) RWUNLOCK((l), (t)) + +/* + * Since node locking is sensitive to both performance and memory footprint, + * we need some trick here. If we have both high-performance rwlock and + * high performance and small-memory reference counters, we use rwlock for + * node lock and isc_refcount for node references. In this case, we don't have + * to protect the access to the counters by locks. + * Otherwise, we simply use ordinary mutex lock for node locking, and use + * simple integers as reference counters which is protected by the lock. + * In most cases, we can simply use wrapper macros such as NODE_LOCK and + * NODE_UNLOCK. In some other cases, however, we need to protect reference + * counters first and then protect other parts of a node as read-only data. + * Special additional macros, NODE_STRONGLOCK(), NODE_WEAKLOCK(), etc, are also + * provided for these special cases. When we can use the efficient backend + * routines, we should only protect the "other members" by NODE_WEAKLOCK(read). + * Otherwise, we should use NODE_STRONGLOCK() to protect the entire critical + * section including the access to the reference counter. + * Note that we cannot use NODE_LOCK()/NODE_UNLOCK() wherever the protected + * section is also protected by NODE_STRONGLOCK(). + */ +typedef isc_rwlock_t nodelock_t; + +#define NODE_INITLOCK(l) isc_rwlock_init((l), 0, 0) +#define NODE_DESTROYLOCK(l) isc_rwlock_destroy(l) +#define NODE_LOCK(l, t) RWLOCK((l), (t)) +#define NODE_UNLOCK(l, t) RWUNLOCK((l), (t)) +#define NODE_TRYUPGRADE(l) isc_rwlock_tryupgrade(l) +#define NODE_DOWNGRADE(l) isc_rwlock_downgrade(l) + +/*% + * Whether to rate-limit updating the LRU to avoid possible thread contention. + * Updating LRU requires write locking, so we don't do it every time the + * record is touched - only after some time passes. + */ +#ifndef DNS_RBTDB_LIMITLRUUPDATE +#define DNS_RBTDB_LIMITLRUUPDATE 1 +#endif + +/*% Time after which we update LRU for glue records, 5 minutes */ +#define DNS_RBTDB_LRUUPDATE_GLUE 300 +/*% Time after which we update LRU for all other records, 10 minutes */ +#define DNS_RBTDB_LRUUPDATE_REGULAR 600 + +/* + * Allow clients with a virtual time of up to 5 minutes in the past to see + * records that would have otherwise have expired. + */ +#define RBTDB_VIRTUAL 300 + +struct noqname { + dns_name_t name; + void *neg; + void *negsig; + dns_rdatatype_t type; +}; + +typedef struct rdatasetheader { + /*% + * Locked by the owning node's lock. + */ + rbtdb_serial_t serial; + dns_ttl_t rdh_ttl; + rbtdb_rdatatype_t type; + atomic_uint_least16_t attributes; + dns_trust_t trust; + atomic_uint_fast32_t last_refresh_fail_ts; + struct noqname *noqname; + struct noqname *closest; + unsigned int resign_lsb : 1; + /*%< + * We don't use the LIST macros, because the LIST structure has + * both head and tail pointers, and is doubly linked. + */ + + struct rdatasetheader *next; + /*%< + * If this is the top header for an rdataset, 'next' points + * to the top header for the next rdataset (i.e., the next type). + * Otherwise, it points up to the header whose down pointer points + * at this header. + */ + + struct rdatasetheader *down; + /*%< + * Points to the header for the next older version of + * this rdataset. + */ + + atomic_uint_fast32_t count; + /*%< + * Monotonously increased every time this rdataset is bound so that + * it is used as the base of the starting point in DNS responses + * when the "cyclic" rrset-order is required. + */ + + dns_rbtnode_t *node; + isc_stdtime_t last_used; + ISC_LINK(struct rdatasetheader) link; + + unsigned int heap_index; + /*%< + * Used for TTL-based cache cleaning. + */ + isc_stdtime_t resign; + /*%< + * Case vector. If the bit is set then the corresponding + * character in the owner name needs to be AND'd with 0x20, + * rendering that character upper case. + */ + unsigned char upper[32]; +} rdatasetheader_t; + +typedef ISC_LIST(rdatasetheader_t) rdatasetheaderlist_t; +typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t; + +#define RDATASET_ATTR_NONEXISTENT 0x0001 +/*%< May be potentially served as stale data. */ +#define RDATASET_ATTR_STALE 0x0002 +#define RDATASET_ATTR_IGNORE 0x0004 +#define RDATASET_ATTR_RETAIN 0x0008 +#define RDATASET_ATTR_NXDOMAIN 0x0010 +#define RDATASET_ATTR_RESIGN 0x0020 +#define RDATASET_ATTR_STATCOUNT 0x0040 +#define RDATASET_ATTR_OPTOUT 0x0080 +#define RDATASET_ATTR_NEGATIVE 0x0100 +#define RDATASET_ATTR_PREFETCH 0x0200 +#define RDATASET_ATTR_CASESET 0x0400 +#define RDATASET_ATTR_ZEROTTL 0x0800 +#define RDATASET_ATTR_CASEFULLYLOWER 0x1000 +/*%< Ancient - awaiting cleanup. */ +#define RDATASET_ATTR_ANCIENT 0x2000 +#define RDATASET_ATTR_STALE_WINDOW 0x4000 + +/* + * XXX + * When the cache will pre-expire data (due to memory low or other + * situations) before the rdataset's TTL has expired, it MUST + * respect the RETAIN bit and not expire the data until its TTL is + * expired. + */ + +#define EXISTS(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_NONEXISTENT) == 0) +#define NONEXISTENT(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_NONEXISTENT) != 0) +#define IGNORE(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_IGNORE) != 0) +#define RETAIN(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_RETAIN) != 0) +#define NXDOMAIN(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_NXDOMAIN) != 0) +#define STALE(header) \ + ((atomic_load_acquire(&(header)->attributes) & RDATASET_ATTR_STALE) != \ + 0) +#define STALE_WINDOW(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_STALE_WINDOW) != 0) +#define RESIGN(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_RESIGN) != 0) +#define OPTOUT(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_OPTOUT) != 0) +#define NEGATIVE(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_NEGATIVE) != 0) +#define PREFETCH(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_PREFETCH) != 0) +#define CASESET(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_CASESET) != 0) +#define ZEROTTL(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_ZEROTTL) != 0) +#define CASEFULLYLOWER(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_CASEFULLYLOWER) != 0) +#define ANCIENT(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_ANCIENT) != 0) +#define STATCOUNT(header) \ + ((atomic_load_acquire(&(header)->attributes) & \ + RDATASET_ATTR_STATCOUNT) != 0) +#define STALE_TTL(header, rbtdb) (NXDOMAIN(header) ? 0 : rbtdb->serve_stale_ttl) + +#define RDATASET_ATTR_GET(header, attribute) \ + (atomic_load_acquire(&(header)->attributes) & attribute) +#define RDATASET_ATTR_SET(header, attribute) \ + atomic_fetch_or_release(&(header)->attributes, attribute) +#define RDATASET_ATTR_CLR(header, attribute) \ + atomic_fetch_and_release(&(header)->attributes, ~(attribute)) + +#define ACTIVE(header, now) \ + (((header)->rdh_ttl > (now)) || \ + ((header)->rdh_ttl == (now) && ZEROTTL(header))) + +#define DEFAULT_NODE_LOCK_COUNT 7 /*%< Should be prime. */ +#define RBTDB_GLUE_TABLE_INIT_BITS 2U +#define RBTDB_GLUE_TABLE_MAX_BITS 32U +#define RBTDB_GLUE_TABLE_OVERCOMMIT 3 + +#define GOLDEN_RATIO_32 0x61C88647 +#define HASHSIZE(bits) (UINT64_C(1) << (bits)) + +static uint32_t +hash_32(uint32_t val, unsigned int bits) { + REQUIRE(bits <= RBTDB_GLUE_TABLE_MAX_BITS); + /* High bits are more random. */ + return (val * GOLDEN_RATIO_32 >> (32 - bits)); +} + +#define EXPIREDOK(rbtiterator) \ + (((rbtiterator)->common.options & DNS_DB_EXPIREDOK) != 0) + +#define STALEOK(rbtiterator) \ + (((rbtiterator)->common.options & DNS_DB_STALEOK) != 0) + +/*% + * Number of buckets for cache DB entries (locks, LRU lists, TTL heaps). + * There is a tradeoff issue about configuring this value: if this is too + * small, it may cause heavier contention between threads; if this is too large, + * LRU purge algorithm won't work well (entries tend to be purged prematurely). + * The default value should work well for most environments, but this can + * also be configurable at compilation time via the + * DNS_RBTDB_CACHE_NODE_LOCK_COUNT variable. This value must be larger than + * 1 due to the assumption of overmem_purge(). + */ +#ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT +#if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 +#error "DNS_RBTDB_CACHE_NODE_LOCK_COUNT must be larger than 1" +#else /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */ +#define DEFAULT_CACHE_NODE_LOCK_COUNT DNS_RBTDB_CACHE_NODE_LOCK_COUNT +#endif /* if DNS_RBTDB_CACHE_NODE_LOCK_COUNT <= 1 */ +#else /* ifdef DNS_RBTDB_CACHE_NODE_LOCK_COUNT */ +#define DEFAULT_CACHE_NODE_LOCK_COUNT 17 +#endif /* DNS_RBTDB_CACHE_NODE_LOCK_COUNT */ + +typedef struct { + nodelock_t lock; + /* Protected in the refcount routines. */ + isc_refcount_t references; + /* Locked by lock. */ + bool exiting; +} rbtdb_nodelock_t; + +typedef struct rbtdb_changed { + dns_rbtnode_t *node; + bool dirty; + ISC_LINK(struct rbtdb_changed) link; +} rbtdb_changed_t; + +typedef ISC_LIST(rbtdb_changed_t) rbtdb_changedlist_t; + +typedef enum { dns_db_insecure, dns_db_partial, dns_db_secure } dns_db_secure_t; + +typedef struct dns_rbtdb dns_rbtdb_t; + +/* Reason for expiring a record from cache */ +typedef enum { expire_lru, expire_ttl, expire_flush } expire_t; + +typedef struct rbtdb_glue rbtdb_glue_t; + +typedef struct rbtdb_glue_table_node { + struct rbtdb_glue_table_node *next; + dns_rbtnode_t *node; + rbtdb_glue_t *glue_list; +} rbtdb_glue_table_node_t; + +typedef enum { + rdataset_ttl_fresh, + rdataset_ttl_stale, + rdataset_ttl_ancient +} rdataset_ttl_t; + +typedef struct rbtdb_version { + /* Not locked */ + rbtdb_serial_t serial; + dns_rbtdb_t *rbtdb; + /* + * Protected in the refcount routines. + * XXXJT: should we change the lock policy based on the refcount + * performance? + */ + isc_refcount_t references; + /* Locked by database lock. */ + bool writer; + bool commit_ok; + rbtdb_changedlist_t changed_list; + rdatasetheaderlist_t resigned_list; + ISC_LINK(struct rbtdb_version) link; + dns_db_secure_t secure; + bool havensec3; + /* NSEC3 parameters */ + dns_hash_t hash; + uint8_t flags; + uint16_t iterations; + uint8_t salt_length; + unsigned char salt[DNS_NSEC3_SALTSIZE]; + + /* + * records and xfrsize are covered by rwlock. + */ + isc_rwlock_t rwlock; + uint64_t records; + uint64_t xfrsize; + + isc_rwlock_t glue_rwlock; + size_t glue_table_bits; + size_t glue_table_nodecount; + rbtdb_glue_table_node_t **glue_table; +} rbtdb_version_t; + +typedef ISC_LIST(rbtdb_version_t) rbtdb_versionlist_t; + +struct dns_rbtdb { + /* Unlocked. */ + dns_db_t common; + /* Locks the data in this struct */ + isc_rwlock_t lock; + /* Locks the tree structure (prevents nodes appearing/disappearing) */ + isc_rwlock_t tree_lock; + /* Locks for individual tree nodes */ + unsigned int node_lock_count; + rbtdb_nodelock_t *node_locks; + dns_rbtnode_t *origin_node; + dns_rbtnode_t *nsec3_origin_node; + dns_stats_t *rrsetstats; /* cache DB only */ + isc_stats_t *cachestats; /* cache DB only */ + isc_stats_t *gluecachestats; /* zone DB only */ + /* Locked by lock. */ + unsigned int active; + isc_refcount_t references; + unsigned int attributes; + rbtdb_serial_t current_serial; + rbtdb_serial_t least_serial; + rbtdb_serial_t next_serial; + rbtdb_version_t *current_version; + rbtdb_version_t *future_version; + rbtdb_versionlist_t open_versions; + isc_task_t *task; + dns_dbnode_t *soanode; + dns_dbnode_t *nsnode; + + /* + * Maximum length of time to keep using a stale answer past its + * normal TTL expiry. + */ + dns_ttl_t serve_stale_ttl; + + /* + * The time after a failed lookup, where stale answers from cache + * may be used directly in a DNS response without attempting a + * new iterative lookup. + */ + uint32_t serve_stale_refresh; + + /* + * This is a linked list used to implement the LRU cache. There will + * be node_lock_count linked lists here. Nodes in bucket 1 will be + * placed on the linked list rdatasets[1]. + */ + rdatasetheaderlist_t *rdatasets; + + /*% + * Temporary storage for stale cache nodes and dynamically deleted + * nodes that await being cleaned up. + */ + rbtnodelist_t *deadnodes; + + /* + * Heaps. These are used for TTL based expiry in a cache, + * or for zone resigning in a zone DB. hmctx is the memory + * context to use for the heap (which differs from the main + * database memory context in the case of a cache). + */ + isc_mem_t *hmctx; + isc_heap_t **heaps; + + /* Locked by tree_lock. */ + dns_rbt_t *tree; + dns_rbt_t *nsec; + dns_rbt_t *nsec3; + + /* Unlocked */ + unsigned int quantum; +}; + +#define RBTDB_ATTR_LOADED 0x01 +#define RBTDB_ATTR_LOADING 0x02 + +#define KEEPSTALE(rbtdb) ((rbtdb)->serve_stale_ttl > 0) + +/*% + * Search Context + */ +typedef struct { + dns_rbtdb_t *rbtdb; + rbtdb_version_t *rbtversion; + rbtdb_serial_t serial; + unsigned int options; + dns_rbtnodechain_t chain; + bool copy_name; + bool need_cleanup; + bool wild; + dns_rbtnode_t *zonecut; + rdatasetheader_t *zonecut_rdataset; + rdatasetheader_t *zonecut_sigrdataset; + dns_fixedname_t zonecut_name; + isc_stdtime_t now; +} rbtdb_search_t; + +/*% + * Load Context + */ +typedef struct { + dns_rbtdb_t *rbtdb; + isc_stdtime_t now; +} rbtdb_load_t; + +static void +delete_callback(void *data, void *arg); +static void +rdataset_disassociate(dns_rdataset_t *rdataset); +static isc_result_t +rdataset_first(dns_rdataset_t *rdataset); +static isc_result_t +rdataset_next(dns_rdataset_t *rdataset); +static void +rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata); +static void +rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target); +static unsigned int +rdataset_count(dns_rdataset_t *rdataset); +static isc_result_t +rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); +static isc_result_t +rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); +static bool +need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now); +static void +update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now); +static void +expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked, + expire_t reason); +static void +overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, + bool tree_locked); +static void +resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader); +static void +resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, + rdatasetheader_t *header); +static void +prune_tree(isc_task_t *task, isc_event_t *event); +static void +rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust); +static void +rdataset_expire(dns_rdataset_t *rdataset); +static void +rdataset_clearprefetch(dns_rdataset_t *rdataset); +static void +rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name); +static void +rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name); +static isc_result_t +rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version, + dns_message_t *msg); +static void +free_gluetable(rbtdb_version_t *version); +static isc_result_t +nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); + +static dns_rdatasetmethods_t rdataset_methods = { rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, /* addnoqname */ + rdataset_getnoqname, + NULL, /* addclosest */ + rdataset_getclosest, + rdataset_settrust, + rdataset_expire, + rdataset_clearprefetch, + rdataset_setownercase, + rdataset_getownercase, + rdataset_addglue }; + +static dns_rdatasetmethods_t slab_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, /* addnoqname */ + NULL, /* getnoqname */ + NULL, /* addclosest */ + NULL, /* getclosest */ + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL, /* getownercase */ + NULL /* addglue */ +}; + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp); +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator); +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator); +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset); + +static dns_rdatasetitermethods_t rdatasetiter_methods = { + rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next, + rdatasetiter_current +}; + +typedef struct rbtdb_rdatasetiter { + dns_rdatasetiter_t common; + rdatasetheader_t *current; +} rbtdb_rdatasetiter_t; + +/* + * Note that these iterators, unless created with either DNS_DB_NSEC3ONLY or + * DNS_DB_NONSEC3, will transparently move between the last node of the + * "regular" RBT ("chain" field) and the root node of the NSEC3 RBT + * ("nsec3chain" field) of the database in question, as if the latter was a + * successor to the former in lexical order. The "current" field always holds + * the address of either "chain" or "nsec3chain", depending on which RBT is + * being traversed at given time. + */ +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp); +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name); +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name); +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name); + +static dns_dbiteratormethods_t dbiterator_methods = { + dbiterator_destroy, dbiterator_first, dbiterator_last, + dbiterator_seek, dbiterator_prev, dbiterator_next, + dbiterator_current, dbiterator_pause, dbiterator_origin +}; + +#define DELETION_BATCH_MAX 64 + +/* + * If 'paused' is true, then the tree lock is not being held. + */ +typedef struct rbtdb_dbiterator { + dns_dbiterator_t common; + bool paused; + bool new_origin; + isc_rwlocktype_t tree_locked; + isc_result_t result; + dns_fixedname_t name; + dns_fixedname_t origin; + dns_rbtnodechain_t chain; + dns_rbtnodechain_t nsec3chain; + dns_rbtnodechain_t *current; + dns_rbtnode_t *node; + dns_rbtnode_t *deletions[DELETION_BATCH_MAX]; + int delcnt; + bool nsec3only; + bool nonsec3; +} rbtdb_dbiterator_t; + +#define IS_STUB(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_STUB) != 0) +#define IS_CACHE(rbtdb) (((rbtdb)->common.attributes & DNS_DBATTR_CACHE) != 0) + +static void +free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event); +static void +overmem(dns_db_t *db, bool over); +static void +setnsec3parameters(dns_db_t *db, rbtdb_version_t *version); +static void +setownercase(rdatasetheader_t *header, const dns_name_t *name); + +/*% + * 'init_count' is used to initialize 'newheader->count' which inturn + * is used to determine where in the cycle rrset-order cyclic starts. + * We don't lock this as we don't care about simultaneous updates. + * + * Note: + * Both init_count and header->count can be UINT32_MAX. + * The count on the returned rdataset however can't be as + * that indicates that the database does not implement cyclic + * processing. + */ +static atomic_uint_fast32_t init_count = 0; + +/* + * Locking + * + * If a routine is going to lock more than one lock in this module, then + * the locking must be done in the following order: + * + * Tree Lock + * + * Node Lock (Only one from the set may be locked at one time by + * any caller) + * + * Database Lock + * + * Failure to follow this hierarchy can result in deadlock. + */ + +/* + * Deleting Nodes + * + * For zone databases the node for the origin of the zone MUST NOT be deleted. + */ + +/* Fixed RRSet helper macros */ + +#define DNS_RDATASET_LENGTH 2; + +#if DNS_RDATASET_FIXED +#define DNS_RDATASET_ORDER 2 +#define DNS_RDATASET_COUNT (count * 4) +#else /* !DNS_RDATASET_FIXED */ +#define DNS_RDATASET_ORDER 0 +#define DNS_RDATASET_COUNT 0 +#endif /* DNS_RDATASET_FIXED */ + +/* + * DB Routines + */ + +static void +attach(dns_db_t *source, dns_db_t **targetp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)source; + + REQUIRE(VALID_RBTDB(rbtdb)); + + isc_refcount_increment(&rbtdb->references); + + *targetp = source; +} + +static void +free_rbtdb_callback(isc_task_t *task, isc_event_t *event) { + dns_rbtdb_t *rbtdb = event->ev_arg; + + UNUSED(task); + + free_rbtdb(rbtdb, true, event); +} + +static void +update_cachestats(dns_rbtdb_t *rbtdb, isc_result_t result) { + INSIST(IS_CACHE(rbtdb)); + + if (rbtdb->cachestats == NULL) { + return; + } + + switch (result) { + case DNS_R_COVERINGNSEC: + isc_stats_increment(rbtdb->cachestats, + dns_cachestatscounter_coveringnsec); + FALLTHROUGH; + case ISC_R_SUCCESS: + case DNS_R_CNAME: + case DNS_R_DNAME: + case DNS_R_DELEGATION: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + isc_stats_increment(rbtdb->cachestats, + dns_cachestatscounter_hits); + break; + default: + isc_stats_increment(rbtdb->cachestats, + dns_cachestatscounter_misses); + } +} + +static bool +do_stats(rdatasetheader_t *header) { + return (EXISTS(header) && STATCOUNT(header)); +} + +static void +update_rrsetstats(dns_rbtdb_t *rbtdb, const rbtdb_rdatatype_t htype, + const uint_least16_t hattributes, const bool increment) { + dns_rdatastatstype_t statattributes = 0; + dns_rdatastatstype_t base = 0; + dns_rdatastatstype_t type; + rdatasetheader_t *header = &(rdatasetheader_t){ + .type = htype, + .attributes = hattributes, + }; + + if (!do_stats(header)) { + return; + } + + /* At the moment we count statistics only for cache DB */ + INSIST(IS_CACHE(rbtdb)); + + if (NEGATIVE(header)) { + if (NXDOMAIN(header)) { + statattributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN; + } else { + statattributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET; + base = RBTDB_RDATATYPE_EXT(header->type); + } + } else { + base = RBTDB_RDATATYPE_BASE(header->type); + } + + if (STALE(header)) { + statattributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + } + if (ANCIENT(header)) { + statattributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT; + } + + type = DNS_RDATASTATSTYPE_VALUE(base, statattributes); + if (increment) { + dns_rdatasetstats_increment(rbtdb->rrsetstats, type); + } else { + dns_rdatasetstats_decrement(rbtdb->rrsetstats, type); + } +} + +static void +set_ttl(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, dns_ttl_t newttl) { + int idx; + isc_heap_t *heap; + dns_ttl_t oldttl; + + if (!IS_CACHE(rbtdb)) { + header->rdh_ttl = newttl; + return; + } + + oldttl = header->rdh_ttl; + header->rdh_ttl = newttl; + + /* + * It's possible the rbtdb is not a cache. If this is the case, + * we will not have a heap, and we move on. If we do, though, + * we might need to adjust things. + */ + if (header->heap_index == 0 || newttl == oldttl) { + return; + } + idx = header->node->locknum; + if (rbtdb->heaps == NULL || rbtdb->heaps[idx] == NULL) { + return; + } + heap = rbtdb->heaps[idx]; + + if (newttl < oldttl) { + isc_heap_increased(heap, header->heap_index); + } else { + isc_heap_decreased(heap, header->heap_index); + } +} + +/*% + * These functions allow the heap code to rank the priority of each + * element. It returns true if v1 happens "sooner" than v2. + */ +static bool +ttl_sooner(void *v1, void *v2) { + rdatasetheader_t *h1 = v1; + rdatasetheader_t *h2 = v2; + + return (h1->rdh_ttl < h2->rdh_ttl); +} + +/*% + * Return which RRset should be resigned sooner. If the RRsets have the + * same signing time, prefer the other RRset over the SOA RRset. + */ +static bool +resign_sooner(void *v1, void *v2) { + rdatasetheader_t *h1 = v1; + rdatasetheader_t *h2 = v2; + + return (h1->resign < h2->resign || + (h1->resign == h2->resign && h1->resign_lsb < h2->resign_lsb) || + (h1->resign == h2->resign && h1->resign_lsb == h2->resign_lsb && + h2->type == RBTDB_RDATATYPE_SIGSOA)); +} + +/*% + * This function sets the heap index into the header. + */ +static void +set_index(void *what, unsigned int idx) { + rdatasetheader_t *h = what; + + h->heap_index = idx; +} + +/*% + * Work out how many nodes can be deleted in the time between two + * requests to the nameserver. Smooth the resulting number and use it + * as a estimate for the number of nodes to be deleted in the next + * iteration. + */ +static unsigned int +adjust_quantum(unsigned int old, isc_time_t *start) { + unsigned int pps = dns_pps; /* packets per second */ + unsigned int interval; + uint64_t usecs; + isc_time_t end; + unsigned int nodes; + + if (pps < 100) { + pps = 100; + } + isc_time_now(&end); + + interval = 1000000 / pps; /* interval in usec */ + if (interval == 0) { + interval = 1; + } + usecs = isc_time_microdiff(&end, start); + if (usecs == 0) { + /* + * We were unable to measure the amount of time taken. + * Double the nodes deleted next time. + */ + old *= 2; + if (old > 1000) { + old = 1000; + } + return (old); + } + nodes = old * interval; + nodes /= (unsigned int)usecs; + if (nodes == 0) { + nodes = 1; + } else if (nodes > 1000) { + nodes = 1000; + } + + /* Smooth */ + nodes = (nodes + old * 3) / 4; + + if (nodes != old) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "adjust_quantum: old=%d, new=%d", old, nodes); + } + + return (nodes); +} + +static void +free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) { + unsigned int i; + isc_result_t result; + char buf[DNS_NAME_FORMATSIZE]; + dns_rbt_t **treep; + isc_time_t start; + + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) { + overmem((dns_db_t *)rbtdb, (bool)-1); + } + + REQUIRE(rbtdb->current_version != NULL || EMPTY(rbtdb->open_versions)); + REQUIRE(rbtdb->future_version == NULL); + + if (rbtdb->current_version != NULL) { + isc_refcount_decrementz(&rbtdb->current_version->references); + UNLINK(rbtdb->open_versions, rbtdb->current_version, link); + isc_rwlock_destroy(&rbtdb->current_version->glue_rwlock); + isc_refcount_destroy(&rbtdb->current_version->references); + isc_rwlock_destroy(&rbtdb->current_version->rwlock); + isc_mem_put(rbtdb->common.mctx, rbtdb->current_version, + sizeof(rbtdb_version_t)); + } + + /* + * We assume the number of remaining dead nodes is reasonably small; + * the overhead of unlinking all nodes here should be negligible. + */ + for (i = 0; i < rbtdb->node_lock_count; i++) { + dns_rbtnode_t *node; + + node = ISC_LIST_HEAD(rbtdb->deadnodes[i]); + while (node != NULL) { + ISC_LIST_UNLINK(rbtdb->deadnodes[i], node, deadlink); + node = ISC_LIST_HEAD(rbtdb->deadnodes[i]); + } + } + + if (event == NULL) { + rbtdb->quantum = (rbtdb->task != NULL) ? 100 : 0; + } + + for (;;) { + /* + * pick the next tree to (start to) destroy + */ + treep = &rbtdb->tree; + if (*treep == NULL) { + treep = &rbtdb->nsec; + if (*treep == NULL) { + treep = &rbtdb->nsec3; + /* + * we're finished after clear cutting + */ + if (*treep == NULL) { + break; + } + } + } + + isc_time_now(&start); + result = dns_rbt_destroy2(treep, rbtdb->quantum); + if (result == ISC_R_QUOTA) { + INSIST(rbtdb->task != NULL); + if (rbtdb->quantum != 0) { + rbtdb->quantum = adjust_quantum(rbtdb->quantum, + &start); + } + if (event == NULL) { + event = isc_event_allocate( + rbtdb->common.mctx, NULL, + DNS_EVENT_FREESTORAGE, + free_rbtdb_callback, rbtdb, + sizeof(isc_event_t)); + } + isc_task_send(rbtdb->task, &event); + return; + } + INSIST(result == ISC_R_SUCCESS && *treep == NULL); + } + + if (event != NULL) { + isc_event_free(&event); + } + if (log) { + if (dns_name_dynamic(&rbtdb->common.origin)) { + dns_name_format(&rbtdb->common.origin, buf, + sizeof(buf)); + } else { + strlcpy(buf, "", sizeof(buf)); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "done free_rbtdb(%s)", buf); + } + if (dns_name_dynamic(&rbtdb->common.origin)) { + dns_name_free(&rbtdb->common.origin, rbtdb->common.mctx); + } + for (i = 0; i < rbtdb->node_lock_count; i++) { + isc_refcount_destroy(&rbtdb->node_locks[i].references); + NODE_DESTROYLOCK(&rbtdb->node_locks[i].lock); + } + + /* + * Clean up LRU / re-signing order lists. + */ + if (rbtdb->rdatasets != NULL) { + for (i = 0; i < rbtdb->node_lock_count; i++) { + INSIST(ISC_LIST_EMPTY(rbtdb->rdatasets[i])); + } + isc_mem_put(rbtdb->common.mctx, rbtdb->rdatasets, + rbtdb->node_lock_count * + sizeof(rdatasetheaderlist_t)); + } + /* + * Clean up dead node buckets. + */ + if (rbtdb->deadnodes != NULL) { + for (i = 0; i < rbtdb->node_lock_count; i++) { + INSIST(ISC_LIST_EMPTY(rbtdb->deadnodes[i])); + } + isc_mem_put(rbtdb->common.mctx, rbtdb->deadnodes, + rbtdb->node_lock_count * sizeof(rbtnodelist_t)); + } + /* + * Clean up heap objects. + */ + if (rbtdb->heaps != NULL) { + for (i = 0; i < rbtdb->node_lock_count; i++) { + isc_heap_destroy(&rbtdb->heaps[i]); + } + isc_mem_put(rbtdb->hmctx, rbtdb->heaps, + rbtdb->node_lock_count * sizeof(isc_heap_t *)); + } + + if (rbtdb->rrsetstats != NULL) { + dns_stats_detach(&rbtdb->rrsetstats); + } + if (rbtdb->cachestats != NULL) { + isc_stats_detach(&rbtdb->cachestats); + } + if (rbtdb->gluecachestats != NULL) { + isc_stats_detach(&rbtdb->gluecachestats); + } + + isc_mem_put(rbtdb->common.mctx, rbtdb->node_locks, + rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t)); + isc_rwlock_destroy(&rbtdb->tree_lock); + isc_refcount_destroy(&rbtdb->references); + if (rbtdb->task != NULL) { + isc_task_detach(&rbtdb->task); + } + + RBTDB_DESTROYLOCK(&rbtdb->lock); + rbtdb->common.magic = 0; + rbtdb->common.impmagic = 0; + isc_mem_detach(&rbtdb->hmctx); + + INSIST(ISC_LIST_EMPTY(rbtdb->common.update_listeners)); + + isc_mem_putanddetach(&rbtdb->common.mctx, rbtdb, sizeof(*rbtdb)); +} + +static void +maybe_free_rbtdb(dns_rbtdb_t *rbtdb) { + bool want_free = false; + unsigned int i; + unsigned int inactive = 0; + + /* XXX check for open versions here */ + + if (rbtdb->soanode != NULL) { + dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->soanode); + } + if (rbtdb->nsnode != NULL) { + dns_db_detachnode((dns_db_t *)rbtdb, &rbtdb->nsnode); + } + + /* + * The current version's glue table needs to be freed early + * so the nodes are dereferenced before we check the active + * node count below. + */ + if (rbtdb->current_version != NULL) { + free_gluetable(rbtdb->current_version); + } + + /* + * Even though there are no external direct references, there still + * may be nodes in use. + */ + for (i = 0; i < rbtdb->node_lock_count; i++) { + NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write); + rbtdb->node_locks[i].exiting = true; + if (isc_refcount_current(&rbtdb->node_locks[i].references) == 0) + { + inactive++; + } + NODE_UNLOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write); + } + + if (inactive != 0) { + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + rbtdb->active -= inactive; + if (rbtdb->active == 0) { + want_free = true; + } + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + if (want_free) { + char buf[DNS_NAME_FORMATSIZE]; + if (dns_name_dynamic(&rbtdb->common.origin)) { + dns_name_format(&rbtdb->common.origin, buf, + sizeof(buf)); + } else { + strlcpy(buf, "", sizeof(buf)); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "calling free_rbtdb(%s)", buf); + free_rbtdb(rbtdb, true, NULL); + } + } +} + +static void +detach(dns_db_t **dbp) { + REQUIRE(dbp != NULL && VALID_RBTDB((dns_rbtdb_t *)(*dbp))); + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(*dbp); + *dbp = NULL; + + if (isc_refcount_decrement(&rbtdb->references) == 1) { + maybe_free_rbtdb(rbtdb); + } +} + +static void +currentversion(dns_db_t *db, dns_dbversion_t **versionp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *version; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + version = rbtdb->current_version; + isc_refcount_increment(&version->references); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + *versionp = (dns_dbversion_t *)version; +} + +static rbtdb_version_t * +allocate_version(isc_mem_t *mctx, rbtdb_serial_t serial, + unsigned int references, bool writer) { + rbtdb_version_t *version; + size_t size; + + version = isc_mem_get(mctx, sizeof(*version)); + version->serial = serial; + + isc_refcount_init(&version->references, references); + isc_rwlock_init(&version->glue_rwlock, 0, 0); + + version->glue_table_bits = RBTDB_GLUE_TABLE_INIT_BITS; + version->glue_table_nodecount = 0U; + + size = HASHSIZE(version->glue_table_bits) * + sizeof(version->glue_table[0]); + version->glue_table = isc_mem_get(mctx, size); + memset(version->glue_table, 0, size); + + version->writer = writer; + version->commit_ok = false; + ISC_LIST_INIT(version->changed_list); + ISC_LIST_INIT(version->resigned_list); + ISC_LINK_INIT(version, link); + + return (version); +} + +static isc_result_t +newversion(dns_db_t *db, dns_dbversion_t **versionp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *version; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(versionp != NULL && *versionp == NULL); + REQUIRE(rbtdb->future_version == NULL); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + RUNTIME_CHECK(rbtdb->next_serial != 0); /* XXX Error? */ + version = allocate_version(rbtdb->common.mctx, rbtdb->next_serial, 1, + true); + version->rbtdb = rbtdb; + version->commit_ok = true; + version->secure = rbtdb->current_version->secure; + version->havensec3 = rbtdb->current_version->havensec3; + if (version->havensec3) { + version->flags = rbtdb->current_version->flags; + version->iterations = rbtdb->current_version->iterations; + version->hash = rbtdb->current_version->hash; + version->salt_length = rbtdb->current_version->salt_length; + memmove(version->salt, rbtdb->current_version->salt, + version->salt_length); + } else { + version->flags = 0; + version->iterations = 0; + version->hash = 0; + version->salt_length = 0; + memset(version->salt, 0, sizeof(version->salt)); + } + isc_rwlock_init(&version->rwlock, 0, 0); + RWLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read); + version->records = rbtdb->current_version->records; + version->xfrsize = rbtdb->current_version->xfrsize; + RWUNLOCK(&rbtdb->current_version->rwlock, isc_rwlocktype_read); + rbtdb->next_serial++; + rbtdb->future_version = version; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + *versionp = version; + + return (ISC_R_SUCCESS); +} + +static void +attachversion(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *rbtversion = source; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion != NULL && rbtversion->rbtdb == rbtdb); + + isc_refcount_increment(&rbtversion->references); + + *targetp = rbtversion; +} + +static rbtdb_changed_t * +add_changed(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, dns_rbtnode_t *node) { + rbtdb_changed_t *changed; + + /* + * Caller must be holding the node lock if its reference must be + * protected by the lock. + */ + + changed = isc_mem_get(rbtdb->common.mctx, sizeof(*changed)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + + REQUIRE(version->writer); + + if (changed != NULL) { + isc_refcount_increment(&node->references); + changed->node = node; + changed->dirty = false; + ISC_LIST_INITANDAPPEND(version->changed_list, changed, link); + } else { + version->commit_ok = false; + } + + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + return (changed); +} + +static void +free_noqname(isc_mem_t *mctx, struct noqname **noqname) { + if (dns_name_dynamic(&(*noqname)->name)) { + dns_name_free(&(*noqname)->name, mctx); + } + if ((*noqname)->neg != NULL) { + isc_mem_put(mctx, (*noqname)->neg, + dns_rdataslab_size((*noqname)->neg, 0)); + } + if ((*noqname)->negsig != NULL) { + isc_mem_put(mctx, (*noqname)->negsig, + dns_rdataslab_size((*noqname)->negsig, 0)); + } + isc_mem_put(mctx, *noqname, sizeof(**noqname)); + *noqname = NULL; +} + +static void +init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) { + ISC_LINK_INIT(h, link); + h->heap_index = 0; + atomic_init(&h->attributes, 0); + atomic_init(&h->last_refresh_fail_ts, 0); + + STATIC_ASSERT((sizeof(h->attributes) == 2), + "The .attributes field of rdatasetheader_t needs to be " + "16-bit int type exactly."); + +#if TRACE_HEADER + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) { + fprintf(stderr, "initialized header: %p\n", h); + } +#else /* if TRACE_HEADER */ + UNUSED(rbtdb); +#endif /* if TRACE_HEADER */ +} + +static void +update_newheader(rdatasetheader_t *newh, rdatasetheader_t *old) { + if (CASESET(old)) { + uint_least16_t attr = RDATASET_ATTR_GET( + old, + (RDATASET_ATTR_CASESET | RDATASET_ATTR_CASEFULLYLOWER)); + RDATASET_ATTR_SET(newh, attr); + memmove(newh->upper, old->upper, sizeof(old->upper)); + } +} + +static rdatasetheader_t * +new_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx) { + rdatasetheader_t *h; + + h = isc_mem_get(mctx, sizeof(*h)); + +#if TRACE_HEADER + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) { + fprintf(stderr, "allocated header: %p\n", h); + } +#endif /* if TRACE_HEADER */ + memset(h->upper, 0xeb, sizeof(h->upper)); + init_rdataset(rbtdb, h); + h->rdh_ttl = 0; + return (h); +} + +static void +free_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *rdataset) { + unsigned int size; + int idx; + + update_rrsetstats(rbtdb, rdataset->type, + atomic_load_acquire(&rdataset->attributes), false); + + idx = rdataset->node->locknum; + if (ISC_LINK_LINKED(rdataset, link)) { + INSIST(IS_CACHE(rbtdb)); + ISC_LIST_UNLINK(rbtdb->rdatasets[idx], rdataset, link); + } + + if (rdataset->heap_index != 0) { + isc_heap_delete(rbtdb->heaps[idx], rdataset->heap_index); + } + rdataset->heap_index = 0; + + if (rdataset->noqname != NULL) { + free_noqname(mctx, &rdataset->noqname); + } + if (rdataset->closest != NULL) { + free_noqname(mctx, &rdataset->closest); + } + + if (NONEXISTENT(rdataset)) { + size = sizeof(*rdataset); + } else { + size = dns_rdataslab_size((unsigned char *)rdataset, + sizeof(*rdataset)); + } + + isc_mem_put(mctx, rdataset, size); +} + +static void +rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) { + rdatasetheader_t *header, *dcurrent; + bool make_dirty = false; + + /* + * Caller must hold the node lock. + */ + + /* + * We set the IGNORE attribute on rdatasets with serial number + * 'serial'. When the reference count goes to zero, these rdatasets + * will be cleaned up; until that time, they will be ignored. + */ + for (header = node->data; header != NULL; header = header->next) { + if (header->serial == serial) { + RDATASET_ATTR_SET(header, RDATASET_ATTR_IGNORE); + make_dirty = true; + } + for (dcurrent = header->down; dcurrent != NULL; + dcurrent = dcurrent->down) + { + if (dcurrent->serial == serial) { + RDATASET_ATTR_SET(dcurrent, + RDATASET_ATTR_IGNORE); + make_dirty = true; + } + } + } + if (make_dirty) { + node->dirty = 1; + } +} + +static void +mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { + uint_least16_t attributes = atomic_load_acquire(&header->attributes); + uint_least16_t newattributes = 0; + + /* + * If we are already ancient there is nothing to do. + */ + do { + if ((attributes & RDATASET_ATTR_ANCIENT) != 0) { + return; + } + newattributes = attributes | RDATASET_ATTR_ANCIENT; + } while (!atomic_compare_exchange_weak_acq_rel( + &header->attributes, &attributes, newattributes)); + + /* + * Decrement the stats counter for the appropriate RRtype. + * If the STALE attribute is set, this will decrement the + * stale type counter, otherwise it decrements the active + * stats type counter. + */ + update_rrsetstats(rbtdb, header->type, attributes, false); + header->node->dirty = 1; + + /* Increment the stats counter for the ancient RRtype. */ + update_rrsetstats(rbtdb, header->type, newattributes, true); +} + +static void +mark_header_stale(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { + uint_least16_t attributes = atomic_load_acquire(&header->attributes); + uint_least16_t newattributes = 0; + + INSIST((attributes & RDATASET_ATTR_ZEROTTL) == 0); + + /* + * If we are already stale there is nothing to do. + */ + do { + if ((attributes & RDATASET_ATTR_STALE) != 0) { + return; + } + newattributes = attributes | RDATASET_ATTR_STALE; + } while (!atomic_compare_exchange_weak_acq_rel( + &header->attributes, &attributes, newattributes)); + + /* Decrement the stats counter for the appropriate RRtype. + * If the ANCIENT attribute is set (although it is very + * unlikely that an RRset goes from ANCIENT to STALE), this + * will decrement the ancient stale type counter, otherwise it + * decrements the active stats type counter. + */ + + update_rrsetstats(rbtdb, header->type, attributes, false); + update_rrsetstats(rbtdb, header->type, newattributes, true); +} + +static void +clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, + rdatasetheader_t *top) { + rdatasetheader_t *d, *down_next; + + for (d = top->down; d != NULL; d = down_next) { + down_next = d->down; + free_rdataset(rbtdb, mctx, d); + } + top->down = NULL; +} + +static void +clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { + rdatasetheader_t *current, *top_prev, *top_next; + isc_mem_t *mctx = rbtdb->common.mctx; + + /* + * Caller must be holding the node lock. + */ + + top_prev = NULL; + for (current = node->data; current != NULL; current = top_next) { + top_next = current->next; + clean_stale_headers(rbtdb, mctx, current); + /* + * If current is nonexistent, ancient, or stale and + * we are not keeping stale, we can clean it up. + */ + if (NONEXISTENT(current) || ANCIENT(current) || + (STALE(current) && !KEEPSTALE(rbtdb))) + { + if (top_prev != NULL) { + top_prev->next = current->next; + } else { + node->data = current->next; + } + free_rdataset(rbtdb, mctx, current); + } else { + top_prev = current; + } + } + node->dirty = 0; +} + +static void +clean_zone_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + rbtdb_serial_t least_serial) { + rdatasetheader_t *current, *dcurrent, *down_next, *dparent; + rdatasetheader_t *top_prev, *top_next; + isc_mem_t *mctx = rbtdb->common.mctx; + bool still_dirty = false; + + /* + * Caller must be holding the node lock. + */ + REQUIRE(least_serial != 0); + + top_prev = NULL; + for (current = node->data; current != NULL; current = top_next) { + top_next = current->next; + + /* + * First, we clean up any instances of multiple rdatasets + * with the same serial number, or that have the IGNORE + * attribute. + */ + dparent = current; + for (dcurrent = current->down; dcurrent != NULL; + dcurrent = down_next) + { + down_next = dcurrent->down; + INSIST(dcurrent->serial <= dparent->serial); + if (dcurrent->serial == dparent->serial || + IGNORE(dcurrent)) + { + if (down_next != NULL) { + down_next->next = dparent; + } + dparent->down = down_next; + free_rdataset(rbtdb, mctx, dcurrent); + } else { + dparent = dcurrent; + } + } + + /* + * We've now eliminated all IGNORE datasets with the possible + * exception of current, which we now check. + */ + if (IGNORE(current)) { + down_next = current->down; + if (down_next == NULL) { + if (top_prev != NULL) { + top_prev->next = current->next; + } else { + node->data = current->next; + } + free_rdataset(rbtdb, mctx, current); + /* + * current no longer exists, so we can + * just continue with the loop. + */ + continue; + } else { + /* + * Pull up current->down, making it the new + * current. + */ + if (top_prev != NULL) { + top_prev->next = down_next; + } else { + node->data = down_next; + } + down_next->next = top_next; + free_rdataset(rbtdb, mctx, current); + current = down_next; + } + } + + /* + * We now try to find the first down node less than the + * least serial. + */ + dparent = current; + for (dcurrent = current->down; dcurrent != NULL; + dcurrent = down_next) + { + down_next = dcurrent->down; + if (dcurrent->serial < least_serial) { + break; + } + dparent = dcurrent; + } + + /* + * If there is a such an rdataset, delete it and any older + * versions. + */ + if (dcurrent != NULL) { + do { + down_next = dcurrent->down; + INSIST(dcurrent->serial <= least_serial); + free_rdataset(rbtdb, mctx, dcurrent); + dcurrent = down_next; + } while (dcurrent != NULL); + dparent->down = NULL; + } + + /* + * Note. The serial number of 'current' might be less than + * least_serial too, but we cannot delete it because it is + * the most recent version, unless it is a NONEXISTENT + * rdataset. + */ + if (current->down != NULL) { + still_dirty = true; + top_prev = current; + } else { + /* + * If this is a NONEXISTENT rdataset, we can delete it. + */ + if (NONEXISTENT(current)) { + if (top_prev != NULL) { + top_prev->next = current->next; + } else { + node->data = current->next; + } + free_rdataset(rbtdb, mctx, current); + } else { + top_prev = current; + } + } + } + if (!still_dirty) { + node->dirty = 0; + } +} + +/* + * tree_lock(write) must be held. + */ +static void +delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { + dns_rbtnode_t *nsecnode; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result = ISC_R_UNEXPECTED; + + INSIST(!ISC_LINK_LINKED(node, deadlink)); + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { + char printname[DNS_NAME_FORMATSIZE]; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "delete_node(): %p %s (bucket %d)", node, + dns_rbt_formatnodename(node, printname, + sizeof(printname)), + node->locknum); + } + + switch (node->nsec) { + case DNS_RBT_NSEC_NORMAL: + result = dns_rbt_deletenode(rbtdb->tree, node, false); + break; + case DNS_RBT_NSEC_HAS_NSEC: + /* + * Though this may be wasteful, it has to be done before + * node is deleted. + */ + name = dns_fixedname_initname(&fname); + dns_rbt_fullnamefromnode(node, name); + /* + * Delete the corresponding node from the auxiliary NSEC + * tree before deleting from the main tree. + */ + nsecnode = NULL; + result = dns_rbt_findnode(rbtdb->nsec, name, NULL, &nsecnode, + NULL, DNS_RBTFIND_EMPTYDATA, NULL, + NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "delete_node: " + "dns_rbt_findnode(nsec): %s", + isc_result_totext(result)); + } else { + result = dns_rbt_deletenode(rbtdb->nsec, nsecnode, + false); + if (result != ISC_R_SUCCESS) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "delete_node(): " + "dns_rbt_deletenode(nsecnode): %s", + isc_result_totext(result)); + } + } + result = dns_rbt_deletenode(rbtdb->tree, node, false); + break; + case DNS_RBT_NSEC_NSEC: + result = dns_rbt_deletenode(rbtdb->nsec, node, false); + break; + case DNS_RBT_NSEC_NSEC3: + result = dns_rbt_deletenode(rbtdb->nsec3, node, false); + break; + } + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "delete_node(): " + "dns_rbt_deletenode: %s", + isc_result_totext(result)); + } +} + +/* + * Caller must be holding the node lock. + */ +static void +new_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + isc_rwlocktype_t locktype) { + if (locktype == isc_rwlocktype_write && ISC_LINK_LINKED(node, deadlink)) + { + ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node, + deadlink); + } + if (isc_refcount_increment0(&node->references) == 0) { + /* this is the first reference to the node */ + isc_refcount_increment0( + &rbtdb->node_locks[node->locknum].references); + } +} + +/*% + * The tree lock must be held for the result to be valid. + */ +static bool +is_leaf(dns_rbtnode_t *node) { + return (node->parent != NULL && node->parent->down == node && + node->left == NULL && node->right == NULL); +} + +static void +send_to_prune_tree(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + isc_rwlocktype_t locktype) { + isc_event_t *ev; + dns_db_t *db; + + ev = isc_event_allocate(rbtdb->common.mctx, NULL, DNS_EVENT_RBTPRUNE, + prune_tree, node, sizeof(isc_event_t)); + new_reference(rbtdb, node, locktype); + db = NULL; + attach((dns_db_t *)rbtdb, &db); + ev->ev_sender = db; + isc_task_send(rbtdb->task, &ev); +} + +/*% + * Clean up dead nodes. These are nodes which have no references, and + * have no data. They are dead but we could not or chose not to delete + * them when we deleted all the data at that node because we did not want + * to wait for the tree write lock. + * + * The caller must hold a tree write lock and bucketnum'th node (write) lock. + */ +static void +cleanup_dead_nodes(dns_rbtdb_t *rbtdb, int bucketnum) { + dns_rbtnode_t *node; + int count = 10; /* XXXJT: should be adjustable */ + + node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]); + while (node != NULL && count > 0) { + ISC_LIST_UNLINK(rbtdb->deadnodes[bucketnum], node, deadlink); + + /* + * We might have reactivated this node without a tree write + * lock, so we couldn't remove this node from deadnodes then + * and we have to do it now. + */ + if (isc_refcount_current(&node->references) != 0 || + node->data != NULL) + { + node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]); + count--; + continue; + } + + if (is_leaf(node) && rbtdb->task != NULL) { + send_to_prune_tree(rbtdb, node, isc_rwlocktype_write); + } else if (node->down == NULL && node->data == NULL) { + /* + * Not a interior node and not needing to be + * reactivated. + */ + delete_node(rbtdb, node); + } else if (node->data == NULL) { + /* + * A interior node without data. Leave linked to + * to be cleaned up when node->down becomes NULL. + */ + ISC_LIST_APPEND(rbtdb->deadnodes[bucketnum], node, + deadlink); + } + node = ISC_LIST_HEAD(rbtdb->deadnodes[bucketnum]); + count--; + } +} + +/* + * This function is assumed to be called when a node is newly referenced + * and can be in the deadnode list. In that case the node must be retrieved + * from the list because it is going to be used. In addition, if the caller + * happens to hold a write lock on the tree, it's a good chance to purge dead + * nodes. + * Note: while a new reference is gained in multiple places, there are only very + * few cases where the node can be in the deadnode list (only empty nodes can + * have been added to the list). + */ +static void +reactivate_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + isc_rwlocktype_t treelocktype) { + isc_rwlocktype_t locktype = isc_rwlocktype_read; + nodelock_t *nodelock = &rbtdb->node_locks[node->locknum].lock; + bool maybe_cleanup = false; + + POST(locktype); + + NODE_LOCK(nodelock, locktype); + + /* + * Check if we can possibly cleanup the dead node. If so, upgrade + * the node lock below to perform the cleanup. + */ + if (!ISC_LIST_EMPTY(rbtdb->deadnodes[node->locknum]) && + treelocktype == isc_rwlocktype_write) + { + maybe_cleanup = true; + } + + if (ISC_LINK_LINKED(node, deadlink) || maybe_cleanup) { + /* + * Upgrade the lock and test if we still need to unlink. + */ + NODE_UNLOCK(nodelock, locktype); + locktype = isc_rwlocktype_write; + POST(locktype); + NODE_LOCK(nodelock, locktype); + if (ISC_LINK_LINKED(node, deadlink)) { + ISC_LIST_UNLINK(rbtdb->deadnodes[node->locknum], node, + deadlink); + } + if (maybe_cleanup) { + cleanup_dead_nodes(rbtdb, node->locknum); + } + } + + new_reference(rbtdb, node, locktype); + + NODE_UNLOCK(nodelock, locktype); +} + +/* + * Caller must be holding the node lock; either the "strong", read or write + * lock. Note that the lock must be held even when node references are + * atomically modified; in that case the decrement operation itself does not + * have to be protected, but we must avoid a race condition where multiple + * threads are decreasing the reference to zero simultaneously and at least + * one of them is going to free the node. + * + * This function returns true if and only if the node reference decreases + * to zero. + * + * NOTE: Decrementing the reference count of a node to zero does not mean it + * will be immediately freed. + */ +static bool +decrement_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + rbtdb_serial_t least_serial, isc_rwlocktype_t nlock, + isc_rwlocktype_t tlock, bool pruning) { + isc_result_t result; + bool write_locked; + bool locked = tlock != isc_rwlocktype_none; + rbtdb_nodelock_t *nodelock; + int bucket = node->locknum; + bool no_reference = true; + uint_fast32_t refs; + + nodelock = &rbtdb->node_locks[bucket]; + +#define KEEP_NODE(n, r, l) \ + ((n)->data != NULL || ((l) && (n)->down != NULL) || \ + (n) == (r)->origin_node || (n) == (r)->nsec3_origin_node) + + /* Handle easy and typical case first. */ + if (!node->dirty && KEEP_NODE(node, rbtdb, locked)) { + if (isc_refcount_decrement(&node->references) == 1) { + refs = isc_refcount_decrement(&nodelock->references); + INSIST(refs > 0); + return (true); + } else { + return (false); + } + } + + /* Upgrade the lock? */ + if (nlock == isc_rwlocktype_read) { + NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read); + NODE_LOCK(&nodelock->lock, isc_rwlocktype_write); + } + + if (isc_refcount_decrement(&node->references) > 1) { + /* Restore the lock? */ + if (nlock == isc_rwlocktype_read) { + NODE_DOWNGRADE(&nodelock->lock); + } + return (false); + } + + if (node->dirty) { + if (IS_CACHE(rbtdb)) { + clean_cache_node(rbtdb, node); + } else { + if (least_serial == 0) { + /* + * Caller doesn't know the least serial. + * Get it. + */ + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + least_serial = rbtdb->least_serial; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + } + clean_zone_node(rbtdb, node, least_serial); + } + } + + /* + * Attempt to switch to a write lock on the tree. If this fails, + * we will add this node to a linked list of nodes in this locking + * bucket which we will free later. + */ + if (tlock != isc_rwlocktype_write) { + /* + * Locking hierarchy notwithstanding, we don't need to free + * the node lock before acquiring the tree write lock because + * we only do a trylock. + */ + if (tlock == isc_rwlocktype_read) { + result = isc_rwlock_tryupgrade(&rbtdb->tree_lock); + } else { + result = isc_rwlock_trylock(&rbtdb->tree_lock, + isc_rwlocktype_write); + } + RUNTIME_CHECK(result == ISC_R_SUCCESS || + result == ISC_R_LOCKBUSY); + + write_locked = (result == ISC_R_SUCCESS); + } else { + write_locked = true; + } + + refs = isc_refcount_decrement(&nodelock->references); + INSIST(refs > 0); + + if (KEEP_NODE(node, rbtdb, locked || write_locked)) { + goto restore_locks; + } + +#undef KEEP_NODE + + if (write_locked) { + /* + * We can now delete the node. + */ + + /* + * If this node is the only one in the level it's in, deleting + * this node may recursively make its parent the only node in + * the parent level; if so, and if no one is currently using + * the parent node, this is almost the only opportunity to + * clean it up. But the recursive cleanup is not that trivial + * since the child and parent may be in different lock buckets, + * which would cause a lock order reversal problem. To avoid + * the trouble, we'll dispatch a separate event for batch + * cleaning. We need to check whether we're deleting the node + * as a result of pruning to avoid infinite dispatching. + * Note: pruning happens only when a task has been set for the + * rbtdb. If the user of the rbtdb chooses not to set a task, + * it's their responsibility to purge stale leaves (e.g. by + * periodic walk-through). + */ + if (!pruning && is_leaf(node) && rbtdb->task != NULL) { + send_to_prune_tree(rbtdb, node, isc_rwlocktype_write); + no_reference = false; + } else { + delete_node(rbtdb, node); + } + } else { + INSIST(node->data == NULL); + if (!ISC_LINK_LINKED(node, deadlink)) { + ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node, + deadlink); + } + } + +restore_locks: + /* Restore the lock? */ + if (nlock == isc_rwlocktype_read) { + NODE_DOWNGRADE(&nodelock->lock); + } + + /* + * Relock a read lock, or unlock the write lock if no lock was held. + */ + if (tlock == isc_rwlocktype_none) { + if (write_locked) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + } + } + + if (tlock == isc_rwlocktype_read) { + if (write_locked) { + isc_rwlock_downgrade(&rbtdb->tree_lock); + } + } + + return (no_reference); +} + +/* + * Prune the tree by recursively cleaning-up single leaves. In the worst + * case, the number of iteration is the number of tree levels, which is at + * most the maximum number of domain name labels, i.e, 127. In practice, this + * should be much smaller (only a few times), and even the worst case would be + * acceptable for a single event. + */ +static void +prune_tree(isc_task_t *task, isc_event_t *event) { + dns_rbtdb_t *rbtdb = event->ev_sender; + dns_rbtnode_t *node = event->ev_arg; + dns_rbtnode_t *parent; + unsigned int locknum; + + UNUSED(task); + + isc_event_free(&event); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + locknum = node->locknum; + NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); + do { + parent = node->parent; + decrement_reference(rbtdb, node, 0, isc_rwlocktype_write, + isc_rwlocktype_write, true); + + if (parent != NULL && parent->down == NULL) { + /* + * node was the only down child of the parent and has + * just been removed. We'll then need to examine the + * parent. Keep the lock if possible; otherwise, + * release the old lock and acquire one for the parent. + */ + if (parent->locknum != locknum) { + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + locknum = parent->locknum; + NODE_LOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + } + + /* + * We need to gain a reference to the node before + * decrementing it in the next iteration. + */ + if (ISC_LINK_LINKED(parent, deadlink)) { + ISC_LIST_UNLINK(rbtdb->deadnodes[locknum], + parent, deadlink); + } + new_reference(rbtdb, parent, isc_rwlocktype_write); + } else { + parent = NULL; + } + + node = parent; + } while (node != NULL); + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + + detach((dns_db_t **)&rbtdb); +} + +static void +make_least_version(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, + rbtdb_changedlist_t *cleanup_list) { + /* + * Caller must be holding the database lock. + */ + + rbtdb->least_serial = version->serial; + *cleanup_list = version->changed_list; + ISC_LIST_INIT(version->changed_list); +} + +static void +cleanup_nondirty(rbtdb_version_t *version, rbtdb_changedlist_t *cleanup_list) { + rbtdb_changed_t *changed, *next_changed; + + /* + * If the changed record is dirty, then + * an update created multiple versions of + * a given rdataset. We keep this list + * until we're the least open version, at + * which point it's safe to get rid of any + * older versions. + * + * If the changed record isn't dirty, then + * we don't need it anymore since we're + * committing and not rolling back. + * + * The caller must be holding the database lock. + */ + for (changed = HEAD(version->changed_list); changed != NULL; + changed = next_changed) + { + next_changed = NEXT(changed, link); + if (!changed->dirty) { + UNLINK(version->changed_list, changed, link); + APPEND(*cleanup_list, changed, link); + } + } +} + +static void +iszonesecure(dns_db_t *db, rbtdb_version_t *version, dns_dbnode_t *origin) { + dns_rdataset_t keyset; + dns_rdataset_t nsecset, signsecset; + bool haszonekey = false; + bool hasnsec = false; + isc_result_t result; + + dns_rdataset_init(&keyset); + result = dns_db_findrdataset(db, origin, version, dns_rdatatype_dnskey, + 0, 0, &keyset, NULL); + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_first(&keyset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t keyrdata = DNS_RDATA_INIT; + dns_rdataset_current(&keyset, &keyrdata); + if (dns_zonekey_iszonekey(&keyrdata)) { + haszonekey = true; + break; + } + result = dns_rdataset_next(&keyset); + } + dns_rdataset_disassociate(&keyset); + } + if (!haszonekey) { + version->secure = dns_db_insecure; + version->havensec3 = false; + return; + } + + dns_rdataset_init(&nsecset); + dns_rdataset_init(&signsecset); + result = dns_db_findrdataset(db, origin, version, dns_rdatatype_nsec, 0, + 0, &nsecset, &signsecset); + if (result == ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&signsecset)) { + hasnsec = true; + dns_rdataset_disassociate(&signsecset); + } + dns_rdataset_disassociate(&nsecset); + } + + setnsec3parameters(db, version); + + /* + * Do we have a valid NSEC/NSEC3 chain? + */ + if (version->havensec3 || hasnsec) { + version->secure = dns_db_secure; + } else { + version->secure = dns_db_insecure; + } +} + +/*%< + * Walk the origin node looking for NSEC3PARAM records. + * Cache the nsec3 parameters. + */ +static void +setnsec3parameters(dns_db_t *db, rbtdb_version_t *version) { + dns_rbtnode_t *node; + dns_rdata_nsec3param_t nsec3param; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t region; + isc_result_t result; + rdatasetheader_t *header, *header_next; + unsigned char *raw; /* RDATASLAB */ + unsigned int count, length; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + version->havensec3 = false; + node = rbtdb->origin_node; + NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + do { + if (header->serial <= version->serial && + !IGNORE(header)) + { + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + + if (header != NULL && + (header->type == dns_rdatatype_nsec3param)) + { + /* + * Find A NSEC3PARAM with a supported algorithm. + */ + raw = (unsigned char *)header + sizeof(*header); + count = raw[0] * 256 + raw[1]; /* count */ + raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH; + while (count-- > 0U) { + length = raw[0] * 256 + raw[1]; + raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH; + region.base = raw; + region.length = length; + raw += length; + dns_rdata_fromregion( + &rdata, rbtdb->common.rdclass, + dns_rdatatype_nsec3param, ®ion); + result = dns_rdata_tostruct(&rdata, &nsec3param, + NULL); + INSIST(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + if (nsec3param.hash != DNS_NSEC3_UNKNOWNALG && + !dns_nsec3_supportedhash(nsec3param.hash)) + { + continue; + } + + if (nsec3param.flags != 0) { + continue; + } + + memmove(version->salt, nsec3param.salt, + nsec3param.salt_length); + version->hash = nsec3param.hash; + version->salt_length = nsec3param.salt_length; + version->iterations = nsec3param.iterations; + version->flags = nsec3param.flags; + version->havensec3 = true; + /* + * Look for a better algorithm than the + * unknown test algorithm. + */ + if (nsec3param.hash != DNS_NSEC3_UNKNOWNALG) { + goto unlock; + } + } + } + } +unlock: + NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); +} + +static void +cleanup_dead_nodes_callback(isc_task_t *task, isc_event_t *event) { + dns_rbtdb_t *rbtdb = event->ev_arg; + bool again = false; + unsigned int locknum; + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + for (locknum = 0; locknum < rbtdb->node_lock_count; locknum++) { + NODE_LOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + cleanup_dead_nodes(rbtdb, locknum); + if (ISC_LIST_HEAD(rbtdb->deadnodes[locknum]) != NULL) { + again = true; + } + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + if (again) { + isc_task_send(task, &event); + } else { + isc_event_free(&event); + if (isc_refcount_decrement(&rbtdb->references) == 1) { + (void)isc_refcount_current(&rbtdb->references); + maybe_free_rbtdb(rbtdb); + } + } +} + +static void +closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *version, *cleanup_version, *least_greater; + bool rollback = false; + rbtdb_changedlist_t cleanup_list; + rdatasetheaderlist_t resigned_list; + rbtdb_changed_t *changed, *next_changed; + rbtdb_serial_t serial, least_serial; + dns_rbtnode_t *rbtnode; + rdatasetheader_t *header; + + REQUIRE(VALID_RBTDB(rbtdb)); + version = (rbtdb_version_t *)*versionp; + INSIST(version->rbtdb == rbtdb); + + cleanup_version = NULL; + ISC_LIST_INIT(cleanup_list); + ISC_LIST_INIT(resigned_list); + + if (isc_refcount_decrement(&version->references) > 1) { + /* typical and easy case first */ + if (commit) { + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + INSIST(!version->writer); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + } + goto end; + } + + /* + * Update the zone's secure status in version before making + * it the current version. + */ + if (version->writer && commit && !IS_CACHE(rbtdb)) { + iszonesecure(db, version, rbtdb->origin_node); + } + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + serial = version->serial; + if (version->writer) { + if (commit) { + unsigned cur_ref; + rbtdb_version_t *cur_version; + + INSIST(version->commit_ok); + INSIST(version == rbtdb->future_version); + /* + * The current version is going to be replaced. + * Release the (likely last) reference to it from the + * DB itself and unlink it from the open list. + */ + cur_version = rbtdb->current_version; + cur_ref = isc_refcount_decrement( + &cur_version->references); + if (cur_ref == 1) { + (void)isc_refcount_current( + &cur_version->references); + if (cur_version->serial == rbtdb->least_serial) + { + INSIST(EMPTY( + cur_version->changed_list)); + } + UNLINK(rbtdb->open_versions, cur_version, link); + } + if (EMPTY(rbtdb->open_versions)) { + /* + * We're going to become the least open + * version. + */ + make_least_version(rbtdb, version, + &cleanup_list); + } else { + /* + * Some other open version is the + * least version. We can't cleanup + * records that were changed in this + * version because the older versions + * may still be in use by an open + * version. + * + * We can, however, discard the + * changed records for things that + * we've added that didn't exist in + * prior versions. + */ + cleanup_nondirty(version, &cleanup_list); + } + /* + * If the (soon to be former) current version + * isn't being used by anyone, we can clean + * it up. + */ + if (cur_ref == 1) { + cleanup_version = cur_version; + APPENDLIST(version->changed_list, + cleanup_version->changed_list, link); + } + /* + * Become the current version. + */ + version->writer = false; + rbtdb->current_version = version; + rbtdb->current_serial = version->serial; + rbtdb->future_version = NULL; + + /* + * Keep the current version in the open list, and + * gain a reference for the DB itself (see the DB + * creation function below). This must be the only + * case where we need to increment the counter from + * zero and need to use isc_refcount_increment0(). + */ + INSIST(isc_refcount_increment0(&version->references) == + 0); + PREPEND(rbtdb->open_versions, rbtdb->current_version, + link); + resigned_list = version->resigned_list; + ISC_LIST_INIT(version->resigned_list); + } else { + /* + * We're rolling back this transaction. + */ + cleanup_list = version->changed_list; + ISC_LIST_INIT(version->changed_list); + resigned_list = version->resigned_list; + ISC_LIST_INIT(version->resigned_list); + rollback = true; + cleanup_version = version; + rbtdb->future_version = NULL; + } + } else { + if (version != rbtdb->current_version) { + /* + * There are no external or internal references + * to this version and it can be cleaned up. + */ + cleanup_version = version; + + /* + * Find the version with the least serial + * number greater than ours. + */ + least_greater = PREV(version, link); + if (least_greater == NULL) { + least_greater = rbtdb->current_version; + } + + INSIST(version->serial < least_greater->serial); + /* + * Is this the least open version? + */ + if (version->serial == rbtdb->least_serial) { + /* + * Yes. Install the new least open + * version. + */ + make_least_version(rbtdb, least_greater, + &cleanup_list); + } else { + /* + * Add any unexecuted cleanups to + * those of the least greater version. + */ + APPENDLIST(least_greater->changed_list, + version->changed_list, link); + } + } else if (version->serial == rbtdb->least_serial) { + INSIST(EMPTY(version->changed_list)); + } + UNLINK(rbtdb->open_versions, version, link); + } + least_serial = rbtdb->least_serial; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + if (cleanup_version != NULL) { + INSIST(EMPTY(cleanup_version->changed_list)); + free_gluetable(cleanup_version); + isc_rwlock_destroy(&cleanup_version->glue_rwlock); + isc_rwlock_destroy(&cleanup_version->rwlock); + isc_mem_put(rbtdb->common.mctx, cleanup_version, + sizeof(*cleanup_version)); + } + + /* + * Commit/rollback re-signed headers. + */ + for (header = HEAD(resigned_list); header != NULL; + header = HEAD(resigned_list)) + { + nodelock_t *lock; + + ISC_LIST_UNLINK(resigned_list, header, link); + + lock = &rbtdb->node_locks[header->node->locknum].lock; + NODE_LOCK(lock, isc_rwlocktype_write); + if (rollback && !IGNORE(header)) { + resign_insert(rbtdb, header->node->locknum, header); + } + decrement_reference(rbtdb, header->node, least_serial, + isc_rwlocktype_write, isc_rwlocktype_none, + false); + NODE_UNLOCK(lock, isc_rwlocktype_write); + } + + if (!EMPTY(cleanup_list)) { + isc_event_t *event = NULL; + isc_rwlocktype_t tlock = isc_rwlocktype_none; + + if (rbtdb->task != NULL) { + event = isc_event_allocate(rbtdb->common.mctx, NULL, + DNS_EVENT_RBTDEADNODES, + cleanup_dead_nodes_callback, + rbtdb, sizeof(isc_event_t)); + } + if (event == NULL) { + /* + * We acquire a tree write lock here in order to make + * sure that stale nodes will be removed in + * decrement_reference(). If we didn't have the lock, + * those nodes could miss the chance to be removed + * until the server stops. The write lock is + * expensive, but this event should be rare enough + * to justify the cost. + */ + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + tlock = isc_rwlocktype_write; + } + + for (changed = HEAD(cleanup_list); changed != NULL; + changed = next_changed) + { + nodelock_t *lock; + + next_changed = NEXT(changed, link); + rbtnode = changed->node; + lock = &rbtdb->node_locks[rbtnode->locknum].lock; + + NODE_LOCK(lock, isc_rwlocktype_write); + /* + * This is a good opportunity to purge any dead nodes, + * so use it. + */ + if (event == NULL) { + cleanup_dead_nodes(rbtdb, rbtnode->locknum); + } + + if (rollback) { + rollback_node(rbtnode, serial); + } + decrement_reference(rbtdb, rbtnode, least_serial, + isc_rwlocktype_write, tlock, false); + + NODE_UNLOCK(lock, isc_rwlocktype_write); + + isc_mem_put(rbtdb->common.mctx, changed, + sizeof(*changed)); + } + if (event != NULL) { + isc_refcount_increment(&rbtdb->references); + isc_task_send(rbtdb->task, &event); + } else { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + } + } + +end: + *versionp = NULL; +} + +/* + * Add the necessary magic for the wildcard name 'name' + * to be found in 'rbtdb'. + * + * In order for wildcard matching to work correctly in + * zone_find(), we must ensure that a node for the wildcarding + * level exists in the database, and has its 'find_callback' + * and 'wild' bits set. + * + * E.g. if the wildcard name is "*.sub.example." then we + * must ensure that "sub.example." exists and is marked as + * a wildcard level. + * + * tree_lock(write) must be held. + */ +static isc_result_t +add_wildcard_magic(dns_rbtdb_t *rbtdb, const dns_name_t *name, bool lock) { + isc_result_t result; + dns_name_t foundname; + dns_offsets_t offsets; + unsigned int n; + dns_rbtnode_t *node = NULL; + + dns_name_init(&foundname, offsets); + n = dns_name_countlabels(name); + INSIST(n >= 2); + n--; + dns_name_getlabelsequence(name, 1, n, &foundname); + result = dns_rbt_addnode(rbtdb->tree, &foundname, &node); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + return (result); + } + if (result == ISC_R_SUCCESS) { + node->nsec = DNS_RBT_NSEC_NORMAL; + } + node->find_callback = 1; + if (lock) { + NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, + isc_rwlocktype_write); + } + node->wild = 1; + if (lock) { + NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock, + isc_rwlocktype_write); + } + return (ISC_R_SUCCESS); +} + +/* + * tree_lock(write) must be held. + */ +static isc_result_t +add_empty_wildcards(dns_rbtdb_t *rbtdb, const dns_name_t *name, bool lock) { + isc_result_t result; + dns_name_t foundname; + dns_offsets_t offsets; + unsigned int n, l, i; + + dns_name_init(&foundname, offsets); + n = dns_name_countlabels(name); + l = dns_name_countlabels(&rbtdb->common.origin); + i = l + 1; + while (i < n) { + dns_rbtnode_t *node = NULL; /* dummy */ + dns_name_getlabelsequence(name, n - i, i, &foundname); + if (dns_name_iswildcard(&foundname)) { + result = add_wildcard_magic(rbtdb, &foundname, lock); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_rbt_addnode(rbtdb->tree, &foundname, + &node); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + return (result); + } + if (result == ISC_R_SUCCESS) { + node->nsec = DNS_RBT_NSEC_NORMAL; + } + } + i++; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnodeintree(dns_rbtdb_t *rbtdb, dns_rbt_t *tree, const dns_name_t *name, + bool create, dns_dbnode_t **nodep) { + dns_rbtnode_t *node = NULL; + dns_name_t nodename; + isc_result_t result; + isc_rwlocktype_t locktype = isc_rwlocktype_read; + + INSIST(tree == rbtdb->tree || tree == rbtdb->nsec3); + + dns_name_init(&nodename, NULL); + RWLOCK(&rbtdb->tree_lock, locktype); + result = dns_rbt_findnode(tree, name, NULL, &node, NULL, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result != ISC_R_SUCCESS) { + RWUNLOCK(&rbtdb->tree_lock, locktype); + if (!create) { + if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + return (result); + } + /* + * It would be nice to try to upgrade the lock instead of + * unlocking then relocking. + */ + locktype = isc_rwlocktype_write; + RWLOCK(&rbtdb->tree_lock, locktype); + node = NULL; + result = dns_rbt_addnode(tree, name, &node); + if (result == ISC_R_SUCCESS) { + dns_rbt_namefromnode(node, &nodename); + node->locknum = node->hashval % rbtdb->node_lock_count; + if (tree == rbtdb->tree) { + add_empty_wildcards(rbtdb, name, true); + + if (dns_name_iswildcard(name)) { + result = add_wildcard_magic(rbtdb, name, + true); + if (result != ISC_R_SUCCESS) { + RWUNLOCK(&rbtdb->tree_lock, + locktype); + return (result); + } + } + } + if (tree == rbtdb->nsec3) { + node->nsec = DNS_RBT_NSEC_NSEC3; + } + } else if (result != ISC_R_EXISTS) { + RWUNLOCK(&rbtdb->tree_lock, locktype); + return (result); + } + } + + if (tree == rbtdb->nsec3) { + INSIST(node->nsec == DNS_RBT_NSEC_NSEC3); + } + + reactivate_node(rbtdb, node, locktype); + + RWUNLOCK(&rbtdb->tree_lock, locktype); + + *nodep = (dns_dbnode_t *)node; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + return (findnodeintree(rbtdb, rbtdb->tree, name, create, nodep)); +} + +static isc_result_t +findnsec3node(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + return (findnodeintree(rbtdb, rbtdb->nsec3, name, create, nodep)); +} + +static isc_result_t +zone_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) { + rbtdb_search_t *search = arg; + rdatasetheader_t *header, *header_next; + rdatasetheader_t *dname_header, *sigdname_header, *ns_header; + rdatasetheader_t *found; + isc_result_t result; + dns_rbtnode_t *onode; + + /* + * We only want to remember the topmost zone cut, since it's the one + * that counts, so we'll just continue if we've already found a + * zonecut. + */ + if (search->zonecut != NULL) { + return (DNS_R_CONTINUE); + } + + found = NULL; + result = DNS_R_CONTINUE; + onode = search->rbtdb->origin_node; + + NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + + /* + * Look for an NS or DNAME rdataset active in our version. + */ + ns_header = NULL; + dname_header = NULL; + sigdname_header = NULL; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (header->type == dns_rdatatype_ns || + header->type == dns_rdatatype_dname || + header->type == RBTDB_RDATATYPE_SIGDNAME) + { + do { + if (header->serial <= search->serial && + !IGNORE(header)) + { + /* + * Is this a "this rdataset doesn't + * exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + if (header->type == dns_rdatatype_dname) { + dname_header = header; + } else if (header->type == + RBTDB_RDATATYPE_SIGDNAME) + { + sigdname_header = header; + } else if (node != onode || + IS_STUB(search->rbtdb)) + { + /* + * We've found an NS rdataset that + * isn't at the origin node. We check + * that they're not at the origin node, + * because otherwise we'd erroneously + * treat the zone top as if it were + * a delegation. + */ + ns_header = header; + } + } + } + } + + /* + * Did we find anything? + */ + if (!IS_CACHE(search->rbtdb) && !IS_STUB(search->rbtdb) && + ns_header != NULL) + { + /* + * Note that NS has precedence over DNAME if both exist + * in a zone. Otherwise DNAME take precedence over NS. + */ + found = ns_header; + search->zonecut_sigrdataset = NULL; + } else if (dname_header != NULL) { + found = dname_header; + search->zonecut_sigrdataset = sigdname_header; + } else if (ns_header != NULL) { + found = ns_header; + search->zonecut_sigrdataset = NULL; + } + + if (found != NULL) { + /* + * We increment the reference count on node to ensure that + * search->zonecut_rdataset will still be valid later. + */ + new_reference(search->rbtdb, node, isc_rwlocktype_read); + search->zonecut = node; + search->zonecut_rdataset = found; + search->need_cleanup = true; + /* + * Since we've found a zonecut, anything beneath it is + * glue and is not subject to wildcard matching, so we + * may clear search->wild. + */ + search->wild = false; + if ((search->options & DNS_DBFIND_GLUEOK) == 0) { + /* + * If the caller does not want to find glue, then + * this is the best answer and the search should + * stop now. + */ + result = DNS_R_PARTIALMATCH; + } else { + dns_name_t *zcname; + + /* + * The search will continue beneath the zone cut. + * This may or may not be the best match. In case it + * is, we need to remember the node name. + */ + zcname = dns_fixedname_name(&search->zonecut_name); + dns_name_copy(name, zcname); + search->copy_name = true; + } + } else { + /* + * There is no zonecut at this node which is active in this + * version. + * + * If this is a "wild" node and the caller hasn't disabled + * wildcard matching, remember that we've seen a wild node + * in case we need to go searching for wildcard matches + * later on. + */ + if (node->wild && (search->options & DNS_DBFIND_NOWILD) == 0) { + search->wild = true; + } + } + + NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + + return (result); +} + +static void +bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header, + isc_stdtime_t now, isc_rwlocktype_t locktype, + dns_rdataset_t *rdataset) { + unsigned char *raw; /* RDATASLAB */ + bool stale = STALE(header); + bool ancient = ANCIENT(header); + + /* + * Caller must be holding the node reader lock. + * XXXJT: technically, we need a writer lock, since we'll increment + * the header count below. However, since the actual counter value + * doesn't matter, we prioritize performance here. (We may want to + * use atomic increment when available). + */ + + if (rdataset == NULL) { + return; + } + + new_reference(rbtdb, node, locktype); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ + + /* + * Mark header stale or ancient if the RRset is no longer active. + */ + if (!ACTIVE(header, now)) { + dns_ttl_t stale_ttl = header->rdh_ttl + + STALE_TTL(header, rbtdb); + /* + * If this data is in the stale window keep it and if + * DNS_DBFIND_STALEOK is not set we tell the caller to + * skip this record. We skip the records with ZEROTTL + * (these records should not be cached anyway). + */ + + if (KEEPSTALE(rbtdb) && stale_ttl > now) { + stale = true; + } else { + /* + * We are not keeping stale, or it is outside the + * stale window. Mark ancient, i.e. ready for cleanup. + */ + ancient = true; + } + } + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = rbtdb->common.rdclass; + rdataset->type = RBTDB_RDATATYPE_BASE(header->type); + rdataset->covers = RBTDB_RDATATYPE_EXT(header->type); + rdataset->ttl = header->rdh_ttl - now; + rdataset->trust = header->trust; + + if (NEGATIVE(header)) { + rdataset->attributes |= DNS_RDATASETATTR_NEGATIVE; + } + if (NXDOMAIN(header)) { + rdataset->attributes |= DNS_RDATASETATTR_NXDOMAIN; + } + if (OPTOUT(header)) { + rdataset->attributes |= DNS_RDATASETATTR_OPTOUT; + } + if (PREFETCH(header)) { + rdataset->attributes |= DNS_RDATASETATTR_PREFETCH; + } + + if (stale && !ancient) { + dns_ttl_t stale_ttl = header->rdh_ttl + + STALE_TTL(header, rbtdb); + if (stale_ttl > now) { + rdataset->ttl = stale_ttl - now; + } else { + rdataset->ttl = 0; + } + if (STALE_WINDOW(header)) { + rdataset->attributes |= DNS_RDATASETATTR_STALE_WINDOW; + } + rdataset->attributes |= DNS_RDATASETATTR_STALE; + } else if (IS_CACHE(rbtdb) && !ACTIVE(header, now)) { + rdataset->attributes |= DNS_RDATASETATTR_ANCIENT; + rdataset->ttl = header->rdh_ttl; + } + + rdataset->private1 = rbtdb; + rdataset->private2 = node; + raw = (unsigned char *)header + sizeof(*header); + rdataset->private3 = raw; + rdataset->count = atomic_fetch_add_relaxed(&header->count, 1); + if (rdataset->count == UINT32_MAX) { + rdataset->count = 0; + } + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + + /* + * Add noqname proof. + */ + rdataset->private6 = header->noqname; + if (rdataset->private6 != NULL) { + rdataset->attributes |= DNS_RDATASETATTR_NOQNAME; + } + rdataset->private7 = header->closest; + if (rdataset->private7 != NULL) { + rdataset->attributes |= DNS_RDATASETATTR_CLOSEST; + } + + /* + * Copy out re-signing information. + */ + if (RESIGN(header)) { + rdataset->attributes |= DNS_RDATASETATTR_RESIGN; + rdataset->resign = (header->resign << 1) | header->resign_lsb; + } else { + rdataset->resign = 0; + } +} + +static isc_result_t +setup_delegation(rbtdb_search_t *search, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + dns_name_t *zcname; + rbtdb_rdatatype_t type; + dns_rbtnode_t *node; + + REQUIRE(search != NULL); + REQUIRE(search->zonecut != NULL); + REQUIRE(search->zonecut_rdataset != NULL); + + /* + * The caller MUST NOT be holding any node locks. + */ + + node = search->zonecut; + type = search->zonecut_rdataset->type; + + /* + * If we have to set foundname, we do it before anything else. + * If we were to set foundname after we had set nodep or bound the + * rdataset, then we'd have to undo that work if dns_name_copy() + * failed. By setting foundname first, there's nothing to undo if + * we have trouble. + */ + if (foundname != NULL && search->copy_name) { + zcname = dns_fixedname_name(&search->zonecut_name); + dns_name_copy(zcname, foundname); + } + if (nodep != NULL) { + /* + * Note that we don't have to increment the node's reference + * count here because we're going to use the reference we + * already have in the search block. + */ + *nodep = node; + search->need_cleanup = false; + } + if (rdataset != NULL) { + NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + bind_rdataset(search->rbtdb, node, search->zonecut_rdataset, + search->now, isc_rwlocktype_read, rdataset); + if (sigrdataset != NULL && search->zonecut_sigrdataset != NULL) + { + bind_rdataset(search->rbtdb, node, + search->zonecut_sigrdataset, search->now, + isc_rwlocktype_read, sigrdataset); + } + NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + } + + if (type == dns_rdatatype_dname) { + return (DNS_R_DNAME); + } + return (DNS_R_DELEGATION); +} + +static bool +valid_glue(rbtdb_search_t *search, dns_name_t *name, rbtdb_rdatatype_t type, + dns_rbtnode_t *node) { + unsigned char *raw; /* RDATASLAB */ + unsigned int count, size; + dns_name_t ns_name; + bool valid = false; + dns_offsets_t offsets; + isc_region_t region; + rdatasetheader_t *header; + + /* + * No additional locking is required. + */ + + /* + * Valid glue types are A, AAAA, A6. NS is also a valid glue type + * if it occurs at a zone cut, but is not valid below it. + */ + if (type == dns_rdatatype_ns) { + if (node != search->zonecut) { + return (false); + } + } else if (type != dns_rdatatype_a && type != dns_rdatatype_aaaa && + type != dns_rdatatype_a6) + { + return (false); + } + + header = search->zonecut_rdataset; + raw = (unsigned char *)header + sizeof(*header); + count = raw[0] * 256 + raw[1]; + raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH; + + while (count > 0) { + count--; + size = raw[0] * 256 + raw[1]; + raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH; + region.base = raw; + region.length = size; + raw += size; + /* + * XXX Until we have rdata structures, we have no choice but + * to directly access the rdata format. + */ + dns_name_init(&ns_name, offsets); + dns_name_fromregion(&ns_name, ®ion); + if (dns_name_compare(&ns_name, name) == 0) { + valid = true; + break; + } + } + + return (valid); +} + +static bool +activeempty(rbtdb_search_t *search, dns_rbtnodechain_t *chain, + const dns_name_t *name) { + dns_fixedname_t fnext; + dns_fixedname_t forigin; + dns_name_t *next; + dns_name_t *origin; + dns_name_t prefix; + dns_rbtdb_t *rbtdb; + dns_rbtnode_t *node; + isc_result_t result; + bool answer = false; + rdatasetheader_t *header; + + rbtdb = search->rbtdb; + + dns_name_init(&prefix, NULL); + next = dns_fixedname_initname(&fnext); + origin = dns_fixedname_initname(&forigin); + + result = dns_rbtnodechain_next(chain, NULL, NULL); + while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + node = NULL; + result = dns_rbtnodechain_current(chain, &prefix, origin, + &node); + if (result != ISC_R_SUCCESS) { + break; + } + NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + for (header = node->data; header != NULL; header = header->next) + { + if (header->serial <= search->serial && + !IGNORE(header) && EXISTS(header)) + { + break; + } + } + NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + if (header != NULL) { + break; + } + result = dns_rbtnodechain_next(chain, NULL, NULL); + } + if (result == ISC_R_SUCCESS) { + result = dns_name_concatenate(&prefix, origin, next, NULL); + } + if (result == ISC_R_SUCCESS && dns_name_issubdomain(next, name)) { + answer = true; + } + return (answer); +} + +static bool +activeemptynode(rbtdb_search_t *search, const dns_name_t *qname, + dns_name_t *wname) { + dns_fixedname_t fnext; + dns_fixedname_t forigin; + dns_fixedname_t fprev; + dns_name_t *next; + dns_name_t *origin; + dns_name_t *prev; + dns_name_t name; + dns_name_t rname; + dns_name_t tname; + dns_rbtdb_t *rbtdb; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + bool check_next = true; + bool check_prev = true; + bool answer = false; + isc_result_t result; + rdatasetheader_t *header; + unsigned int n; + + rbtdb = search->rbtdb; + + dns_name_init(&name, NULL); + dns_name_init(&tname, NULL); + dns_name_init(&rname, NULL); + next = dns_fixedname_initname(&fnext); + prev = dns_fixedname_initname(&fprev); + origin = dns_fixedname_initname(&forigin); + + /* + * Find if qname is at or below a empty node. + * Use our own copy of the chain. + */ + + chain = search->chain; + do { + node = NULL; + result = dns_rbtnodechain_current(&chain, &name, origin, &node); + if (result != ISC_R_SUCCESS) { + break; + } + NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + for (header = node->data; header != NULL; header = header->next) + { + if (header->serial <= search->serial && + !IGNORE(header) && EXISTS(header)) + { + break; + } + } + NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + if (header != NULL) { + break; + } + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + } while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN); + if (result == ISC_R_SUCCESS) { + result = dns_name_concatenate(&name, origin, prev, NULL); + } + if (result != ISC_R_SUCCESS) { + check_prev = false; + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + node = NULL; + result = dns_rbtnodechain_current(&chain, &name, origin, &node); + if (result != ISC_R_SUCCESS) { + break; + } + NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + for (header = node->data; header != NULL; header = header->next) + { + if (header->serial <= search->serial && + !IGNORE(header) && EXISTS(header)) + { + break; + } + } + NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + if (header != NULL) { + break; + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + if (result == ISC_R_SUCCESS) { + result = dns_name_concatenate(&name, origin, next, NULL); + } + if (result != ISC_R_SUCCESS) { + check_next = false; + } + + dns_name_clone(qname, &rname); + + /* + * Remove the wildcard label to find the terminal name. + */ + n = dns_name_countlabels(wname); + dns_name_getlabelsequence(wname, 1, n - 1, &tname); + + do { + if ((check_prev && dns_name_issubdomain(prev, &rname)) || + (check_next && dns_name_issubdomain(next, &rname))) + { + answer = true; + break; + } + /* + * Remove the left hand label. + */ + n = dns_name_countlabels(&rname); + dns_name_getlabelsequence(&rname, 1, n - 1, &rname); + } while (!dns_name_equal(&rname, &tname)); + return (answer); +} + +static isc_result_t +find_wildcard(rbtdb_search_t *search, dns_rbtnode_t **nodep, + const dns_name_t *qname) { + unsigned int i, j; + dns_rbtnode_t *node, *level_node, *wnode; + rdatasetheader_t *header; + isc_result_t result = ISC_R_NOTFOUND; + dns_name_t name; + dns_name_t *wname; + dns_fixedname_t fwname; + dns_rbtdb_t *rbtdb; + bool done, wild, active; + dns_rbtnodechain_t wchain; + + /* + * Caller must be holding the tree lock and MUST NOT be holding + * any node locks. + */ + + /* + * Examine each ancestor level. If the level's wild bit + * is set, then construct the corresponding wildcard name and + * search for it. If the wildcard node exists, and is active in + * this version, we're done. If not, then we next check to see + * if the ancestor is active in this version. If so, then there + * can be no possible wildcard match and again we're done. If not, + * continue the search. + */ + + rbtdb = search->rbtdb; + i = search->chain.level_matches; + done = false; + node = *nodep; + do { + NODE_LOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + + /* + * First we try to figure out if this node is active in + * the search's version. We do this now, even though we + * may not need the information, because it simplifies the + * locking and code flow. + */ + for (header = node->data; header != NULL; header = header->next) + { + if (header->serial <= search->serial && + !IGNORE(header) && EXISTS(header) && + !ANCIENT(header)) + { + break; + } + } + if (header != NULL) { + active = true; + } else { + active = false; + } + + if (node->wild) { + wild = true; + } else { + wild = false; + } + + NODE_UNLOCK(&(rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + + if (wild) { + /* + * Construct the wildcard name for this level. + */ + dns_name_init(&name, NULL); + dns_rbt_namefromnode(node, &name); + wname = dns_fixedname_initname(&fwname); + result = dns_name_concatenate(dns_wildcardname, &name, + wname, NULL); + j = i; + while (result == ISC_R_SUCCESS && j != 0) { + j--; + level_node = search->chain.levels[j]; + dns_name_init(&name, NULL); + dns_rbt_namefromnode(level_node, &name); + result = dns_name_concatenate(wname, &name, + wname, NULL); + } + if (result != ISC_R_SUCCESS) { + break; + } + + wnode = NULL; + dns_rbtnodechain_init(&wchain); + result = dns_rbt_findnode( + rbtdb->tree, wname, NULL, &wnode, &wchain, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result == ISC_R_SUCCESS) { + nodelock_t *lock; + + /* + * We have found the wildcard node. If it + * is active in the search's version, we're + * done. + */ + lock = &rbtdb->node_locks[wnode->locknum].lock; + NODE_LOCK(lock, isc_rwlocktype_read); + for (header = wnode->data; header != NULL; + header = header->next) + { + if (header->serial <= search->serial && + !IGNORE(header) && EXISTS(header) && + !ANCIENT(header)) + { + break; + } + } + NODE_UNLOCK(lock, isc_rwlocktype_read); + if (header != NULL || + activeempty(search, &wchain, wname)) + { + if (activeemptynode(search, qname, + wname)) + { + return (ISC_R_NOTFOUND); + } + /* + * The wildcard node is active! + * + * Note: result is still ISC_R_SUCCESS + * so we don't have to set it. + */ + *nodep = wnode; + break; + } + } else if (result != ISC_R_NOTFOUND && + result != DNS_R_PARTIALMATCH) + { + /* + * An error has occurred. Bail out. + */ + break; + } + } + + if (active) { + /* + * The level node is active. Any wildcarding + * present at higher levels has no + * effect and we're done. + */ + result = ISC_R_NOTFOUND; + break; + } + + if (i > 0) { + i--; + node = search->chain.levels[i]; + } else { + done = true; + } + } while (!done); + + return (result); +} + +static bool +matchparams(rdatasetheader_t *header, rbtdb_search_t *search) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + unsigned char *raw; /* RDATASLAB */ + unsigned int rdlen, count; + isc_region_t region; + isc_result_t result; + + REQUIRE(header->type == dns_rdatatype_nsec3); + + raw = (unsigned char *)header + sizeof(*header); + count = raw[0] * 256 + raw[1]; /* count */ + raw += DNS_RDATASET_COUNT + DNS_RDATASET_LENGTH; + + while (count-- > 0) { + rdlen = raw[0] * 256 + raw[1]; + raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH; + region.base = raw; + region.length = rdlen; + dns_rdata_fromregion(&rdata, search->rbtdb->common.rdclass, + dns_rdatatype_nsec3, ®ion); + raw += rdlen; + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + INSIST(result == ISC_R_SUCCESS); + if (nsec3.hash == search->rbtversion->hash && + nsec3.iterations == search->rbtversion->iterations && + nsec3.salt_length == search->rbtversion->salt_length && + memcmp(nsec3.salt, search->rbtversion->salt, + nsec3.salt_length) == 0) + { + return (true); + } + dns_rdata_reset(&rdata); + } + return (false); +} + +/* + * Find node of the NSEC/NSEC3 record that is 'name'. + */ +static isc_result_t +previous_closest_nsec(dns_rdatatype_t type, rbtdb_search_t *search, + dns_name_t *name, dns_name_t *origin, + dns_rbtnode_t **nodep, dns_rbtnodechain_t *nsecchain, + bool *firstp) { + dns_fixedname_t ftarget; + dns_name_t *target; + dns_rbtnode_t *nsecnode; + isc_result_t result; + + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(type == dns_rdatatype_nsec3 || firstp != NULL); + + if (type == dns_rdatatype_nsec3) { + result = dns_rbtnodechain_prev(&search->chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + return (result); + } + result = dns_rbtnodechain_current(&search->chain, name, origin, + nodep); + return (result); + } + + target = dns_fixedname_initname(&ftarget); + + for (;;) { + if (*firstp) { + /* + * Construct the name of the second node to check. + * It is the first node sought in the NSEC tree. + */ + *firstp = false; + dns_rbtnodechain_init(nsecchain); + result = dns_name_concatenate(name, origin, target, + NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + nsecnode = NULL; + result = dns_rbt_findnode( + search->rbtdb->nsec, target, NULL, &nsecnode, + nsecchain, DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result == ISC_R_SUCCESS) { + /* + * Since this was the first loop, finding the + * name in the NSEC tree implies that the first + * node checked in the main tree had an + * unacceptable NSEC record. + * Try the previous node in the NSEC tree. + */ + result = dns_rbtnodechain_prev(nsecchain, name, + origin); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + } else if (result == ISC_R_NOTFOUND || + result == DNS_R_PARTIALMATCH) + { + result = dns_rbtnodechain_current( + nsecchain, name, origin, NULL); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_NOMORE; + } + } + } else { + /* + * This is a second or later trip through the auxiliary + * tree for the name of a third or earlier NSEC node in + * the main tree. Previous trips through the NSEC tree + * must have found nodes in the main tree with NSEC + * records. Perhaps they lacked signature records. + */ + result = dns_rbtnodechain_prev(nsecchain, name, origin); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Construct the name to seek in the main tree. + */ + result = dns_name_concatenate(name, origin, target, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + *nodep = NULL; + result = dns_rbt_findnode(search->rbtdb->tree, target, NULL, + nodep, &search->chain, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (result); + } + + /* + * There should always be a node in the main tree with the + * same name as the node in the auxiliary NSEC tree, except for + * nodes in the auxiliary tree that are awaiting deletion. + */ + if (result != DNS_R_PARTIALMATCH && result != ISC_R_NOTFOUND) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_ERROR, + "previous_closest_nsec(): %s", + isc_result_totext(result)); + return (DNS_R_BADDB); + } + } +} + +/* + * Find the NSEC/NSEC3 which is or before the current point on the + * search chain. For NSEC3 records only NSEC3 records that match the + * current NSEC3PARAM record are considered. + */ +static isc_result_t +find_closest_nsec(rbtdb_search_t *search, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, dns_rbt_t *tree, + dns_db_secure_t secure) { + dns_rbtnode_t *node, *prevnode; + rdatasetheader_t *header, *header_next, *found, *foundsig; + dns_rbtnodechain_t nsecchain; + bool empty_node; + isc_result_t result; + dns_fixedname_t fname, forigin; + dns_name_t *name, *origin; + dns_rdatatype_t type; + rbtdb_rdatatype_t sigtype; + bool wraps; + bool first = true; + bool need_sig = (secure == dns_db_secure); + + if (tree == search->rbtdb->nsec3) { + type = dns_rdatatype_nsec3; + sigtype = RBTDB_RDATATYPE_SIGNSEC3; + wraps = true; + } else { + type = dns_rdatatype_nsec; + sigtype = RBTDB_RDATATYPE_SIGNSEC; + wraps = false; + } + + /* + * Use the auxiliary tree only starting with the second node in the + * hope that the original node will be right much of the time. + */ + name = dns_fixedname_initname(&fname); + origin = dns_fixedname_initname(&forigin); +again: + node = NULL; + prevnode = NULL; + result = dns_rbtnodechain_current(&search->chain, name, origin, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + do { + NODE_LOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + found = NULL; + foundsig = NULL; + empty_node = true; + for (header = node->data; header != NULL; header = header_next) + { + header_next = header->next; + /* + * Look for an active, extant NSEC or RRSIG NSEC. + */ + do { + if (header->serial <= search->serial && + !IGNORE(header)) + { + /* + * Is this a "this rdataset doesn't + * exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + /* + * We now know that there is at least one + * active rdataset at this node. + */ + empty_node = false; + if (header->type == type) { + found = header; + if (foundsig != NULL) { + break; + } + } else if (header->type == sigtype) { + foundsig = header; + if (found != NULL) { + break; + } + } + } + } + if (!empty_node) { + if (found != NULL && search->rbtversion->havensec3 && + found->type == dns_rdatatype_nsec3 && + !matchparams(found, search)) + { + empty_node = true; + found = NULL; + foundsig = NULL; + result = previous_closest_nsec( + type, search, name, origin, &prevnode, + NULL, NULL); + } else if (found != NULL && + (foundsig != NULL || !need_sig)) + { + /* + * We've found the right NSEC/NSEC3 record. + * + * Note: for this to really be the right + * NSEC record, it's essential that the NSEC + * records of any nodes obscured by a zone + * cut have been removed; we assume this is + * the case. + */ + result = dns_name_concatenate(name, origin, + foundname, NULL); + if (result == ISC_R_SUCCESS) { + if (nodep != NULL) { + new_reference( + search->rbtdb, node, + isc_rwlocktype_read); + *nodep = node; + } + bind_rdataset(search->rbtdb, node, + found, search->now, + isc_rwlocktype_read, + rdataset); + if (foundsig != NULL) { + bind_rdataset( + search->rbtdb, node, + foundsig, search->now, + isc_rwlocktype_read, + sigrdataset); + } + } + } else if (found == NULL && foundsig == NULL) { + /* + * This node is active, but has no NSEC or + * RRSIG NSEC. That means it's glue or + * other obscured zone data that isn't + * relevant for our search. Treat the + * node as if it were empty and keep looking. + */ + empty_node = true; + result = previous_closest_nsec( + type, search, name, origin, &prevnode, + &nsecchain, &first); + } else { + /* + * We found an active node, but either the + * NSEC or the RRSIG NSEC is missing. This + * shouldn't happen. + */ + result = DNS_R_BADDB; + } + } else { + /* + * This node isn't active. We've got to keep + * looking. + */ + result = previous_closest_nsec(type, search, name, + origin, &prevnode, + &nsecchain, &first); + } + NODE_UNLOCK(&(search->rbtdb->node_locks[node->locknum].lock), + isc_rwlocktype_read); + node = prevnode; + prevnode = NULL; + } while (empty_node && result == ISC_R_SUCCESS); + + if (!first) { + dns_rbtnodechain_invalidate(&nsecchain); + } + + if (result == ISC_R_NOMORE && wraps) { + result = dns_rbtnodechain_last(&search->chain, tree, NULL, + NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + wraps = false; + goto again; + } + } + + /* + * If the result is ISC_R_NOMORE, then we got to the beginning of + * the database and didn't find a NSEC record. This shouldn't + * happen. + */ + if (result == ISC_R_NOMORE) { + result = DNS_R_BADDB; + } + + return (result); +} + +static isc_result_t +zone_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + dns_rbtnode_t *node = NULL; + isc_result_t result; + rbtdb_search_t search; + bool cname_ok = true; + bool close_version = false; + bool maybe_zonecut = false; + bool at_zonecut = false; + bool wild; + bool empty_node; + rdatasetheader_t *header, *header_next, *found, *nsecheader; + rdatasetheader_t *foundsig, *cnamesig, *nsecsig; + rbtdb_rdatatype_t sigtype; + bool active; + nodelock_t *lock; + dns_rbt_t *tree; + + search.rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(search.rbtdb)); + INSIST(version == NULL || + ((rbtdb_version_t *)version)->rbtdb == (dns_rbtdb_t *)db); + + /* + * We don't care about 'now'. + */ + UNUSED(now); + + /* + * If the caller didn't supply a version, attach to the current + * version. + */ + if (version == NULL) { + currentversion(db, &version); + close_version = true; + } + + search.rbtversion = version; + search.serial = search.rbtversion->serial; + search.options = options; + search.copy_name = false; + search.need_cleanup = false; + search.wild = false; + search.zonecut = NULL; + dns_fixedname_init(&search.zonecut_name); + dns_rbtnodechain_init(&search.chain); + search.now = 0; + + /* + * 'wild' will be true iff. we've matched a wildcard. + */ + wild = false; + + RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * Search down from the root of the tree. If, while going down, we + * encounter a callback node, zone_zonecut_callback() will search the + * rdatasets at the zone cut for active DNAME or NS rdatasets. + */ + tree = (options & DNS_DBFIND_FORCENSEC3) != 0 ? search.rbtdb->nsec3 + : search.rbtdb->tree; + result = dns_rbt_findnode(tree, name, foundname, &node, &search.chain, + DNS_RBTFIND_EMPTYDATA, zone_zonecut_callback, + &search); + + if (result == DNS_R_PARTIALMATCH) { + partial_match: + if (search.zonecut != NULL) { + result = setup_delegation(&search, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } + + if (search.wild) { + /* + * At least one of the levels in the search chain + * potentially has a wildcard. For each such level, + * we must see if there's a matching wildcard active + * in the current version. + */ + result = find_wildcard(&search, &node, name); + if (result == ISC_R_SUCCESS) { + dns_name_copy(name, foundname); + wild = true; + goto found; + } else if (result != ISC_R_NOTFOUND) { + goto tree_exit; + } + } + + active = false; + if ((options & DNS_DBFIND_FORCENSEC3) == 0) { + /* + * The NSEC3 tree won't have empty nodes, + * so it isn't necessary to check for them. + */ + dns_rbtnodechain_t chain = search.chain; + active = activeempty(&search, &chain, name); + } + + /* + * If we're here, then the name does not exist, is not + * beneath a zonecut, and there's no matching wildcard. + */ + if ((search.rbtversion->secure == dns_db_secure && + !search.rbtversion->havensec3) || + (search.options & DNS_DBFIND_FORCENSEC) != 0 || + (search.options & DNS_DBFIND_FORCENSEC3) != 0) + { + result = find_closest_nsec(&search, nodep, foundname, + rdataset, sigrdataset, tree, + search.rbtversion->secure); + if (result == ISC_R_SUCCESS) { + result = active ? DNS_R_EMPTYNAME + : DNS_R_NXDOMAIN; + } + } else { + result = active ? DNS_R_EMPTYNAME : DNS_R_NXDOMAIN; + } + goto tree_exit; + } else if (result != ISC_R_SUCCESS) { + goto tree_exit; + } + +found: + /* + * We have found a node whose name is the desired name, or we + * have matched a wildcard. + */ + + if (search.zonecut != NULL) { + /* + * If we're beneath a zone cut, we don't want to look for + * CNAMEs because they're not legitimate zone glue. + */ + cname_ok = false; + } else { + /* + * The node may be a zone cut itself. If it might be one, + * make sure we check for it later. + * + * DS records live above the zone cut in ordinary zone so + * we want to ignore any referral. + * + * Stub zones don't have anything "above" the delegation so + * we always return a referral. + */ + if (node->find_callback && + ((node != search.rbtdb->origin_node && + !dns_rdatatype_atparent(type)) || + IS_STUB(search.rbtdb))) + { + maybe_zonecut = true; + } + } + + /* + * Certain DNSSEC types are not subject to CNAME matching + * (RFC4035, section 2.5 and RFC3007). + * + * We don't check for RRSIG, because we don't store RRSIG records + * directly. + */ + if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) { + cname_ok = false; + } + + /* + * We now go looking for rdata... + */ + + lock = &search.rbtdb->node_locks[node->locknum].lock; + NODE_LOCK(lock, isc_rwlocktype_read); + + found = NULL; + foundsig = NULL; + sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type); + nsecheader = NULL; + nsecsig = NULL; + cnamesig = NULL; + empty_node = true; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + /* + * Look for an active, extant rdataset. + */ + do { + if (header->serial <= search.serial && !IGNORE(header)) + { + /* + * Is this a "this rdataset doesn't + * exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + /* + * We now know that there is at least one active + * rdataset at this node. + */ + empty_node = false; + + /* + * Do special zone cut handling, if requested. + */ + if (maybe_zonecut && header->type == dns_rdatatype_ns) { + /* + * We increment the reference count on node to + * ensure that search->zonecut_rdataset will + * still be valid later. + */ + new_reference(search.rbtdb, node, + isc_rwlocktype_read); + search.zonecut = node; + search.zonecut_rdataset = header; + search.zonecut_sigrdataset = NULL; + search.need_cleanup = true; + maybe_zonecut = false; + at_zonecut = true; + /* + * It is not clear if KEY should still be + * allowed at the parent side of the zone + * cut or not. It is needed for RFC3007 + * validated updates. + */ + if ((search.options & DNS_DBFIND_GLUEOK) == 0 && + type != dns_rdatatype_nsec && + type != dns_rdatatype_key) + { + /* + * Glue is not OK, but any answer we + * could return would be glue. Return + * the delegation. + */ + found = NULL; + break; + } + if (found != NULL && foundsig != NULL) { + break; + } + } + + /* + * If the NSEC3 record doesn't match the chain + * we are using behave as if it isn't here. + */ + if (header->type == dns_rdatatype_nsec3 && + !matchparams(header, &search)) + { + NODE_UNLOCK(lock, isc_rwlocktype_read); + goto partial_match; + } + /* + * If we found a type we were looking for, + * remember it. + */ + if (header->type == type || type == dns_rdatatype_any || + (header->type == dns_rdatatype_cname && cname_ok)) + { + /* + * We've found the answer! + */ + found = header; + if (header->type == dns_rdatatype_cname && + cname_ok) + { + /* + * We may be finding a CNAME instead + * of the desired type. + * + * If we've already got the CNAME RRSIG, + * use it, otherwise change sigtype + * so that we find it. + */ + if (cnamesig != NULL) { + foundsig = cnamesig; + } else { + sigtype = + RBTDB_RDATATYPE_SIGCNAME; + } + } + /* + * If we've got all we need, end the search. + */ + if (!maybe_zonecut && foundsig != NULL) { + break; + } + } else if (header->type == sigtype) { + /* + * We've found the RRSIG rdataset for our + * target type. Remember it. + */ + foundsig = header; + /* + * If we've got all we need, end the search. + */ + if (!maybe_zonecut && found != NULL) { + break; + } + } else if (header->type == dns_rdatatype_nsec && + !search.rbtversion->havensec3) + { + /* + * Remember a NSEC rdataset even if we're + * not specifically looking for it, because + * we might need it later. + */ + nsecheader = header; + } else if (header->type == RBTDB_RDATATYPE_SIGNSEC && + !search.rbtversion->havensec3) + { + /* + * If we need the NSEC rdataset, we'll also + * need its signature. + */ + nsecsig = header; + } else if (cname_ok && + header->type == RBTDB_RDATATYPE_SIGCNAME) + { + /* + * If we get a CNAME match, we'll also need + * its signature. + */ + cnamesig = header; + } + } + } + + if (empty_node) { + /* + * We have an exact match for the name, but there are no + * active rdatasets in the desired version. That means that + * this node doesn't exist in the desired version, and that + * we really have a partial match. + */ + if (!wild) { + NODE_UNLOCK(lock, isc_rwlocktype_read); + goto partial_match; + } + } + + /* + * If we didn't find what we were looking for... + */ + if (found == NULL) { + if (search.zonecut != NULL) { + /* + * We were trying to find glue at a node beneath a + * zone cut, but didn't. + * + * Return the delegation. + */ + NODE_UNLOCK(lock, isc_rwlocktype_read); + result = setup_delegation(&search, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } + /* + * The desired type doesn't exist. + */ + result = DNS_R_NXRRSET; + if (search.rbtversion->secure == dns_db_secure && + !search.rbtversion->havensec3 && + (nsecheader == NULL || nsecsig == NULL)) + { + /* + * The zone is secure but there's no NSEC, + * or the NSEC has no signature! + */ + if (!wild) { + result = DNS_R_BADDB; + goto node_exit; + } + + NODE_UNLOCK(lock, isc_rwlocktype_read); + result = find_closest_nsec(&search, nodep, foundname, + rdataset, sigrdataset, + search.rbtdb->tree, + search.rbtversion->secure); + if (result == ISC_R_SUCCESS) { + result = DNS_R_EMPTYWILD; + } + goto tree_exit; + } + if ((search.options & DNS_DBFIND_FORCENSEC) != 0 && + nsecheader == NULL) + { + /* + * There's no NSEC record, and we were told + * to find one. + */ + result = DNS_R_BADDB; + goto node_exit; + } + if (nodep != NULL) { + new_reference(search.rbtdb, node, isc_rwlocktype_read); + *nodep = node; + } + if ((search.rbtversion->secure == dns_db_secure && + !search.rbtversion->havensec3) || + (search.options & DNS_DBFIND_FORCENSEC) != 0) + { + bind_rdataset(search.rbtdb, node, nsecheader, 0, + isc_rwlocktype_read, rdataset); + if (nsecsig != NULL) { + bind_rdataset(search.rbtdb, node, nsecsig, 0, + isc_rwlocktype_read, sigrdataset); + } + } + if (wild) { + foundname->attributes |= DNS_NAMEATTR_WILDCARD; + } + goto node_exit; + } + + /* + * We found what we were looking for, or we found a CNAME. + */ + + if (type != found->type && type != dns_rdatatype_any && + found->type == dns_rdatatype_cname) + { + /* + * We weren't doing an ANY query and we found a CNAME instead + * of the type we were looking for, so we need to indicate + * that result to the caller. + */ + result = DNS_R_CNAME; + } else if (search.zonecut != NULL) { + /* + * If we're beneath a zone cut, we must indicate that the + * result is glue, unless we're actually at the zone cut + * and the type is NSEC or KEY. + */ + if (search.zonecut == node) { + /* + * It is not clear if KEY should still be + * allowed at the parent side of the zone + * cut or not. It is needed for RFC3007 + * validated updates. + */ + if (type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3 || + type == dns_rdatatype_key) + { + result = ISC_R_SUCCESS; + } else if (type == dns_rdatatype_any) { + result = DNS_R_ZONECUT; + } else { + result = DNS_R_GLUE; + } + } else { + result = DNS_R_GLUE; + } + /* + * We might have found data that isn't glue, but was occluded + * by a dynamic update. If the caller cares about this, they + * will have told us to validate glue. + * + * XXX We should cache the glue validity state! + */ + if (result == DNS_R_GLUE && + (search.options & DNS_DBFIND_VALIDATEGLUE) != 0 && + !valid_glue(&search, foundname, type, node)) + { + NODE_UNLOCK(lock, isc_rwlocktype_read); + result = setup_delegation(&search, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } + } else { + /* + * An ordinary successful query! + */ + result = ISC_R_SUCCESS; + } + + if (nodep != NULL) { + if (!at_zonecut) { + new_reference(search.rbtdb, node, isc_rwlocktype_read); + } else { + search.need_cleanup = false; + } + *nodep = node; + } + + if (type != dns_rdatatype_any) { + bind_rdataset(search.rbtdb, node, found, 0, isc_rwlocktype_read, + rdataset); + if (foundsig != NULL) { + bind_rdataset(search.rbtdb, node, foundsig, 0, + isc_rwlocktype_read, sigrdataset); + } + } + + if (wild) { + foundname->attributes |= DNS_NAMEATTR_WILDCARD; + } + +node_exit: + NODE_UNLOCK(lock, isc_rwlocktype_read); + +tree_exit: + RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * If we found a zonecut but aren't going to use it, we have to + * let go of it. + */ + if (search.need_cleanup) { + node = search.zonecut; + INSIST(node != NULL); + lock = &(search.rbtdb->node_locks[node->locknum].lock); + + NODE_LOCK(lock, isc_rwlocktype_read); + decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read, + isc_rwlocktype_none, false); + NODE_UNLOCK(lock, isc_rwlocktype_read); + } + + if (close_version) { + closeversion(db, &version, false); + } + + dns_rbtnodechain_reset(&search.chain); + + return (result); +} + +static isc_result_t +zone_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_name_t *dcname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(dcname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + FATAL_ERROR("zone_findzonecut() called!"); + + UNREACHABLE(); + return (ISC_R_NOTIMPLEMENTED); +} + +static bool +check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header, + isc_rwlocktype_t *locktype, nodelock_t *lock, + rbtdb_search_t *search, rdatasetheader_t **header_prev) { + if (!ACTIVE(header, search->now)) { + dns_ttl_t stale = header->rdh_ttl + + STALE_TTL(header, search->rbtdb); + /* + * If this data is in the stale window keep it and if + * DNS_DBFIND_STALEOK is not set we tell the caller to + * skip this record. We skip the records with ZEROTTL + * (these records should not be cached anyway). + */ + + RDATASET_ATTR_CLR(header, RDATASET_ATTR_STALE_WINDOW); + if (!ZEROTTL(header) && KEEPSTALE(search->rbtdb) && + stale > search->now) + { + mark_header_stale(search->rbtdb, header); + *header_prev = header; + /* + * If DNS_DBFIND_STALESTART is set then it means we + * failed to resolve the name during recursion, in + * this case we mark the time in which the refresh + * failed. + */ + if ((search->options & DNS_DBFIND_STALESTART) != 0) { + atomic_store_release( + &header->last_refresh_fail_ts, + search->now); + } else if ((search->options & + DNS_DBFIND_STALEENABLED) != 0 && + search->now < + (atomic_load_acquire( + &header->last_refresh_fail_ts) + + search->rbtdb->serve_stale_refresh)) + { + /* + * If we are within interval between last + * refresh failure time + 'stale-refresh-time', + * then don't skip this stale entry but use it + * instead. + */ + RDATASET_ATTR_SET(header, + RDATASET_ATTR_STALE_WINDOW); + return (false); + } else if ((search->options & + DNS_DBFIND_STALETIMEOUT) != 0) + { + /* + * We want stale RRset due to timeout, so we + * don't skip it. + */ + return (false); + } + return ((search->options & DNS_DBFIND_STALEOK) == 0); + } + + /* + * This rdataset is stale. If no one else is using the + * node, we can clean it up right now, otherwise we mark + * it as ancient, and the node as dirty, so it will get + * cleaned up later. + */ + if ((header->rdh_ttl < search->now - RBTDB_VIRTUAL) && + (*locktype == isc_rwlocktype_write || + NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS)) + { + /* + * We update the node's status only when we can + * get write access; otherwise, we leave others + * to this work. Periodical cleaning will + * eventually take the job as the last resort. + * We won't downgrade the lock, since other + * rdatasets are probably stale, too. + */ + *locktype = isc_rwlocktype_write; + + if (isc_refcount_current(&node->references) == 0) { + isc_mem_t *mctx; + + /* + * header->down can be non-NULL if the + * refcount has just decremented to 0 + * but decrement_reference() has not + * performed clean_cache_node(), in + * which case we need to purge the stale + * headers first. + */ + mctx = search->rbtdb->common.mctx; + clean_stale_headers(search->rbtdb, mctx, + header); + if (*header_prev != NULL) { + (*header_prev)->next = header->next; + } else { + node->data = header->next; + } + free_rdataset(search->rbtdb, mctx, header); + } else { + mark_header_ancient(search->rbtdb, header); + *header_prev = header; + } + } else { + *header_prev = header; + } + return (true); + } + return (false); +} + +static isc_result_t +cache_zonecut_callback(dns_rbtnode_t *node, dns_name_t *name, void *arg) { + rbtdb_search_t *search = arg; + rdatasetheader_t *header, *header_prev, *header_next; + rdatasetheader_t *dname_header, *sigdname_header; + isc_result_t result; + nodelock_t *lock; + isc_rwlocktype_t locktype; + + /* XXX comment */ + + REQUIRE(search->zonecut == NULL); + + /* + * Keep compiler silent. + */ + UNUSED(name); + + lock = &(search->rbtdb->node_locks[node->locknum].lock); + locktype = isc_rwlocktype_read; + NODE_LOCK(lock, locktype); + + /* + * Look for a DNAME or RRSIG DNAME rdataset. + */ + dname_header = NULL; + sigdname_header = NULL; + header_prev = NULL; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (check_stale_header(node, header, &locktype, lock, search, + &header_prev)) + { + /* Do nothing. */ + } else if (header->type == dns_rdatatype_dname && + EXISTS(header) && !ANCIENT(header)) + { + dname_header = header; + header_prev = header; + } else if (header->type == RBTDB_RDATATYPE_SIGDNAME && + EXISTS(header) && !ANCIENT(header)) + { + sigdname_header = header; + header_prev = header; + } else { + header_prev = header; + } + } + + if (dname_header != NULL && + (!DNS_TRUST_PENDING(dname_header->trust) || + (search->options & DNS_DBFIND_PENDINGOK) != 0)) + { + /* + * We increment the reference count on node to ensure that + * search->zonecut_rdataset will still be valid later. + */ + new_reference(search->rbtdb, node, locktype); + search->zonecut = node; + search->zonecut_rdataset = dname_header; + search->zonecut_sigrdataset = sigdname_header; + search->need_cleanup = true; + result = DNS_R_PARTIALMATCH; + } else { + result = DNS_R_CONTINUE; + } + + NODE_UNLOCK(lock, locktype); + + return (result); +} + +static isc_result_t +find_deepest_zonecut(rbtdb_search_t *search, dns_rbtnode_t *node, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + unsigned int i; + dns_rbtnode_t *level_node; + rdatasetheader_t *header, *header_prev, *header_next; + rdatasetheader_t *found, *foundsig; + isc_result_t result = ISC_R_NOTFOUND; + dns_name_t name; + dns_rbtdb_t *rbtdb; + bool done; + nodelock_t *lock; + isc_rwlocktype_t locktype; + + /* + * Caller must be holding the tree lock. + */ + + rbtdb = search->rbtdb; + i = search->chain.level_matches; + done = false; + do { + locktype = isc_rwlocktype_read; + lock = &rbtdb->node_locks[node->locknum].lock; + NODE_LOCK(lock, locktype); + + /* + * Look for NS and RRSIG NS rdatasets. + */ + found = NULL; + foundsig = NULL; + header_prev = NULL; + for (header = node->data; header != NULL; header = header_next) + { + header_next = header->next; + if (check_stale_header(node, header, &locktype, lock, + search, &header_prev)) + { + /* Do nothing. */ + } else if (EXISTS(header) && !ANCIENT(header)) { + /* + * We've found an extant rdataset. See if + * we're interested in it. + */ + if (header->type == dns_rdatatype_ns) { + found = header; + if (foundsig != NULL) { + break; + } + } else if (header->type == + RBTDB_RDATATYPE_SIGNS) + { + foundsig = header; + if (found != NULL) { + break; + } + } + header_prev = header; + } else { + header_prev = header; + } + } + + if (found != NULL) { + /* + * If we have to set foundname, we do it before + * anything else. If we were to set foundname after + * we had set nodep or bound the rdataset, then we'd + * have to undo that work if dns_name_concatenate() + * failed. By setting foundname first, there's + * nothing to undo if we have trouble. + */ + if (foundname != NULL) { + dns_name_init(&name, NULL); + dns_rbt_namefromnode(node, &name); + dns_name_copy(&name, foundname); + while (i > 0) { + i--; + level_node = search->chain.levels[i]; + dns_name_init(&name, NULL); + dns_rbt_namefromnode(level_node, &name); + result = dns_name_concatenate( + foundname, &name, foundname, + NULL); + if (result != ISC_R_SUCCESS) { + if (nodep != NULL) { + *nodep = NULL; + } + goto node_exit; + } + } + } + result = DNS_R_DELEGATION; + if (nodep != NULL) { + new_reference(search->rbtdb, node, locktype); + *nodep = node; + } + bind_rdataset(search->rbtdb, node, found, search->now, + locktype, rdataset); + if (foundsig != NULL) { + bind_rdataset(search->rbtdb, node, foundsig, + search->now, locktype, + sigrdataset); + } + if (need_headerupdate(found, search->now) || + (foundsig != NULL && + need_headerupdate(foundsig, search->now))) + { + if (locktype != isc_rwlocktype_write) { + NODE_UNLOCK(lock, locktype); + NODE_LOCK(lock, isc_rwlocktype_write); + locktype = isc_rwlocktype_write; + POST(locktype); + } + if (need_headerupdate(found, search->now)) { + update_header(search->rbtdb, found, + search->now); + } + if (foundsig != NULL && + need_headerupdate(foundsig, search->now)) + { + update_header(search->rbtdb, foundsig, + search->now); + } + } + } + + node_exit: + NODE_UNLOCK(lock, locktype); + + if (found == NULL && i > 0) { + i--; + node = search->chain.levels[i]; + } else { + done = true; + } + } while (!done); + + return (result); +} + +/* + * Look for a potentially covering NSEC in the cache where `name` + * is known not to exist. This uses the auxiliary NSEC tree to find + * the potential NSEC owner. If found, we update 'foundname', 'nodep', + * 'rdataset' and 'sigrdataset', and return DNS_R_COVERINGNSEC. + * Otherwise, return ISC_R_NOTFOUND. + */ +static isc_result_t +find_coveringnsec(rbtdb_search_t *search, const dns_name_t *name, + dns_dbnode_t **nodep, isc_stdtime_t now, + dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + dns_fixedname_t fprefix, forigin, ftarget, fixed; + dns_name_t *prefix = NULL, *origin = NULL; + dns_name_t *target = NULL, *fname = NULL; + dns_rbtnode_t *node = NULL; + dns_rbtnodechain_t chain; + isc_result_t result; + isc_rwlocktype_t locktype; + nodelock_t *lock = NULL; + rbtdb_rdatatype_t matchtype, sigmatchtype; + rdatasetheader_t *found = NULL, *foundsig = NULL; + rdatasetheader_t *header = NULL; + rdatasetheader_t *header_next = NULL, *header_prev = NULL; + + /* + * Look for the node in the auxilary tree. + */ + dns_rbtnodechain_init(&chain); + target = dns_fixedname_initname(&ftarget); + result = dns_rbt_findnode(search->rbtdb->nsec, name, target, &node, + &chain, DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result != DNS_R_PARTIALMATCH) { + dns_rbtnodechain_reset(&chain); + return (ISC_R_NOTFOUND); + } + + prefix = dns_fixedname_initname(&fprefix); + origin = dns_fixedname_initname(&forigin); + target = dns_fixedname_initname(&ftarget); + fname = dns_fixedname_initname(&fixed); + + locktype = isc_rwlocktype_read; + matchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_nsec, 0); + sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, + dns_rdatatype_nsec); + + /* + * Extract predecessor from chain. + */ + result = dns_rbtnodechain_current(&chain, prefix, origin, NULL); + dns_rbtnodechain_reset(&chain); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + return (ISC_R_NOTFOUND); + } + + result = dns_name_concatenate(prefix, origin, target, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * Lookup the predecessor in the main tree. + */ + node = NULL; + result = dns_rbt_findnode(search->rbtdb->tree, target, fname, &node, + NULL, DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + lock = &(search->rbtdb->node_locks[node->locknum].lock); + NODE_LOCK(lock, locktype); + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (check_stale_header(node, header, &locktype, lock, search, + &header_prev)) + { + continue; + } + if (NONEXISTENT(header) || + RBTDB_RDATATYPE_BASE(header->type) == 0) + { + header_prev = header; + continue; + } + if (header->type == matchtype) { + found = header; + if (foundsig != NULL) { + break; + } + } else if (header->type == sigmatchtype) { + foundsig = header; + if (found != NULL) { + break; + } + } + header_prev = header; + } + if (found != NULL) { + bind_rdataset(search->rbtdb, node, found, now, locktype, + rdataset); + if (foundsig != NULL) { + bind_rdataset(search->rbtdb, node, foundsig, now, + locktype, sigrdataset); + } + new_reference(search->rbtdb, node, locktype); + + dns_name_copy(fname, foundname); + + *nodep = node; + result = DNS_R_COVERINGNSEC; + } else { + result = ISC_R_NOTFOUND; + } + NODE_UNLOCK(lock, locktype); + return (result); +} + +static isc_result_t +cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_rbtnode_t *node = NULL; + isc_result_t result; + rbtdb_search_t search; + bool cname_ok = true; + bool found_noqname = false; + bool all_negative = true; + bool empty_node; + nodelock_t *lock; + isc_rwlocktype_t locktype; + rdatasetheader_t *header, *header_prev, *header_next; + rdatasetheader_t *found, *nsheader; + rdatasetheader_t *foundsig, *nssig, *cnamesig; + rdatasetheader_t *update, *updatesig; + rdatasetheader_t *nsecheader, *nsecsig; + rbtdb_rdatatype_t sigtype, negtype; + + UNUSED(version); + + search.rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(search.rbtdb)); + REQUIRE(version == NULL); + + if (now == 0) { + isc_stdtime_get(&now); + } + + search.rbtversion = NULL; + search.serial = 1; + search.options = options; + search.copy_name = false; + search.need_cleanup = false; + search.wild = false; + search.zonecut = NULL; + search.zonecut_rdataset = NULL; + search.zonecut_sigrdataset = NULL; + dns_fixedname_init(&search.zonecut_name); + dns_rbtnodechain_init(&search.chain); + search.now = now; + update = NULL; + updatesig = NULL; + + RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * Search down from the root of the tree. If, while going down, we + * encounter a callback node, cache_zonecut_callback() will search the + * rdatasets at the zone cut for a DNAME rdataset. + */ + result = dns_rbt_findnode(search.rbtdb->tree, name, foundname, &node, + &search.chain, DNS_RBTFIND_EMPTYDATA, + cache_zonecut_callback, &search); + + if (result == DNS_R_PARTIALMATCH) { + /* + * If dns_rbt_findnode discovered a covering DNAME skip + * looking for a covering NSEC. + */ + if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 && + (search.zonecut_rdataset == NULL || + search.zonecut_rdataset->type != dns_rdatatype_dname)) + { + result = find_coveringnsec(&search, name, nodep, now, + foundname, rdataset, + sigrdataset); + if (result == DNS_R_COVERINGNSEC) { + goto tree_exit; + } + } + if (search.zonecut != NULL) { + result = setup_delegation(&search, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } else { + find_ns: + result = find_deepest_zonecut(&search, node, nodep, + foundname, rdataset, + sigrdataset); + goto tree_exit; + } + } else if (result != ISC_R_SUCCESS) { + goto tree_exit; + } + + /* + * Certain DNSSEC types are not subject to CNAME matching + * (RFC4035, section 2.5 and RFC3007). + * + * We don't check for RRSIG, because we don't store RRSIG records + * directly. + */ + if (type == dns_rdatatype_key || type == dns_rdatatype_nsec) { + cname_ok = false; + } + + /* + * We now go looking for rdata... + */ + + lock = &(search.rbtdb->node_locks[node->locknum].lock); + locktype = isc_rwlocktype_read; + NODE_LOCK(lock, locktype); + + found = NULL; + foundsig = NULL; + sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type); + negtype = RBTDB_RDATATYPE_VALUE(0, type); + nsheader = NULL; + nsecheader = NULL; + nssig = NULL; + nsecsig = NULL; + cnamesig = NULL; + empty_node = true; + header_prev = NULL; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (check_stale_header(node, header, &locktype, lock, &search, + &header_prev)) + { + /* Do nothing. */ + } else if (EXISTS(header) && !ANCIENT(header)) { + /* + * We now know that there is at least one active + * non-stale rdataset at this node. + */ + empty_node = false; + if (header->noqname != NULL && + header->trust == dns_trust_secure) + { + found_noqname = true; + } + if (!NEGATIVE(header)) { + all_negative = false; + } + + /* + * If we found a type we were looking for, remember + * it. + */ + if (header->type == type || + (type == dns_rdatatype_any && + RBTDB_RDATATYPE_BASE(header->type) != 0) || + (cname_ok && header->type == dns_rdatatype_cname)) + { + /* + * We've found the answer. + */ + found = header; + if (header->type == dns_rdatatype_cname && + cname_ok && cnamesig != NULL) + { + /* + * If we've already got the + * CNAME RRSIG, use it. + */ + foundsig = cnamesig; + } + } else if (header->type == sigtype) { + /* + * We've found the RRSIG rdataset for our + * target type. Remember it. + */ + foundsig = header; + } else if (header->type == RBTDB_RDATATYPE_NCACHEANY || + header->type == negtype) + { + /* + * We've found a negative cache entry. + */ + found = header; + } else if (header->type == dns_rdatatype_ns) { + /* + * Remember a NS rdataset even if we're + * not specifically looking for it, because + * we might need it later. + */ + nsheader = header; + } else if (header->type == RBTDB_RDATATYPE_SIGNS) { + /* + * If we need the NS rdataset, we'll also + * need its signature. + */ + nssig = header; + } else if (header->type == dns_rdatatype_nsec) { + nsecheader = header; + } else if (header->type == RBTDB_RDATATYPE_SIGNSEC) { + nsecsig = header; + } else if (cname_ok && + header->type == RBTDB_RDATATYPE_SIGCNAME) + { + /* + * If we get a CNAME match, we'll also need + * its signature. + */ + cnamesig = header; + } + header_prev = header; + } else { + header_prev = header; + } + } + + if (empty_node) { + /* + * We have an exact match for the name, but there are no + * extant rdatasets. That means that this node doesn't + * meaningfully exist, and that we really have a partial match. + */ + NODE_UNLOCK(lock, locktype); + if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0) { + result = find_coveringnsec(&search, name, nodep, now, + foundname, rdataset, + sigrdataset); + if (result == DNS_R_COVERINGNSEC) { + goto tree_exit; + } + } + goto find_ns; + } + + /* + * If we didn't find what we were looking for... + */ + if (found == NULL || + (DNS_TRUST_ADDITIONAL(found->trust) && + ((options & DNS_DBFIND_ADDITIONALOK) == 0)) || + (found->trust == dns_trust_glue && + ((options & DNS_DBFIND_GLUEOK) == 0)) || + (DNS_TRUST_PENDING(found->trust) && + ((options & DNS_DBFIND_PENDINGOK) == 0))) + { + /* + * Return covering NODATA NSEC record. + */ + if ((search.options & DNS_DBFIND_COVERINGNSEC) != 0 && + nsecheader != NULL) + { + if (nodep != NULL) { + new_reference(search.rbtdb, node, locktype); + *nodep = node; + } + bind_rdataset(search.rbtdb, node, nsecheader, + search.now, locktype, rdataset); + if (need_headerupdate(nsecheader, search.now)) { + update = nsecheader; + } + if (nsecsig != NULL) { + bind_rdataset(search.rbtdb, node, nsecsig, + search.now, locktype, + sigrdataset); + if (need_headerupdate(nsecsig, search.now)) { + updatesig = nsecsig; + } + } + result = DNS_R_COVERINGNSEC; + goto node_exit; + } + + /* + * This name was from a wild card. Look for a covering NSEC. + */ + if (found == NULL && (found_noqname || all_negative) && + (search.options & DNS_DBFIND_COVERINGNSEC) != 0) + { + NODE_UNLOCK(lock, locktype); + result = find_coveringnsec(&search, name, nodep, now, + foundname, rdataset, + sigrdataset); + if (result == DNS_R_COVERINGNSEC) { + goto tree_exit; + } + goto find_ns; + } + + /* + * If there is an NS rdataset at this node, then this is the + * deepest zone cut. + */ + if (nsheader != NULL) { + if (nodep != NULL) { + new_reference(search.rbtdb, node, locktype); + *nodep = node; + } + bind_rdataset(search.rbtdb, node, nsheader, search.now, + locktype, rdataset); + if (need_headerupdate(nsheader, search.now)) { + update = nsheader; + } + if (nssig != NULL) { + bind_rdataset(search.rbtdb, node, nssig, + search.now, locktype, + sigrdataset); + if (need_headerupdate(nssig, search.now)) { + updatesig = nssig; + } + } + result = DNS_R_DELEGATION; + goto node_exit; + } + + /* + * Go find the deepest zone cut. + */ + NODE_UNLOCK(lock, locktype); + goto find_ns; + } + + /* + * We found what we were looking for, or we found a CNAME. + */ + + if (nodep != NULL) { + new_reference(search.rbtdb, node, locktype); + *nodep = node; + } + + if (NEGATIVE(found)) { + /* + * We found a negative cache entry. + */ + if (NXDOMAIN(found)) { + result = DNS_R_NCACHENXDOMAIN; + } else { + result = DNS_R_NCACHENXRRSET; + } + } else if (type != found->type && type != dns_rdatatype_any && + found->type == dns_rdatatype_cname) + { + /* + * We weren't doing an ANY query and we found a CNAME instead + * of the type we were looking for, so we need to indicate + * that result to the caller. + */ + result = DNS_R_CNAME; + } else { + /* + * An ordinary successful query! + */ + result = ISC_R_SUCCESS; + } + + if (type != dns_rdatatype_any || result == DNS_R_NCACHENXDOMAIN || + result == DNS_R_NCACHENXRRSET) + { + bind_rdataset(search.rbtdb, node, found, search.now, locktype, + rdataset); + if (need_headerupdate(found, search.now)) { + update = found; + } + if (!NEGATIVE(found) && foundsig != NULL) { + bind_rdataset(search.rbtdb, node, foundsig, search.now, + locktype, sigrdataset); + if (need_headerupdate(foundsig, search.now)) { + updatesig = foundsig; + } + } + } + +node_exit: + if ((update != NULL || updatesig != NULL) && + locktype != isc_rwlocktype_write) + { + NODE_UNLOCK(lock, locktype); + NODE_LOCK(lock, isc_rwlocktype_write); + locktype = isc_rwlocktype_write; + POST(locktype); + } + if (update != NULL && need_headerupdate(update, search.now)) { + update_header(search.rbtdb, update, search.now); + } + if (updatesig != NULL && need_headerupdate(updatesig, search.now)) { + update_header(search.rbtdb, updatesig, search.now); + } + + NODE_UNLOCK(lock, locktype); + +tree_exit: + RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * If we found a zonecut but aren't going to use it, we have to + * let go of it. + */ + if (search.need_cleanup) { + node = search.zonecut; + INSIST(node != NULL); + lock = &(search.rbtdb->node_locks[node->locknum].lock); + + NODE_LOCK(lock, isc_rwlocktype_read); + decrement_reference(search.rbtdb, node, 0, isc_rwlocktype_read, + isc_rwlocktype_none, false); + NODE_UNLOCK(lock, isc_rwlocktype_read); + } + + dns_rbtnodechain_reset(&search.chain); + + update_cachestats(search.rbtdb, result); + return (result); +} + +static isc_result_t +cache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_name_t *dcname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_rbtnode_t *node = NULL; + nodelock_t *lock; + isc_result_t result; + rbtdb_search_t search; + rdatasetheader_t *header, *header_prev, *header_next; + rdatasetheader_t *found, *foundsig; + unsigned int rbtoptions = DNS_RBTFIND_EMPTYDATA; + isc_rwlocktype_t locktype; + bool dcnull = (dcname == NULL); + + search.rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(search.rbtdb)); + + if (now == 0) { + isc_stdtime_get(&now); + } + + search.rbtversion = NULL; + search.serial = 1; + search.options = options; + search.copy_name = false; + search.need_cleanup = false; + search.wild = false; + search.zonecut = NULL; + dns_fixedname_init(&search.zonecut_name); + dns_rbtnodechain_init(&search.chain); + search.now = now; + + if (dcnull) { + dcname = foundname; + } + + if ((options & DNS_DBFIND_NOEXACT) != 0) { + rbtoptions |= DNS_RBTFIND_NOEXACT; + } + + RWLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * Search down from the root of the tree. + */ + result = dns_rbt_findnode(search.rbtdb->tree, name, dcname, &node, + &search.chain, rbtoptions, NULL, &search); + + if (result == DNS_R_PARTIALMATCH) { + result = find_deepest_zonecut(&search, node, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } else if (result != ISC_R_SUCCESS) { + goto tree_exit; + } else if (!dcnull) { + dns_name_copy(dcname, foundname); + } + + /* + * We now go looking for an NS rdataset at the node. + */ + + lock = &(search.rbtdb->node_locks[node->locknum].lock); + locktype = isc_rwlocktype_read; + NODE_LOCK(lock, locktype); + + found = NULL; + foundsig = NULL; + header_prev = NULL; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (check_stale_header(node, header, &locktype, lock, &search, + &header_prev)) + { + /* + * The function dns_rbt_findnode found us the a matching + * node for 'name' and stored the result in 'dcname'. + * This is the deepest known zonecut in our database. + * However, this node may be stale and if serve-stale + * is not enabled (in other words 'stale-answer-enable' + * is set to no), this node may not be used as a + * zonecut we know about. If so, find the deepest + * zonecut from this node up and return that instead. + */ + NODE_UNLOCK(lock, locktype); + result = find_deepest_zonecut(&search, node, nodep, + foundname, rdataset, + sigrdataset); + dns_name_copy(foundname, dcname); + goto tree_exit; + } else if (EXISTS(header) && !ANCIENT(header)) { + /* + * If we found a type we were looking for, remember + * it. + */ + if (header->type == dns_rdatatype_ns) { + /* + * Remember a NS rdataset even if we're + * not specifically looking for it, because + * we might need it later. + */ + found = header; + } else if (header->type == RBTDB_RDATATYPE_SIGNS) { + /* + * If we need the NS rdataset, we'll also + * need its signature. + */ + foundsig = header; + } + header_prev = header; + } else { + header_prev = header; + } + } + + if (found == NULL) { + /* + * No NS records here. + */ + NODE_UNLOCK(lock, locktype); + result = find_deepest_zonecut(&search, node, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } + + if (nodep != NULL) { + new_reference(search.rbtdb, node, locktype); + *nodep = node; + } + + bind_rdataset(search.rbtdb, node, found, search.now, locktype, + rdataset); + if (foundsig != NULL) { + bind_rdataset(search.rbtdb, node, foundsig, search.now, + locktype, sigrdataset); + } + + if (need_headerupdate(found, search.now) || + (foundsig != NULL && need_headerupdate(foundsig, search.now))) + { + if (locktype != isc_rwlocktype_write) { + NODE_UNLOCK(lock, locktype); + NODE_LOCK(lock, isc_rwlocktype_write); + locktype = isc_rwlocktype_write; + POST(locktype); + } + if (need_headerupdate(found, search.now)) { + update_header(search.rbtdb, found, search.now); + } + if (foundsig != NULL && need_headerupdate(foundsig, search.now)) + { + update_header(search.rbtdb, foundsig, search.now); + } + } + + NODE_UNLOCK(lock, locktype); + +tree_exit: + RWUNLOCK(&search.rbtdb->tree_lock, isc_rwlocktype_read); + + INSIST(!search.need_cleanup); + + dns_rbtnodechain_reset(&search.chain); + + if (result == DNS_R_DELEGATION) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +static void +attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *node = (dns_rbtnode_t *)source; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&node->references); + + *targetp = source; +} + +static void +detachnode(dns_db_t *db, dns_dbnode_t **targetp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *node; + bool want_free = false; + bool inactive = false; + rbtdb_nodelock_t *nodelock; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(targetp != NULL && *targetp != NULL); + + node = (dns_rbtnode_t *)(*targetp); + nodelock = &rbtdb->node_locks[node->locknum]; + + NODE_LOCK(&nodelock->lock, isc_rwlocktype_read); + + if (decrement_reference(rbtdb, node, 0, isc_rwlocktype_read, + isc_rwlocktype_none, false)) + { + if (isc_refcount_current(&nodelock->references) == 0 && + nodelock->exiting) + { + inactive = true; + } + } + + NODE_UNLOCK(&nodelock->lock, isc_rwlocktype_read); + + *targetp = NULL; + + if (inactive) { + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + rbtdb->active--; + if (rbtdb->active == 0) { + want_free = true; + } + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + if (want_free) { + char buf[DNS_NAME_FORMATSIZE]; + if (dns_name_dynamic(&rbtdb->common.origin)) { + dns_name_format(&rbtdb->common.origin, buf, + sizeof(buf)); + } else { + strlcpy(buf, "", sizeof(buf)); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "calling free_rbtdb(%s)", buf); + free_rbtdb(rbtdb, true, NULL); + } + } +} + +static isc_result_t +expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = node; + rdatasetheader_t *header; + bool force_expire = false; + /* + * These are the category and module used by the cache cleaner. + */ + bool log = false; + isc_logcategory_t *category = DNS_LOGCATEGORY_DATABASE; + isc_logmodule_t *module = DNS_LOGMODULE_CACHE; + int level = ISC_LOG_DEBUG(2); + char printname[DNS_NAME_FORMATSIZE]; + + REQUIRE(VALID_RBTDB(rbtdb)); + + /* + * Caller must hold a tree lock. + */ + + if (now == 0) { + isc_stdtime_get(&now); + } + + if (isc_mem_isovermem(rbtdb->common.mctx)) { + /* + * Force expire with 25% probability. + * XXXDCL Could stand to have a better policy, like LRU. + */ + force_expire = (rbtnode->down == NULL && + (isc_random32() % 4) == 0); + + /* + * Note that 'log' can be true IFF overmem is also true. + * overmem can currently only be true for cache + * databases -- hence all of the "overmem cache" log strings. + */ + log = isc_log_wouldlog(dns_lctx, level); + if (log) { + isc_log_write( + dns_lctx, category, module, level, + "overmem cache: %s %s", + force_expire ? "FORCE" : "check", + dns_rbt_formatnodename(rbtnode, printname, + sizeof(printname))); + } + } + + /* + * We may not need write access, but this code path is not performance + * sensitive, so it should be okay to always lock as a writer. + */ + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + for (header = rbtnode->data; header != NULL; header = header->next) { + if (header->rdh_ttl + STALE_TTL(header, rbtdb) <= + now - RBTDB_VIRTUAL) + { + /* + * We don't check if refcurrent(rbtnode) == 0 and try + * to free like we do in cache_find(), because + * refcurrent(rbtnode) must be non-zero. This is so + * because 'node' is an argument to the function. + */ + mark_header_ancient(rbtdb, header); + if (log) { + isc_log_write(dns_lctx, category, module, level, + "overmem cache: ancient %s", + printname); + } + } else if (force_expire) { + if (!RETAIN(header)) { + set_ttl(rbtdb, header, 0); + mark_header_ancient(rbtdb, header); + } else if (log) { + isc_log_write(dns_lctx, category, module, level, + "overmem cache: " + "reprieve by RETAIN() %s", + printname); + } + } else if (isc_mem_isovermem(rbtdb->common.mctx) && log) { + isc_log_write(dns_lctx, category, module, level, + "overmem cache: saved %s", printname); + } + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + return (ISC_R_SUCCESS); +} + +static void +overmem(dns_db_t *db, bool over) { + /* This is an empty callback. See adb.c:water() */ + + UNUSED(db); + UNUSED(over); + + return; +} + +static void +printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = node; + bool first; + uint32_t refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + refs = isc_refcount_current(&rbtnode->references); + fprintf(out, "node %p, %" PRIu32 " references, locknum = %u\n", rbtnode, + refs, rbtnode->locknum); + if (rbtnode->data != NULL) { + rdatasetheader_t *current, *top_next; + + for (current = rbtnode->data; current != NULL; + current = top_next) + { + top_next = current->next; + first = true; + fprintf(out, "\ttype %u", current->type); + do { + uint_least16_t attributes = atomic_load_acquire( + ¤t->attributes); + if (!first) { + fprintf(out, "\t"); + } + first = false; + fprintf(out, + "\tserial = %lu, ttl = %u, " + "trust = %u, attributes = %" PRIuLEAST16 + ", " + "resign = %u\n", + (unsigned long)current->serial, + current->rdh_ttl, current->trust, + attributes, + (current->resign << 1) | + current->resign_lsb); + current = current->down; + } while (current != NULL); + } + } else { + fprintf(out, "(empty)\n"); + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); +} + +static isc_result_t +createiterator(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_dbiterator_t *rbtdbiter; + + REQUIRE(VALID_RBTDB(rbtdb)); + + rbtdbiter = isc_mem_get(rbtdb->common.mctx, sizeof(*rbtdbiter)); + + rbtdbiter->common.methods = &dbiterator_methods; + rbtdbiter->common.db = NULL; + dns_db_attach(db, &rbtdbiter->common.db); + rbtdbiter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) != + 0); + rbtdbiter->common.magic = DNS_DBITERATOR_MAGIC; + rbtdbiter->common.cleaning = false; + rbtdbiter->paused = true; + rbtdbiter->tree_locked = isc_rwlocktype_none; + rbtdbiter->result = ISC_R_SUCCESS; + dns_fixedname_init(&rbtdbiter->name); + dns_fixedname_init(&rbtdbiter->origin); + rbtdbiter->node = NULL; + rbtdbiter->delcnt = 0; + rbtdbiter->nsec3only = ((options & DNS_DB_NSEC3ONLY) != 0); + rbtdbiter->nonsec3 = ((options & DNS_DB_NONSEC3) != 0); + memset(rbtdbiter->deletions, 0, sizeof(rbtdbiter->deletions)); + dns_rbtnodechain_init(&rbtdbiter->chain); + dns_rbtnodechain_init(&rbtdbiter->nsec3chain); + if (rbtdbiter->nsec3only) { + rbtdbiter->current = &rbtdbiter->nsec3chain; + } else { + rbtdbiter->current = &rbtdbiter->chain; + } + + *iteratorp = (dns_dbiterator_t *)rbtdbiter; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +zone_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, + isc_stdtime_t now, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rdatasetheader_t *header, *header_next, *found, *foundsig; + rbtdb_serial_t serial; + rbtdb_version_t *rbtversion = version; + bool close_version = false; + rbtdb_rdatatype_t matchtype, sigmatchtype; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(type != dns_rdatatype_any); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + if (rbtversion == NULL) { + currentversion(db, (dns_dbversion_t **)(void *)(&rbtversion)); + close_version = true; + } + serial = rbtversion->serial; + now = 0; + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + found = NULL; + foundsig = NULL; + matchtype = RBTDB_RDATATYPE_VALUE(type, covers); + if (covers == 0) { + sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type); + } else { + sigmatchtype = 0; + } + + for (header = rbtnode->data; header != NULL; header = header_next) { + header_next = header->next; + do { + if (header->serial <= serial && !IGNORE(header)) { + /* + * Is this a "this rdataset doesn't + * exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + /* + * We have an active, extant rdataset. If it's a + * type we're looking for, remember it. + */ + if (header->type == matchtype) { + found = header; + if (foundsig != NULL) { + break; + } + } else if (header->type == sigmatchtype) { + foundsig = header; + if (found != NULL) { + break; + } + } + } + } + if (found != NULL) { + bind_rdataset(rbtdb, rbtnode, found, now, isc_rwlocktype_read, + rdataset); + if (foundsig != NULL) { + bind_rdataset(rbtdb, rbtnode, foundsig, now, + isc_rwlocktype_read, sigrdataset); + } + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + if (close_version) { + closeversion(db, (dns_dbversion_t **)(void *)(&rbtversion), + false); + } + + if (found == NULL) { + return (ISC_R_NOTFOUND); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, + isc_stdtime_t now, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rdatasetheader_t *header, *header_next, *found, *foundsig; + rbtdb_rdatatype_t matchtype, sigmatchtype, negtype; + isc_result_t result; + nodelock_t *lock; + isc_rwlocktype_t locktype; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(type != dns_rdatatype_any); + + UNUSED(version); + + result = ISC_R_SUCCESS; + + if (now == 0) { + isc_stdtime_get(&now); + } + + lock = &rbtdb->node_locks[rbtnode->locknum].lock; + locktype = isc_rwlocktype_read; + NODE_LOCK(lock, locktype); + + found = NULL; + foundsig = NULL; + matchtype = RBTDB_RDATATYPE_VALUE(type, covers); + negtype = RBTDB_RDATATYPE_VALUE(0, type); + if (covers == 0) { + sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, type); + } else { + sigmatchtype = 0; + } + + for (header = rbtnode->data; header != NULL; header = header_next) { + header_next = header->next; + if (!ACTIVE(header, now)) { + if ((header->rdh_ttl + STALE_TTL(header, rbtdb) < + now - RBTDB_VIRTUAL) && + (locktype == isc_rwlocktype_write || + NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS)) + { + /* + * We update the node's status only when we + * can get write access. + */ + locktype = isc_rwlocktype_write; + + /* + * We don't check if refcurrent(rbtnode) == 0 + * and try to free like we do in cache_find(), + * because refcurrent(rbtnode) must be + * non-zero. This is so because 'node' is an + * argument to the function. + */ + mark_header_ancient(rbtdb, header); + } + } else if (EXISTS(header) && !ANCIENT(header)) { + if (header->type == matchtype) { + found = header; + } else if (header->type == RBTDB_RDATATYPE_NCACHEANY || + header->type == negtype) + { + found = header; + } else if (header->type == sigmatchtype) { + foundsig = header; + } + } + } + if (found != NULL) { + bind_rdataset(rbtdb, rbtnode, found, now, locktype, rdataset); + if (!NEGATIVE(found) && foundsig != NULL) { + bind_rdataset(rbtdb, rbtnode, foundsig, now, locktype, + sigrdataset); + } + } + + NODE_UNLOCK(lock, locktype); + + if (found == NULL) { + return (ISC_R_NOTFOUND); + } + + if (NEGATIVE(found)) { + /* + * We found a negative cache entry. + */ + if (NXDOMAIN(found)) { + result = DNS_R_NCACHENXDOMAIN; + } else { + result = DNS_R_NCACHENXRRSET; + } + } + + update_cachestats(rbtdb, result); + + return (result); +} + +static isc_result_t +allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rbtdb_version_t *rbtversion = version; + rbtdb_rdatasetiter_t *iterator; + + REQUIRE(VALID_RBTDB(rbtdb)); + + iterator = isc_mem_get(rbtdb->common.mctx, sizeof(*iterator)); + + if ((db->attributes & DNS_DBATTR_CACHE) == 0) { + now = 0; + if (rbtversion == NULL) { + currentversion( + db, (dns_dbversion_t **)(void *)(&rbtversion)); + } else { + INSIST(rbtversion->rbtdb == rbtdb); + + (void)isc_refcount_increment(&rbtversion->references); + } + } else { + if (now == 0) { + isc_stdtime_get(&now); + } + rbtversion = NULL; + } + + iterator->common.magic = DNS_RDATASETITER_MAGIC; + iterator->common.methods = &rdatasetiter_methods; + iterator->common.db = db; + iterator->common.node = node; + iterator->common.version = (dns_dbversion_t *)rbtversion; + iterator->common.options = options; + iterator->common.now = now; + + isc_refcount_increment(&rbtnode->references); + + iterator->current = NULL; + + *iteratorp = (dns_rdatasetiter_t *)iterator; + + return (ISC_R_SUCCESS); +} + +static bool +cname_and_other_data(dns_rbtnode_t *node, rbtdb_serial_t serial) { + rdatasetheader_t *header, *header_next; + bool cname, other_data; + dns_rdatatype_t rdtype; + + /* + * The caller must hold the node lock. + */ + + /* + * Look for CNAME and "other data" rdatasets active in our version. + */ + cname = false; + other_data = false; + for (header = node->data; header != NULL; header = header_next) { + header_next = header->next; + if (header->type == dns_rdatatype_cname) { + /* + * Look for an active extant CNAME. + */ + do { + if (header->serial <= serial && !IGNORE(header)) + { + /* + * Is this a "this rdataset doesn't + * exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + cname = true; + } + } else { + /* + * Look for active extant "other data". + * + * "Other data" is any rdataset whose type is not + * KEY, NSEC, SIG or RRSIG. + */ + rdtype = RBTDB_RDATATYPE_BASE(header->type); + if (rdtype != dns_rdatatype_key && + rdtype != dns_rdatatype_sig && + rdtype != dns_rdatatype_nsec && + rdtype != dns_rdatatype_rrsig) + { + /* + * Is it active and extant? + */ + do { + if (header->serial <= serial && + !IGNORE(header)) + { + /* + * Is this a "this rdataset + * doesn't exist" record? + */ + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + other_data = true; + } + } + } + } + + if (cname && other_data) { + return (true); + } + + return (false); +} + +static void +resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader) { + INSIST(!IS_CACHE(rbtdb)); + INSIST(newheader->heap_index == 0); + INSIST(!ISC_LINK_LINKED(newheader, link)); + + isc_heap_insert(rbtdb->heaps[idx], newheader); +} + +/* + * node write lock must be held. + */ +static void +resign_delete(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, + rdatasetheader_t *header) { + /* + * Remove the old header from the heap + */ + if (header != NULL && header->heap_index != 0) { + isc_heap_delete(rbtdb->heaps[header->node->locknum], + header->heap_index); + header->heap_index = 0; + if (version != NULL) { + new_reference(rbtdb, header->node, + isc_rwlocktype_write); + ISC_LIST_APPEND(version->resigned_list, header, link); + } + } +} + +static uint64_t +recordsize(rdatasetheader_t *header, unsigned int namelen) { + return (dns_rdataslab_rdatasize((unsigned char *)header, + sizeof(*header)) + + sizeof(dns_ttl_t) + sizeof(dns_rdatatype_t) + + sizeof(dns_rdataclass_t) + namelen); +} + +static void +update_recordsandxfrsize(bool add, rbtdb_version_t *rbtversion, + rdatasetheader_t *header, unsigned int namelen) { + unsigned char *hdr = (unsigned char *)header; + size_t hdrsize = sizeof(*header); + + RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write); + if (add) { + rbtversion->records += dns_rdataslab_count(hdr, hdrsize); + rbtversion->xfrsize += recordsize(header, namelen); + } else { + rbtversion->records -= dns_rdataslab_count(hdr, hdrsize); + rbtversion->xfrsize -= recordsize(header, namelen); + } + RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write); +} + +/* + * write lock on rbtnode must be held. + */ +static isc_result_t +add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename, + rbtdb_version_t *rbtversion, rdatasetheader_t *newheader, + unsigned int options, bool loading, dns_rdataset_t *addedrdataset, + isc_stdtime_t now) { + rbtdb_changed_t *changed = NULL; + rdatasetheader_t *topheader = NULL, *topheader_prev = NULL; + rdatasetheader_t *header = NULL, *sigheader = NULL; + unsigned char *merged = NULL; + isc_result_t result; + bool header_nx; + bool newheader_nx; + bool merge; + dns_rdatatype_t rdtype, covers; + rbtdb_rdatatype_t negtype, sigtype; + dns_trust_t trust; + int idx; + + /* + * Add an rdatasetheader_t to a node. + */ + + /* + * Caller must be holding the node lock. + */ + + if ((options & DNS_DBADD_MERGE) != 0) { + REQUIRE(rbtversion != NULL); + merge = true; + } else { + merge = false; + } + + if ((options & DNS_DBADD_FORCE) != 0) { + trust = dns_trust_ultimate; + } else { + trust = newheader->trust; + } + + if (rbtversion != NULL && !loading) { + /* + * We always add a changed record, even if no changes end up + * being made to this node, because it's harmless and + * simplifies the code. + */ + changed = add_changed(rbtdb, rbtversion, rbtnode); + if (changed == NULL) { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + return (ISC_R_NOMEMORY); + } + } + + newheader_nx = NONEXISTENT(newheader) ? true : false; + topheader_prev = NULL; + sigheader = NULL; + negtype = 0; + if (rbtversion == NULL && !newheader_nx) { + rdtype = RBTDB_RDATATYPE_BASE(newheader->type); + covers = RBTDB_RDATATYPE_EXT(newheader->type); + sigtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, covers); + if (NEGATIVE(newheader)) { + /* + * We're adding a negative cache entry. + */ + if (covers == dns_rdatatype_any) { + /* + * If we're adding an negative cache entry + * which covers all types (NXDOMAIN, + * NODATA(QTYPE=ANY)), + * + * We make all other data ancient so that the + * only rdataset that can be found at this + * node is the negative cache entry. + */ + for (topheader = rbtnode->data; + topheader != NULL; + topheader = topheader->next) + { + set_ttl(rbtdb, topheader, 0); + mark_header_ancient(rbtdb, topheader); + } + goto find_header; + } + /* + * Otherwise look for any RRSIGs of the given + * type so they can be marked ancient later. + */ + for (topheader = rbtnode->data; topheader != NULL; + topheader = topheader->next) + { + if (topheader->type == sigtype) { + sigheader = topheader; + } + } + negtype = RBTDB_RDATATYPE_VALUE(covers, 0); + } else { + /* + * We're adding something that isn't a + * negative cache entry. Look for an extant + * non-ancient NXDOMAIN/NODATA(QTYPE=ANY) negative + * cache entry. If we're adding an RRSIG, also + * check for an extant non-ancient NODATA ncache + * entry which covers the same type as the RRSIG. + */ + for (topheader = rbtnode->data; topheader != NULL; + topheader = topheader->next) + { + if ((topheader->type == + RBTDB_RDATATYPE_NCACHEANY) || + (newheader->type == sigtype && + topheader->type == + RBTDB_RDATATYPE_VALUE(0, covers))) + { + break; + } + } + if (topheader != NULL && EXISTS(topheader) && + ACTIVE(topheader, now)) + { + /* + * Found one. + */ + if (trust < topheader->trust) { + /* + * The NXDOMAIN/NODATA(QTYPE=ANY) + * is more trusted. + */ + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + if (addedrdataset != NULL) { + bind_rdataset( + rbtdb, rbtnode, + topheader, now, + isc_rwlocktype_write, + addedrdataset); + } + return (DNS_R_UNCHANGED); + } + /* + * The new rdataset is better. Expire the + * ncache entry. + */ + set_ttl(rbtdb, topheader, 0); + mark_header_ancient(rbtdb, topheader); + topheader = NULL; + goto find_header; + } + negtype = RBTDB_RDATATYPE_VALUE(0, rdtype); + } + } + + for (topheader = rbtnode->data; topheader != NULL; + topheader = topheader->next) + { + if (topheader->type == newheader->type || + topheader->type == negtype) + { + break; + } + topheader_prev = topheader; + } + +find_header: + /* + * If header isn't NULL, we've found the right type. There may be + * IGNORE rdatasets between the top of the chain and the first real + * data. We skip over them. + */ + header = topheader; + while (header != NULL && IGNORE(header)) { + header = header->down; + } + if (header != NULL) { + header_nx = NONEXISTENT(header) ? true : false; + + /* + * Deleting an already non-existent rdataset has no effect. + */ + if (header_nx && newheader_nx) { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + return (DNS_R_UNCHANGED); + } + + /* + * Trying to add an rdataset with lower trust to a cache + * DB has no effect, provided that the cache data isn't + * stale. If the cache data is stale, new lower trust + * data will supersede it below. Unclear what the best + * policy is here. + */ + if (rbtversion == NULL && trust < header->trust && + (ACTIVE(header, now) || header_nx)) + { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + if (addedrdataset != NULL) { + bind_rdataset(rbtdb, rbtnode, header, now, + isc_rwlocktype_write, + addedrdataset); + } + return (DNS_R_UNCHANGED); + } + + /* + * Don't merge if a nonexistent rdataset is involved. + */ + if (merge && (header_nx || newheader_nx)) { + merge = false; + } + + /* + * If 'merge' is true, we'll try to create a new rdataset + * that is the union of 'newheader' and 'header'. + */ + if (merge) { + unsigned int flags = 0; + INSIST(rbtversion->serial >= header->serial); + merged = NULL; + result = ISC_R_SUCCESS; + + if ((options & DNS_DBADD_EXACT) != 0) { + flags |= DNS_RDATASLAB_EXACT; + } + /* + * TTL use here is irrelevant to the cache; + * merge is only done with zonedbs. + */ + if ((options & DNS_DBADD_EXACTTTL) != 0 && + newheader->rdh_ttl != header->rdh_ttl) + { + result = DNS_R_NOTEXACT; + } else if (newheader->rdh_ttl != header->rdh_ttl) { + flags |= DNS_RDATASLAB_FORCE; + } + if (result == ISC_R_SUCCESS) { + result = dns_rdataslab_merge( + (unsigned char *)header, + (unsigned char *)newheader, + (unsigned int)(sizeof(*newheader)), + rbtdb->common.mctx, + rbtdb->common.rdclass, + (dns_rdatatype_t)header->type, flags, + &merged); + } + if (result == ISC_R_SUCCESS) { + /* + * If 'header' has the same serial number as + * we do, we could clean it up now if we knew + * that our caller had no references to it. + * We don't know this, however, so we leave it + * alone. It will get cleaned up when + * clean_zone_node() runs. + */ + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + newheader = (rdatasetheader_t *)merged; + init_rdataset(rbtdb, newheader); + update_newheader(newheader, header); + if (loading && RESIGN(newheader) && + RESIGN(header) && + resign_sooner(header, newheader)) + { + newheader->resign = header->resign; + newheader->resign_lsb = + header->resign_lsb; + } + } else { + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + return (result); + } + } + /* + * Don't replace existing NS, A and AAAA RRsets in the + * cache if they are already exist. This prevents named + * being locked to old servers. Don't lower trust of + * existing record if the update is forced. Nothing + * special to be done w.r.t stale data; it gets replaced + * normally further down. + */ + if (IS_CACHE(rbtdb) && ACTIVE(header, now) && + header->type == dns_rdatatype_ns && !header_nx && + !newheader_nx && header->trust >= newheader->trust && + dns_rdataslab_equalx((unsigned char *)header, + (unsigned char *)newheader, + (unsigned int)(sizeof(*newheader)), + rbtdb->common.rdclass, + (dns_rdatatype_t)header->type)) + { + /* + * Honour the new ttl if it is less than the + * older one. + */ + if (header->rdh_ttl > newheader->rdh_ttl) { + set_ttl(rbtdb, header, newheader->rdh_ttl); + } + if (header->noqname == NULL && + newheader->noqname != NULL) + { + header->noqname = newheader->noqname; + newheader->noqname = NULL; + } + if (header->closest == NULL && + newheader->closest != NULL) + { + header->closest = newheader->closest; + newheader->closest = NULL; + } + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + if (addedrdataset != NULL) { + bind_rdataset(rbtdb, rbtnode, header, now, + isc_rwlocktype_write, + addedrdataset); + } + return (ISC_R_SUCCESS); + } + /* + * If we have will be replacing a NS RRset force its TTL + * to be no more than the current NS RRset's TTL. This + * ensures the delegations that are withdrawn are honoured. + */ + if (IS_CACHE(rbtdb) && ACTIVE(header, now) && + header->type == dns_rdatatype_ns && !header_nx && + !newheader_nx && header->trust <= newheader->trust) + { + if (newheader->rdh_ttl > header->rdh_ttl) { + newheader->rdh_ttl = header->rdh_ttl; + } + } + if (IS_CACHE(rbtdb) && ACTIVE(header, now) && + (options & DNS_DBADD_PREFETCH) == 0 && + (header->type == dns_rdatatype_a || + header->type == dns_rdatatype_aaaa || + header->type == dns_rdatatype_ds || + header->type == RBTDB_RDATATYPE_SIGDS) && + !header_nx && !newheader_nx && + header->trust >= newheader->trust && + dns_rdataslab_equal((unsigned char *)header, + (unsigned char *)newheader, + (unsigned int)(sizeof(*newheader)))) + { + /* + * Honour the new ttl if it is less than the + * older one. + */ + if (header->rdh_ttl > newheader->rdh_ttl) { + set_ttl(rbtdb, header, newheader->rdh_ttl); + } + if (header->noqname == NULL && + newheader->noqname != NULL) + { + header->noqname = newheader->noqname; + newheader->noqname = NULL; + } + if (header->closest == NULL && + newheader->closest != NULL) + { + header->closest = newheader->closest; + newheader->closest = NULL; + } + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + if (addedrdataset != NULL) { + bind_rdataset(rbtdb, rbtnode, header, now, + isc_rwlocktype_write, + addedrdataset); + } + return (ISC_R_SUCCESS); + } + INSIST(rbtversion == NULL || + rbtversion->serial >= topheader->serial); + if (loading) { + newheader->down = NULL; + idx = newheader->node->locknum; + if (IS_CACHE(rbtdb)) { + if (ZEROTTL(newheader)) { + ISC_LIST_APPEND(rbtdb->rdatasets[idx], + newheader, link); + } else { + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], + newheader, link); + } + INSIST(rbtdb->heaps != NULL); + isc_heap_insert(rbtdb->heaps[idx], newheader); + } else if (RESIGN(newheader)) { + resign_insert(rbtdb, idx, newheader); + /* + * Don't call resign_delete as we don't need + * to reverse the delete. The free_rdataset + * call below will clean up the heap entry. + */ + } + + /* + * There are no other references to 'header' when + * loading, so we MAY clean up 'header' now. + * Since we don't generate changed records when + * loading, we MUST clean up 'header' now. + */ + if (topheader_prev != NULL) { + topheader_prev->next = newheader; + } else { + rbtnode->data = newheader; + } + newheader->next = topheader->next; + if (rbtversion != NULL && !header_nx) { + update_recordsandxfrsize(false, rbtversion, + header, + nodename->length); + } + free_rdataset(rbtdb, rbtdb->common.mctx, header); + } else { + idx = newheader->node->locknum; + if (IS_CACHE(rbtdb)) { + INSIST(rbtdb->heaps != NULL); + isc_heap_insert(rbtdb->heaps[idx], newheader); + if (ZEROTTL(newheader)) { + ISC_LIST_APPEND(rbtdb->rdatasets[idx], + newheader, link); + } else { + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], + newheader, link); + } + } else if (RESIGN(newheader)) { + resign_insert(rbtdb, idx, newheader); + resign_delete(rbtdb, rbtversion, header); + } + if (topheader_prev != NULL) { + topheader_prev->next = newheader; + } else { + rbtnode->data = newheader; + } + newheader->next = topheader->next; + newheader->down = topheader; + topheader->next = newheader; + rbtnode->dirty = 1; + if (changed != NULL) { + changed->dirty = true; + } + if (rbtversion == NULL) { + set_ttl(rbtdb, header, 0); + mark_header_ancient(rbtdb, header); + if (sigheader != NULL) { + set_ttl(rbtdb, sigheader, 0); + mark_header_ancient(rbtdb, sigheader); + } + } + if (rbtversion != NULL && !header_nx) { + update_recordsandxfrsize(false, rbtversion, + header, + nodename->length); + } + } + } else { + /* + * No non-IGNORED rdatasets of the given type exist at + * this node. + */ + + /* + * If we're trying to delete the type, don't bother. + */ + if (newheader_nx) { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + return (DNS_R_UNCHANGED); + } + + idx = newheader->node->locknum; + if (IS_CACHE(rbtdb)) { + isc_heap_insert(rbtdb->heaps[idx], newheader); + if (ZEROTTL(newheader)) { + ISC_LIST_APPEND(rbtdb->rdatasets[idx], + newheader, link); + } else { + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], + newheader, link); + } + } else if (RESIGN(newheader)) { + resign_insert(rbtdb, idx, newheader); + resign_delete(rbtdb, rbtversion, header); + } + + if (topheader != NULL) { + /* + * We have an list of rdatasets of the given type, + * but they're all marked IGNORE. We simply insert + * the new rdataset at the head of the list. + * + * Ignored rdatasets cannot occur during loading, so + * we INSIST on it. + */ + INSIST(!loading); + INSIST(rbtversion == NULL || + rbtversion->serial >= topheader->serial); + if (topheader_prev != NULL) { + topheader_prev->next = newheader; + } else { + rbtnode->data = newheader; + } + newheader->next = topheader->next; + newheader->down = topheader; + topheader->next = newheader; + rbtnode->dirty = 1; + if (changed != NULL) { + changed->dirty = true; + } + } else { + /* + * No rdatasets of the given type exist at the node. + */ + newheader->next = rbtnode->data; + newheader->down = NULL; + rbtnode->data = newheader; + } + } + + if (rbtversion != NULL && !newheader_nx) { + update_recordsandxfrsize(true, rbtversion, newheader, + nodename->length); + } + + /* + * Check if the node now contains CNAME and other data. + */ + if (rbtversion != NULL && + cname_and_other_data(rbtnode, rbtversion->serial)) + { + return (DNS_R_CNAMEANDOTHER); + } + + if (addedrdataset != NULL) { + bind_rdataset(rbtdb, rbtnode, newheader, now, + isc_rwlocktype_write, addedrdataset); + } + + return (ISC_R_SUCCESS); +} + +static bool +delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + rbtdb_rdatatype_t type) { + if (IS_CACHE(rbtdb)) { + if (type == dns_rdatatype_dname) { + return (true); + } else { + return (false); + } + } else if (type == dns_rdatatype_dname || + (type == dns_rdatatype_ns && + (node != rbtdb->origin_node || IS_STUB(rbtdb)))) + { + return (true); + } + return (false); +} + +static isc_result_t +addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader, + dns_rdataset_t *rdataset) { + struct noqname *noqname; + isc_mem_t *mctx = rbtdb->common.mctx; + dns_name_t name; + dns_rdataset_t neg, negsig; + isc_result_t result; + isc_region_t r; + + dns_name_init(&name, NULL); + dns_rdataset_init(&neg); + dns_rdataset_init(&negsig); + + result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + noqname = isc_mem_get(mctx, sizeof(*noqname)); + dns_name_init(&noqname->name, NULL); + noqname->neg = NULL; + noqname->negsig = NULL; + noqname->type = neg.type; + dns_name_dup(&name, mctx, &noqname->name); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + noqname->neg = r.base; + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + noqname->negsig = r.base; + dns_rdataset_disassociate(&neg); + dns_rdataset_disassociate(&negsig); + newheader->noqname = noqname; + return (ISC_R_SUCCESS); + +cleanup: + dns_rdataset_disassociate(&neg); + dns_rdataset_disassociate(&negsig); + free_noqname(mctx, &noqname); + return (result); +} + +static isc_result_t +addclosest(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader, + dns_rdataset_t *rdataset) { + struct noqname *closest; + isc_mem_t *mctx = rbtdb->common.mctx; + dns_name_t name; + dns_rdataset_t neg, negsig; + isc_result_t result; + isc_region_t r; + + dns_name_init(&name, NULL); + dns_rdataset_init(&neg); + dns_rdataset_init(&negsig); + + result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + closest = isc_mem_get(mctx, sizeof(*closest)); + dns_name_init(&closest->name, NULL); + closest->neg = NULL; + closest->negsig = NULL; + closest->type = neg.type; + dns_name_dup(&name, mctx, &closest->name); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + closest->neg = r.base; + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + closest->negsig = r.base; + dns_rdataset_disassociate(&neg); + dns_rdataset_disassociate(&negsig); + newheader->closest = closest; + return (ISC_R_SUCCESS); + +cleanup: + dns_rdataset_disassociate(&neg); + dns_rdataset_disassociate(&negsig); + free_noqname(mctx, &closest); + return (result); +} + +static dns_dbmethods_t zone_methods; + +static size_t +rdataset_size(rdatasetheader_t *header) { + if (!NONEXISTENT(header)) { + return (dns_rdataslab_size((unsigned char *)header, + sizeof(*header))); + } + + return (sizeof(*header)); +} + +static isc_result_t +addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *addedrdataset) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rbtdb_version_t *rbtversion = version; + isc_region_t region; + rdatasetheader_t *newheader; + rdatasetheader_t *header; + isc_result_t result; + bool delegating; + bool newnsec; + bool tree_locked = false; + bool cache_is_overmem = false; + dns_fixedname_t fixed; + dns_name_t *name; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + if (rbtdb->common.methods == &zone_methods) { + /* + * SOA records are only allowed at top of zone. + */ + if (rdataset->type == dns_rdatatype_soa && + node != rbtdb->origin_node) + { + return (DNS_R_NOTZONETOP); + } + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 && + (rdataset->type == dns_rdatatype_nsec3 || + rdataset->covers == dns_rdatatype_nsec3)) || + (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 && + rdataset->type != dns_rdatatype_nsec3 && + rdataset->covers != dns_rdatatype_nsec3))); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + } + + if (rbtversion == NULL) { + if (now == 0) { + isc_stdtime_get(&now); + } + } else { + now = 0; + } + + result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, + ®ion, sizeof(rdatasetheader_t)); + if (result != ISC_R_SUCCESS) { + return (result); + } + + name = dns_fixedname_initname(&fixed); + nodefullname(db, node, name); + dns_rdataset_getownercase(rdataset, name); + + newheader = (rdatasetheader_t *)region.base; + init_rdataset(rbtdb, newheader); + setownercase(newheader, name); + set_ttl(rbtdb, newheader, rdataset->ttl + now); + newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type, + rdataset->covers); + atomic_init(&newheader->attributes, 0); + if (rdataset->ttl == 0U) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_ZEROTTL); + } + newheader->noqname = NULL; + newheader->closest = NULL; + atomic_init(&newheader->count, + atomic_fetch_add_relaxed(&init_count, 1)); + newheader->trust = rdataset->trust; + newheader->last_used = now; + newheader->node = rbtnode; + if (rbtversion != NULL) { + newheader->serial = rbtversion->serial; + now = 0; + + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN); + newheader->resign = + (isc_stdtime_t)(dns_time64_from32( + rdataset->resign) >> + 1); + newheader->resign_lsb = rdataset->resign & 0x1; + } else { + newheader->resign = 0; + newheader->resign_lsb = 0; + } + } else { + newheader->serial = 1; + newheader->resign = 0; + newheader->resign_lsb = 0; + if ((rdataset->attributes & DNS_RDATASETATTR_PREFETCH) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_PREFETCH); + } + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_NEGATIVE); + } + if ((rdataset->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_NXDOMAIN); + } + if ((rdataset->attributes & DNS_RDATASETATTR_OPTOUT) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_OPTOUT); + } + if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) { + result = addnoqname(rbtdb, newheader, rdataset); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + return (result); + } + } + if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) { + result = addclosest(rbtdb, newheader, rdataset); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + return (result); + } + } + } + + /* + * If we're adding a delegation type (e.g. NS or DNAME for a zone, + * just DNAME for the cache), then we need to set the callback bit + * on the node. + */ + if (delegating_type(rbtdb, rbtnode, rdataset->type)) { + delegating = true; + } else { + delegating = false; + } + + /* + * Add to the auxiliary NSEC tree if we're adding an NSEC record. + */ + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + if (rbtnode->nsec != DNS_RBT_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec) + { + newnsec = true; + } else { + newnsec = false; + } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + /* + * If we're adding a delegation type, adding to the auxiliary NSEC + * tree, or the DB is a cache in an overmem state, hold an + * exclusive lock on the tree. In the latter case the lock does + * not necessarily have to be acquired but it will help purge + * ancient entries more effectively. + */ + if (IS_CACHE(rbtdb) && isc_mem_isovermem(rbtdb->common.mctx)) { + cache_is_overmem = true; + } + if (delegating || newnsec || cache_is_overmem) { + tree_locked = true; + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + } + + if (cache_is_overmem) { + overmem_purge(rbtdb, rbtnode->locknum, rdataset_size(newheader), + tree_locked); + } + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + if (rbtdb->rrsetstats != NULL) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_STATCOUNT); + update_rrsetstats(rbtdb, newheader->type, + atomic_load_acquire(&newheader->attributes), + true); + } + + if (IS_CACHE(rbtdb)) { + if (tree_locked) { + cleanup_dead_nodes(rbtdb, rbtnode->locknum); + } + + header = isc_heap_element(rbtdb->heaps[rbtnode->locknum], 1); + if (header != NULL) { + dns_ttl_t rdh_ttl = header->rdh_ttl; + + /* Only account for stale TTL if cache is not overmem */ + if (!cache_is_overmem) { + rdh_ttl += STALE_TTL(header, rbtdb); + } + + if (rdh_ttl < now - RBTDB_VIRTUAL) { + expire_header(rbtdb, header, tree_locked, + expire_ttl); + } + } + + /* + * If we've been holding a write lock on the tree just for + * cleaning, we can release it now. However, we still need the + * node lock. + */ + if (tree_locked && !delegating && !newnsec) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + tree_locked = false; + } + } + + result = ISC_R_SUCCESS; + if (newnsec) { + dns_rbtnode_t *nsecnode; + + nsecnode = NULL; + result = dns_rbt_addnode(rbtdb->nsec, name, &nsecnode); + if (result == ISC_R_SUCCESS) { + nsecnode->nsec = DNS_RBT_NSEC_NSEC; + rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC; + } else if (result == ISC_R_EXISTS) { + rbtnode->nsec = DNS_RBT_NSEC_HAS_NSEC; + result = ISC_R_SUCCESS; + } + } + + if (result == ISC_R_SUCCESS) { + result = add32(rbtdb, rbtnode, name, rbtversion, newheader, + options, false, addedrdataset, now); + } + if (result == ISC_R_SUCCESS && delegating) { + rbtnode->find_callback = 1; + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + if (tree_locked) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + } + + /* + * Update the zone's secure status. If version is non-NULL + * this is deferred until closeversion() is called. + */ + if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) { + iszonesecure(db, version, rbtdb->origin_node); + } + + return (result); +} + +static isc_result_t +subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *newrdataset) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rbtdb_version_t *rbtversion = version; + dns_fixedname_t fname; + dns_name_t *nodename = dns_fixedname_initname(&fname); + rdatasetheader_t *topheader, *topheader_prev, *header, *newheader; + unsigned char *subresult; + isc_region_t region; + isc_result_t result; + rbtdb_changed_t *changed; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(rbtversion != NULL && rbtversion->rbtdb == rbtdb); + + if (rbtdb->common.methods == &zone_methods) { + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + REQUIRE(((rbtnode->nsec == DNS_RBT_NSEC_NSEC3 && + (rdataset->type == dns_rdatatype_nsec3 || + rdataset->covers == dns_rdatatype_nsec3)) || + (rbtnode->nsec != DNS_RBT_NSEC_NSEC3 && + rdataset->type != dns_rdatatype_nsec3 && + rdataset->covers != dns_rdatatype_nsec3))); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + } + + nodefullname(db, node, nodename); + + result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, + ®ion, sizeof(rdatasetheader_t)); + if (result != ISC_R_SUCCESS) { + return (result); + } + newheader = (rdatasetheader_t *)region.base; + init_rdataset(rbtdb, newheader); + set_ttl(rbtdb, newheader, rdataset->ttl); + newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type, + rdataset->covers); + atomic_init(&newheader->attributes, 0); + newheader->serial = rbtversion->serial; + newheader->trust = 0; + newheader->noqname = NULL; + newheader->closest = NULL; + atomic_init(&newheader->count, + atomic_fetch_add_relaxed(&init_count, 1)); + newheader->last_used = 0; + newheader->node = rbtnode; + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN); + newheader->resign = + (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >> + 1); + newheader->resign_lsb = rdataset->resign & 0x1; + } else { + newheader->resign = 0; + newheader->resign_lsb = 0; + } + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + changed = add_changed(rbtdb, rbtversion, rbtnode); + if (changed == NULL) { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + return (ISC_R_NOMEMORY); + } + + topheader_prev = NULL; + for (topheader = rbtnode->data; topheader != NULL; + topheader = topheader->next) + { + if (topheader->type == newheader->type) { + break; + } + topheader_prev = topheader; + } + /* + * If header isn't NULL, we've found the right type. There may be + * IGNORE rdatasets between the top of the chain and the first real + * data. We skip over them. + */ + header = topheader; + while (header != NULL && IGNORE(header)) { + header = header->down; + } + if (header != NULL && EXISTS(header)) { + unsigned int flags = 0; + subresult = NULL; + result = ISC_R_SUCCESS; + if ((options & DNS_DBSUB_EXACT) != 0) { + flags |= DNS_RDATASLAB_EXACT; + if (newheader->rdh_ttl != header->rdh_ttl) { + result = DNS_R_NOTEXACT; + } + } + if (result == ISC_R_SUCCESS) { + result = dns_rdataslab_subtract( + (unsigned char *)header, + (unsigned char *)newheader, + (unsigned int)(sizeof(*newheader)), + rbtdb->common.mctx, rbtdb->common.rdclass, + (dns_rdatatype_t)header->type, flags, + &subresult); + } + if (result == ISC_R_SUCCESS) { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + newheader = (rdatasetheader_t *)subresult; + init_rdataset(rbtdb, newheader); + update_newheader(newheader, header); + if (RESIGN(header)) { + RDATASET_ATTR_SET(newheader, + RDATASET_ATTR_RESIGN); + newheader->resign = header->resign; + newheader->resign_lsb = header->resign_lsb; + resign_insert(rbtdb, rbtnode->locknum, + newheader); + } + /* + * We have to set the serial since the rdataslab + * subtraction routine copies the reserved portion of + * header, not newheader. + */ + newheader->serial = rbtversion->serial; + /* + * XXXJT: dns_rdataslab_subtract() copied the pointers + * to additional info. We need to clear these fields + * to avoid having duplicated references. + */ + update_recordsandxfrsize(true, rbtversion, newheader, + nodename->length); + } else if (result == DNS_R_NXRRSET) { + /* + * This subtraction would remove all of the rdata; + * add a nonexistent header instead. + */ + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + newheader = new_rdataset(rbtdb, rbtdb->common.mctx); + if (newheader == NULL) { + result = ISC_R_NOMEMORY; + goto unlock; + } + init_rdataset(rbtdb, newheader); + set_ttl(rbtdb, newheader, 0); + newheader->type = topheader->type; + atomic_init(&newheader->attributes, + RDATASET_ATTR_NONEXISTENT); + newheader->trust = 0; + newheader->serial = rbtversion->serial; + newheader->noqname = NULL; + newheader->closest = NULL; + atomic_init(&newheader->count, 0); + newheader->node = rbtnode; + newheader->resign = 0; + newheader->resign_lsb = 0; + newheader->last_used = 0; + } else { + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + goto unlock; + } + + /* + * If we're here, we want to link newheader in front of + * topheader. + */ + INSIST(rbtversion->serial >= topheader->serial); + update_recordsandxfrsize(false, rbtversion, header, + nodename->length); + if (topheader_prev != NULL) { + topheader_prev->next = newheader; + } else { + rbtnode->data = newheader; + } + newheader->next = topheader->next; + newheader->down = topheader; + topheader->next = newheader; + rbtnode->dirty = 1; + changed->dirty = true; + resign_delete(rbtdb, rbtversion, header); + } else { + /* + * The rdataset doesn't exist, so we don't need to do anything + * to satisfy the deletion request. + */ + free_rdataset(rbtdb, rbtdb->common.mctx, newheader); + if ((options & DNS_DBSUB_EXACT) != 0) { + result = DNS_R_NOTEXACT; + } else { + result = DNS_R_UNCHANGED; + } + } + + if (result == ISC_R_SUCCESS && newrdataset != NULL) { + bind_rdataset(rbtdb, rbtnode, newheader, 0, + isc_rwlocktype_write, newrdataset); + } + + if (result == DNS_R_NXRRSET && newrdataset != NULL && + (options & DNS_DBSUB_WANTOLD) != 0) + { + bind_rdataset(rbtdb, rbtnode, header, 0, isc_rwlocktype_write, + newrdataset); + } + +unlock: + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + /* + * Update the zone's secure status. If version is non-NULL + * this is deferred until closeversion() is called. + */ + if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) { + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + version = rbtdb->current_version; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + iszonesecure(db, version, rbtdb->origin_node); + } + + return (result); +} + +static isc_result_t +deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + rbtdb_version_t *rbtversion = version; + dns_fixedname_t fname; + dns_name_t *nodename = dns_fixedname_initname(&fname); + isc_result_t result; + rdatasetheader_t *newheader; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + if (type == dns_rdatatype_any) { + return (ISC_R_NOTIMPLEMENTED); + } + if (type == dns_rdatatype_rrsig && covers == 0) { + return (ISC_R_NOTIMPLEMENTED); + } + + newheader = new_rdataset(rbtdb, rbtdb->common.mctx); + if (newheader == NULL) { + return (ISC_R_NOMEMORY); + } + init_rdataset(rbtdb, newheader); + set_ttl(rbtdb, newheader, 0); + newheader->type = RBTDB_RDATATYPE_VALUE(type, covers); + atomic_init(&newheader->attributes, RDATASET_ATTR_NONEXISTENT); + newheader->trust = 0; + newheader->noqname = NULL; + newheader->closest = NULL; + if (rbtversion != NULL) { + newheader->serial = rbtversion->serial; + } else { + newheader->serial = 0; + } + atomic_init(&newheader->count, 0); + newheader->last_used = 0; + newheader->node = rbtnode; + + nodefullname(db, node, nodename); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + result = add32(rbtdb, rbtnode, nodename, rbtversion, newheader, + DNS_DBADD_FORCE, false, NULL, 0); + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + /* + * Update the zone's secure status. If version is non-NULL + * this is deferred until closeversion() is called. + */ + if (result == ISC_R_SUCCESS && version == NULL && !IS_CACHE(rbtdb)) { + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + version = rbtdb->current_version; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + iszonesecure(db, version, rbtdb->origin_node); + } + + return (result); +} + +/* + * load a non-NSEC3 node in the main tree and optionally to the auxiliary NSEC + */ +static isc_result_t +loadnode(dns_rbtdb_t *rbtdb, const dns_name_t *name, dns_rbtnode_t **nodep, + bool hasnsec) { + isc_result_t noderesult, nsecresult, tmpresult; + dns_rbtnode_t *nsecnode = NULL, *node = NULL; + + noderesult = dns_rbt_addnode(rbtdb->tree, name, &node); + if (!hasnsec) { + goto done; + } + if (noderesult == ISC_R_EXISTS) { + /* + * Add a node to the auxiliary NSEC tree for an old node + * just now getting an NSEC record. + */ + if (node->nsec == DNS_RBT_NSEC_HAS_NSEC) { + goto done; + } + } else if (noderesult != ISC_R_SUCCESS) { + goto done; + } + + /* + * Build the auxiliary tree for NSECs as we go. + * This tree speeds searches for closest NSECs that would otherwise + * need to examine many irrelevant nodes in large TLDs. + * + * Add nodes to the auxiliary tree after corresponding nodes have + * been added to the main tree. + */ + nsecresult = dns_rbt_addnode(rbtdb->nsec, name, &nsecnode); + if (nsecresult == ISC_R_SUCCESS) { + nsecnode->nsec = DNS_RBT_NSEC_NSEC; + node->nsec = DNS_RBT_NSEC_HAS_NSEC; + goto done; + } + + if (nsecresult == ISC_R_EXISTS) { +#if 1 /* 0 */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "addnode: NSEC node already exists"); +#endif /* if 1 */ + node->nsec = DNS_RBT_NSEC_HAS_NSEC; + goto done; + } + + if (noderesult == ISC_R_SUCCESS) { + /* + * Remove the node we just added above. + */ + tmpresult = dns_rbt_deletenode(rbtdb->tree, node, false); + if (tmpresult != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "loading_addrdataset: " + "dns_rbt_deletenode: %s after " + "dns_rbt_addnode(NSEC): %s", + isc_result_totext(tmpresult), + isc_result_totext(noderesult)); + } + } + + /* + * Set the error condition to be returned. + */ + noderesult = nsecresult; + +done: + if (noderesult == ISC_R_SUCCESS || noderesult == ISC_R_EXISTS) { + *nodep = node; + } + + return (noderesult); +} + +static isc_result_t +loading_addrdataset(void *arg, const dns_name_t *name, + dns_rdataset_t *rdataset) { + rbtdb_load_t *loadctx = arg; + dns_rbtdb_t *rbtdb = loadctx->rbtdb; + dns_rbtnode_t *node; + isc_result_t result; + isc_region_t region; + rdatasetheader_t *newheader; + + REQUIRE(rdataset->rdclass == rbtdb->common.rdclass); + + /* + * SOA records are only allowed at top of zone. + */ + if (rdataset->type == dns_rdatatype_soa && !IS_CACHE(rbtdb) && + !dns_name_equal(name, &rbtdb->common.origin)) + { + return (DNS_R_NOTZONETOP); + } + + if (rdataset->type != dns_rdatatype_nsec3 && + rdataset->covers != dns_rdatatype_nsec3) + { + add_empty_wildcards(rbtdb, name, false); + } + + if (dns_name_iswildcard(name)) { + /* + * NS record owners cannot legally be wild cards. + */ + if (rdataset->type == dns_rdatatype_ns) { + return (DNS_R_INVALIDNS); + } + /* + * NSEC3 record owners cannot legally be wild cards. + */ + if (rdataset->type == dns_rdatatype_nsec3) { + return (DNS_R_INVALIDNSEC3); + } + result = add_wildcard_magic(rbtdb, name, false); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + node = NULL; + if (rdataset->type == dns_rdatatype_nsec3 || + rdataset->covers == dns_rdatatype_nsec3) + { + result = dns_rbt_addnode(rbtdb->nsec3, name, &node); + if (result == ISC_R_SUCCESS) { + node->nsec = DNS_RBT_NSEC_NSEC3; + } + } else if (rdataset->type == dns_rdatatype_nsec) { + result = loadnode(rbtdb, name, &node, true); + } else { + result = loadnode(rbtdb, name, &node, false); + } + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + return (result); + } + if (result == ISC_R_SUCCESS) { + node->locknum = node->hashval % rbtdb->node_lock_count; + } + + result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, + ®ion, sizeof(rdatasetheader_t)); + if (result != ISC_R_SUCCESS) { + return (result); + } + newheader = (rdatasetheader_t *)region.base; + init_rdataset(rbtdb, newheader); + set_ttl(rbtdb, newheader, rdataset->ttl + loadctx->now); /* XXX overflow + * check */ + newheader->type = RBTDB_RDATATYPE_VALUE(rdataset->type, + rdataset->covers); + atomic_init(&newheader->attributes, 0); + newheader->trust = rdataset->trust; + newheader->serial = 1; + newheader->noqname = NULL; + newheader->closest = NULL; + atomic_init(&newheader->count, + atomic_fetch_add_relaxed(&init_count, 1)); + newheader->last_used = 0; + newheader->node = node; + setownercase(newheader, name); + + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + RDATASET_ATTR_SET(newheader, RDATASET_ATTR_RESIGN); + newheader->resign = + (isc_stdtime_t)(dns_time64_from32(rdataset->resign) >> + 1); + newheader->resign_lsb = rdataset->resign & 0x1; + } else { + newheader->resign = 0; + newheader->resign_lsb = 0; + } + + NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, isc_rwlocktype_write); + result = add32(rbtdb, node, name, rbtdb->current_version, newheader, + DNS_DBADD_MERGE, true, NULL, 0); + NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock, + isc_rwlocktype_write); + + if (result == ISC_R_SUCCESS && + delegating_type(rbtdb, node, rdataset->type)) + { + node->find_callback = 1; + } else if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +static isc_result_t +beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + rbtdb_load_t *loadctx; + dns_rbtdb_t *rbtdb; + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + REQUIRE(VALID_RBTDB(rbtdb)); + + loadctx = isc_mem_get(rbtdb->common.mctx, sizeof(*loadctx)); + + loadctx->rbtdb = rbtdb; + if (IS_CACHE(rbtdb)) { + isc_stdtime_get(&loadctx->now); + } else { + loadctx->now = 0; + } + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + + REQUIRE((rbtdb->attributes & + (RBTDB_ATTR_LOADED | RBTDB_ATTR_LOADING)) == 0); + rbtdb->attributes |= RBTDB_ATTR_LOADING; + + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + callbacks->add = loading_addrdataset; + callbacks->add_private = loadctx; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + rbtdb_load_t *loadctx; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(DNS_CALLBACK_VALID(callbacks)); + loadctx = callbacks->add_private; + REQUIRE(loadctx != NULL); + REQUIRE(loadctx->rbtdb == rbtdb); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + + REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADING) != 0); + REQUIRE((rbtdb->attributes & RBTDB_ATTR_LOADED) == 0); + + rbtdb->attributes &= ~RBTDB_ATTR_LOADING; + rbtdb->attributes |= RBTDB_ATTR_LOADED; + + /* + * If there's a KEY rdataset at the zone origin containing a + * zone key, we consider the zone secure. + */ + if (!IS_CACHE(rbtdb) && rbtdb->origin_node != NULL) { + dns_dbversion_t *version = rbtdb->current_version; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + iszonesecure(db, version, rbtdb->origin_node); + } else { + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + } + + callbacks->add = NULL; + callbacks->add_private = NULL; + + isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx)); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +dump(dns_db_t *db, dns_dbversion_t *version, const char *filename, + dns_masterformat_t masterformat) { + dns_rbtdb_t *rbtdb; + rbtdb_version_t *rbtversion = version; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + return (dns_master_dump(rbtdb->common.mctx, db, version, + &dns_master_style_default, filename, + masterformat, NULL)); +} + +static void +delete_callback(void *data, void *arg) { + dns_rbtdb_t *rbtdb = arg; + rdatasetheader_t *current, *next; + unsigned int locknum; + + current = data; + locknum = current->node->locknum; + NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); + while (current != NULL) { + next = current->next; + free_rdataset(rbtdb, rbtdb->common.mctx, current); + current = next; + } + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); +} + +static bool +issecure(dns_db_t *db) { + dns_rbtdb_t *rbtdb; + bool secure; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + secure = (rbtdb->current_version->secure == dns_db_secure); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + return (secure); +} + +static bool +isdnssec(dns_db_t *db) { + dns_rbtdb_t *rbtdb; + bool dnssec; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + dnssec = (rbtdb->current_version->secure != dns_db_insecure); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + return (dnssec); +} + +static unsigned int +nodecount(dns_db_t *db, dns_dbtree_t tree) { + dns_rbtdb_t *rbtdb; + unsigned int count; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + switch (tree) { + case dns_dbtree_main: + count = dns_rbt_nodecount(rbtdb->tree); + break; + case dns_dbtree_nsec: + count = dns_rbt_nodecount(rbtdb->nsec); + break; + case dns_dbtree_nsec3: + count = dns_rbt_nodecount(rbtdb->nsec3); + break; + default: + UNREACHABLE(); + } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + return (count); +} + +static size_t +hashsize(dns_db_t *db) { + dns_rbtdb_t *rbtdb; + size_t size; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + size = dns_rbt_hashsize(rbtdb->tree); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + return (size); +} + +static void +settask(dns_db_t *db, isc_task_t *task) { + dns_rbtdb_t *rbtdb; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + if (rbtdb->task != NULL) { + isc_task_detach(&rbtdb->task); + } + if (task != NULL) { + isc_task_attach(task, &rbtdb->task); + } + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); +} + +static bool +ispersistent(dns_db_t *db) { + UNUSED(db); + return (false); +} + +static isc_result_t +getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *onode; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + /* Note that the access to origin_node doesn't require a DB lock */ + onode = (dns_rbtnode_t *)rbtdb->origin_node; + if (onode != NULL) { + new_reference(rbtdb, onode, isc_rwlocktype_none); + *nodep = rbtdb->origin_node; + } else { + INSIST(IS_CACHE(rbtdb)); + result = ISC_R_NOTFOUND; + } + + return (result); +} + +static isc_result_t +getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash, + uint8_t *flags, uint16_t *iterations, unsigned char *salt, + size_t *salt_length) { + dns_rbtdb_t *rbtdb; + isc_result_t result = ISC_R_NOTFOUND; + rbtdb_version_t *rbtversion = version; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + if (rbtversion == NULL) { + rbtversion = rbtdb->current_version; + } + + if (rbtversion->havensec3) { + if (hash != NULL) { + *hash = rbtversion->hash; + } + if (salt != NULL && salt_length != NULL) { + REQUIRE(*salt_length >= rbtversion->salt_length); + memmove(salt, rbtversion->salt, + rbtversion->salt_length); + } + if (salt_length != NULL) { + *salt_length = rbtversion->salt_length; + } + if (iterations != NULL) { + *iterations = rbtversion->iterations; + } + if (flags != NULL) { + *flags = rbtversion->flags; + } + result = ISC_R_SUCCESS; + } + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + return (result); +} + +static isc_result_t +getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, + uint64_t *xfrsize) { + dns_rbtdb_t *rbtdb; + isc_result_t result = ISC_R_SUCCESS; + rbtdb_version_t *rbtversion = version; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + if (rbtversion == NULL) { + rbtversion = rbtdb->current_version; + } + + RWLOCK(&rbtversion->rwlock, isc_rwlocktype_read); + if (records != NULL) { + *records = rbtversion->records; + } + + if (xfrsize != NULL) { + *xfrsize = rbtversion->xfrsize; + } + RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_read); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + return (result); +} + +static isc_result_t +setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, isc_stdtime_t resign) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rdatasetheader_t *header, oldheader; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(!IS_CACHE(rbtdb)); + REQUIRE(rdataset != NULL); + + header = rdataset->private3; + header--; + + NODE_LOCK(&rbtdb->node_locks[header->node->locknum].lock, + isc_rwlocktype_write); + + oldheader = *header; + /* + * Only break the heap invariant (by adjusting resign and resign_lsb) + * if we are going to be restoring it by calling isc_heap_increased + * or isc_heap_decreased. + */ + if (resign != 0) { + header->resign = (isc_stdtime_t)(dns_time64_from32(resign) >> + 1); + header->resign_lsb = resign & 0x1; + } + if (header->heap_index != 0) { + INSIST(RESIGN(header)); + if (resign == 0) { + isc_heap_delete(rbtdb->heaps[header->node->locknum], + header->heap_index); + header->heap_index = 0; + } else if (resign_sooner(header, &oldheader)) { + isc_heap_increased(rbtdb->heaps[header->node->locknum], + header->heap_index); + } else if (resign_sooner(&oldheader, header)) { + isc_heap_decreased(rbtdb->heaps[header->node->locknum], + header->heap_index); + } + } else if (resign != 0) { + RDATASET_ATTR_SET(header, RDATASET_ATTR_RESIGN); + resign_insert(rbtdb, header->node->locknum, header); + } + NODE_UNLOCK(&rbtdb->node_locks[header->node->locknum].lock, + isc_rwlocktype_write); + return (ISC_R_SUCCESS); +} + +static isc_result_t +getsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, dns_name_t *foundname) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rdatasetheader_t *header = NULL, *this; + unsigned int i; + isc_result_t result = ISC_R_NOTFOUND; + unsigned int locknum = 0; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + for (i = 0; i < rbtdb->node_lock_count; i++) { + NODE_LOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_read); + + /* + * Find for the earliest signing time among all of the + * heaps, each of which is covered by a different bucket + * lock. + */ + this = isc_heap_element(rbtdb->heaps[i], 1); + if (this == NULL) { + /* Nothing found; unlock and try the next heap. */ + NODE_UNLOCK(&rbtdb->node_locks[i].lock, + isc_rwlocktype_read); + continue; + } + + if (header == NULL) { + /* + * Found a signing time: retain the bucket lock and + * preserve the lock number so we can unlock it + * later. + */ + header = this; + locknum = i; + } else if (resign_sooner(this, header)) { + /* + * Found an earlier signing time; release the + * previous bucket lock and retain this one instead. + */ + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_read); + header = this; + locknum = i; + } else { + /* + * Earliest signing time in this heap isn't + * an improvement; unlock and try the next heap. + */ + NODE_UNLOCK(&rbtdb->node_locks[i].lock, + isc_rwlocktype_read); + } + } + + if (header != NULL) { + /* + * Found something; pass back the answer and unlock + * the bucket. + */ + bind_rdataset(rbtdb, header->node, header, 0, + isc_rwlocktype_read, rdataset); + + if (foundname != NULL) { + dns_rbt_fullnamefromnode(header->node, foundname); + } + + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_read); + + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + return (result); +} + +static void +resigned(dns_db_t *db, dns_rdataset_t *rdataset, dns_dbversion_t *version) { + rbtdb_version_t *rbtversion = (rbtdb_version_t *)version; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *node; + rdatasetheader_t *header; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->methods == &rdataset_methods); + REQUIRE(rbtdb->future_version == rbtversion); + REQUIRE(rbtversion != NULL); + REQUIRE(rbtversion->writer); + REQUIRE(rbtversion->rbtdb == rbtdb); + + node = rdataset->private2; + INSIST(node != NULL); + header = rdataset->private3; + INSIST(header != NULL); + header--; + + if (header->heap_index == 0) { + return; + } + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + NODE_LOCK(&rbtdb->node_locks[node->locknum].lock, isc_rwlocktype_write); + /* + * Delete from heap and save to re-signed list so that it can + * be restored if we backout of this change. + */ + resign_delete(rbtdb, rbtversion, header); + NODE_UNLOCK(&rbtdb->node_locks[node->locknum].lock, + isc_rwlocktype_write); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); +} + +static isc_result_t +setcachestats(dns_db_t *db, isc_stats_t *stats) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); /* current restriction */ + REQUIRE(stats != NULL); + + isc_stats_attach(stats, &rbtdb->cachestats); + return (ISC_R_SUCCESS); +} + +static isc_result_t +setgluecachestats(dns_db_t *db, isc_stats_t *stats) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb)); + REQUIRE(stats != NULL); + + isc_stats_attach(stats, &rbtdb->gluecachestats); + return (ISC_R_SUCCESS); +} + +static dns_stats_t * +getrrsetstats(dns_db_t *db) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); /* current restriction */ + + return (rbtdb->rrsetstats); +} + +static isc_result_t +nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + dns_rbtnode_t *rbtnode = (dns_rbtnode_t *)node; + isc_result_t result; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(node != NULL); + REQUIRE(name != NULL); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + result = dns_rbt_fullnamefromnode(rbtnode, name); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + return (result); +} + +static isc_result_t +setservestalettl(dns_db_t *db, dns_ttl_t ttl) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + /* currently no bounds checking. 0 means disable. */ + rbtdb->serve_stale_ttl = ttl; + return (ISC_R_SUCCESS); +} + +static isc_result_t +getservestalettl(dns_db_t *db, dns_ttl_t *ttl) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + *ttl = rbtdb->serve_stale_ttl; + return (ISC_R_SUCCESS); +} + +static isc_result_t +setservestalerefresh(dns_db_t *db, uint32_t interval) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + /* currently no bounds checking. 0 means disable. */ + rbtdb->serve_stale_refresh = interval; + return (ISC_R_SUCCESS); +} + +static isc_result_t +getservestalerefresh(dns_db_t *db, uint32_t *interval) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + *interval = rbtdb->serve_stale_refresh; + return (ISC_R_SUCCESS); +} + +static dns_dbmethods_t zone_methods = { attach, + detach, + beginload, + endload, + dump, + currentversion, + newversion, + attachversion, + closeversion, + findnode, + zone_find, + zone_findzonecut, + attachnode, + detachnode, + expirenode, + printnode, + createiterator, + zone_findrdataset, + allrdatasets, + addrdataset, + subtractrdataset, + deleterdataset, + issecure, + nodecount, + ispersistent, + overmem, + settask, + getoriginnode, + NULL, /* transfernode */ + getnsec3parameters, + findnsec3node, + setsigningtime, + getsigningtime, + resigned, + isdnssec, + NULL, /* getrrsetstats */ + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ + NULL, /* findnodeext */ + NULL, /* findext */ + NULL, /* setcachestats */ + hashsize, + nodefullname, + getsize, + NULL, /* setservestalettl */ + NULL, /* getservestalettl */ + NULL, /* setservestalerefresh */ + NULL, /* getservestalerefresh */ + setgluecachestats }; + +static dns_dbmethods_t cache_methods = { attach, + detach, + beginload, + endload, + dump, + currentversion, + newversion, + attachversion, + closeversion, + findnode, + cache_find, + cache_findzonecut, + attachnode, + detachnode, + expirenode, + printnode, + createiterator, + cache_findrdataset, + allrdatasets, + addrdataset, + subtractrdataset, + deleterdataset, + issecure, + nodecount, + ispersistent, + overmem, + settask, + getoriginnode, + NULL, /* transfernode */ + NULL, /* getnsec3parameters */ + NULL, /* findnsec3node */ + NULL, /* setsigningtime */ + NULL, /* getsigningtime */ + NULL, /* resigned */ + isdnssec, + getrrsetstats, + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ + NULL, /* findnodeext */ + NULL, /* findext */ + setcachestats, + hashsize, + nodefullname, + NULL, /* getsize */ + setservestalettl, + getservestalettl, + setservestalerefresh, + getservestalerefresh, + NULL }; + +isc_result_t +dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type, + dns_rdataclass_t rdclass, unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp) { + dns_rbtdb_t *rbtdb; + isc_result_t result; + int i; + dns_name_t name; + bool (*sooner)(void *, void *); + isc_mem_t *hmctx = mctx; + + /* Keep the compiler happy. */ + UNUSED(driverarg); + + rbtdb = isc_mem_get(mctx, sizeof(*rbtdb)); + + /* + * If argv[0] exists, it points to a memory context to use for heap + */ + if (argc != 0) { + hmctx = (isc_mem_t *)argv[0]; + } + + memset(rbtdb, '\0', sizeof(*rbtdb)); + dns_name_init(&rbtdb->common.origin, NULL); + rbtdb->common.attributes = 0; + if (type == dns_dbtype_cache) { + rbtdb->common.methods = &cache_methods; + rbtdb->common.attributes |= DNS_DBATTR_CACHE; + } else if (type == dns_dbtype_stub) { + rbtdb->common.methods = &zone_methods; + rbtdb->common.attributes |= DNS_DBATTR_STUB; + } else { + rbtdb->common.methods = &zone_methods; + } + rbtdb->common.rdclass = rdclass; + rbtdb->common.mctx = NULL; + + ISC_LIST_INIT(rbtdb->common.update_listeners); + + RBTDB_INITLOCK(&rbtdb->lock); + + isc_rwlock_init(&rbtdb->tree_lock, 0, 0); + + /* + * Initialize node_lock_count in a generic way to support future + * extension which allows the user to specify this value on creation. + * Note that when specified for a cache DB it must be larger than 1 + * as commented with the definition of DEFAULT_CACHE_NODE_LOCK_COUNT. + */ + if (rbtdb->node_lock_count == 0) { + if (IS_CACHE(rbtdb)) { + rbtdb->node_lock_count = DEFAULT_CACHE_NODE_LOCK_COUNT; + } else { + rbtdb->node_lock_count = DEFAULT_NODE_LOCK_COUNT; + } + } else if (rbtdb->node_lock_count < 2 && IS_CACHE(rbtdb)) { + result = ISC_R_RANGE; + goto cleanup_tree_lock; + } + INSIST(rbtdb->node_lock_count < (1 << DNS_RBT_LOCKLENGTH)); + rbtdb->node_locks = isc_mem_get(mctx, rbtdb->node_lock_count * + sizeof(rbtdb_nodelock_t)); + + rbtdb->cachestats = NULL; + rbtdb->gluecachestats = NULL; + + rbtdb->rrsetstats = NULL; + if (IS_CACHE(rbtdb)) { + result = dns_rdatasetstats_create(mctx, &rbtdb->rrsetstats); + if (result != ISC_R_SUCCESS) { + goto cleanup_node_locks; + } + rbtdb->rdatasets = isc_mem_get( + mctx, + rbtdb->node_lock_count * sizeof(rdatasetheaderlist_t)); + for (i = 0; i < (int)rbtdb->node_lock_count; i++) { + ISC_LIST_INIT(rbtdb->rdatasets[i]); + } + } else { + rbtdb->rdatasets = NULL; + } + + /* + * Create the heaps. + */ + rbtdb->heaps = isc_mem_get(hmctx, rbtdb->node_lock_count * + sizeof(isc_heap_t *)); + for (i = 0; i < (int)rbtdb->node_lock_count; i++) { + rbtdb->heaps[i] = NULL; + } + sooner = IS_CACHE(rbtdb) ? ttl_sooner : resign_sooner; + for (i = 0; i < (int)rbtdb->node_lock_count; i++) { + isc_heap_create(hmctx, sooner, set_index, 0, &rbtdb->heaps[i]); + } + + /* + * Create deadnode lists. + */ + rbtdb->deadnodes = isc_mem_get(mctx, rbtdb->node_lock_count * + sizeof(rbtnodelist_t)); + for (i = 0; i < (int)rbtdb->node_lock_count; i++) { + ISC_LIST_INIT(rbtdb->deadnodes[i]); + } + + rbtdb->active = rbtdb->node_lock_count; + + for (i = 0; i < (int)(rbtdb->node_lock_count); i++) { + NODE_INITLOCK(&rbtdb->node_locks[i].lock); + isc_refcount_init(&rbtdb->node_locks[i].references, 0); + rbtdb->node_locks[i].exiting = false; + } + + /* + * Attach to the mctx. The database will persist so long as there + * are references to it, and attaching to the mctx ensures that our + * mctx won't disappear out from under us. + */ + isc_mem_attach(mctx, &rbtdb->common.mctx); + isc_mem_attach(hmctx, &rbtdb->hmctx); + + /* + * Make a copy of the origin name. + */ + result = dns_name_dupwithoffsets(origin, mctx, &rbtdb->common.origin); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, false, NULL); + return (result); + } + + /* + * Make the Red-Black Trees. + */ + result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->tree); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, false, NULL); + return (result); + } + + result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, false, NULL); + return (result); + } + + result = dns_rbt_create(mctx, delete_callback, rbtdb, &rbtdb->nsec3); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, false, NULL); + return (result); + } + + /* + * In order to set the node callback bit correctly in zone databases, + * we need to know if the node has the origin name of the zone. + * In loading_addrdataset() we could simply compare the new name + * to the origin name, but this is expensive. Also, we don't know the + * node name in addrdataset(), so we need another way of knowing the + * zone's top. + * + * We now explicitly create a node for the zone's origin, and then + * we simply remember the node's address. This is safe, because + * the top-of-zone node can never be deleted, nor can its address + * change. + */ + if (!IS_CACHE(rbtdb)) { + rbtdb->origin_node = NULL; + result = dns_rbt_addnode(rbtdb->tree, &rbtdb->common.origin, + &rbtdb->origin_node); + if (result != ISC_R_SUCCESS) { + INSIST(result != ISC_R_EXISTS); + free_rbtdb(rbtdb, false, NULL); + return (result); + } + INSIST(rbtdb->origin_node != NULL); + rbtdb->origin_node->nsec = DNS_RBT_NSEC_NORMAL; + /* + * We need to give the origin node the right locknum. + */ + dns_name_init(&name, NULL); + dns_rbt_namefromnode(rbtdb->origin_node, &name); + rbtdb->origin_node->locknum = rbtdb->origin_node->hashval % + rbtdb->node_lock_count; + /* + * Add an apex node to the NSEC3 tree so that NSEC3 searches + * return partial matches when there is only a single NSEC3 + * record in the tree. + */ + rbtdb->nsec3_origin_node = NULL; + result = dns_rbt_addnode(rbtdb->nsec3, &rbtdb->common.origin, + &rbtdb->nsec3_origin_node); + if (result != ISC_R_SUCCESS) { + INSIST(result != ISC_R_EXISTS); + free_rbtdb(rbtdb, false, NULL); + return (result); + } + rbtdb->nsec3_origin_node->nsec = DNS_RBT_NSEC_NSEC3; + /* + * We need to give the nsec3 origin node the right locknum. + */ + dns_name_init(&name, NULL); + dns_rbt_namefromnode(rbtdb->nsec3_origin_node, &name); + rbtdb->nsec3_origin_node->locknum = + rbtdb->nsec3_origin_node->hashval % + rbtdb->node_lock_count; + } + + /* + * Misc. Initialization. + */ + isc_refcount_init(&rbtdb->references, 1); + rbtdb->attributes = 0; + rbtdb->task = NULL; + rbtdb->serve_stale_ttl = 0; + + /* + * Version Initialization. + */ + rbtdb->current_serial = 1; + rbtdb->least_serial = 1; + rbtdb->next_serial = 2; + rbtdb->current_version = allocate_version(mctx, 1, 1, false); + rbtdb->current_version->rbtdb = rbtdb; + rbtdb->current_version->secure = dns_db_insecure; + rbtdb->current_version->havensec3 = false; + rbtdb->current_version->flags = 0; + rbtdb->current_version->iterations = 0; + rbtdb->current_version->hash = 0; + rbtdb->current_version->salt_length = 0; + memset(rbtdb->current_version->salt, 0, + sizeof(rbtdb->current_version->salt)); + isc_rwlock_init(&rbtdb->current_version->rwlock, 0, 0); + rbtdb->current_version->records = 0; + rbtdb->current_version->xfrsize = 0; + rbtdb->future_version = NULL; + ISC_LIST_INIT(rbtdb->open_versions); + /* + * Keep the current version in the open list so that list operation + * won't happen in normal lookup operations. + */ + PREPEND(rbtdb->open_versions, rbtdb->current_version, link); + + rbtdb->common.magic = DNS_DB_MAGIC; + rbtdb->common.impmagic = RBTDB_MAGIC; + + *dbp = (dns_db_t *)rbtdb; + + return (ISC_R_SUCCESS); + +cleanup_node_locks: + isc_mem_put(mctx, rbtdb->node_locks, + rbtdb->node_lock_count * sizeof(rbtdb_nodelock_t)); + +cleanup_tree_lock: + isc_rwlock_destroy(&rbtdb->tree_lock); + RBTDB_DESTROYLOCK(&rbtdb->lock); + isc_mem_put(mctx, rbtdb, sizeof(*rbtdb)); + return (result); +} + +/* + * Slabbed Rdataset Methods + */ + +static void +rdataset_disassociate(dns_rdataset_t *rdataset) { + dns_db_t *db = rdataset->private1; + dns_dbnode_t *node = rdataset->private2; + + detachnode(db, &node); +} + +static isc_result_t +rdataset_first(dns_rdataset_t *rdataset) { + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + unsigned int count; + + count = raw[0] * 256 + raw[1]; + if (count == 0) { + rdataset->private5 = NULL; + return (ISC_R_NOMORE); + } + + if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0) { + raw += DNS_RDATASET_COUNT; + } + + raw += DNS_RDATASET_LENGTH; + + /* + * The privateuint4 field is the number of rdata beyond the + * cursor position, so we decrement the total count by one + * before storing it. + * + * If DNS_RDATASETATTR_LOADORDER is not set 'raw' points to the + * first record. If DNS_RDATASETATTR_LOADORDER is set 'raw' points + * to the first entry in the offset table. + */ + count--; + rdataset->privateuint4 = count; + rdataset->private5 = raw; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdataset_next(dns_rdataset_t *rdataset) { + unsigned int count; + unsigned int length; + unsigned char *raw; /* RDATASLAB */ + + count = rdataset->privateuint4; + if (count == 0) { + return (ISC_R_NOMORE); + } + count--; + rdataset->privateuint4 = count; + + /* + * Skip forward one record (length + 4) or one offset (4). + */ + raw = rdataset->private5; +#if DNS_RDATASET_FIXED + if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0) +#endif /* DNS_RDATASET_FIXED */ + { + length = raw[0] * 256 + raw[1]; + raw += length; + } + + rdataset->private5 = raw + DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH; + + return (ISC_R_SUCCESS); +} + +static void +rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + unsigned char *raw = rdataset->private5; /* RDATASLAB */ + unsigned int length; + isc_region_t r; + unsigned int flags = 0; + + REQUIRE(raw != NULL); + + /* + * Find the start of the record if not already in private5 + * then skip the length and order fields. + */ +#if DNS_RDATASET_FIXED + if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) != 0) { + unsigned int offset; + offset = ((unsigned int)raw[0] << 24) + + ((unsigned int)raw[1] << 16) + + ((unsigned int)raw[2] << 8) + (unsigned int)raw[3]; + raw = rdataset->private3; + raw += offset; + } +#endif /* if DNS_RDATASET_FIXED */ + + length = raw[0] * 256 + raw[1]; + + raw += DNS_RDATASET_ORDER + DNS_RDATASET_LENGTH; + + if (rdataset->type == dns_rdatatype_rrsig) { + if (*raw & DNS_RDATASLAB_OFFLINE) { + flags |= DNS_RDATA_OFFLINE; + } + length--; + raw++; + } + r.length = length; + r.base = raw; + dns_rdata_fromregion(rdata, rdataset->rdclass, rdataset->type, &r); + rdata->flags |= flags; +} + +static void +rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + dns_db_t *db = source->private1; + dns_dbnode_t *node = source->private2; + dns_dbnode_t *cloned_node = NULL; + + attachnode(db, node, &cloned_node); + INSIST(!ISC_LINK_LINKED(target, link)); + *target = *source; + ISC_LINK_INIT(target, link); + + /* + * Reset iterator state. + */ + target->privateuint4 = 0; + target->private5 = NULL; +} + +static unsigned int +rdataset_count(dns_rdataset_t *rdataset) { + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + unsigned int count; + + count = raw[0] * 256 + raw[1]; + + return (count); +} + +static isc_result_t +rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) { + dns_db_t *db = rdataset->private1; + dns_dbnode_t *node = rdataset->private2; + dns_dbnode_t *cloned_node; + const struct noqname *noqname = rdataset->private6; + + cloned_node = NULL; + attachnode(db, node, &cloned_node); + nsec->methods = &slab_methods; + nsec->rdclass = db->rdclass; + nsec->type = noqname->type; + nsec->covers = 0; + nsec->ttl = rdataset->ttl; + nsec->trust = rdataset->trust; + nsec->private1 = rdataset->private1; + nsec->private2 = rdataset->private2; + nsec->private3 = noqname->neg; + nsec->privateuint4 = 0; + nsec->private5 = NULL; + nsec->private6 = NULL; + nsec->private7 = NULL; + + cloned_node = NULL; + attachnode(db, node, &cloned_node); + nsecsig->methods = &slab_methods; + nsecsig->rdclass = db->rdclass; + nsecsig->type = dns_rdatatype_rrsig; + nsecsig->covers = noqname->type; + nsecsig->ttl = rdataset->ttl; + nsecsig->trust = rdataset->trust; + nsecsig->private1 = rdataset->private1; + nsecsig->private2 = rdataset->private2; + nsecsig->private3 = noqname->negsig; + nsecsig->privateuint4 = 0; + nsecsig->private5 = NULL; + nsec->private6 = NULL; + nsec->private7 = NULL; + + dns_name_clone(&noqname->name, name); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *nsec, dns_rdataset_t *nsecsig) { + dns_db_t *db = rdataset->private1; + dns_dbnode_t *node = rdataset->private2; + dns_dbnode_t *cloned_node; + const struct noqname *closest = rdataset->private7; + + cloned_node = NULL; + attachnode(db, node, &cloned_node); + nsec->methods = &slab_methods; + nsec->rdclass = db->rdclass; + nsec->type = closest->type; + nsec->covers = 0; + nsec->ttl = rdataset->ttl; + nsec->trust = rdataset->trust; + nsec->private1 = rdataset->private1; + nsec->private2 = rdataset->private2; + nsec->private3 = closest->neg; + nsec->privateuint4 = 0; + nsec->private5 = NULL; + nsec->private6 = NULL; + nsec->private7 = NULL; + + cloned_node = NULL; + attachnode(db, node, &cloned_node); + nsecsig->methods = &slab_methods; + nsecsig->rdclass = db->rdclass; + nsecsig->type = dns_rdatatype_rrsig; + nsecsig->covers = closest->type; + nsecsig->ttl = rdataset->ttl; + nsecsig->trust = rdataset->trust; + nsecsig->private1 = rdataset->private1; + nsecsig->private2 = rdataset->private2; + nsecsig->private3 = closest->negsig; + nsecsig->privateuint4 = 0; + nsecsig->private5 = NULL; + nsec->private6 = NULL; + nsec->private7 = NULL; + + dns_name_clone(&closest->name, name); + + return (ISC_R_SUCCESS); +} + +static void +rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + rdatasetheader_t *header = rdataset->private3; + + header--; + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + header->trust = rdataset->trust = trust; + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); +} + +static void +rdataset_expire(dns_rdataset_t *rdataset) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + rdatasetheader_t *header = rdataset->private3; + + header--; + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + expire_header(rbtdb, header, false, expire_flush); + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); +} + +static void +rdataset_clearprefetch(dns_rdataset_t *rdataset) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + rdatasetheader_t *header = rdataset->private3; + + header--; + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + RDATASET_ATTR_CLR(header, RDATASET_ATTR_PREFETCH); + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); +} + +/* + * Rdataset Iterator Methods + */ + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + rbtdb_rdatasetiter_t *rbtiterator; + + rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp); + + if (rbtiterator->common.version != NULL) { + closeversion(rbtiterator->common.db, + &rbtiterator->common.version, false); + } + detachnode(rbtiterator->common.db, &rbtiterator->common.node); + isc_mem_put(rbtiterator->common.db->mctx, rbtiterator, + sizeof(*rbtiterator)); + + *iteratorp = NULL; +} + +static bool +iterator_active(dns_rbtdb_t *rbtdb, rbtdb_rdatasetiter_t *rbtiterator, + rdatasetheader_t *header) { + dns_ttl_t stale_ttl = header->rdh_ttl + STALE_TTL(header, rbtdb); + + /* + * Is this a "this rdataset doesn't exist" record? + */ + if (NONEXISTENT(header)) { + return (false); + } + + /* + * If this is a zone or this header still active then return it. + */ + if (!IS_CACHE(rbtdb) || ACTIVE(header, rbtiterator->common.now)) { + return (true); + } + + /* + * If we are not returning stale records or the rdataset is + * too old don't return it. + */ + if (!STALEOK(rbtiterator) || (rbtiterator->common.now > stale_ttl)) { + return (false); + } + return (true); +} + +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator) { + rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db); + dns_rbtnode_t *rbtnode = rbtiterator->common.node; + rbtdb_version_t *rbtversion = rbtiterator->common.version; + rdatasetheader_t *header, *top_next; + rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial; + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + for (header = rbtnode->data; header != NULL; header = top_next) { + top_next = header->next; + do { + if (EXPIREDOK(rbtiterator)) { + if (!NONEXISTENT(header)) { + break; + } + header = header->down; + } else if (header->serial <= serial && !IGNORE(header)) + { + if (!iterator_active(rbtdb, rbtiterator, + header)) + { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + break; + } + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + rbtiterator->current = header; + + if (header == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator) { + rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db); + dns_rbtnode_t *rbtnode = rbtiterator->common.node; + rbtdb_version_t *rbtversion = rbtiterator->common.version; + rdatasetheader_t *header, *top_next; + rbtdb_serial_t serial = IS_CACHE(rbtdb) ? 1 : rbtversion->serial; + rbtdb_rdatatype_t type, negtype; + dns_rdatatype_t rdtype, covers; + bool expiredok = EXPIREDOK(rbtiterator); + + header = rbtiterator->current; + if (header == NULL) { + return (ISC_R_NOMORE); + } + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + type = header->type; + rdtype = RBTDB_RDATATYPE_BASE(header->type); + if (NEGATIVE(header)) { + covers = RBTDB_RDATATYPE_EXT(header->type); + negtype = RBTDB_RDATATYPE_VALUE(covers, 0); + } else { + negtype = RBTDB_RDATATYPE_VALUE(0, rdtype); + } + + /* + * Find the start of the header chain for the next type + * by walking back up the list. + */ + top_next = header->next; + while (top_next != NULL && + (top_next->type == type || top_next->type == negtype)) + { + top_next = top_next->next; + } + if (expiredok) { + /* + * Keep walking down the list if possible or + * start the next type. + */ + header = header->down != NULL ? header->down : top_next; + } else { + header = top_next; + } + for (; header != NULL; header = top_next) { + top_next = header->next; + do { + if (expiredok) { + if (!NONEXISTENT(header)) { + break; + } + header = header->down; + } else if (header->serial <= serial && !IGNORE(header)) + { + if (!iterator_active(rbtdb, rbtiterator, + header)) + { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + if (header != NULL) { + break; + } + /* + * Find the start of the header chain for the next type + * by walking back up the list. + */ + while (top_next != NULL && + (top_next->type == type || top_next->type == negtype)) + { + top_next = top_next->next; + } + } + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + rbtiterator->current = header; + + if (header == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) { + rbtdb_rdatasetiter_t *rbtiterator = (rbtdb_rdatasetiter_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(rbtiterator->common.db); + dns_rbtnode_t *rbtnode = rbtiterator->common.node; + rdatasetheader_t *header; + + header = rbtiterator->current; + REQUIRE(header != NULL); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + bind_rdataset(rbtdb, rbtnode, header, rbtiterator->common.now, + isc_rwlocktype_read, rdataset); + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); +} + +/* + * Database Iterator Methods + */ + +static void +reference_iter_node(rbtdb_dbiterator_t *rbtdbiter) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db; + dns_rbtnode_t *node = rbtdbiter->node; + + if (node == NULL) { + return; + } + + INSIST(rbtdbiter->tree_locked != isc_rwlocktype_none); + reactivate_node(rbtdb, node, rbtdbiter->tree_locked); +} + +static void +dereference_iter_node(rbtdb_dbiterator_t *rbtdbiter) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db; + dns_rbtnode_t *node = rbtdbiter->node; + nodelock_t *lock; + + if (node == NULL) { + return; + } + + lock = &rbtdb->node_locks[node->locknum].lock; + NODE_LOCK(lock, isc_rwlocktype_read); + decrement_reference(rbtdb, node, 0, isc_rwlocktype_read, + rbtdbiter->tree_locked, false); + NODE_UNLOCK(lock, isc_rwlocktype_read); + + rbtdbiter->node = NULL; +} + +static void +flush_deletions(rbtdb_dbiterator_t *rbtdbiter) { + dns_rbtnode_t *node; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db; + bool was_read_locked = false; + nodelock_t *lock; + int i; + + if (rbtdbiter->delcnt != 0) { + /* + * Note that "%d node of %d in tree" can report things like + * "flush_deletions: 59 nodes of 41 in tree". This means + * That some nodes appear on the deletions list more than + * once. Only the last occurrence will actually be deleted. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_DEBUG(1), + "flush_deletions: %d nodes of %d in tree", + rbtdbiter->delcnt, + dns_rbt_nodecount(rbtdb->tree)); + + if (rbtdbiter->tree_locked == isc_rwlocktype_read) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + was_read_locked = true; + } + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + rbtdbiter->tree_locked = isc_rwlocktype_write; + + for (i = 0; i < rbtdbiter->delcnt; i++) { + node = rbtdbiter->deletions[i]; + lock = &rbtdb->node_locks[node->locknum].lock; + + NODE_LOCK(lock, isc_rwlocktype_read); + decrement_reference(rbtdb, node, 0, isc_rwlocktype_read, + rbtdbiter->tree_locked, false); + NODE_UNLOCK(lock, isc_rwlocktype_read); + } + + rbtdbiter->delcnt = 0; + + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + if (was_read_locked) { + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + rbtdbiter->tree_locked = isc_rwlocktype_read; + } else { + rbtdbiter->tree_locked = isc_rwlocktype_none; + } + } +} + +static void +resume_iteration(rbtdb_dbiterator_t *rbtdbiter) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db; + + REQUIRE(rbtdbiter->paused); + REQUIRE(rbtdbiter->tree_locked == isc_rwlocktype_none); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + rbtdbiter->tree_locked = isc_rwlocktype_read; + + rbtdbiter->paused = false; +} + +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp) { + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)(*iteratorp); + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)rbtdbiter->common.db; + dns_db_t *db = NULL; + + if (rbtdbiter->tree_locked == isc_rwlocktype_read) { + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + rbtdbiter->tree_locked = isc_rwlocktype_none; + } else { + INSIST(rbtdbiter->tree_locked == isc_rwlocktype_none); + } + + dereference_iter_node(rbtdbiter); + + flush_deletions(rbtdbiter); + + dns_db_attach(rbtdbiter->common.db, &db); + dns_db_detach(&rbtdbiter->common.db); + + dns_rbtnodechain_reset(&rbtdbiter->chain); + dns_rbtnodechain_reset(&rbtdbiter->nsec3chain); + isc_mem_put(db->mctx, rbtdbiter, sizeof(*rbtdbiter)); + dns_db_detach(&db); + + *iteratorp = NULL; +} + +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator) { + isc_result_t result; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + dns_name_t *name, *origin; + + if (rbtdbiter->result != ISC_R_SUCCESS && + rbtdbiter->result != ISC_R_NOTFOUND && + rbtdbiter->result != DNS_R_PARTIALMATCH && + rbtdbiter->result != ISC_R_NOMORE) + { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + dereference_iter_node(rbtdbiter); + + name = dns_fixedname_name(&rbtdbiter->name); + origin = dns_fixedname_name(&rbtdbiter->origin); + dns_rbtnodechain_reset(&rbtdbiter->chain); + dns_rbtnodechain_reset(&rbtdbiter->nsec3chain); + + if (rbtdbiter->nsec3only) { + rbtdbiter->current = &rbtdbiter->nsec3chain; + result = dns_rbtnodechain_first(rbtdbiter->current, + rbtdb->nsec3, name, origin); + } else { + rbtdbiter->current = &rbtdbiter->chain; + result = dns_rbtnodechain_first(rbtdbiter->current, rbtdb->tree, + name, origin); + if (!rbtdbiter->nonsec3 && result == ISC_R_NOTFOUND) { + rbtdbiter->current = &rbtdbiter->nsec3chain; + result = dns_rbtnodechain_first( + rbtdbiter->current, rbtdb->nsec3, name, origin); + } + } + if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + result = dns_rbtnodechain_current(rbtdbiter->current, NULL, + NULL, &rbtdbiter->node); + if (result == ISC_R_SUCCESS) { + rbtdbiter->new_origin = true; + reference_iter_node(rbtdbiter); + } + } else { + INSIST(result == ISC_R_NOTFOUND); + result = ISC_R_NOMORE; /* The tree is empty. */ + } + + rbtdbiter->result = result; + + if (result != ISC_R_SUCCESS) { + ENSURE(!rbtdbiter->paused); + } + + return (result); +} + +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator) { + isc_result_t result; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + dns_name_t *name, *origin; + + if (rbtdbiter->result != ISC_R_SUCCESS && + rbtdbiter->result != ISC_R_NOTFOUND && + rbtdbiter->result != DNS_R_PARTIALMATCH && + rbtdbiter->result != ISC_R_NOMORE) + { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + dereference_iter_node(rbtdbiter); + + name = dns_fixedname_name(&rbtdbiter->name); + origin = dns_fixedname_name(&rbtdbiter->origin); + dns_rbtnodechain_reset(&rbtdbiter->chain); + dns_rbtnodechain_reset(&rbtdbiter->nsec3chain); + + result = ISC_R_NOTFOUND; + if (rbtdbiter->nsec3only && !rbtdbiter->nonsec3) { + rbtdbiter->current = &rbtdbiter->nsec3chain; + result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->nsec3, + name, origin); + } + if (!rbtdbiter->nsec3only && result == ISC_R_NOTFOUND) { + rbtdbiter->current = &rbtdbiter->chain; + result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree, + name, origin); + } + if (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + result = dns_rbtnodechain_current(rbtdbiter->current, NULL, + NULL, &rbtdbiter->node); + if (result == ISC_R_SUCCESS) { + rbtdbiter->new_origin = true; + reference_iter_node(rbtdbiter); + } + } else { + INSIST(result == ISC_R_NOTFOUND); + result = ISC_R_NOMORE; /* The tree is empty. */ + } + + rbtdbiter->result = result; + + return (result); +} + +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) { + isc_result_t result, tresult; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + dns_name_t *iname, *origin; + + if (rbtdbiter->result != ISC_R_SUCCESS && + rbtdbiter->result != ISC_R_NOTFOUND && + rbtdbiter->result != DNS_R_PARTIALMATCH && + rbtdbiter->result != ISC_R_NOMORE) + { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + dereference_iter_node(rbtdbiter); + + iname = dns_fixedname_name(&rbtdbiter->name); + origin = dns_fixedname_name(&rbtdbiter->origin); + dns_rbtnodechain_reset(&rbtdbiter->chain); + dns_rbtnodechain_reset(&rbtdbiter->nsec3chain); + + if (rbtdbiter->nsec3only) { + rbtdbiter->current = &rbtdbiter->nsec3chain; + result = dns_rbt_findnode(rbtdb->nsec3, name, NULL, + &rbtdbiter->node, rbtdbiter->current, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + } else if (rbtdbiter->nonsec3) { + rbtdbiter->current = &rbtdbiter->chain; + result = dns_rbt_findnode(rbtdb->tree, name, NULL, + &rbtdbiter->node, rbtdbiter->current, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + } else { + /* + * Stay on main chain if not found on either chain. + */ + rbtdbiter->current = &rbtdbiter->chain; + result = dns_rbt_findnode(rbtdb->tree, name, NULL, + &rbtdbiter->node, rbtdbiter->current, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result == DNS_R_PARTIALMATCH) { + dns_rbtnode_t *node = NULL; + tresult = dns_rbt_findnode( + rbtdb->nsec3, name, NULL, &node, + &rbtdbiter->nsec3chain, DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + if (tresult == ISC_R_SUCCESS) { + rbtdbiter->node = node; + rbtdbiter->current = &rbtdbiter->nsec3chain; + result = tresult; + } + } + } + + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + tresult = dns_rbtnodechain_current(rbtdbiter->current, iname, + origin, NULL); + if (tresult == ISC_R_SUCCESS) { + rbtdbiter->new_origin = true; + reference_iter_node(rbtdbiter); + } else { + result = tresult; + rbtdbiter->node = NULL; + } + } else { + rbtdbiter->node = NULL; + } + + rbtdbiter->result = (result == DNS_R_PARTIALMATCH) ? ISC_R_SUCCESS + : result; + + return (result); +} + +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator) { + isc_result_t result; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_name_t *name, *origin; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + + REQUIRE(rbtdbiter->node != NULL); + + if (rbtdbiter->result != ISC_R_SUCCESS) { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + name = dns_fixedname_name(&rbtdbiter->name); + origin = dns_fixedname_name(&rbtdbiter->origin); + result = dns_rbtnodechain_prev(rbtdbiter->current, name, origin); + if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only && + !rbtdbiter->nonsec3 && &rbtdbiter->nsec3chain == rbtdbiter->current) + { + rbtdbiter->current = &rbtdbiter->chain; + dns_rbtnodechain_reset(rbtdbiter->current); + result = dns_rbtnodechain_last(rbtdbiter->current, rbtdb->tree, + name, origin); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_NOMORE; + } + } + + dereference_iter_node(rbtdbiter); + + if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN); + result = dns_rbtnodechain_current(rbtdbiter->current, NULL, + NULL, &rbtdbiter->node); + } + + if (result == ISC_R_SUCCESS) { + reference_iter_node(rbtdbiter); + } + + rbtdbiter->result = result; + + return (result); +} + +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator) { + isc_result_t result; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_name_t *name, *origin; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + + REQUIRE(rbtdbiter->node != NULL); + + if (rbtdbiter->result != ISC_R_SUCCESS) { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + name = dns_fixedname_name(&rbtdbiter->name); + origin = dns_fixedname_name(&rbtdbiter->origin); + result = dns_rbtnodechain_next(rbtdbiter->current, name, origin); + if (result == ISC_R_NOMORE && !rbtdbiter->nsec3only && + !rbtdbiter->nonsec3 && &rbtdbiter->chain == rbtdbiter->current) + { + rbtdbiter->current = &rbtdbiter->nsec3chain; + dns_rbtnodechain_reset(rbtdbiter->current); + result = dns_rbtnodechain_first(rbtdbiter->current, + rbtdb->nsec3, name, origin); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_NOMORE; + } + } + + dereference_iter_node(rbtdbiter); + + if (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + rbtdbiter->new_origin = (result == DNS_R_NEWORIGIN); + result = dns_rbtnodechain_current(rbtdbiter->current, NULL, + NULL, &rbtdbiter->node); + } + if (result == ISC_R_SUCCESS) { + reference_iter_node(rbtdbiter); + } + + rbtdbiter->result = result; + + return (result); +} + +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_rbtnode_t *node = rbtdbiter->node; + isc_result_t result; + dns_name_t *nodename = dns_fixedname_name(&rbtdbiter->name); + dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin); + + REQUIRE(rbtdbiter->result == ISC_R_SUCCESS); + REQUIRE(rbtdbiter->node != NULL); + + if (rbtdbiter->paused) { + resume_iteration(rbtdbiter); + } + + if (name != NULL) { + if (rbtdbiter->common.relative_names) { + origin = NULL; + } + result = dns_name_concatenate(nodename, origin, name, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (rbtdbiter->common.relative_names && rbtdbiter->new_origin) { + result = DNS_R_NEWORIGIN; + } + } else { + result = ISC_R_SUCCESS; + } + + new_reference(rbtdb, node, isc_rwlocktype_none); + + *nodep = rbtdbiter->node; + + if (iterator->cleaning && result == ISC_R_SUCCESS) { + isc_result_t expire_result; + + /* + * If the deletion array is full, flush it before trying + * to expire the current node. The current node can't + * fully deleted while the iteration cursor is still on it. + */ + if (rbtdbiter->delcnt == DELETION_BATCH_MAX) { + flush_deletions(rbtdbiter); + } + + expire_result = expirenode(iterator->db, *nodep, 0); + + /* + * expirenode() currently always returns success. + */ + if (expire_result == ISC_R_SUCCESS && node->down == NULL) { + rbtdbiter->deletions[rbtdbiter->delcnt++] = node; + isc_refcount_increment(&node->references); + } + } + + return (result); +} + +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)iterator->db; + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + + if (rbtdbiter->result != ISC_R_SUCCESS && + rbtdbiter->result != ISC_R_NOTFOUND && + rbtdbiter->result != DNS_R_PARTIALMATCH && + rbtdbiter->result != ISC_R_NOMORE) + { + return (rbtdbiter->result); + } + + if (rbtdbiter->paused) { + return (ISC_R_SUCCESS); + } + + rbtdbiter->paused = true; + + if (rbtdbiter->tree_locked != isc_rwlocktype_none) { + INSIST(rbtdbiter->tree_locked == isc_rwlocktype_read); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + rbtdbiter->tree_locked = isc_rwlocktype_none; + } + + flush_deletions(rbtdbiter); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) { + rbtdb_dbiterator_t *rbtdbiter = (rbtdb_dbiterator_t *)iterator; + dns_name_t *origin = dns_fixedname_name(&rbtdbiter->origin); + + if (rbtdbiter->result != ISC_R_SUCCESS) { + return (rbtdbiter->result); + } + + dns_name_copy(origin, name); + return (ISC_R_SUCCESS); +} + +static void +setownercase(rdatasetheader_t *header, const dns_name_t *name) { + unsigned int i; + bool fully_lower; + + /* + * We do not need to worry about label lengths as they are all + * less than or equal to 63. + */ + memset(header->upper, 0, sizeof(header->upper)); + fully_lower = true; + for (i = 0; i < name->length; i++) { + if (isupper(name->ndata[i])) { + header->upper[i / 8] |= 1 << (i % 8); + fully_lower = false; + } + } + RDATASET_ATTR_SET(header, RDATASET_ATTR_CASESET); + if (fully_lower) { + RDATASET_ATTR_SET(header, RDATASET_ATTR_CASEFULLYLOWER); + } +} + +static void +rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + rdatasetheader_t *header; + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + setownercase(header, name); + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); +} + +static void +rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + rdatasetheader_t *header = NULL; + uint8_t mask = (1 << 7); + uint8_t bits = 0; + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + if (!CASESET(header)) { + goto unlock; + } + + if (CASEFULLYLOWER(header)) { + for (size_t i = 0; i < name->length; i++) { + name->ndata[i] = tolower(name->ndata[i]); + } + } else { + for (size_t i = 0; i < name->length; i++) { + if (mask == (1 << 7)) { + bits = header->upper[i / 8]; + mask = 1; + } else { + mask <<= 1; + } + + name->ndata[i] = ((bits & mask) != 0) + ? toupper(name->ndata[i]) + : tolower(name->ndata[i]); + } + } + +unlock: + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); +} + +struct rbtdb_glue { + struct rbtdb_glue *next; + dns_fixedname_t fixedname; + dns_rdataset_t rdataset_a; + dns_rdataset_t sigrdataset_a; + dns_rdataset_t rdataset_aaaa; + dns_rdataset_t sigrdataset_aaaa; +}; + +typedef struct { + rbtdb_glue_t *glue_list; + dns_rbtdb_t *rbtdb; + rbtdb_version_t *rbtversion; +} rbtdb_glue_additionaldata_ctx_t; + +static void +free_gluelist(rbtdb_glue_t *glue_list, dns_rbtdb_t *rbtdb) { + rbtdb_glue_t *cur, *cur_next; + + if (glue_list == (void *)-1) { + return; + } + + cur = glue_list; + while (cur != NULL) { + cur_next = cur->next; + + if (dns_rdataset_isassociated(&cur->rdataset_a)) { + dns_rdataset_disassociate(&cur->rdataset_a); + } + if (dns_rdataset_isassociated(&cur->sigrdataset_a)) { + dns_rdataset_disassociate(&cur->sigrdataset_a); + } + + if (dns_rdataset_isassociated(&cur->rdataset_aaaa)) { + dns_rdataset_disassociate(&cur->rdataset_aaaa); + } + if (dns_rdataset_isassociated(&cur->sigrdataset_aaaa)) { + dns_rdataset_disassociate(&cur->sigrdataset_aaaa); + } + + dns_rdataset_invalidate(&cur->rdataset_a); + dns_rdataset_invalidate(&cur->sigrdataset_a); + dns_rdataset_invalidate(&cur->rdataset_aaaa); + dns_rdataset_invalidate(&cur->sigrdataset_aaaa); + + isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur)); + cur = cur_next; + } +} + +static void +free_gluetable(rbtdb_version_t *version) { + dns_rbtdb_t *rbtdb; + size_t size, i; + + RWLOCK(&version->glue_rwlock, isc_rwlocktype_write); + + rbtdb = version->rbtdb; + + for (i = 0; i < HASHSIZE(version->glue_table_bits); i++) { + rbtdb_glue_table_node_t *cur, *cur_next; + + cur = version->glue_table[i]; + while (cur != NULL) { + cur_next = cur->next; + /* isc_refcount_decrement(&cur->node->references); */ + cur->node = NULL; + free_gluelist(cur->glue_list, rbtdb); + cur->glue_list = NULL; + isc_mem_put(rbtdb->common.mctx, cur, sizeof(*cur)); + cur = cur_next; + } + version->glue_table[i] = NULL; + } + + size = HASHSIZE(version->glue_table_bits) * + sizeof(*version->glue_table); + isc_mem_put(rbtdb->common.mctx, version->glue_table, size); + + RWUNLOCK(&version->glue_rwlock, isc_rwlocktype_write); +} + +static uint32_t +rehash_bits(rbtdb_version_t *version, size_t newcount) { + uint32_t oldbits = version->glue_table_bits; + uint32_t newbits = oldbits; + + while (newcount >= HASHSIZE(newbits) && + newbits <= RBTDB_GLUE_TABLE_MAX_BITS) + { + newbits += 1; + } + + return (newbits); +} + +/*% + * Write lock (version->glue_rwlock) must be held. + */ +static void +rehash_gluetable(rbtdb_version_t *version) { + uint32_t oldbits, newbits; + size_t newsize, oldcount, i; + rbtdb_glue_table_node_t **oldtable; + + oldbits = version->glue_table_bits; + oldcount = HASHSIZE(oldbits); + oldtable = version->glue_table; + + newbits = rehash_bits(version, version->glue_table_nodecount); + newsize = HASHSIZE(newbits) * sizeof(version->glue_table[0]); + + version->glue_table = isc_mem_get(version->rbtdb->common.mctx, newsize); + version->glue_table_bits = newbits; + memset(version->glue_table, 0, newsize); + + for (i = 0; i < oldcount; i++) { + rbtdb_glue_table_node_t *gluenode; + rbtdb_glue_table_node_t *nextgluenode; + for (gluenode = oldtable[i]; gluenode != NULL; + gluenode = nextgluenode) + { + uint32_t hash = isc_hash32( + &gluenode->node, sizeof(gluenode->node), true); + uint32_t idx = hash_32(hash, newbits); + nextgluenode = gluenode->next; + gluenode->next = version->glue_table[idx]; + version->glue_table[idx] = gluenode; + } + } + + isc_mem_put(version->rbtdb->common.mctx, oldtable, + oldcount * sizeof(*version->glue_table)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ZONE, + ISC_LOG_DEBUG(3), + "rehash_gluetable(): " + "resized glue table from %zu to " + "%zu", + oldcount, newsize / sizeof(version->glue_table[0])); +} + +static void +maybe_rehash_gluetable(rbtdb_version_t *version) { + size_t overcommit = HASHSIZE(version->glue_table_bits) * + RBTDB_GLUE_TABLE_OVERCOMMIT; + if (version->glue_table_nodecount < overcommit) { + return; + } + + rehash_gluetable(version); +} + +static isc_result_t +glue_nsdname_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype, + dns_rdataset_t *unused) { + rbtdb_glue_additionaldata_ctx_t *ctx; + isc_result_t result; + dns_fixedname_t fixedname_a; + dns_name_t *name_a = NULL; + dns_rdataset_t rdataset_a, sigrdataset_a; + dns_rbtnode_t *node_a = NULL; + dns_fixedname_t fixedname_aaaa; + dns_name_t *name_aaaa = NULL; + dns_rdataset_t rdataset_aaaa, sigrdataset_aaaa; + dns_rbtnode_t *node_aaaa = NULL; + rbtdb_glue_t *glue = NULL; + dns_name_t *gluename = NULL; + + UNUSED(unused); + + /* + * NS records want addresses in additional records. + */ + INSIST(qtype == dns_rdatatype_a); + + ctx = (rbtdb_glue_additionaldata_ctx_t *)arg; + + name_a = dns_fixedname_initname(&fixedname_a); + dns_rdataset_init(&rdataset_a); + dns_rdataset_init(&sigrdataset_a); + + name_aaaa = dns_fixedname_initname(&fixedname_aaaa); + dns_rdataset_init(&rdataset_aaaa); + dns_rdataset_init(&sigrdataset_aaaa); + + result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion, + dns_rdatatype_a, DNS_DBFIND_GLUEOK, 0, + (dns_dbnode_t **)&node_a, name_a, &rdataset_a, + &sigrdataset_a); + if (result == DNS_R_GLUE) { + glue = isc_mem_get(ctx->rbtdb->common.mctx, sizeof(*glue)); + + gluename = dns_fixedname_initname(&glue->fixedname); + dns_name_copy(name_a, gluename); + + dns_rdataset_init(&glue->rdataset_a); + dns_rdataset_init(&glue->sigrdataset_a); + dns_rdataset_init(&glue->rdataset_aaaa); + dns_rdataset_init(&glue->sigrdataset_aaaa); + + dns_rdataset_clone(&rdataset_a, &glue->rdataset_a); + if (dns_rdataset_isassociated(&sigrdataset_a)) { + dns_rdataset_clone(&sigrdataset_a, + &glue->sigrdataset_a); + } + } + + result = zone_find((dns_db_t *)ctx->rbtdb, name, ctx->rbtversion, + dns_rdatatype_aaaa, DNS_DBFIND_GLUEOK, 0, + (dns_dbnode_t **)&node_aaaa, name_aaaa, + &rdataset_aaaa, &sigrdataset_aaaa); + if (result == DNS_R_GLUE) { + if (glue == NULL) { + glue = isc_mem_get(ctx->rbtdb->common.mctx, + sizeof(*glue)); + + gluename = dns_fixedname_initname(&glue->fixedname); + dns_name_copy(name_aaaa, gluename); + + dns_rdataset_init(&glue->rdataset_a); + dns_rdataset_init(&glue->sigrdataset_a); + dns_rdataset_init(&glue->rdataset_aaaa); + dns_rdataset_init(&glue->sigrdataset_aaaa); + } else { + INSIST(node_a == node_aaaa); + INSIST(dns_name_equal(name_a, name_aaaa)); + } + + dns_rdataset_clone(&rdataset_aaaa, &glue->rdataset_aaaa); + if (dns_rdataset_isassociated(&sigrdataset_aaaa)) { + dns_rdataset_clone(&sigrdataset_aaaa, + &glue->sigrdataset_aaaa); + } + } + + if (glue != NULL) { + glue->next = ctx->glue_list; + ctx->glue_list = glue; + } + + result = ISC_R_SUCCESS; + + if (dns_rdataset_isassociated(&rdataset_a)) { + rdataset_disassociate(&rdataset_a); + } + if (dns_rdataset_isassociated(&sigrdataset_a)) { + rdataset_disassociate(&sigrdataset_a); + } + + if (dns_rdataset_isassociated(&rdataset_aaaa)) { + rdataset_disassociate(&rdataset_aaaa); + } + if (dns_rdataset_isassociated(&sigrdataset_aaaa)) { + rdataset_disassociate(&sigrdataset_aaaa); + } + + if (node_a != NULL) { + detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_a); + } + if (node_aaaa != NULL) { + detachnode((dns_db_t *)ctx->rbtdb, (dns_dbnode_t *)&node_aaaa); + } + + return (result); +} + +static isc_result_t +rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version, + dns_message_t *msg) { + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *node = rdataset->private2; + rbtdb_version_t *rbtversion = version; + uint32_t idx; + rbtdb_glue_table_node_t *cur; + bool found = false; + bool restarted = false; + rbtdb_glue_t *ge; + rbtdb_glue_additionaldata_ctx_t ctx; + isc_result_t result; + uint64_t hash; + + REQUIRE(rdataset->type == dns_rdatatype_ns); + REQUIRE(rbtdb == rbtversion->rbtdb); + REQUIRE(!IS_CACHE(rbtdb) && !IS_STUB(rbtdb)); + + /* + * The glue table cache that forms a part of the DB version + * structure is not explicitly bounded and there's no cache + * cleaning. The zone data size itself is an implicit bound. + * + * The key into the glue hashtable is the node pointer. This is + * because the glue hashtable is a property of the DB version, + * and the glue is keyed for the ownername/NS tuple. We don't + * bother with using an expensive dns_name_t comparison here as + * the node pointer is a fixed value that won't change for a DB + * version and can be compared directly. + */ + hash = isc_hash_function(&node, sizeof(node), true); + +restart: + /* + * First, check if we have the additional entries already cached + * in the glue table. + */ + RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read); + + idx = hash_32(hash, rbtversion->glue_table_bits); + + for (cur = rbtversion->glue_table[idx]; cur != NULL; cur = cur->next) { + if (cur->node == node) { + break; + } + } + + if (cur == NULL) { + goto no_glue; + } + /* + * We found a cached result. Add it to the message and + * return. + */ + found = true; + ge = cur->glue_list; + + /* + * (void *) -1 is a special value that means no glue is + * present in the zone. + */ + if (ge == (void *)-1) { + if (!restarted && (rbtdb->gluecachestats != NULL)) { + isc_stats_increment( + rbtdb->gluecachestats, + dns_gluecachestatscounter_hits_absent); + } + goto no_glue; + } else { + if (!restarted && (rbtdb->gluecachestats != NULL)) { + isc_stats_increment( + rbtdb->gluecachestats, + dns_gluecachestatscounter_hits_present); + } + } + + for (; ge != NULL; ge = ge->next) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset_a = NULL; + dns_rdataset_t *sigrdataset_a = NULL; + dns_rdataset_t *rdataset_aaaa = NULL; + dns_rdataset_t *sigrdataset_aaaa = NULL; + dns_name_t *gluename = dns_fixedname_name(&ge->fixedname); + + result = dns_message_gettempname(msg, &name); + if (result != ISC_R_SUCCESS) { + goto no_glue; + } + + dns_name_copy(gluename, name); + + if (dns_rdataset_isassociated(&ge->rdataset_a)) { + result = dns_message_gettemprdataset(msg, &rdataset_a); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(msg, &name); + goto no_glue; + } + } + + if (dns_rdataset_isassociated(&ge->sigrdataset_a)) { + result = dns_message_gettemprdataset(msg, + &sigrdataset_a); + if (result != ISC_R_SUCCESS) { + if (rdataset_a != NULL) { + dns_message_puttemprdataset( + msg, &rdataset_a); + } + dns_message_puttempname(msg, &name); + goto no_glue; + } + } + + if (dns_rdataset_isassociated(&ge->rdataset_aaaa)) { + result = dns_message_gettemprdataset(msg, + &rdataset_aaaa); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(msg, &name); + if (rdataset_a != NULL) { + dns_message_puttemprdataset( + msg, &rdataset_a); + } + if (sigrdataset_a != NULL) { + dns_message_puttemprdataset( + msg, &sigrdataset_a); + } + goto no_glue; + } + } + + if (dns_rdataset_isassociated(&ge->sigrdataset_aaaa)) { + result = dns_message_gettemprdataset(msg, + &sigrdataset_aaaa); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(msg, &name); + if (rdataset_a != NULL) { + dns_message_puttemprdataset( + msg, &rdataset_a); + } + if (sigrdataset_a != NULL) { + dns_message_puttemprdataset( + msg, &sigrdataset_a); + } + if (rdataset_aaaa != NULL) { + dns_message_puttemprdataset( + msg, &rdataset_aaaa); + } + goto no_glue; + } + } + + if (rdataset_a != NULL) { + dns_rdataset_clone(&ge->rdataset_a, rdataset_a); + ISC_LIST_APPEND(name->list, rdataset_a, link); + } + + if (sigrdataset_a != NULL) { + dns_rdataset_clone(&ge->sigrdataset_a, sigrdataset_a); + ISC_LIST_APPEND(name->list, sigrdataset_a, link); + } + + if (rdataset_aaaa != NULL) { + dns_rdataset_clone(&ge->rdataset_aaaa, rdataset_aaaa); + ISC_LIST_APPEND(name->list, rdataset_aaaa, link); + } + if (sigrdataset_aaaa != NULL) { + dns_rdataset_clone(&ge->sigrdataset_aaaa, + sigrdataset_aaaa); + ISC_LIST_APPEND(name->list, sigrdataset_aaaa, link); + } + + dns_message_addname(msg, name, DNS_SECTION_ADDITIONAL); + } + +no_glue: + RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_read); + + if (found) { + return (ISC_R_SUCCESS); + } + + if (restarted) { + return (ISC_R_FAILURE); + } + + /* + * No cached glue was found in the table. Cache it and restart + * this function. + * + * Due to the gap between the read lock and the write lock, it's + * possible that we may cache a duplicate glue table entry, but + * we don't care. + */ + + ctx.glue_list = NULL; + ctx.rbtdb = rbtdb; + ctx.rbtversion = rbtversion; + + RWLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write); + + maybe_rehash_gluetable(rbtversion); + idx = hash_32(hash, rbtversion->glue_table_bits); + + (void)dns_rdataset_additionaldata(rdataset, dns_rootname, + glue_nsdname_cb, &ctx); + + cur = isc_mem_get(rbtdb->common.mctx, sizeof(*cur)); + + /* + * XXXMUKS: it looks like the dns_dbversion is not destroyed + * when named is terminated by a keyboard break. This doesn't + * cleanup the node reference and keeps the process dangling. + */ + /* isc_refcount_increment0(&node->references); */ + cur->node = node; + + if (ctx.glue_list == NULL) { + /* + * No glue was found. Cache it so. + */ + cur->glue_list = (void *)-1; + if (rbtdb->gluecachestats != NULL) { + isc_stats_increment( + rbtdb->gluecachestats, + dns_gluecachestatscounter_inserts_absent); + } + } else { + cur->glue_list = ctx.glue_list; + if (rbtdb->gluecachestats != NULL) { + isc_stats_increment( + rbtdb->gluecachestats, + dns_gluecachestatscounter_inserts_present); + } + } + + cur->next = rbtversion->glue_table[idx]; + rbtversion->glue_table[idx] = cur; + rbtversion->glue_table_nodecount++; + + RWUNLOCK(&rbtversion->glue_rwlock, isc_rwlocktype_write); + + restarted = true; + goto restart; + + /* UNREACHABLE */ +} + +/*% + * Routines for LRU-based cache management. + */ + +/*% + * See if a given cache entry that is being reused needs to be updated + * in the LRU-list. From the LRU management point of view, this function is + * expected to return true for almost all cases. When used with threads, + * however, this may cause a non-negligible performance penalty because a + * writer lock will have to be acquired before updating the list. + * If DNS_RBTDB_LIMITLRUUPDATE is defined to be non 0 at compilation time, this + * function returns true if the entry has not been updated for some period of + * time. We differentiate the NS or glue address case and the others since + * experiments have shown that the former tends to be accessed relatively + * infrequently and the cost of cache miss is higher (e.g., a missing NS records + * may cause external queries at a higher level zone, involving more + * transactions). + * + * Caller must hold the node (read or write) lock. + */ +static bool +need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) { + if (RDATASET_ATTR_GET(header, (RDATASET_ATTR_NONEXISTENT | + RDATASET_ATTR_ANCIENT | + RDATASET_ATTR_ZEROTTL)) != 0) + { + return (false); + } + +#if DNS_RBTDB_LIMITLRUUPDATE + if (header->type == dns_rdatatype_ns || + (header->trust == dns_trust_glue && + (header->type == dns_rdatatype_a || + header->type == dns_rdatatype_aaaa))) + { + /* + * Glue records are updated if at least DNS_RBTDB_LRUUPDATE_GLUE + * seconds have passed since the previous update time. + */ + return (header->last_used + DNS_RBTDB_LRUUPDATE_GLUE <= now); + } + + /* + * Other records are updated if DNS_RBTDB_LRUUPDATE_REGULAR seconds + * have passed. + */ + return (header->last_used + DNS_RBTDB_LRUUPDATE_REGULAR <= now); +#else + UNUSED(now); + + return (true); +#endif /* if DNS_RBTDB_LIMITLRUUPDATE */ +} + +/*% + * Update the timestamp of a given cache entry and move it to the head + * of the corresponding LRU list. + * + * Caller must hold the node (write) lock. + * + * Note that the we do NOT touch the heap here, as the TTL has not changed. + */ +static void +update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now) { + INSIST(IS_CACHE(rbtdb)); + + /* To be checked: can we really assume this? XXXMLG */ + INSIST(ISC_LINK_LINKED(header, link)); + + ISC_LIST_UNLINK(rbtdb->rdatasets[header->node->locknum], header, link); + header->last_used = now; + ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link); +} + +static size_t +expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize, + bool tree_locked) { + rdatasetheader_t *header, *header_prev; + size_t purged = 0; + + for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); + header != NULL && purged <= purgesize; header = header_prev) + { + header_prev = ISC_LIST_PREV(header, link); + /* + * Unlink the entry at this point to avoid checking it + * again even if it's currently used someone else and + * cannot be purged at this moment. This entry won't be + * referenced any more (so unlinking is safe) since the + * TTL was reset to 0. + */ + ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link); + size_t header_size = rdataset_size(header); + expire_header(rbtdb, header, tree_locked, expire_lru); + purged += header_size; + } + + return (purged); +} + +/*% + * Purge some stale (i.e. unused for some period - LRU based cleaning) cache + * entries under the overmem condition. To recover from this condition quickly, + * we cleanup entries up to the size of newly added rdata (passed as purgesize). + * + * This process is triggered while adding a new entry, and we specifically avoid + * purging entries in the same LRU bucket as the one to which the new entry will + * belong. Otherwise, we might purge entries of the same name of different RR + * types while adding RRsets from a single response (consider the case where + * we're adding A and AAAA glue records of the same NS name). + */ +static void +overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, + bool tree_locked) { + unsigned int locknum; + size_t purged = 0; + + for (locknum = (locknum_start + 1) % rbtdb->node_lock_count; + locknum != locknum_start && purged <= purgesize; + locknum = (locknum + 1) % rbtdb->node_lock_count) + { + NODE_LOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + + purged += expire_lru_headers(rbtdb, locknum, purgesize - purged, + tree_locked); + + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + } +} + +static void +expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked, + expire_t reason) { + set_ttl(rbtdb, header, 0); + mark_header_ancient(rbtdb, header); + + /* + * Caller must hold the node (write) lock. + */ + + if (isc_refcount_current(&header->node->references) == 0) { + /* + * If no one else is using the node, we can clean it up now. + * We first need to gain a new reference to the node to meet a + * requirement of decrement_reference(). + */ + new_reference(rbtdb, header->node, isc_rwlocktype_write); + decrement_reference(rbtdb, header->node, 0, + isc_rwlocktype_write, + tree_locked ? isc_rwlocktype_write + : isc_rwlocktype_none, + false); + + if (rbtdb->cachestats == NULL) { + return; + } + + switch (reason) { + case expire_ttl: + isc_stats_increment(rbtdb->cachestats, + dns_cachestatscounter_deletettl); + break; + case expire_lru: + isc_stats_increment(rbtdb->cachestats, + dns_cachestatscounter_deletelru); + break; + default: + break; + } + } +} diff --git a/lib/dns/rbtdb.h b/lib/dns/rbtdb.h new file mode 100644 index 0000000..44e5102 --- /dev/null +++ b/lib/dns/rbtdb.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#include + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * DNS Red-Black Tree DB Implementation + */ + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *base, dns_dbtype_t type, + dns_rdataclass_t rdclass, unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp); + +/*%< + * Create a new database of type "rbt" (or "rbt64"). Called via + * dns_db_create(); see documentation for that function for more details. + * + * If argv[0] is set, it points to a valid memory context to be used for + * allocation of heap memory. Generally this is used for cache databases + * only. + * + * Requires: + * + * \li argc == 0 or argv[0] is a valid memory context. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c new file mode 100644 index 0000000..78897e9 --- /dev/null +++ b/lib/dns/rcode.c @@ -0,0 +1,585 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +#define NUMBERSIZE sizeof("037777777777") /* 2^32-1 octal + NUL */ + +#define TOTEXTONLY 0x01 + +#define RCODENAMES \ + /* standard rcodes */ \ + { dns_rcode_noerror, "NOERROR", 0 }, \ + { dns_rcode_formerr, "FORMERR", 0 }, \ + { dns_rcode_servfail, "SERVFAIL", 0 }, \ + { dns_rcode_nxdomain, "NXDOMAIN", 0 }, \ + { dns_rcode_notimp, "NOTIMP", 0 }, \ + { dns_rcode_refused, "REFUSED", 0 }, \ + { dns_rcode_yxdomain, "YXDOMAIN", 0 }, \ + { dns_rcode_yxrrset, "YXRRSET", 0 }, \ + { dns_rcode_nxrrset, "NXRRSET", 0 }, \ + { dns_rcode_notauth, "NOTAUTH", 0 }, \ + { dns_rcode_notzone, "NOTZONE", 0 }, \ + { 11, "RESERVED11", TOTEXTONLY }, \ + { 12, "RESERVED12", TOTEXTONLY }, \ + { 13, "RESERVED13", TOTEXTONLY }, \ + { 14, "RESERVED14", TOTEXTONLY }, \ + { 15, "RESERVED15", TOTEXTONLY }, + +#define ERCODENAMES \ + /* extended rcodes */ \ + { dns_rcode_badvers, "BADVERS", 0 }, \ + { dns_rcode_badcookie, "BADCOOKIE", 0 }, { \ + 0, NULL, 0 \ + } + +#define TSIGRCODENAMES \ + /* extended rcodes */ \ + { dns_tsigerror_badsig, "BADSIG", 0 }, \ + { dns_tsigerror_badkey, "BADKEY", 0 }, \ + { dns_tsigerror_badtime, "BADTIME", 0 }, \ + { dns_tsigerror_badmode, "BADMODE", 0 }, \ + { dns_tsigerror_badname, "BADNAME", 0 }, \ + { dns_tsigerror_badalg, "BADALG", 0 }, \ + { dns_tsigerror_badtrunc, "BADTRUNC", 0 }, { \ + 0, NULL, 0 \ + } + +/* RFC4398 section 2.1 */ + +#define CERTNAMES \ + { 1, "PKIX", 0 }, { 2, "SPKI", 0 }, { 3, "PGP", 0 }, \ + { 4, "IPKIX", 0 }, { 5, "ISPKI", 0 }, { 6, "IPGP", 0 }, \ + { 7, "ACPKIX", 0 }, { 8, "IACPKIX", 0 }, { 253, "URI", 0 }, \ + { 254, "OID", 0 }, { \ + 0, NULL, 0 \ + } + +/* RFC2535 section 7, RFC3110 */ + +#define SECALGNAMES \ + { DNS_KEYALG_RSAMD5, "RSAMD5", 0 }, { DNS_KEYALG_DH, "DH", 0 }, \ + { DNS_KEYALG_DSA, "DSA", 0 }, \ + { DNS_KEYALG_RSASHA1, "RSASHA1", 0 }, \ + { DNS_KEYALG_NSEC3DSA, "NSEC3DSA", 0 }, \ + { DNS_KEYALG_NSEC3RSASHA1, "NSEC3RSASHA1", 0 }, \ + { DNS_KEYALG_RSASHA256, "RSASHA256", 0 }, \ + { DNS_KEYALG_RSASHA512, "RSASHA512", 0 }, \ + { DNS_KEYALG_ECCGOST, "ECCGOST", 0 }, \ + { DNS_KEYALG_ECDSA256, "ECDSAP256SHA256", 0 }, \ + { DNS_KEYALG_ECDSA256, "ECDSA256", 0 }, \ + { DNS_KEYALG_ECDSA384, "ECDSAP384SHA384", 0 }, \ + { DNS_KEYALG_ECDSA384, "ECDSA384", 0 }, \ + { DNS_KEYALG_ED25519, "ED25519", 0 }, \ + { DNS_KEYALG_ED448, "ED448", 0 }, \ + { DNS_KEYALG_INDIRECT, "INDIRECT", 0 }, \ + { DNS_KEYALG_PRIVATEDNS, "PRIVATEDNS", 0 }, \ + { DNS_KEYALG_PRIVATEOID, "PRIVATEOID", 0 }, { \ + 0, NULL, 0 \ + } + +/* RFC2535 section 7.1 */ + +#define SECPROTONAMES \ + { 0, "NONE", 0 }, { 1, "TLS", 0 }, { 2, "EMAIL", 0 }, \ + { 3, "DNSSEC", 0 }, { 4, "IPSEC", 0 }, { 255, "ALL", 0 }, { \ + 0, NULL, 0 \ + } + +#define HASHALGNAMES \ + { 1, "SHA-1", 0 }, { 0, NULL, 0 } + +/* RFC3658, RFC4509, RFC5933, RFC6605 */ + +#define DSDIGESTNAMES \ + { DNS_DSDIGEST_SHA1, "SHA-1", 0 }, { DNS_DSDIGEST_SHA1, "SHA1", 0 }, \ + { DNS_DSDIGEST_SHA256, "SHA-256", 0 }, \ + { DNS_DSDIGEST_SHA256, "SHA256", 0 }, \ + { DNS_DSDIGEST_GOST, "GOST", 0 }, \ + { DNS_DSDIGEST_SHA384, "SHA-384", 0 }, \ + { DNS_DSDIGEST_SHA384, "SHA384", 0 }, { \ + 0, NULL, 0 \ + } + +struct tbl { + unsigned int value; + const char *name; + int flags; +}; + +static struct tbl rcodes[] = { RCODENAMES ERCODENAMES }; +static struct tbl tsigrcodes[] = { RCODENAMES TSIGRCODENAMES }; +static struct tbl certs[] = { CERTNAMES }; +static struct tbl secalgs[] = { SECALGNAMES }; +static struct tbl secprotos[] = { SECPROTONAMES }; +static struct tbl hashalgs[] = { HASHALGNAMES }; +static struct tbl dsdigests[] = { DSDIGESTNAMES }; + +static struct keyflag { + const char *name; + unsigned int value; + unsigned int mask; +} keyflags[] = { { "NOCONF", 0x4000, 0xC000 }, + { "NOAUTH", 0x8000, 0xC000 }, + { "NOKEY", 0xC000, 0xC000 }, + { "FLAG2", 0x2000, 0x2000 }, + { "EXTEND", 0x1000, 0x1000 }, + { "FLAG4", 0x0800, 0x0800 }, + { "FLAG5", 0x0400, 0x0400 }, + { "USER", 0x0000, 0x0300 }, + { "ZONE", 0x0100, 0x0300 }, + { "HOST", 0x0200, 0x0300 }, + { "NTYP3", 0x0300, 0x0300 }, + { "FLAG8", 0x0080, 0x0080 }, + { "FLAG9", 0x0040, 0x0040 }, + { "FLAG10", 0x0020, 0x0020 }, + { "FLAG11", 0x0010, 0x0010 }, + { "SIG0", 0x0000, 0x000F }, + { "SIG1", 0x0001, 0x000F }, + { "SIG2", 0x0002, 0x000F }, + { "SIG3", 0x0003, 0x000F }, + { "SIG4", 0x0004, 0x000F }, + { "SIG5", 0x0005, 0x000F }, + { "SIG6", 0x0006, 0x000F }, + { "SIG7", 0x0007, 0x000F }, + { "SIG8", 0x0008, 0x000F }, + { "SIG9", 0x0009, 0x000F }, + { "SIG10", 0x000A, 0x000F }, + { "SIG11", 0x000B, 0x000F }, + { "SIG12", 0x000C, 0x000F }, + { "SIG13", 0x000D, 0x000F }, + { "SIG14", 0x000E, 0x000F }, + { "SIG15", 0x000F, 0x000F }, + { "KSK", DNS_KEYFLAG_KSK, DNS_KEYFLAG_KSK }, + { NULL, 0, 0 } }; + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +maybe_numeric(unsigned int *valuep, isc_textregion_t *source, unsigned int max, + bool hex_allowed) { + isc_result_t result; + uint32_t n; + char buffer[NUMBERSIZE]; + int v; + + if (!isdigit((unsigned char)source->base[0]) || + source->length > NUMBERSIZE - 1) + { + return (ISC_R_BADNUMBER); + } + + /* + * We have a potential number. Try to parse it with + * isc_parse_uint32(). isc_parse_uint32() requires + * null termination, so we must make a copy. + */ + v = snprintf(buffer, sizeof(buffer), "%.*s", (int)source->length, + source->base); + if (v < 0 || (unsigned)v != source->length) { + return (ISC_R_BADNUMBER); + } + INSIST(buffer[source->length] == '\0'); + + result = isc_parse_uint32(&n, buffer, 10); + if (result == ISC_R_BADNUMBER && hex_allowed) { + result = isc_parse_uint32(&n, buffer, 16); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + if (n > max) { + return (ISC_R_RANGE); + } + *valuep = n; + return (ISC_R_SUCCESS); +} + +static isc_result_t +dns_mnemonic_fromtext(unsigned int *valuep, isc_textregion_t *source, + struct tbl *table, unsigned int max) { + isc_result_t result; + int i; + + result = maybe_numeric(valuep, source, max, false); + if (result != ISC_R_BADNUMBER) { + return (result); + } + + for (i = 0; table[i].name != NULL; i++) { + unsigned int n; + n = strlen(table[i].name); + if (n == source->length && (table[i].flags & TOTEXTONLY) == 0 && + strncasecmp(source->base, table[i].name, n) == 0) + { + *valuep = table[i].value; + return (ISC_R_SUCCESS); + } + } + return (DNS_R_UNKNOWN); +} + +static isc_result_t +dns_mnemonic_totext(unsigned int value, isc_buffer_t *target, + struct tbl *table) { + int i = 0; + char buf[sizeof("4294967296")]; + while (table[i].name != NULL) { + if (table[i].value == value) { + return (str_totext(table[i].name, target)); + } + i++; + } + snprintf(buf, sizeof(buf), "%u", value); + return (str_totext(buf, target)); +} + +isc_result_t +dns_rcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, rcodes, 0xffff)); + *rcodep = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rcode_totext(dns_rcode_t rcode, isc_buffer_t *target) { + return (dns_mnemonic_totext(rcode, target, rcodes)); +} + +isc_result_t +dns_tsigrcode_fromtext(dns_rcode_t *rcodep, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, tsigrcodes, 0xffff)); + *rcodep = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_tsigrcode_totext(dns_rcode_t rcode, isc_buffer_t *target) { + return (dns_mnemonic_totext(rcode, target, tsigrcodes)); +} + +isc_result_t +dns_cert_fromtext(dns_cert_t *certp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, certs, 0xffff)); + *certp = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_cert_totext(dns_cert_t cert, isc_buffer_t *target) { + return (dns_mnemonic_totext(cert, target, certs)); +} + +isc_result_t +dns_secalg_fromtext(dns_secalg_t *secalgp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, secalgs, 0xff)); + *secalgp = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_secalg_totext(dns_secalg_t secalg, isc_buffer_t *target) { + return (dns_mnemonic_totext(secalg, target, secalgs)); +} + +void +dns_secalg_format(dns_secalg_t alg, char *cp, unsigned int size) { + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + REQUIRE(cp != NULL && size > 0); + isc_buffer_init(&b, cp, size - 1); + result = dns_secalg_totext(alg, &b); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + if (result != ISC_R_SUCCESS) { + r.base[0] = 0; + } +} + +isc_result_t +dns_secproto_fromtext(dns_secproto_t *secprotop, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, secprotos, 0xff)); + *secprotop = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_secproto_totext(dns_secproto_t secproto, isc_buffer_t *target) { + return (dns_mnemonic_totext(secproto, target, secprotos)); +} + +isc_result_t +dns_hashalg_fromtext(unsigned char *hashalg, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, hashalgs, 0xff)); + *hashalg = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_keyflags_fromtext(dns_keyflags_t *flagsp, isc_textregion_t *source) { + isc_result_t result; + char *text, *end; + unsigned int value = 0; +#ifdef notyet + unsigned int mask = 0; +#endif /* ifdef notyet */ + + result = maybe_numeric(&value, source, 0xffff, true); + if (result == ISC_R_SUCCESS) { + *flagsp = value; + return (ISC_R_SUCCESS); + } + if (result != ISC_R_BADNUMBER) { + return (result); + } + + text = source->base; + end = source->base + source->length; + + while (text < end) { + struct keyflag *p; + unsigned int len; + char *delim = memchr(text, '|', end - text); + if (delim != NULL) { + len = (unsigned int)(delim - text); + } else { + len = (unsigned int)(end - text); + } + for (p = keyflags; p->name != NULL; p++) { + if (strncasecmp(p->name, text, len) == 0) { + break; + } + } + if (p->name == NULL) { + return (DNS_R_UNKNOWNFLAG); + } + value |= p->value; +#ifdef notyet + if ((mask & p->mask) != 0) { + warn("overlapping key flags"); + } + mask |= p->mask; +#endif /* ifdef notyet */ + text += len; + if (delim != NULL) { + text++; /* Skip "|" */ + } + } + *flagsp = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dsdigest_fromtext(dns_dsdigest_t *dsdigestp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, dsdigests, 0xff)); + *dsdigestp = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dsdigest_totext(dns_dsdigest_t dsdigest, isc_buffer_t *target) { + return (dns_mnemonic_totext(dsdigest, target, dsdigests)); +} + +void +dns_dsdigest_format(dns_dsdigest_t typ, char *cp, unsigned int size) { + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + REQUIRE(cp != NULL && size > 0); + isc_buffer_init(&b, cp, size - 1); + result = dns_dsdigest_totext(typ, &b); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + if (result != ISC_R_SUCCESS) { + r.base[0] = 0; + } +} + +/* + * This uses lots of hard coded values, but how often do we actually + * add classes? + */ +isc_result_t +dns_rdataclass_fromtext(dns_rdataclass_t *classp, isc_textregion_t *source) { +#define COMPARE(string, rdclass) \ + if (((sizeof(string) - 1) == source->length) && \ + (strncasecmp(source->base, string, source->length) == 0)) \ + { \ + *classp = rdclass; \ + return (ISC_R_SUCCESS); \ + } + + switch (tolower((unsigned char)source->base[0])) { + case 'a': + COMPARE("any", dns_rdataclass_any); + break; + case 'c': + /* + * RFC1035 says the mnemonic for the CHAOS class is CH, + * but historical BIND practice is to call it CHAOS. + * We will accept both forms, but only generate CH. + */ + COMPARE("ch", dns_rdataclass_chaos); + COMPARE("chaos", dns_rdataclass_chaos); + + if (source->length > 5 && + source->length < (5 + sizeof("65000")) && + strncasecmp("class", source->base, 5) == 0) + { + char buf[sizeof("65000")]; + char *endp; + unsigned int val; + + /* + * source->base is not required to be NUL terminated. + * Copy up to remaining bytes and NUL terminate. + */ + snprintf(buf, sizeof(buf), "%.*s", + (int)(source->length - 5), source->base + 5); + val = strtoul(buf, &endp, 10); + if (*endp == '\0' && val <= 0xffff) { + *classp = (dns_rdataclass_t)val; + return (ISC_R_SUCCESS); + } + } + break; + case 'h': + COMPARE("hs", dns_rdataclass_hs); + COMPARE("hesiod", dns_rdataclass_hs); + break; + case 'i': + COMPARE("in", dns_rdataclass_in); + break; + case 'n': + COMPARE("none", dns_rdataclass_none); + break; + case 'r': + COMPARE("reserved0", dns_rdataclass_reserved0); + break; + } + +#undef COMPARE + + return (DNS_R_UNKNOWN); +} + +isc_result_t +dns_rdataclass_totext(dns_rdataclass_t rdclass, isc_buffer_t *target) { + switch (rdclass) { + case dns_rdataclass_any: + return (str_totext("ANY", target)); + case dns_rdataclass_chaos: + return (str_totext("CH", target)); + case dns_rdataclass_hs: + return (str_totext("HS", target)); + case dns_rdataclass_in: + return (str_totext("IN", target)); + case dns_rdataclass_none: + return (str_totext("NONE", target)); + case dns_rdataclass_reserved0: + return (str_totext("RESERVED0", target)); + default: + return (dns_rdataclass_tounknowntext(rdclass, target)); + } +} + +isc_result_t +dns_rdataclass_tounknowntext(dns_rdataclass_t rdclass, isc_buffer_t *target) { + char buf[sizeof("CLASS65535")]; + + snprintf(buf, sizeof(buf), "CLASS%u", rdclass); + return (str_totext(buf, target)); +} + +void +dns_rdataclass_format(dns_rdataclass_t rdclass, char *array, + unsigned int size) { + isc_result_t result; + isc_buffer_t buf; + + if (size == 0U) { + return; + } + + isc_buffer_init(&buf, array, size); + result = dns_rdataclass_totext(rdclass, &buf); + /* + * Null terminate. + */ + if (result == ISC_R_SUCCESS) { + if (isc_buffer_availablelength(&buf) >= 1) { + isc_buffer_putuint8(&buf, 0); + } else { + result = ISC_R_NOSPACE; + } + } + if (result != ISC_R_SUCCESS) { + strlcpy(array, "", size); + } +} diff --git a/lib/dns/rdata.c b/lib/dns/rdata.c new file mode 100644 index 0000000..b7f9ed2 --- /dev/null +++ b/lib/dns/rdata.c @@ -0,0 +1,2367 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +#define RETTOK(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) { \ + isc_lex_ungettoken(lexer, &token); \ + return (_r); \ + } \ + } while (0) + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +#define CHECKTOK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + isc_lex_ungettoken(lexer, &token); \ + goto cleanup; \ + } \ + } while (0) + +#define DNS_AS_STR(t) ((t).value.as_textregion.base) + +#define ARGS_FROMTEXT \ + int rdclass, dns_rdatatype_t type, isc_lex_t *lexer, \ + const dns_name_t *origin, unsigned int options, \ + isc_buffer_t *target, dns_rdatacallbacks_t *callbacks + +#define CALL_FROMTEXT rdclass, type, lexer, origin, options, target, callbacks + +#define ARGS_TOTEXT \ + dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, isc_buffer_t *target + +#define CALL_TOTEXT rdata, tctx, target + +#define ARGS_FROMWIRE \ + int rdclass, dns_rdatatype_t type, isc_buffer_t *source, \ + dns_decompress_t *dctx, unsigned int options, \ + isc_buffer_t *target + +#define CALL_FROMWIRE rdclass, type, source, dctx, options, target + +#define ARGS_TOWIRE \ + dns_rdata_t *rdata, dns_compress_t *cctx, isc_buffer_t *target + +#define CALL_TOWIRE rdata, cctx, target + +#define ARGS_COMPARE const dns_rdata_t *rdata1, const dns_rdata_t *rdata2 + +#define CALL_COMPARE rdata1, rdata2 + +#define ARGS_FROMSTRUCT \ + int rdclass, dns_rdatatype_t type, void *source, isc_buffer_t *target + +#define CALL_FROMSTRUCT rdclass, type, source, target + +#define ARGS_TOSTRUCT const dns_rdata_t *rdata, void *target, isc_mem_t *mctx + +#define CALL_TOSTRUCT rdata, target, mctx + +#define ARGS_FREESTRUCT void *source + +#define CALL_FREESTRUCT source + +#define ARGS_ADDLDATA \ + dns_rdata_t *rdata, const dns_name_t *owner, \ + dns_additionaldatafunc_t add, void *arg + +#define CALL_ADDLDATA rdata, owner, add, arg + +#define ARGS_DIGEST dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg + +#define CALL_DIGEST rdata, digest, arg + +#define ARGS_CHECKOWNER \ + const dns_name_t *name, dns_rdataclass_t rdclass, \ + dns_rdatatype_t type, bool wildcard + +#define CALL_CHECKOWNER name, rdclass, type, wildcard + +#define ARGS_CHECKNAMES \ + dns_rdata_t *rdata, const dns_name_t *owner, dns_name_t *bad + +#define CALL_CHECKNAMES rdata, owner, bad + +/*% + * Context structure for the totext_ functions. + * Contains formatting options for rdata-to-text + * conversion. + */ +typedef struct dns_rdata_textctx { + const dns_name_t *origin; /*%< Current origin, or NULL. */ + dns_masterstyle_flags_t flags; /*%< DNS_STYLEFLAG_* */ + unsigned int width; /*%< Width of rdata column. */ + const char *linebreak; /*%< Line break string. */ +} dns_rdata_textctx_t; + +static isc_result_t +txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target); + +static isc_result_t +txt_fromtext(isc_textregion_t *source, isc_buffer_t *target); + +static isc_result_t +txt_fromwire(isc_buffer_t *source, isc_buffer_t *target); + +static isc_result_t +commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target); + +static isc_result_t +commatxt_totext(isc_region_t *source, bool quote, bool comma, + isc_buffer_t *target); + +static isc_result_t +multitxt_totext(isc_region_t *source, isc_buffer_t *target); + +static isc_result_t +multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target); + +static bool +name_prefix(dns_name_t *name, const dns_name_t *origin, dns_name_t *target); + +static unsigned int +name_length(const dns_name_t *name); + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target); + +static isc_result_t +inet_totext(int af, uint32_t flags, isc_region_t *src, isc_buffer_t *target); + +static bool +buffer_empty(isc_buffer_t *source); + +static void +buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region); + +static isc_result_t +uint32_tobuffer(uint32_t, isc_buffer_t *target); + +static isc_result_t +uint16_tobuffer(uint32_t, isc_buffer_t *target); + +static isc_result_t +uint8_tobuffer(uint32_t, isc_buffer_t *target); + +static isc_result_t +name_tobuffer(const dns_name_t *name, isc_buffer_t *target); + +static uint32_t +uint32_fromregion(isc_region_t *region); + +static uint16_t +uint16_fromregion(isc_region_t *region); + +static uint8_t +uint8_fromregion(isc_region_t *region); + +static uint8_t +uint8_consume_fromregion(isc_region_t *region); + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length); + +static int +hexvalue(char value); + +static int +decvalue(char value); + +static void +default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *, ...) + ISC_FORMAT_PRINTF(2, 3); + +static void +fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...), + dns_rdatacallbacks_t *callbacks, const char *name, + unsigned long line, isc_token_t *token, isc_result_t result); + +static void +fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks); + +static isc_result_t +rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, + isc_buffer_t *target); + +static void +warn_badname(const dns_name_t *name, isc_lex_t *lexer, + dns_rdatacallbacks_t *callbacks); + +static void +warn_badmx(isc_token_t *token, isc_lex_t *lexer, + dns_rdatacallbacks_t *callbacks); + +static uint16_t +uint16_consume_fromregion(isc_region_t *region); + +static isc_result_t +unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, + isc_buffer_t *target); + +static isc_result_t generic_fromtext_key(ARGS_FROMTEXT); + +static isc_result_t generic_totext_key(ARGS_TOTEXT); + +static isc_result_t generic_fromwire_key(ARGS_FROMWIRE); + +static isc_result_t generic_fromstruct_key(ARGS_FROMSTRUCT); + +static isc_result_t generic_tostruct_key(ARGS_TOSTRUCT); + +static void generic_freestruct_key(ARGS_FREESTRUCT); + +static isc_result_t generic_fromtext_txt(ARGS_FROMTEXT); + +static isc_result_t generic_totext_txt(ARGS_TOTEXT); + +static isc_result_t generic_fromwire_txt(ARGS_FROMWIRE); + +static isc_result_t generic_fromstruct_txt(ARGS_FROMSTRUCT); + +static isc_result_t generic_tostruct_txt(ARGS_TOSTRUCT); + +static void generic_freestruct_txt(ARGS_FREESTRUCT); + +static isc_result_t +generic_txt_first(dns_rdata_txt_t *txt); + +static isc_result_t +generic_txt_next(dns_rdata_txt_t *txt); + +static isc_result_t +generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string); + +static isc_result_t generic_totext_ds(ARGS_TOTEXT); + +static isc_result_t generic_tostruct_ds(ARGS_TOSTRUCT); + +static isc_result_t generic_fromtext_ds(ARGS_FROMTEXT); + +static isc_result_t generic_fromwire_ds(ARGS_FROMWIRE); + +static isc_result_t generic_fromstruct_ds(ARGS_FROMSTRUCT); + +static isc_result_t generic_fromtext_tlsa(ARGS_FROMTEXT); + +static isc_result_t generic_totext_tlsa(ARGS_TOTEXT); + +static isc_result_t generic_fromwire_tlsa(ARGS_FROMWIRE); + +static isc_result_t generic_fromstruct_tlsa(ARGS_FROMSTRUCT); + +static isc_result_t generic_tostruct_tlsa(ARGS_TOSTRUCT); + +static void generic_freestruct_tlsa(ARGS_FREESTRUCT); + +static isc_result_t generic_fromtext_in_svcb(ARGS_FROMTEXT); +static isc_result_t generic_totext_in_svcb(ARGS_TOTEXT); +static isc_result_t generic_fromwire_in_svcb(ARGS_FROMWIRE); +static isc_result_t generic_towire_in_svcb(ARGS_TOWIRE); +static isc_result_t generic_fromstruct_in_svcb(ARGS_FROMSTRUCT); +static isc_result_t generic_tostruct_in_svcb(ARGS_TOSTRUCT); +static void generic_freestruct_in_svcb(ARGS_FREESTRUCT); +static isc_result_t generic_additionaldata_in_svcb(ARGS_ADDLDATA); +static bool generic_checknames_in_svcb(ARGS_CHECKNAMES); +static isc_result_t +generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *); +static isc_result_t +generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *); +static void +generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *); + +/*% INT16 Size */ +#define NS_INT16SZ 2 +/*% IPv6 Address Size */ +#define NS_LOCATORSZ 8 + +/* + * Active Directory gc._msdcs. prefix. + */ +static unsigned char gc_msdcs_data[] = "\002gc\006_msdcs"; +static unsigned char gc_msdcs_offset[] = { 0, 3 }; + +static dns_name_t const gc_msdcs = DNS_NAME_INITNONABSOLUTE(gc_msdcs_data, + gc_msdcs_offset); + +/*% + * convert presentation level address to network order binary form. + * \return + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * \note + * (1) does not touch `dst' unless it's returning 1. + */ +static int +locator_pton(const char *src, unsigned char *dst) { + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_LOCATORSZ]; + unsigned char *tp = tmp, *endp; + const char *xdigits; + int ch, seen_xdigits; + unsigned int val; + + memset(tp, '\0', NS_LOCATORSZ); + endp = tp + NS_LOCATORSZ; + seen_xdigits = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + pch = strchr((xdigits = xdigits_l), ch); + if (pch == NULL) { + pch = strchr((xdigits = xdigits_u), ch); + } + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (++seen_xdigits > 4) { + return (0); + } + continue; + } + if (ch == ':') { + if (!seen_xdigits) { + return (0); + } + if (tp + NS_INT16SZ > endp) { + return (0); + } + *tp++ = (unsigned char)(val >> 8) & 0xff; + *tp++ = (unsigned char)val & 0xff; + seen_xdigits = 0; + val = 0; + continue; + } + return (0); + } + if (seen_xdigits) { + if (tp + NS_INT16SZ > endp) { + return (0); + } + *tp++ = (unsigned char)(val >> 8) & 0xff; + *tp++ = (unsigned char)val & 0xff; + } + if (tp != endp) { + return (0); + } + memmove(dst, tmp, NS_LOCATORSZ); + return (1); +} + +static void +name_duporclone(const dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) { + if (mctx != NULL) { + dns_name_dup(source, mctx, target); + } else { + dns_name_clone(source, target); + } +} + +static void * +mem_maybedup(isc_mem_t *mctx, void *source, size_t length) { + void *copy; + + if (mctx == NULL) { + return (source); + } + copy = isc_mem_allocate(mctx, length); + memmove(copy, source, length); + + return (copy); +} + +static isc_result_t +typemap_fromtext(isc_lex_t *lexer, isc_buffer_t *target, bool allow_empty) { + isc_token_t token; + unsigned char bm[8 * 1024]; /* 64k bits */ + dns_rdatatype_t covered, max_used; + int octet; + unsigned int max_octet, newend, end; + int window; + bool first = true; + + max_used = 0; + bm[0] = 0; + end = 0; + + do { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, true)); + if (token.type != isc_tokentype_string) { + break; + } + RETTOK(dns_rdatatype_fromtext(&covered, + &token.value.as_textregion)); + if (covered > max_used) { + newend = covered / 8; + if (newend > end) { + memset(&bm[end + 1], 0, newend - end); + end = newend; + } + max_used = covered; + } + bm[covered / 8] |= (0x80 >> (covered % 8)); + first = false; + } while (1); + isc_lex_ungettoken(lexer, &token); + if (!allow_empty && first) { + return (DNS_R_FORMERR); + } + + for (window = 0; window < 256; window++) { + if (max_used < window * 256) { + break; + } + + max_octet = max_used - (window * 256); + if (max_octet >= 256) { + max_octet = 31; + } else { + max_octet /= 8; + } + + /* + * Find if we have a type in this window. + */ + for (octet = max_octet; octet >= 0; octet--) { + if (bm[window * 32 + octet] != 0) { + break; + } + } + if (octet < 0) { + continue; + } + RETERR(uint8_tobuffer(window, target)); + RETERR(uint8_tobuffer(octet + 1, target)); + RETERR(mem_tobuffer(target, &bm[window * 32], octet + 1)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +typemap_totext(isc_region_t *sr, dns_rdata_textctx_t *tctx, + isc_buffer_t *target) { + unsigned int i, j, k; + unsigned int window, len; + bool first = true; + + for (i = 0; i < sr->length; i += len) { + if (tctx != NULL && + (tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) + { + RETERR(str_totext(tctx->linebreak, target)); + first = true; + } + INSIST(i + 2 <= sr->length); + window = sr->base[i]; + len = sr->base[i + 1]; + INSIST(len > 0 && len <= 32); + i += 2; + INSIST(i + len <= sr->length); + for (j = 0; j < len; j++) { + dns_rdatatype_t t; + if (sr->base[i + j] == 0) { + continue; + } + for (k = 0; k < 8; k++) { + if ((sr->base[i + j] & (0x80 >> k)) == 0) { + continue; + } + t = window * 256 + j * 8 + k; + if (!first) { + RETERR(str_totext(" ", target)); + } + first = false; + if (dns_rdatatype_isknown(t)) { + RETERR(dns_rdatatype_totext(t, target)); + } else { + char buf[sizeof("TYPE65535")]; + snprintf(buf, sizeof(buf), "TYPE%u", t); + RETERR(str_totext(buf, target)); + } + } + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +typemap_test(isc_region_t *sr, bool allow_empty) { + unsigned int window, lastwindow = 0; + unsigned int len; + bool first = true; + unsigned int i; + + for (i = 0; i < sr->length; i += len) { + /* + * Check for overflow. + */ + if (i + 2 > sr->length) { + RETERR(DNS_R_FORMERR); + } + window = sr->base[i]; + len = sr->base[i + 1]; + i += 2; + /* + * Check that bitmap windows are in the correct order. + */ + if (!first && window <= lastwindow) { + RETERR(DNS_R_FORMERR); + } + /* + * Check for legal lengths. + */ + if (len < 1 || len > 32) { + RETERR(DNS_R_FORMERR); + } + /* + * Check for overflow. + */ + if (i + len > sr->length) { + RETERR(DNS_R_FORMERR); + } + /* + * The last octet of the bitmap must be non zero. + */ + if (sr->base[i + len - 1] == 0) { + RETERR(DNS_R_FORMERR); + } + lastwindow = window; + first = false; + } + if (i != sr->length) { + return (DNS_R_EXTRADATA); + } + if (!allow_empty && first) { + RETERR(DNS_R_FORMERR); + } + return (ISC_R_SUCCESS); +} + +static const char hexdigits[] = "0123456789abcdef"; +static const char decdigits[] = "0123456789"; + +#include "code.h" + +#define META 0x0001 +#define RESERVED 0x0002 + +/*** + *** Initialization + ***/ + +void +dns_rdata_init(dns_rdata_t *rdata) { + REQUIRE(rdata != NULL); + + rdata->data = NULL; + rdata->length = 0; + rdata->rdclass = 0; + rdata->type = 0; + rdata->flags = 0; + ISC_LINK_INIT(rdata, link); + /* ISC_LIST_INIT(rdata->list); */ +} + +void +dns_rdata_reset(dns_rdata_t *rdata) { + REQUIRE(rdata != NULL); + + REQUIRE(!ISC_LINK_LINKED(rdata, link)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + rdata->data = NULL; + rdata->length = 0; + rdata->rdclass = 0; + rdata->type = 0; + rdata->flags = 0; +} + +/*** + *** + ***/ + +void +dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target) { + REQUIRE(src != NULL); + REQUIRE(target != NULL); + + REQUIRE(DNS_RDATA_INITIALIZED(target)); + + REQUIRE(DNS_RDATA_VALIDFLAGS(src)); + REQUIRE(DNS_RDATA_VALIDFLAGS(target)); + + target->data = src->data; + target->length = src->length; + target->rdclass = src->rdclass; + target->type = src->type; + target->flags = src->flags; +} + +/*** + *** Comparisons + ***/ + +int +dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) { + int result = 0; + bool use_default = false; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->length == 0 || rdata1->data != NULL); + REQUIRE(rdata2->length == 0 || rdata2->data != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2)); + + if (rdata1->rdclass != rdata2->rdclass) { + return (rdata1->rdclass < rdata2->rdclass ? -1 : 1); + } + + if (rdata1->type != rdata2->type) { + return (rdata1->type < rdata2->type ? -1 : 1); + } + + COMPARESWITCH + + if (use_default) { + isc_region_t r1; + isc_region_t r2; + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + result = isc_region_compare(&r1, &r2); + } + return (result); +} + +int +dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) { + int result = 0; + bool use_default = false; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->length == 0 || rdata1->data != NULL); + REQUIRE(rdata2->length == 0 || rdata2->data != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2)); + + if (rdata1->rdclass != rdata2->rdclass) { + return (rdata1->rdclass < rdata2->rdclass ? -1 : 1); + } + + if (rdata1->type != rdata2->type) { + return (rdata1->type < rdata2->type ? -1 : 1); + } + + CASECOMPARESWITCH + + if (use_default) { + isc_region_t r1; + isc_region_t r2; + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + result = isc_region_compare(&r1, &r2); + } + return (result); +} + +/*** + *** Conversions + ***/ + +void +dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_region_t *r) { + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + REQUIRE(r != NULL); + + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + rdata->data = r->base; + rdata->length = r->length; + rdata->rdclass = rdclass; + rdata->type = type; + rdata->flags = 0; +} + +void +dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r) { + REQUIRE(rdata != NULL); + REQUIRE(r != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + r->base = rdata->data; + r->length = rdata->length; +} + +isc_result_t +dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_buffer_t *source, + dns_decompress_t *dctx, unsigned int options, + isc_buffer_t *target) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + isc_region_t region; + isc_buffer_t ss; + isc_buffer_t st; + bool use_default = false; + uint32_t activelength; + unsigned int length; + + REQUIRE(dctx != NULL); + if (rdata != NULL) { + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + } + REQUIRE(source != NULL); + REQUIRE(target != NULL); + + if (type == 0) { + return (DNS_R_FORMERR); + } + + ss = *source; + st = *target; + + activelength = isc_buffer_activelength(source); + INSIST(activelength < 65536); + + FROMWIRESWITCH + + if (use_default) { + if (activelength > isc_buffer_availablelength(target)) { + result = ISC_R_NOSPACE; + } else { + isc_buffer_putmem(target, isc_buffer_current(source), + activelength); + isc_buffer_forward(source, activelength); + result = ISC_R_SUCCESS; + } + } + + /* + * Reject any rdata that expands out to more than DNS_RDATA_MAXLENGTH + * as we cannot transmit it. + */ + length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st); + if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) { + result = DNS_R_FORMERR; + } + + /* + * We should have consumed all of our buffer. + */ + if (result == ISC_R_SUCCESS && !buffer_empty(source)) { + result = DNS_R_EXTRADATA; + } + + if (rdata != NULL && result == ISC_R_SUCCESS) { + region.base = isc_buffer_used(&st); + region.length = length; + dns_rdata_fromregion(rdata, rdclass, type, ®ion); + } + + if (result != ISC_R_SUCCESS) { + *source = ss; + *target = st; + } + return (result); +} + +isc_result_t +dns_rdata_towire(dns_rdata_t *rdata, dns_compress_t *cctx, + isc_buffer_t *target) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + bool use_default = false; + isc_region_t tr; + isc_buffer_t st; + + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + /* + * Some DynDNS meta-RRs have empty rdata. + */ + if ((rdata->flags & DNS_RDATA_UPDATE) != 0) { + INSIST(rdata->length == 0); + return (ISC_R_SUCCESS); + } + + st = *target; + + TOWIRESWITCH + + if (use_default) { + isc_buffer_availableregion(target, &tr); + if (tr.length < rdata->length) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, rdata->data, rdata->length); + isc_buffer_add(target, rdata->length); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + *target = st; + INSIST(target->used < 65536); + dns_compress_rollback(cctx, (uint16_t)target->used); + } + return (result); +} + +/* + * If the binary data in 'src' is valid uncompressed wire format + * rdata of class 'rdclass' and type 'type', return ISC_R_SUCCESS + * and copy the validated rdata to 'dest'. Otherwise return an error. + */ +static isc_result_t +rdata_validate(isc_buffer_t *src, isc_buffer_t *dest, dns_rdataclass_t rdclass, + dns_rdatatype_t type) { + dns_decompress_t dctx; + isc_result_t result; + + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); + isc_buffer_setactive(src, isc_buffer_usedlength(src)); + result = dns_rdata_fromwire(NULL, rdclass, type, src, &dctx, 0, dest); + dns_decompress_invalidate(&dctx); + + return (result); +} + +static isc_result_t +unknown_fromtext(dns_rdataclass_t rdclass, dns_rdatatype_t type, + isc_lex_t *lexer, isc_mem_t *mctx, isc_buffer_t *target) { + isc_result_t result; + isc_buffer_t *buf = NULL; + isc_token_t token; + + if (type == 0 || dns_rdatatype_ismeta(type)) { + return (DNS_R_METATYPE); + } + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 65535U) { + return (ISC_R_RANGE); + } + isc_buffer_allocate(mctx, &buf, token.value.as_ulong); + + if (token.value.as_ulong != 0U) { + result = isc_hex_tobuffer(lexer, buf, + (unsigned int)token.value.as_ulong); + if (result != ISC_R_SUCCESS) { + goto failure; + } + if (isc_buffer_usedlength(buf) != token.value.as_ulong) { + result = ISC_R_UNEXPECTEDEND; + goto failure; + } + } + + if (dns_rdatatype_isknown(type)) { + result = rdata_validate(buf, target, rdclass, type); + } else { + isc_region_t r; + isc_buffer_usedregion(buf, &r); + result = isc_buffer_copyregion(target, &r); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + isc_buffer_free(&buf); + return (ISC_R_SUCCESS); + +failure: + isc_buffer_free(&buf); + return (result); +} + +isc_result_t +dns_rdata_fromtext(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, isc_lex_t *lexer, + const dns_name_t *origin, unsigned int options, + isc_mem_t *mctx, isc_buffer_t *target, + dns_rdatacallbacks_t *callbacks) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + isc_region_t region; + isc_buffer_t st; + isc_token_t token; + unsigned int lexoptions = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | + ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE; + char *name; + unsigned long line; + void (*callback)(dns_rdatacallbacks_t *, const char *, ...); + isc_result_t tresult; + unsigned int length; + bool unknown; + + REQUIRE(origin == NULL || dns_name_isabsolute(origin)); + if (rdata != NULL) { + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + } + if (callbacks != NULL) { + REQUIRE(callbacks->warn != NULL); + REQUIRE(callbacks->error != NULL); + } + + st = *target; + + if (callbacks != NULL) { + callback = callbacks->error; + } else { + callback = default_fromtext_callback; + } + + result = isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + true); + if (result != ISC_R_SUCCESS) { + name = isc_lex_getsourcename(lexer); + line = isc_lex_getsourceline(lexer); + fromtext_error(callback, callbacks, name, line, NULL, result); + return (result); + } + + unknown = false; + if (token.type == isc_tokentype_string && + strcmp(DNS_AS_STR(token), "\\#") == 0) + { + /* + * If this is a TXT record '\#' could be a escaped '#'. + * Look to see if the next token is a number and if so + * treat it as a unknown record format. + */ + if (type == dns_rdatatype_txt) { + result = isc_lex_getmastertoken( + lexer, &token, isc_tokentype_number, false); + if (result == ISC_R_SUCCESS) { + isc_lex_ungettoken(lexer, &token); + } + } + + if (result == ISC_R_SUCCESS) { + unknown = true; + result = unknown_fromtext(rdclass, type, lexer, mctx, + target); + } else { + options |= DNS_RDATA_UNKNOWNESCAPE; + } + } else { + isc_lex_ungettoken(lexer, &token); + } + + if (!unknown) { + FROMTEXTSWITCH + + /* + * Consume to end of line / file. + * If not at end of line initially set error code. + * Call callback via fromtext_error once if there was an error. + */ + } + do { + name = isc_lex_getsourcename(lexer); + line = isc_lex_getsourceline(lexer); + tresult = isc_lex_gettoken(lexer, lexoptions, &token); + if (tresult != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + result = tresult; + } + if (callback != NULL) { + fromtext_error(callback, callbacks, name, line, + NULL, result); + } + break; + } else if (token.type != isc_tokentype_eol && + token.type != isc_tokentype_eof) + { + if (result == ISC_R_SUCCESS) { + result = DNS_R_EXTRATOKEN; + } + if (callback != NULL) { + fromtext_error(callback, callbacks, name, line, + &token, result); + callback = NULL; + } + } else if (result != ISC_R_SUCCESS && callback != NULL) { + fromtext_error(callback, callbacks, name, line, &token, + result); + break; + } else { + if (token.type == isc_tokentype_eof) { + fromtext_warneof(lexer, callbacks); + } + break; + } + } while (1); + + length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st); + if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) { + result = ISC_R_NOSPACE; + } + + if (rdata != NULL && result == ISC_R_SUCCESS) { + region.base = isc_buffer_used(&st); + region.length = length; + dns_rdata_fromregion(rdata, rdclass, type, ®ion); + } + if (result != ISC_R_SUCCESS) { + *target = st; + } + return (result); +} + +static isc_result_t +unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, + isc_buffer_t *target) { + isc_result_t result; + char buf[sizeof("65535")]; + isc_region_t sr; + + strlcpy(buf, "\\# ", sizeof(buf)); + result = str_totext(buf, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_toregion(rdata, &sr); + INSIST(sr.length < 65536); + snprintf(buf, sizeof(buf), "%u", sr.length); + result = str_totext(buf, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (sr.length != 0U) { + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + result = str_totext(" ( ", target); + } else { + result = str_totext(" ", target); + } + + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (tctx->width == 0) { /* No splitting */ + result = isc_hex_totext(&sr, 0, "", target); + } else { + result = isc_hex_totext(&sr, tctx->width - 2, + tctx->linebreak, target); + } + if (result == ISC_R_SUCCESS && + (tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) + { + result = str_totext(" )", target); + } + } + return (result); +} + +static isc_result_t +rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, + isc_buffer_t *target) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + bool use_default = false; + unsigned int cur; + + REQUIRE(rdata != NULL); + REQUIRE(tctx->origin == NULL || dns_name_isabsolute(tctx->origin)); + + /* + * Some DynDNS meta-RRs have empty rdata. + */ + if ((rdata->flags & DNS_RDATA_UPDATE) != 0) { + INSIST(rdata->length == 0); + return (ISC_R_SUCCESS); + } + + if ((tctx->flags & DNS_STYLEFLAG_UNKNOWNFORMAT) != 0) { + return (unknown_totext(rdata, tctx, target)); + } + + cur = isc_buffer_usedlength(target); + + TOTEXTSWITCH + + if (use_default || (result == ISC_R_NOTIMPLEMENTED)) { + unsigned int u = isc_buffer_usedlength(target); + + INSIST(u >= cur); + isc_buffer_subtract(target, u - cur); + result = unknown_totext(rdata, tctx, target); + } + + return (result); +} + +isc_result_t +dns_rdata_totext(dns_rdata_t *rdata, const dns_name_t *origin, + isc_buffer_t *target) { + dns_rdata_textctx_t tctx; + + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + /* + * Set up formatting options for single-line output. + */ + tctx.origin = origin; + tctx.flags = 0; + tctx.width = 60; + tctx.linebreak = " "; + return (rdata_totext(rdata, &tctx, target)); +} + +isc_result_t +dns_rdata_tofmttext(dns_rdata_t *rdata, const dns_name_t *origin, + dns_masterstyle_flags_t flags, unsigned int width, + unsigned int split_width, const char *linebreak, + isc_buffer_t *target) { + dns_rdata_textctx_t tctx; + + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + /* + * Set up formatting options for formatted output. + */ + tctx.origin = origin; + tctx.flags = flags; + if (split_width == 0xffffffff) { + tctx.width = width; + } else { + tctx.width = split_width; + } + + if ((flags & DNS_STYLEFLAG_MULTILINE) != 0) { + tctx.linebreak = linebreak; + } else { + if (split_width == 0xffffffff) { + tctx.width = 60; /* Used for hex word length only. */ + } + tctx.linebreak = " "; + } + return (rdata_totext(rdata, &tctx, target)); +} + +isc_result_t +dns_rdata_fromstruct(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t type, void *source, isc_buffer_t *target) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + isc_buffer_t st; + isc_region_t region; + bool use_default = false; + unsigned int length; + + REQUIRE(source != NULL); + if (rdata != NULL) { + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + } + + st = *target; + + FROMSTRUCTSWITCH + + if (use_default) { + (void)NULL; + } + + length = isc_buffer_usedlength(target) - isc_buffer_usedlength(&st); + if (result == ISC_R_SUCCESS && length > DNS_RDATA_MAXLENGTH) { + result = ISC_R_NOSPACE; + } + + if (rdata != NULL && result == ISC_R_SUCCESS) { + region.base = isc_buffer_used(&st); + region.length = length; + dns_rdata_fromregion(rdata, rdclass, type, ®ion); + } + if (result != ISC_R_SUCCESS) { + *target = st; + } + return (result); +} + +isc_result_t +dns_rdata_tostruct(const dns_rdata_t *rdata, void *target, isc_mem_t *mctx) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + bool use_default = false; + + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + REQUIRE((rdata->flags & DNS_RDATA_UPDATE) == 0); + + TOSTRUCTSWITCH + + if (use_default) { + (void)NULL; + } + + return (result); +} + +void +dns_rdata_freestruct(void *source) { + dns_rdatacommon_t *common = source; + REQUIRE(common != NULL); + + FREESTRUCTSWITCH +} + +isc_result_t +dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner, + dns_additionaldatafunc_t add, void *arg) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + bool use_default = false; + + /* + * Call 'add' for each name and type from 'rdata' which is subject to + * additional section processing. + */ + + REQUIRE(rdata != NULL); + REQUIRE(add != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + ADDITIONALDATASWITCH + + /* No additional processing for unknown types */ + if (use_default) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +isc_result_t +dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg) { + isc_result_t result = ISC_R_NOTIMPLEMENTED; + bool use_default = false; + isc_region_t r; + + /* + * Send 'rdata' in DNSSEC canonical form to 'digest'. + */ + + REQUIRE(rdata != NULL); + REQUIRE(digest != NULL); + REQUIRE(DNS_RDATA_VALIDFLAGS(rdata)); + + DIGESTSWITCH + + if (use_default) { + dns_rdata_toregion(rdata, &r); + result = (digest)(arg, &r); + } + + return (result); +} + +bool +dns_rdata_checkowner(const dns_name_t *name, dns_rdataclass_t rdclass, + dns_rdatatype_t type, bool wildcard) { + bool result; + + CHECKOWNERSWITCH + return (result); +} + +bool +dns_rdata_checknames(dns_rdata_t *rdata, const dns_name_t *owner, + dns_name_t *bad) { + bool result; + + CHECKNAMESSWITCH + return (result); +} + +unsigned int +dns_rdatatype_attributes(dns_rdatatype_t type) { + RDATATYPE_ATTRIBUTE_SW + if (type >= (dns_rdatatype_t)128 && type <= (dns_rdatatype_t)255) { + return (DNS_RDATATYPEATTR_UNKNOWN | DNS_RDATATYPEATTR_META); + } + return (DNS_RDATATYPEATTR_UNKNOWN); +} + +isc_result_t +dns_rdatatype_fromtext(dns_rdatatype_t *typep, isc_textregion_t *source) { + unsigned int hash; + unsigned int n; + unsigned char a, b; + + n = source->length; + + if (n == 0) { + return (DNS_R_UNKNOWN); + } + + a = tolower((unsigned char)source->base[0]); + b = tolower((unsigned char)source->base[n - 1]); + + hash = ((a + n) * b) % 256; + + /* + * This switch block is inlined via \#define, and will use "return" + * to return a result to the caller if it is a valid (known) + * rdatatype name. + */ + RDATATYPE_FROMTEXT_SW(hash, source->base, n, typep); + + if (source->length > 4 && source->length < (4 + sizeof("65000")) && + strncasecmp("type", source->base, 4) == 0) + { + char buf[sizeof("65000")]; + char *endp; + unsigned int val; + + /* + * source->base is not required to be NUL terminated. + * Copy up to remaining bytes and NUL terminate. + */ + snprintf(buf, sizeof(buf), "%.*s", (int)(source->length - 4), + source->base + 4); + val = strtoul(buf, &endp, 10); + if (*endp == '\0' && val <= 0xffff) { + *typep = (dns_rdatatype_t)val; + return (ISC_R_SUCCESS); + } + } + + return (DNS_R_UNKNOWN); +} + +isc_result_t +dns_rdatatype_totext(dns_rdatatype_t type, isc_buffer_t *target) { + RDATATYPE_TOTEXT_SW + + return (dns_rdatatype_tounknowntext(type, target)); +} + +isc_result_t +dns_rdatatype_tounknowntext(dns_rdatatype_t type, isc_buffer_t *target) { + char buf[sizeof("TYPE65535")]; + + snprintf(buf, sizeof(buf), "TYPE%u", type); + return (str_totext(buf, target)); +} + +void +dns_rdatatype_format(dns_rdatatype_t rdtype, char *array, unsigned int size) { + isc_result_t result; + isc_buffer_t buf; + + if (size == 0U) { + return; + } + + isc_buffer_init(&buf, array, size); + result = dns_rdatatype_totext(rdtype, &buf); + /* + * Null terminate. + */ + if (result == ISC_R_SUCCESS) { + if (isc_buffer_availablelength(&buf) >= 1) { + isc_buffer_putuint8(&buf, 0); + } else { + result = ISC_R_NOSPACE; + } + } + if (result != ISC_R_SUCCESS) { + strlcpy(array, "", size); + } +} + +/* + * Private function. + */ + +static unsigned int +name_length(const dns_name_t *name) { + return (name->length); +} + +static isc_result_t +commatxt_totext(isc_region_t *source, bool quote, bool comma, + isc_buffer_t *target) { + unsigned int tl; + unsigned int n; + unsigned char *sp; + char *tp; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + sp = source->base; + tp = (char *)region.base; + tl = region.length; + + n = *sp++; + + REQUIRE(n + 1 <= source->length); + if (n == 0U) { + REQUIRE(quote); + } + + if (quote) { + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = '"'; + tl--; + } + while (n--) { + /* + * \DDD space (0x20) if not quoting. + */ + if (*sp < (quote ? ' ' : '!') || *sp >= 0x7f) { + if (tl < 4) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + *tp++ = '0' + ((*sp / 100) % 10); + *tp++ = '0' + ((*sp / 10) % 10); + *tp++ = '0' + (*sp % 10); + sp++; + tl -= 4; + continue; + } + /* + * Escape double quote and backslash. If we are not + * enclosing the string in double quotes, also escape + * at sign (@) and semicolon (;) unless comma is set. + * If comma is set, then only escape commas (,). + */ + if (*sp == '"' || *sp == '\\' || (comma && *sp == ',') || + (!comma && !quote && (*sp == '@' || *sp == ';'))) + { + if (tl < 2) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + tl--; + /* + * Perform comma escape processing. + * ',' => '\\,' + * '\' => '\\\\' + */ + if (comma && (*sp == ',' || *sp == '\\')) { + if (tl < ((*sp == '\\') ? 3 : 2)) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + tl--; + if (*sp == '\\') { + *tp++ = '\\'; + tl--; + } + } + } + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = *sp++; + tl--; + } + if (quote) { + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = '"'; + tl--; + POST(tl); + } + isc_buffer_add(target, (unsigned int)(tp - (char *)region.base)); + isc_region_consume(source, *source->base + 1); + return (ISC_R_SUCCESS); +} + +static isc_result_t +txt_totext(isc_region_t *source, bool quote, isc_buffer_t *target) { + return (commatxt_totext(source, quote, false, target)); +} + +static isc_result_t +commatxt_fromtext(isc_textregion_t *source, bool comma, isc_buffer_t *target) { + isc_region_t tregion; + bool escape = false, comma_escape = false, seen_comma = false; + unsigned int n, nrem; + char *s; + unsigned char *t; + int d; + int c; + + isc_buffer_availableregion(target, &tregion); + s = source->base; + n = source->length; + t = tregion.base; + nrem = tregion.length; + if (nrem < 1) { + return (ISC_R_NOSPACE); + } + /* + * Length byte. + */ + nrem--; + t++; + /* + * Maximum text string length. + */ + if (nrem > 255) { + nrem = 255; + } + while (n-- != 0) { + c = (*s++) & 0xff; + if (escape && (d = decvalue((char)c)) != -1) { + c = d; + if (n == 0) { + return (DNS_R_SYNTAX); + } + n--; + if ((d = decvalue(*s++)) != -1) { + c = c * 10 + d; + } else { + return (DNS_R_SYNTAX); + } + if (n == 0) { + return (DNS_R_SYNTAX); + } + n--; + if ((d = decvalue(*s++)) != -1) { + c = c * 10 + d; + } else { + return (DNS_R_SYNTAX); + } + if (c > 255) { + return (DNS_R_SYNTAX); + } + } else if (!escape && c == '\\') { + escape = true; + continue; + } + escape = false; + /* + * Level 1 escape processing complete. + * If comma is set perform comma escape processing. + * + * Level 1 Level 2 ALPN's + * h1\,h2 => h1,h2 => h1 and h2 + * h1\\,h2 => h1\,h2 => h1,h2 + * h1\\h2 => h1\h2 => h1h2 + * h1\\\\h2 => h1\\h2 => h1\h2 + */ + if (comma && !comma_escape && c == ',') { + seen_comma = true; + break; + } + if (comma && !comma_escape && c == '\\') { + comma_escape = true; + continue; + } + comma_escape = false; + if (nrem == 0) { + return ((tregion.length <= 256U) ? ISC_R_NOSPACE + : DNS_R_SYNTAX); + } + *t++ = c; + nrem--; + } + + /* + * Incomplete escape processing? + */ + if (escape || (comma && comma_escape)) { + return (DNS_R_SYNTAX); + } + + if (comma) { + /* + * Disallow empty ALPN at start (",h1") or in the + * middle ("h1,,h2"). + */ + if (s == source->base || (seen_comma && s == source->base + 1)) + { + return (DNS_R_SYNTAX); + } + isc_textregion_consume(source, s - source->base); + /* + * Disallow empty ALPN at end ("h1,"). + */ + if (seen_comma && source->length == 0) { + return (DNS_R_SYNTAX); + } + } + *tregion.base = (unsigned char)(t - tregion.base - 1); + isc_buffer_add(target, *tregion.base + 1); + return (ISC_R_SUCCESS); +} + +static isc_result_t +txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { + return (commatxt_fromtext(source, false, target)); +} + +static isc_result_t +txt_fromwire(isc_buffer_t *source, isc_buffer_t *target) { + unsigned int n; + isc_region_t sregion; + isc_region_t tregion; + + isc_buffer_activeregion(source, &sregion); + if (sregion.length == 0) { + return (ISC_R_UNEXPECTEDEND); + } + n = *sregion.base + 1; + if (n > sregion.length) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_availableregion(target, &tregion); + if (n > tregion.length) { + return (ISC_R_NOSPACE); + } + + if (tregion.base != sregion.base) { + memmove(tregion.base, sregion.base, n); + } + isc_buffer_forward(source, n); + isc_buffer_add(target, n); + return (ISC_R_SUCCESS); +} + +/* + * Conversion of TXT-like rdata fields without length limits. + */ +static isc_result_t +multitxt_totext(isc_region_t *source, isc_buffer_t *target) { + unsigned int tl; + unsigned int n0, n; + unsigned char *sp; + char *tp; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + sp = source->base; + tp = (char *)region.base; + tl = region.length; + + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = '"'; + tl--; + do { + n = source->length; + n0 = source->length - 1; + + while (n--) { + if (*sp < ' ' || *sp >= 0x7f) { + if (tl < 4) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + *tp++ = '0' + ((*sp / 100) % 10); + *tp++ = '0' + ((*sp / 10) % 10); + *tp++ = '0' + (*sp % 10); + sp++; + tl -= 4; + continue; + } + /* double quote, backslash */ + if (*sp == '"' || *sp == '\\') { + if (tl < 2) { + return (ISC_R_NOSPACE); + } + *tp++ = '\\'; + tl--; + } + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = *sp++; + tl--; + } + isc_region_consume(source, n0 + 1); + } while (source->length != 0); + if (tl < 1) { + return (ISC_R_NOSPACE); + } + *tp++ = '"'; + tl--; + POST(tl); + isc_buffer_add(target, (unsigned int)(tp - (char *)region.base)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +multitxt_fromtext(isc_textregion_t *source, isc_buffer_t *target) { + isc_region_t tregion; + bool escape; + unsigned int n, nrem; + char *s; + unsigned char *t0, *t; + int d; + int c; + + s = source->base; + n = source->length; + escape = false; + + do { + isc_buffer_availableregion(target, &tregion); + t0 = t = tregion.base; + nrem = tregion.length; + if (nrem < 1) { + return (ISC_R_NOSPACE); + } + + while (n != 0) { + --n; + c = (*s++) & 0xff; + if (escape && (d = decvalue((char)c)) != -1) { + c = d; + if (n == 0) { + return (DNS_R_SYNTAX); + } + n--; + if ((d = decvalue(*s++)) != -1) { + c = c * 10 + d; + } else { + return (DNS_R_SYNTAX); + } + if (n == 0) { + return (DNS_R_SYNTAX); + } + n--; + if ((d = decvalue(*s++)) != -1) { + c = c * 10 + d; + } else { + return (DNS_R_SYNTAX); + } + if (c > 255) { + return (DNS_R_SYNTAX); + } + } else if (!escape && c == '\\') { + escape = true; + continue; + } + escape = false; + *t++ = c; + nrem--; + if (nrem == 0) { + break; + } + } + if (escape) { + return (DNS_R_SYNTAX); + } + + isc_buffer_add(target, (unsigned int)(t - t0)); + } while (n != 0); + return (ISC_R_SUCCESS); +} + +static bool +name_prefix(dns_name_t *name, const dns_name_t *origin, dns_name_t *target) { + int l1, l2; + + if (origin == NULL) { + goto return_false; + } + + if (dns_name_compare(origin, dns_rootname) == 0) { + goto return_false; + } + + if (!dns_name_issubdomain(name, origin)) { + goto return_false; + } + + l1 = dns_name_countlabels(name); + l2 = dns_name_countlabels(origin); + + if (l1 == l2) { + goto return_false; + } + + /* Master files should be case preserving. */ + dns_name_getlabelsequence(name, l1 - l2, l2, target); + if (!dns_name_caseequal(origin, target)) { + goto return_false; + } + + dns_name_getlabelsequence(name, 0, l1 - l2, target); + return (true); + +return_false: + *target = *name; + return (false); +} + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +inet_totext(int af, uint32_t flags, isc_region_t *src, isc_buffer_t *target) { + char tmpbuf[64]; + + /* Note - inet_ntop doesn't do size checking on its input. */ + if (inet_ntop(af, src->base, tmpbuf, sizeof(tmpbuf)) == NULL) { + return (ISC_R_NOSPACE); + } + if (strlen(tmpbuf) > isc_buffer_availablelength(target)) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(target, tmpbuf); + + /* + * An IPv6 address ending in "::" breaks YAML + * parsing, so append 0 in that case. + */ + if (af == AF_INET6 && (flags & DNS_STYLEFLAG_YAML) != 0) { + isc_region_t r; + isc_buffer_usedregion(target, &r); + if (r.length > 0 && r.base[r.length - 1] == ':') { + if (isc_buffer_availablelength(target) == 0) { + return (ISC_R_NOSPACE); + } + isc_buffer_putmem(target, (const unsigned char *)"0", + 1); + } + } + + return (ISC_R_SUCCESS); +} + +static bool +buffer_empty(isc_buffer_t *source) { + return ((source->current == source->active) ? true : false); +} + +static void +buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region) { + isc_buffer_init(buffer, region->base, region->length); + isc_buffer_add(buffer, region->length); + isc_buffer_setactive(buffer, region->length); +} + +static isc_result_t +uint32_tobuffer(uint32_t value, isc_buffer_t *target) { + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint32(target, value); + return (ISC_R_SUCCESS); +} + +static isc_result_t +uint16_tobuffer(uint32_t value, isc_buffer_t *target) { + isc_region_t region; + + if (value > 0xffff) { + return (ISC_R_RANGE); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 2) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint16(target, (uint16_t)value); + return (ISC_R_SUCCESS); +} + +static isc_result_t +uint8_tobuffer(uint32_t value, isc_buffer_t *target) { + isc_region_t region; + + if (value > 0xff) { + return (ISC_R_RANGE); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 1) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint8(target, (uint8_t)value); + return (ISC_R_SUCCESS); +} + +static isc_result_t +name_tobuffer(const dns_name_t *name, isc_buffer_t *target) { + isc_region_t r; + dns_name_toregion(name, &r); + return (isc_buffer_copyregion(target, &r)); +} + +static uint32_t +uint32_fromregion(isc_region_t *region) { + uint32_t value; + + REQUIRE(region->length >= 4); + value = (uint32_t)region->base[0] << 24; + value |= (uint32_t)region->base[1] << 16; + value |= (uint32_t)region->base[2] << 8; + value |= (uint32_t)region->base[3]; + return (value); +} + +static uint16_t +uint16_consume_fromregion(isc_region_t *region) { + uint16_t r = uint16_fromregion(region); + + isc_region_consume(region, 2); + return (r); +} + +static uint16_t +uint16_fromregion(isc_region_t *region) { + REQUIRE(region->length >= 2); + + return ((region->base[0] << 8) | region->base[1]); +} + +static uint8_t +uint8_fromregion(isc_region_t *region) { + REQUIRE(region->length >= 1); + + return (region->base[0]); +} + +static uint8_t +uint8_consume_fromregion(isc_region_t *region) { + uint8_t r = uint8_fromregion(region); + + isc_region_consume(region, 1); + return (r); +} + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) { + isc_region_t tr; + + if (length == 0U) { + return (ISC_R_SUCCESS); + } + + isc_buffer_availableregion(target, &tr); + if (length > tr.length) { + return (ISC_R_NOSPACE); + } + if (tr.base != base) { + memmove(tr.base, base, length); + } + isc_buffer_add(target, length); + return (ISC_R_SUCCESS); +} + +static int +hexvalue(char value) { + const char *s; + unsigned char c; + + c = (unsigned char)value; + + if (!isascii(c)) { + return (-1); + } + if (isupper(c)) { + c = tolower(c); + } + if ((s = strchr(hexdigits, c)) == NULL) { + return (-1); + } + return ((int)(s - hexdigits)); +} + +static int +decvalue(char value) { + const char *s; + + /* + * isascii() is valid for full range of int values, no need to + * mask or cast. + */ + if (!isascii(value)) { + return (-1); + } + if ((s = strchr(decdigits, value)) == NULL) { + return (-1); + } + return ((int)(s - decdigits)); +} + +static void +default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *fmt, + ...) { + va_list ap; + + UNUSED(callbacks); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void +fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks) { + if (isc_lex_isfile(lexer) && callbacks != NULL) { + const char *name = isc_lex_getsourcename(lexer); + if (name == NULL) { + name = "UNKNOWN"; + } + (*callbacks->warn)(callbacks, + "%s:%lu: file does not end with newline", + name, isc_lex_getsourceline(lexer)); + } +} + +static void +warn_badmx(isc_token_t *token, isc_lex_t *lexer, + dns_rdatacallbacks_t *callbacks) { + const char *file; + unsigned long line; + + if (lexer != NULL) { + file = isc_lex_getsourcename(lexer); + line = isc_lex_getsourceline(lexer); + (*callbacks->warn)(callbacks, "%s:%u: warning: '%s': %s", file, + line, DNS_AS_STR(*token), + isc_result_totext(DNS_R_MXISADDRESS)); + } +} + +static void +warn_badname(const dns_name_t *name, isc_lex_t *lexer, + dns_rdatacallbacks_t *callbacks) { + const char *file; + unsigned long line; + char namebuf[DNS_NAME_FORMATSIZE]; + + if (lexer != NULL) { + file = isc_lex_getsourcename(lexer); + line = isc_lex_getsourceline(lexer); + dns_name_format(name, namebuf, sizeof(namebuf)); + (*callbacks->warn)(callbacks, "%s:%u: warning: %s: %s", file, + line, namebuf, + isc_result_totext(DNS_R_BADNAME)); + } +} + +static void +fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...), + dns_rdatacallbacks_t *callbacks, const char *name, + unsigned long line, isc_token_t *token, isc_result_t result) { + if (name == NULL) { + name = "UNKNOWN"; + } + + if (token != NULL) { + switch (token->type) { + case isc_tokentype_eol: + (*callback)(callbacks, "%s: %s:%lu: near eol: %s", + "dns_rdata_fromtext", name, line, + isc_result_totext(result)); + break; + case isc_tokentype_eof: + (*callback)(callbacks, "%s: %s:%lu: near eof: %s", + "dns_rdata_fromtext", name, line, + isc_result_totext(result)); + break; + case isc_tokentype_number: + (*callback)(callbacks, "%s: %s:%lu: near %lu: %s", + "dns_rdata_fromtext", name, line, + token->value.as_ulong, + isc_result_totext(result)); + break; + case isc_tokentype_string: + case isc_tokentype_qstring: + (*callback)(callbacks, "%s: %s:%lu: near '%s': %s", + "dns_rdata_fromtext", name, line, + DNS_AS_STR(*token), + isc_result_totext(result)); + break; + default: + (*callback)(callbacks, "%s: %s:%lu: %s", + "dns_rdata_fromtext", name, line, + isc_result_totext(result)); + break; + } + } else { + (*callback)(callbacks, "dns_rdata_fromtext: %s:%lu: %s", name, + line, isc_result_totext(result)); + } +} + +dns_rdatatype_t +dns_rdata_covers(dns_rdata_t *rdata) { + if (rdata->type == dns_rdatatype_rrsig) { + return (covers_rrsig(rdata)); + } + return (covers_sig(rdata)); +} + +bool +dns_rdatatype_ismeta(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0) { + return (true); + } + return (false); +} + +bool +dns_rdatatype_issingleton(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON) != 0) + { + return (true); + } + return (false); +} + +bool +dns_rdatatype_notquestion(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_NOTQUESTION) != + 0) + { + return (true); + } + return (false); +} + +bool +dns_rdatatype_questiononly(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_QUESTIONONLY) != + 0) + { + return (true); + } + return (false); +} + +bool +dns_rdatatype_atcname(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATCNAME) != 0) { + return (true); + } + return (false); +} + +bool +dns_rdatatype_atparent(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) != 0) + { + return (true); + } + return (false); +} + +bool +dns_rdatatype_followadditional(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & + DNS_RDATATYPEATTR_FOLLOWADDITIONAL) != 0) + { + return (true); + } + return (false); +} + +bool +dns_rdataclass_ismeta(dns_rdataclass_t rdclass) { + if (rdclass == dns_rdataclass_reserved0 || + rdclass == dns_rdataclass_none || rdclass == dns_rdataclass_any) + { + return (true); + } + + return (false); /* Assume it is not a meta class. */ +} + +bool +dns_rdatatype_isdnssec(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0) { + return (true); + } + return (false); +} + +bool +dns_rdatatype_iszonecutauth(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ZONECUTAUTH) != + 0) + { + return (true); + } + return (false); +} + +bool +dns_rdatatype_isknown(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN) == 0) { + return (true); + } + return (false); +} + +void +dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type) { + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + + rdata->data = NULL; + rdata->length = 0; + rdata->flags = DNS_RDATA_UPDATE; + rdata->type = type; + rdata->rdclass = dns_rdataclass_any; +} + +void +dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type) { + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + + rdata->data = NULL; + rdata->length = 0; + rdata->flags = DNS_RDATA_UPDATE; + rdata->type = type; + rdata->rdclass = dns_rdataclass_none; +} + +void +dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type) { + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + + rdata->data = NULL; + rdata->length = 0; + rdata->flags = DNS_RDATA_UPDATE; + rdata->type = type; + rdata->rdclass = dns_rdataclass_any; +} + +void +dns_rdata_makedelete(dns_rdata_t *rdata) { + REQUIRE(rdata != NULL); + + rdata->rdclass = dns_rdataclass_none; +} + +const char * +dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section) { + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + + switch (section) { + case DNS_SECTION_PREREQUISITE: + switch (rdata->rdclass) { + case dns_rdataclass_none: + switch (rdata->type) { + case dns_rdatatype_any: + return ("domain doesn't exist"); + default: + return ("rrset doesn't exist"); + } + case dns_rdataclass_any: + switch (rdata->type) { + case dns_rdatatype_any: + return ("domain exists"); + default: + return ("rrset exists (value independent)"); + } + default: + return ("rrset exists (value dependent)"); + } + case DNS_SECTION_UPDATE: + switch (rdata->rdclass) { + case dns_rdataclass_none: + return ("delete"); + case dns_rdataclass_any: + switch (rdata->type) { + case dns_rdatatype_any: + return ("delete all rrsets"); + default: + return ("delete rrset"); + } + default: + return ("add"); + } + } + return ("invalid"); +} diff --git a/lib/dns/rdata/any_255/tsig_250.c b/lib/dns/rdata/any_255/tsig_250.c new file mode 100644 index 0000000..5fe9254 --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.c @@ -0,0 +1,622 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_ANY_255_TSIG_250_C +#define RDATA_ANY_255_TSIG_250_C + +#define RRTYPE_TSIG_ATTRIBUTES \ + (DNS_RDATATYPEATTR_META | DNS_RDATATYPEATTR_NOTQUESTION) + +static isc_result_t +fromtext_any_tsig(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + uint64_t sigtime; + isc_buffer_t buffer; + dns_rcode_t rcode; + long i; + char *e; + + REQUIRE(type == dns_rdatatype_tsig); + REQUIRE(rdclass == dns_rdataclass_any); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Algorithm Name. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + /* + * Time Signed: 48 bits. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + sigtime = strtoull(DNS_AS_STR(token), &e, 10); + if (*e != 0) { + RETTOK(DNS_R_SYNTAX); + } + if ((sigtime >> 48) != 0) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer((uint16_t)(sigtime >> 32), target)); + RETERR(uint32_tobuffer((uint32_t)(sigtime & 0xffffffffU), target)); + + /* + * Fudge. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Signature Size. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Signature. + */ + RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong)); + + /* + * Original ID. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Error. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) != + ISC_R_SUCCESS) + { + i = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0) { + RETTOK(DNS_R_UNKNOWN); + } + if (i < 0 || i > 0xffff) { + RETTOK(ISC_R_RANGE); + } + rcode = (dns_rcode_t)i; + } + RETERR(uint16_tobuffer(rcode, target)); + + /* + * Other Len. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Other Data. + */ + return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong)); +} + +static isc_result_t +totext_any_tsig(ARGS_TOTEXT) { + isc_region_t sr; + isc_region_t sigr; + char buf[sizeof(" 281474976710655 ")]; + char *bufp; + dns_name_t name; + dns_name_t prefix; + bool sub; + uint64_t sigtime; + unsigned short n; + + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + /* + * Algorithm Name. + */ + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + dns_name_fromregion(&name, &sr); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + RETERR(str_totext(" ", target)); + isc_region_consume(&sr, name_length(&name)); + + /* + * Time Signed. + */ + sigtime = ((uint64_t)sr.base[0] << 40) | ((uint64_t)sr.base[1] << 32) | + ((uint64_t)sr.base[2] << 24) | ((uint64_t)sr.base[3] << 16) | + ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5]; + isc_region_consume(&sr, 6); + bufp = &buf[sizeof(buf) - 1]; + *bufp-- = 0; + *bufp-- = ' '; + do { + *bufp-- = decdigits[sigtime % 10]; + sigtime /= 10; + } while (sigtime != 0); + bufp++; + RETERR(str_totext(bufp, target)); + + /* + * Fudge. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Signature Size. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u", n); + RETERR(str_totext(buf, target)); + + /* + * Signature. + */ + if (n != 0U) { + REQUIRE(n <= sr.length); + sigr = sr; + sigr.length = n; + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sigr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sigr, tctx->width - 2, + tctx->linebreak, target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" ) ", target)); + } else { + RETERR(str_totext(" ", target)); + } + isc_region_consume(&sr, n); + } else { + RETERR(str_totext(" ", target)); + } + + /* + * Original ID. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Error. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + RETERR(dns_tsigrcode_totext((dns_rcode_t)n, target)); + + /* + * Other Size. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), " %u ", n); + RETERR(str_totext(buf, target)); + + /* + * Other. + */ + if (tctx->width == 0) { /* No splitting */ + return (isc_base64_totext(&sr, 60, "", target)); + } else { + return (isc_base64_totext(&sr, 60, " ", target)); + } +} + +static isc_result_t +fromwire_any_tsig(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + unsigned long n; + + REQUIRE(type == dns_rdatatype_tsig); + REQUIRE(rdclass == dns_rdataclass_any); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + /* + * Algorithm Name. + */ + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + isc_buffer_activeregion(source, &sr); + /* + * Time Signed + Fudge. + */ + if (sr.length < 8) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, 8)); + isc_region_consume(&sr, 8); + isc_buffer_forward(source, 8); + + /* + * Signature Length + Signature. + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + n = uint16_fromregion(&sr); + if (sr.length < n + 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, n + 2)); + isc_region_consume(&sr, n + 2); + isc_buffer_forward(source, n + 2); + + /* + * Original ID + Error. + */ + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, 4)); + isc_region_consume(&sr, 4); + isc_buffer_forward(source, 4); + + /* + * Other Length + Other. + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + n = uint16_fromregion(&sr); + if (sr.length < n + 2) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, n + 2); + return (mem_tobuffer(target, sr.base, n + 2)); +} + +static isc_result_t +towire_any_tsig(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_rdata_toregion(rdata, &sr); + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + RETERR(dns_name_towire(&name, cctx, target)); + isc_region_consume(&sr, name_length(&name)); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_any_tsig(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_tsig); + REQUIRE(rdata1->rdclass == dns_rdataclass_any); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_any_tsig(ARGS_FROMSTRUCT) { + dns_rdata_any_tsig_t *tsig = source; + isc_region_t tr; + + REQUIRE(type == dns_rdatatype_tsig); + REQUIRE(rdclass == dns_rdataclass_any); + REQUIRE(tsig != NULL); + REQUIRE(tsig->common.rdclass == rdclass); + REQUIRE(tsig->common.rdtype == type); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Algorithm Name. + */ + RETERR(name_tobuffer(&tsig->algorithm, target)); + + isc_buffer_availableregion(target, &tr); + if (tr.length < 6 + 2 + 2) { + return (ISC_R_NOSPACE); + } + + /* + * Time Signed: 48 bits. + */ + RETERR(uint16_tobuffer((uint16_t)(tsig->timesigned >> 32), target)); + RETERR(uint32_tobuffer((uint32_t)(tsig->timesigned & 0xffffffffU), + target)); + + /* + * Fudge. + */ + RETERR(uint16_tobuffer(tsig->fudge, target)); + + /* + * Signature Size. + */ + RETERR(uint16_tobuffer(tsig->siglen, target)); + + /* + * Signature. + */ + RETERR(mem_tobuffer(target, tsig->signature, tsig->siglen)); + + isc_buffer_availableregion(target, &tr); + if (tr.length < 2 + 2 + 2) { + return (ISC_R_NOSPACE); + } + + /* + * Original ID. + */ + RETERR(uint16_tobuffer(tsig->originalid, target)); + + /* + * Error. + */ + RETERR(uint16_tobuffer(tsig->error, target)); + + /* + * Other Len. + */ + RETERR(uint16_tobuffer(tsig->otherlen, target)); + + /* + * Other Data. + */ + return (mem_tobuffer(target, tsig->other, tsig->otherlen)); +} + +static isc_result_t +tostruct_any_tsig(ARGS_TOSTRUCT) { + dns_rdata_any_tsig_t *tsig; + dns_name_t alg; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + REQUIRE(rdata->length != 0); + + tsig = (dns_rdata_any_tsig_t *)target; + tsig->common.rdclass = rdata->rdclass; + tsig->common.rdtype = rdata->type; + ISC_LINK_INIT(&tsig->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Algorithm Name. + */ + dns_name_init(&alg, NULL); + dns_name_fromregion(&alg, &sr); + dns_name_init(&tsig->algorithm, NULL); + name_duporclone(&alg, mctx, &tsig->algorithm); + + isc_region_consume(&sr, name_length(&tsig->algorithm)); + + /* + * Time Signed. + */ + INSIST(sr.length >= 6); + tsig->timesigned = ((uint64_t)sr.base[0] << 40) | + ((uint64_t)sr.base[1] << 32) | + ((uint64_t)sr.base[2] << 24) | + ((uint64_t)sr.base[3] << 16) | + ((uint64_t)sr.base[4] << 8) | (uint64_t)sr.base[5]; + isc_region_consume(&sr, 6); + + /* + * Fudge. + */ + tsig->fudge = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Signature Size. + */ + tsig->siglen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Signature. + */ + INSIST(sr.length >= tsig->siglen); + tsig->signature = mem_maybedup(mctx, sr.base, tsig->siglen); + if (tsig->signature == NULL) { + goto cleanup; + } + isc_region_consume(&sr, tsig->siglen); + + /* + * Original ID. + */ + tsig->originalid = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Error. + */ + tsig->error = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Other Size. + */ + tsig->otherlen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Other. + */ + INSIST(sr.length == tsig->otherlen); + tsig->other = mem_maybedup(mctx, sr.base, tsig->otherlen); + if (tsig->other == NULL) { + goto cleanup; + } + + tsig->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&tsig->algorithm, tsig->mctx); + } + if (mctx != NULL && tsig->signature != NULL) { + isc_mem_free(mctx, tsig->signature); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_any_tsig(ARGS_FREESTRUCT) { + dns_rdata_any_tsig_t *tsig = (dns_rdata_any_tsig_t *)source; + + REQUIRE(tsig != NULL); + REQUIRE(tsig->common.rdtype == dns_rdatatype_tsig); + REQUIRE(tsig->common.rdclass == dns_rdataclass_any); + + if (tsig->mctx == NULL) { + return; + } + + dns_name_free(&tsig->algorithm, tsig->mctx); + if (tsig->signature != NULL) { + isc_mem_free(tsig->mctx, tsig->signature); + } + if (tsig->other != NULL) { + isc_mem_free(tsig->mctx, tsig->other); + } + tsig->mctx = NULL; +} + +static isc_result_t +additionaldata_any_tsig(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_any_tsig(ARGS_DIGEST) { + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static bool +checkowner_any_tsig(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_tsig); + REQUIRE(rdclass == dns_rdataclass_any); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_any_tsig(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_tsig); + REQUIRE(rdata->rdclass == dns_rdataclass_any); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_any_tsig(ARGS_COMPARE) { + return (compare_any_tsig(rdata1, rdata2)); +} + +#endif /* RDATA_ANY_255_TSIG_250_C */ diff --git a/lib/dns/rdata/any_255/tsig_250.h b/lib/dns/rdata/any_255/tsig_250.h new file mode 100644 index 0000000..ca7806e --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*% RFC2845 */ +typedef struct dns_rdata_any_tsig { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t algorithm; + uint64_t timesigned; + uint16_t fudge; + uint16_t siglen; + unsigned char *signature; + uint16_t originalid; + uint16_t error; + uint16_t otherlen; + unsigned char *other; +} dns_rdata_any_tsig_t; diff --git a/lib/dns/rdata/ch_3/a_1.c b/lib/dns/rdata/ch_3/a_1.c new file mode 100644 index 0000000..a3303a8 --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* by Bjorn.Victor@it.uu.se, 2005-05-07 */ +/* Based on generic/soa_6.c and generic/mx_15.c */ + +#ifndef RDATA_CH_3_A_1_C +#define RDATA_CH_3_A_1_C + +#include + +#define RRTYPE_A_ATTRIBUTES (0) + +static isc_result_t +fromtext_ch_a(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_ch); /* 3 */ + + UNUSED(type); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + /* get domain name */ + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + if ((options & DNS_RDATA_CHECKNAMES) != 0 && + (options & DNS_RDATA_CHECKREVERSE) != 0) + { + bool ok; + ok = dns_name_ishostname(&name, false); + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + } + + /* 16-bit octal address */ + RETERR(isc_lex_getoctaltoken(lexer, &token, false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + return (uint16_tobuffer(token.value.as_ulong, target)); +} + +static isc_result_t +totext_ch_a(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("0177777")]; + uint16_t addr; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); /* 3 */ + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + addr = uint16_fromregion(®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + snprintf(buf, sizeof(buf), "%o", addr); /* note octal */ + RETERR(str_totext(" ", target)); + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_ch_a(ARGS_FROMWIRE) { + isc_region_t sregion; + isc_region_t tregion; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_ch); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + if (tregion.length < 2) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 2); + isc_buffer_forward(source, 2); + isc_buffer_add(target, 2); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_ch_a(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + + dns_rdata_toregion(rdata, &sregion); + + dns_name_fromregion(&name, &sregion); + isc_region_consume(&sregion, name_length(&name)); + RETERR(dns_name_towire(&name, cctx, target)); + + isc_buffer_availableregion(target, &tregion); + if (tregion.length < 2) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 2); + isc_buffer_add(target, 2); + return (ISC_R_SUCCESS); +} + +static int +compare_ch_a(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_a); + REQUIRE(rdata1->rdclass == dns_rdataclass_ch); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + order = memcmp(region1.base, region2.base, 2); + if (order != 0) { + order = (order < 0) ? -1 : 1; + } + return (order); +} + +static isc_result_t +fromstruct_ch_a(ARGS_FROMSTRUCT) { + dns_rdata_ch_a_t *a = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(a != NULL); + REQUIRE(a->common.rdtype == type); + REQUIRE(a->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&a->ch_addr_dom, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + + return (uint16_tobuffer(ntohs(a->ch_addr), target)); +} + +static isc_result_t +tostruct_ch_a(ARGS_TOSTRUCT) { + dns_rdata_ch_a_t *a = target; + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); + REQUIRE(rdata->length != 0); + + a->common.rdclass = rdata->rdclass; + a->common.rdtype = rdata->type; + ISC_LINK_INIT(&a->common, link); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + + dns_name_init(&a->ch_addr_dom, NULL); + name_duporclone(&name, mctx, &a->ch_addr_dom); + a->ch_addr = htons(uint16_fromregion(®ion)); + a->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_ch_a(ARGS_FREESTRUCT) { + dns_rdata_ch_a_t *a = source; + + REQUIRE(a != NULL); + REQUIRE(a->common.rdtype == dns_rdatatype_a); + + if (a->mctx == NULL) { + return; + } + + dns_name_free(&a->ch_addr_dom, a->mctx); + a->mctx = NULL; +} + +static isc_result_t +additionaldata_ch_a(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ch_a(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + isc_region_consume(&r, name_length(&name)); + RETERR(dns_name_digest(&name, digest, arg)); + return ((digest)(arg, &r)); +} + +static bool +checkowner_ch_a(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_ch); + + UNUSED(type); + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_ch_a(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_ch); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + + return (true); +} + +static int +casecompare_ch_a(ARGS_COMPARE) { + return (compare_ch_a(rdata1, rdata2)); +} +#endif /* RDATA_CH_3_A_1_C */ diff --git a/lib/dns/rdata/ch_3/a_1.h b/lib/dns/rdata/ch_3/a_1.h new file mode 100644 index 0000000..4ac6169 --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* by Bjorn.Victor@it.uu.se, 2005-05-07 */ +/* Based on generic/mx_15.h */ + +#pragma once + +typedef uint16_t ch_addr_t; + +typedef struct dns_rdata_ch_a { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t ch_addr_dom; /* ch-addr domain for back mapping + * */ + ch_addr_t ch_addr; /* chaos address (16 bit) network + * order */ +} dns_rdata_ch_a_t; diff --git a/lib/dns/rdata/generic/afsdb_18.c b/lib/dns/rdata/generic/afsdb_18.c new file mode 100644 index 0000000..077e0ac --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1183 */ + +#ifndef RDATA_GENERIC_AFSDB_18_C +#define RDATA_GENERIC_AFSDB_18_C + +#define RRTYPE_AFSDB_ATTRIBUTES (0) + +static isc_result_t +fromtext_afsdb(ARGS_FROMTEXT) { + isc_token_t token; + isc_buffer_t buffer; + dns_name_t name; + bool ok; + + REQUIRE(type == dns_rdatatype_afsdb); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Subtype. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Hostname. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_afsdb(ARGS_TOTEXT) { + dns_name_t name; + dns_name_t prefix; + isc_region_t region; + char buf[sizeof("64000 ")]; + bool sub; + unsigned int num; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u ", num); + RETERR(str_totext(buf, target)); + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_afsdb(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sr; + isc_region_t tr; + + REQUIRE(type == dns_rdatatype_afsdb); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, &sr); + isc_buffer_availableregion(target, &tr); + if (tr.length < 2) { + return (ISC_R_NOSPACE); + } + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + memmove(tr.base, sr.base, 2); + isc_buffer_forward(source, 2); + isc_buffer_add(target, 2); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_afsdb(ARGS_TOWIRE) { + isc_region_t tr; + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + isc_buffer_availableregion(target, &tr); + dns_rdata_toregion(rdata, &sr); + if (tr.length < 2) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, sr.base, 2); + isc_region_consume(&sr, 2); + isc_buffer_add(target, 2); + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_afsdb(ARGS_COMPARE) { + int result; + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_afsdb); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + result = memcmp(rdata1->data, rdata2->data, 2); + if (result != 0) { + return (result < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_afsdb(ARGS_FROMSTRUCT) { + dns_rdata_afsdb_t *afsdb = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_afsdb); + REQUIRE(afsdb != NULL); + REQUIRE(afsdb->common.rdclass == rdclass); + REQUIRE(afsdb->common.rdtype == type); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(afsdb->subtype, target)); + dns_name_toregion(&afsdb->server, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_afsdb(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_afsdb_t *afsdb = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + REQUIRE(afsdb != NULL); + REQUIRE(rdata->length != 0); + + afsdb->common.rdclass = rdata->rdclass; + afsdb->common.rdtype = rdata->type; + ISC_LINK_INIT(&afsdb->common, link); + + dns_name_init(&afsdb->server, NULL); + + dns_rdata_toregion(rdata, ®ion); + + afsdb->subtype = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + + name_duporclone(&name, mctx, &afsdb->server); + afsdb->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_afsdb(ARGS_FREESTRUCT) { + dns_rdata_afsdb_t *afsdb = source; + + REQUIRE(afsdb != NULL); + REQUIRE(afsdb->common.rdtype == dns_rdatatype_afsdb); + + if (afsdb->mctx == NULL) { + return; + } + + dns_name_free(&afsdb->server, afsdb->mctx); + afsdb->mctx = NULL; +} + +static isc_result_t +additionaldata_afsdb(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_afsdb(ARGS_DIGEST) { + isc_region_t r1, r2; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 2); + r1.length = 2; + RETERR((digest)(arg, &r1)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_afsdb(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_afsdb); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_afsdb(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_afsdb); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_afsdb(ARGS_COMPARE) { + return (compare_afsdb(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_AFSDB_18_C */ diff --git a/lib/dns/rdata/generic/afsdb_18.h b/lib/dns/rdata/generic/afsdb_18.h new file mode 100644 index 0000000..16b390c --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1183 */ + +typedef struct dns_rdata_afsdb { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t subtype; + dns_name_t server; +} dns_rdata_afsdb_t; diff --git a/lib/dns/rdata/generic/amtrelay_260.c b/lib/dns/rdata/generic/amtrelay_260.c new file mode 100644 index 0000000..a6d30e1 --- /dev/null +++ b/lib/dns/rdata/generic/amtrelay_260.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_AMTRELAY_260_C +#define RDATA_GENERIC_AMTRELAY_260_C + +#include + +#include + +#define RRTYPE_AMTRELAY_ATTRIBUTES (0) + +static isc_result_t +fromtext_amtrelay(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + unsigned int discovery; + unsigned int gateway; + struct in_addr addr; + unsigned char addr6[16]; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_amtrelay); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Precedence. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Discovery. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 1U) { + RETTOK(ISC_R_RANGE); + } + discovery = token.value.as_ulong; + + /* + * Gateway type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0x7fU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong | (discovery << 7), target)); + gateway = token.value.as_ulong; + + if (gateway == 0) { + return (ISC_R_SUCCESS); + } + + if (gateway > 3) { + return (ISC_R_NOTIMPLEMENTED); + } + + /* + * Gateway. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + switch (gateway) { + case 1: + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); + + case 2: + if (inet_pton(AF_INET6, DNS_AS_STR(token), addr6) != 1) { + RETTOK(DNS_R_BADAAAA); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 16) { + return (ISC_R_NOSPACE); + } + memmove(region.base, addr6, 16); + isc_buffer_add(target, 16); + return (ISC_R_SUCCESS); + + case 3: + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + return (dns_name_fromtext(&name, &buffer, origin, options, + target)); + default: + UNREACHABLE(); + } +} + +static isc_result_t +totext_amtrelay(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + char buf[sizeof("0 255 ")]; + unsigned char precedence; + unsigned char discovery; + unsigned char gateway; + const char *space; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + REQUIRE(rdata->length >= 2); + + if ((rdata->data[1] & 0x7f) > 3U) { + return (ISC_R_NOTIMPLEMENTED); + } + + /* + * Precedence. + */ + dns_rdata_toregion(rdata, ®ion); + precedence = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u ", precedence); + RETERR(str_totext(buf, target)); + + /* + * Discovery and Gateway type. + */ + gateway = uint8_fromregion(®ion); + discovery = gateway >> 7; + gateway &= 0x7f; + space = (gateway != 0U) ? " " : ""; + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u %u%s", discovery, gateway, space); + RETERR(str_totext(buf, target)); + + /* + * Gateway. + */ + switch (gateway) { + case 0: + break; + case 1: + return (inet_totext(AF_INET, tctx->flags, ®ion, target)); + + case 2: + return (inet_totext(AF_INET6, tctx->flags, ®ion, target)); + + case 3: + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + return (dns_name_totext(&name, false, target)); + + default: + UNREACHABLE(); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_amtrelay(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_amtrelay); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + + switch (region.base[1] & 0x7f) { + case 0: + if (region.length != 2) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 1: + if (region.length != 6) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 2: + if (region.length != 18) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 3: + RETERR(mem_tobuffer(target, region.base, 2)); + isc_buffer_forward(source, 2); + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, + target)); + + default: + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + } +} + +static isc_result_t +towire_amtrelay(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_amtrelay(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_amtrelay); + REQUIRE(rdata1->length >= 2); + REQUIRE(rdata2->length >= 2); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_amtrelay(ARGS_FROMSTRUCT) { + dns_rdata_amtrelay_t *amtrelay = source; + isc_region_t region; + uint32_t n; + + REQUIRE(type == dns_rdatatype_amtrelay); + REQUIRE(amtrelay != NULL); + REQUIRE(amtrelay->common.rdtype == type); + REQUIRE(amtrelay->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(amtrelay->precedence, target)); + n = (amtrelay->discovery ? 0x80 : 0) | amtrelay->gateway_type; + RETERR(uint8_tobuffer(n, target)); + + switch (amtrelay->gateway_type) { + case 0: + return (ISC_R_SUCCESS); + + case 1: + n = ntohl(amtrelay->in_addr.s_addr); + return (uint32_tobuffer(n, target)); + + case 2: + return (mem_tobuffer(target, amtrelay->in6_addr.s6_addr, 16)); + break; + + case 3: + dns_name_toregion(&amtrelay->gateway, ®ion); + return (isc_buffer_copyregion(target, ®ion)); + break; + + default: + return (mem_tobuffer(target, amtrelay->data, amtrelay->length)); + } +} + +static isc_result_t +tostruct_amtrelay(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_amtrelay_t *amtrelay = target; + dns_name_t name; + uint32_t n; + + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + REQUIRE(amtrelay != NULL); + REQUIRE(rdata->length >= 2); + + amtrelay->common.rdclass = rdata->rdclass; + amtrelay->common.rdtype = rdata->type; + ISC_LINK_INIT(&amtrelay->common, link); + + dns_name_init(&amtrelay->gateway, NULL); + amtrelay->data = NULL; + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + + amtrelay->precedence = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + amtrelay->gateway_type = uint8_fromregion(®ion); + amtrelay->discovery = (amtrelay->gateway_type & 0x80) != 0; + amtrelay->gateway_type &= 0x7f; + isc_region_consume(®ion, 1); + + switch (amtrelay->gateway_type) { + case 0: + break; + + case 1: + n = uint32_fromregion(®ion); + amtrelay->in_addr.s_addr = htonl(n); + isc_region_consume(®ion, 4); + break; + + case 2: + memmove(amtrelay->in6_addr.s6_addr, region.base, 16); + isc_region_consume(®ion, 16); + break; + + case 3: + dns_name_fromregion(&name, ®ion); + name_duporclone(&name, mctx, &amtrelay->gateway); + isc_region_consume(®ion, name_length(&name)); + break; + + default: + if (region.length != 0) { + amtrelay->data = mem_maybedup(mctx, region.base, + region.length); + if (amtrelay->data == NULL) { + return (ISC_R_NOMEMORY); + } + } + amtrelay->length = region.length; + } + amtrelay->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_amtrelay(ARGS_FREESTRUCT) { + dns_rdata_amtrelay_t *amtrelay = source; + + REQUIRE(amtrelay != NULL); + REQUIRE(amtrelay->common.rdtype == dns_rdatatype_amtrelay); + + if (amtrelay->mctx == NULL) { + return; + } + + if (amtrelay->gateway_type == 3) { + dns_name_free(&amtrelay->gateway, amtrelay->mctx); + } + + if (amtrelay->data != NULL) { + isc_mem_free(amtrelay->mctx, amtrelay->data); + } + + amtrelay->mctx = NULL; +} + +static isc_result_t +additionaldata_amtrelay(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_amtrelay(ARGS_DIGEST) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + + dns_rdata_toregion(rdata, ®ion); + return ((digest)(arg, ®ion)); +} + +static bool +checkowner_amtrelay(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_amtrelay); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_amtrelay(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_amtrelay); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_amtrelay(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_amtrelay); + REQUIRE(rdata1->length >= 2); + REQUIRE(rdata2->length >= 2); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + if (memcmp(region1.base, region2.base, 2) != 0 || + (region1.base[1] & 0x7f) != 3) + { + return (isc_region_compare(®ion1, ®ion2)); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +#endif /* RDATA_GENERIC_AMTRELAY_260_C */ diff --git a/lib/dns/rdata/generic/amtrelay_260.h b/lib/dns/rdata/generic/amtrelay_260.h new file mode 100644 index 0000000..8680c35 --- /dev/null +++ b/lib/dns/rdata/generic/amtrelay_260.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_amtrelay { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t precedence; + bool discovery; + uint8_t gateway_type; + struct in_addr in_addr; /* gateway type 1 */ + struct in6_addr in6_addr; /* gateway type 2 */ + dns_name_t gateway; /* gateway type 3 */ + unsigned char *data; /* gateway type > 3 */ + uint16_t length; +} dns_rdata_amtrelay_t; diff --git a/lib/dns/rdata/generic/avc_258.c b/lib/dns/rdata/generic/avc_258.c new file mode 100644 index 0000000..8c7c8d0 --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_AVC_258_C +#define RDATA_GENERIC_AVC_258_C + +#define RRTYPE_AVC_ATTRIBUTES (0) + +static isc_result_t +fromtext_avc(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_avc); + + return (generic_fromtext_txt(CALL_FROMTEXT)); +} + +static isc_result_t +totext_avc(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_avc); + + return (generic_totext_txt(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_avc(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_avc); + + return (generic_fromwire_txt(CALL_FROMWIRE)); +} + +static isc_result_t +towire_avc(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_avc(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_avc); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_avc(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_avc); + + return (generic_fromstruct_txt(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_avc(ARGS_TOSTRUCT) { + dns_rdata_avc_t *avc = target; + + REQUIRE(rdata->type == dns_rdatatype_avc); + REQUIRE(avc != NULL); + + avc->common.rdclass = rdata->rdclass; + avc->common.rdtype = rdata->type; + ISC_LINK_INIT(&avc->common, link); + + return (generic_tostruct_txt(CALL_TOSTRUCT)); +} + +static void +freestruct_avc(ARGS_FREESTRUCT) { + dns_rdata_avc_t *avc = source; + + REQUIRE(avc != NULL); + REQUIRE(avc->common.rdtype == dns_rdatatype_avc); + + generic_freestruct_txt(source); +} + +static isc_result_t +additionaldata_avc(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_avc(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_avc); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_avc(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_avc); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_avc(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_avc(ARGS_COMPARE) { + return (compare_avc(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_AVC_258_C */ diff --git a/lib/dns/rdata/generic/avc_258.h b/lib/dns/rdata/generic/avc_258.h new file mode 100644 index 0000000..828d7f0 --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef dns_rdata_txt_string_t dns_rdata_avc_string_t; + +typedef struct dns_rdata_avc { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *data; + uint16_t length; + /* private */ + uint16_t offset; +} dns_rdata_avc_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ diff --git a/lib/dns/rdata/generic/caa_257.c b/lib/dns/rdata/generic/caa_257.c new file mode 100644 index 0000000..70bf9f2 --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.c @@ -0,0 +1,628 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef GENERIC_CAA_257_C +#define GENERIC_CAA_257_C 1 + +#define RRTYPE_CAA_ATTRIBUTES (0) + +static unsigned char const alphanumeric[256] = { + /* 0x00-0x0f */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x10-0x1f */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x20-0x2f */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x30-0x3f */ 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x40-0x4f */ 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + /* 0x50-0x5f */ 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + /* 0x60-0x6f */ 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + /* 0x70-0x7f */ 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + /* 0x80-0x8f */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x90-0x9f */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xa0-0xaf */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xb0-0xbf */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xc0-0xcf */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xd0-0xdf */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xe0-0xef */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0xf0-0xff */ 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; + +static isc_result_t +fromtext_caa(ARGS_FROMTEXT) { + isc_token_t token; + isc_textregion_t tr; + uint8_t flags; + unsigned int i; + + REQUIRE(type == dns_rdatatype_caa); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* Flags. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 255U) { + RETTOK(ISC_R_RANGE); + } + flags = (uint8_t)(token.value.as_ulong & 255U); + RETERR(uint8_tobuffer(flags, target)); + + /* + * Tag + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + tr = token.value.as_textregion; + for (i = 0; i < tr.length; i++) { + if (!alphanumeric[(unsigned char)tr.base[i]]) { + RETTOK(DNS_R_SYNTAX); + } + } + RETERR(uint8_tobuffer(tr.length, target)); + RETERR(mem_tobuffer(target, tr.base, tr.length)); + + /* + * Value + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + if (token.type != isc_tokentype_qstring && + token.type != isc_tokentype_string) + { + RETERR(DNS_R_SYNTAX); + } + RETERR(multitxt_fromtext(&token.value.as_textregion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_caa(ARGS_TOTEXT) { + isc_region_t region; + uint8_t flags; + char buf[256]; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(rdata->length >= 3U); + REQUIRE(rdata->data != NULL); + + dns_rdata_toregion(rdata, ®ion); + + /* + * Flags + */ + flags = uint8_consume_fromregion(®ion); + snprintf(buf, sizeof(buf), "%u ", flags); + RETERR(str_totext(buf, target)); + + /* + * Tag + */ + RETERR(txt_totext(®ion, false, target)); + RETERR(str_totext(" ", target)); + + /* + * Value + */ + RETERR(multitxt_totext(®ion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_caa(ARGS_FROMWIRE) { + isc_region_t sr; + unsigned int len, i; + + REQUIRE(type == dns_rdatatype_caa); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + /* + * Flags + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Flags, tag length + */ + RETERR(mem_tobuffer(target, sr.base, 2)); + len = sr.base[1]; + isc_region_consume(&sr, 2); + isc_buffer_forward(source, 2); + + /* + * Zero length tag fields are illegal. + */ + if (sr.length < len || len == 0) { + RETERR(DNS_R_FORMERR); + } + + /* Check the Tag's value */ + for (i = 0; i < len; i++) { + if (!alphanumeric[sr.base[i]]) { + RETERR(DNS_R_FORMERR); + /* + * Tag + Value + */ + } + } + /* + * Tag + Value + */ + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_caa(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(rdata->length >= 3U); + REQUIRE(rdata->data != NULL); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_caa(ARGS_COMPARE) { + isc_region_t r1, r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_caa); + REQUIRE(rdata1->length >= 3U); + REQUIRE(rdata2->length >= 3U); + REQUIRE(rdata1->data != NULL); + REQUIRE(rdata2->data != NULL); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_caa(ARGS_FROMSTRUCT) { + dns_rdata_caa_t *caa = source; + isc_region_t region; + unsigned int i; + + REQUIRE(type == dns_rdatatype_caa); + REQUIRE(caa != NULL); + REQUIRE(caa->common.rdtype == type); + REQUIRE(caa->common.rdclass == rdclass); + REQUIRE(caa->tag != NULL && caa->tag_len != 0); + REQUIRE(caa->value != NULL); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Flags + */ + RETERR(uint8_tobuffer(caa->flags, target)); + + /* + * Tag length + */ + RETERR(uint8_tobuffer(caa->tag_len, target)); + + /* + * Tag + */ + region.base = caa->tag; + region.length = caa->tag_len; + for (i = 0; i < region.length; i++) { + if (!alphanumeric[region.base[i]]) { + RETERR(DNS_R_SYNTAX); + } + } + RETERR(isc_buffer_copyregion(target, ®ion)); + + /* + * Value + */ + region.base = caa->value; + region.length = caa->value_len; + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_caa(ARGS_TOSTRUCT) { + dns_rdata_caa_t *caa = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(caa != NULL); + REQUIRE(rdata->length >= 3U); + REQUIRE(rdata->data != NULL); + + caa->common.rdclass = rdata->rdclass; + caa->common.rdtype = rdata->type; + ISC_LINK_INIT(&caa->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Flags + */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + caa->flags = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Tag length + */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + caa->tag_len = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Tag + */ + if (sr.length < caa->tag_len) { + return (ISC_R_UNEXPECTEDEND); + } + caa->tag = mem_maybedup(mctx, sr.base, caa->tag_len); + if (caa->tag == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(&sr, caa->tag_len); + + /* + * Value + */ + caa->value_len = sr.length; + caa->value = mem_maybedup(mctx, sr.base, sr.length); + if (caa->value == NULL) { + return (ISC_R_NOMEMORY); + } + + caa->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_caa(ARGS_FREESTRUCT) { + dns_rdata_caa_t *caa = (dns_rdata_caa_t *)source; + + REQUIRE(caa != NULL); + REQUIRE(caa->common.rdtype == dns_rdatatype_caa); + + if (caa->mctx == NULL) { + return; + } + + if (caa->tag != NULL) { + isc_mem_free(caa->mctx, caa->tag); + } + if (caa->value != NULL) { + isc_mem_free(caa->mctx, caa->value); + } + caa->mctx = NULL; +} + +static isc_result_t +additionaldata_caa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(rdata->data != NULL); + REQUIRE(rdata->length >= 3U); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_caa(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(rdata->data != NULL); + REQUIRE(rdata->length >= 3U); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_caa(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_caa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_caa(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(rdata->data != NULL); + REQUIRE(rdata->length >= 3U); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_caa(ARGS_COMPARE) { + return (compare_caa(rdata1, rdata2)); +} + +#endif /* GENERIC_CAA_257_C */ diff --git a/lib/dns/rdata/generic/caa_257.h b/lib/dns/rdata/generic/caa_257.h new file mode 100644 index 0000000..f5f348d --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_caa { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t flags; + unsigned char *tag; + uint8_t tag_len; + unsigned char *value; + uint16_t value_len; +} dns_rdata_caa_t; diff --git a/lib/dns/rdata/generic/cdnskey_60.c b/lib/dns/rdata/generic/cdnskey_60.c new file mode 100644 index 0000000..3da66d6 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-delegation-trust-maintainance-14 */ + +#ifndef RDATA_GENERIC_CDNSKEY_60_C +#define RDATA_GENERIC_CDNSKEY_60_C + +#include + +#define RRTYPE_CDNSKEY_ATTRIBUTES 0 + +static isc_result_t +fromtext_cdnskey(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromtext_key(CALL_FROMTEXT)); +} + +static isc_result_t +totext_cdnskey(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + return (generic_totext_key(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_cdnskey(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromwire_key(CALL_FROMWIRE)); +} + +static isc_result_t +towire_cdnskey(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_cdnskey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_cdnskey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_cdnskey(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromstruct_key(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_cdnskey(ARGS_TOSTRUCT) { + dns_rdata_cdnskey_t *dnskey = target; + + REQUIRE(dnskey != NULL); + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + dnskey->common.rdclass = rdata->rdclass; + dnskey->common.rdtype = rdata->type; + ISC_LINK_INIT(&dnskey->common, link); + + return (generic_tostruct_key(CALL_TOSTRUCT)); +} + +static void +freestruct_cdnskey(ARGS_FREESTRUCT) { + dns_rdata_cdnskey_t *dnskey = (dns_rdata_cdnskey_t *)source; + + REQUIRE(dnskey != NULL); + REQUIRE(dnskey->common.rdtype == dns_rdatatype_cdnskey); + + generic_freestruct_key(source); +} + +static isc_result_t +additionaldata_cdnskey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_cdnskey(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_cdnskey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_cdnskey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_cdnskey(ARGS_CHECKNAMES) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_cdnskey(ARGS_COMPARE) { + /* + * Treat ALG 253 (private DNS) subtype name case sensitively. + */ + return (compare_cdnskey(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_CDNSKEY_60_C */ diff --git a/lib/dns/rdata/generic/cdnskey_60.h b/lib/dns/rdata/generic/cdnskey_60.h new file mode 100644 index 0000000..b2c8771 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* CDNSKEY records have the same RDATA fields as DNSKEY records. */ +typedef struct dns_rdata_key dns_rdata_cdnskey_t; diff --git a/lib/dns/rdata/generic/cds_59.c b/lib/dns/rdata/generic/cds_59.c new file mode 100644 index 0000000..7c64158 --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-delegation-trust-maintainance-14 */ + +#ifndef RDATA_GENERIC_CDS_59_C +#define RDATA_GENERIC_CDS_59_C + +#define RRTYPE_CDS_ATTRIBUTES 0 + +#include + +static isc_result_t +fromtext_cds(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromtext_ds(CALL_FROMTEXT)); +} + +static isc_result_t +totext_cds(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cds); + + return (generic_totext_ds(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_cds(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromwire_ds(CALL_FROMWIRE)); +} + +static isc_result_t +towire_cds(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_cds); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_cds(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_cds); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_cds(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromstruct_ds(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_cds(ARGS_TOSTRUCT) { + dns_rdata_cds_t *cds = target; + + REQUIRE(rdata->type == dns_rdatatype_cds); + REQUIRE(cds != NULL); + REQUIRE(rdata->length != 0); + + /* + * Checked by generic_tostruct_ds(). + */ + cds->common.rdclass = rdata->rdclass; + cds->common.rdtype = rdata->type; + ISC_LINK_INIT(&cds->common, link); + + return (generic_tostruct_ds(CALL_TOSTRUCT)); +} + +static void +freestruct_cds(ARGS_FREESTRUCT) { + dns_rdata_cds_t *cds = source; + + REQUIRE(cds != NULL); + REQUIRE(cds->common.rdtype == dns_rdatatype_cds); + + if (cds->mctx == NULL) { + return; + } + + if (cds->digest != NULL) { + isc_mem_free(cds->mctx, cds->digest); + } + cds->mctx = NULL; +} + +static isc_result_t +additionaldata_cds(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_cds(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_cds); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_cds(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_cds); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_cds(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_cds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_cds(ARGS_COMPARE) { + return (compare_cds(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_CDS_59_C */ diff --git a/lib/dns/rdata/generic/cds_59.h b/lib/dns/rdata/generic/cds_59.h new file mode 100644 index 0000000..b5b409a --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* CDS records have the same RDATA fields as DS records. */ +typedef struct dns_rdata_ds dns_rdata_cds_t; diff --git a/lib/dns/rdata/generic/cert_37.c b/lib/dns/rdata/generic/cert_37.c new file mode 100644 index 0000000..eb307a7 --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.c @@ -0,0 +1,285 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2538 */ + +#ifndef RDATA_GENERIC_CERT_37_C +#define RDATA_GENERIC_CERT_37_C + +#define RRTYPE_CERT_ATTRIBUTES (0) + +static isc_result_t +fromtext_cert(ARGS_FROMTEXT) { + isc_token_t token; + dns_secalg_t secalg; + dns_cert_t cert; + + REQUIRE(type == dns_rdatatype_cert); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Cert type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_cert_fromtext(&cert, &token.value.as_textregion)); + RETERR(uint16_tobuffer(cert, target)); + + /* + * Key tag. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&secalg, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &secalg, 1)); + + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_cert(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000 ")]; + unsigned int n; + + REQUIRE(rdata->type == dns_rdatatype_cert); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + /* + * Type. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + RETERR(dns_cert_totext((dns_cert_t)n, target)); + RETERR(str_totext(" ", target)); + + /* + * Key tag. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Algorithm. + */ + RETERR(dns_secalg_totext(sr.base[0], target)); + isc_region_consume(&sr, 1); + + /* + * Cert. + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_cert(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_cert); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 6) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_cert(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_cert); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_cert(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_cert); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_cert(ARGS_FROMSTRUCT) { + dns_rdata_cert_t *cert = source; + + REQUIRE(type == dns_rdatatype_cert); + REQUIRE(cert != NULL); + REQUIRE(cert->common.rdtype == type); + REQUIRE(cert->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(cert->type, target)); + RETERR(uint16_tobuffer(cert->key_tag, target)); + RETERR(uint8_tobuffer(cert->algorithm, target)); + + return (mem_tobuffer(target, cert->certificate, cert->length)); +} + +static isc_result_t +tostruct_cert(ARGS_TOSTRUCT) { + dns_rdata_cert_t *cert = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_cert); + REQUIRE(cert != NULL); + REQUIRE(rdata->length != 0); + + cert->common.rdclass = rdata->rdclass; + cert->common.rdtype = rdata->type; + ISC_LINK_INIT(&cert->common, link); + + dns_rdata_toregion(rdata, ®ion); + + cert->type = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + cert->key_tag = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + cert->algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + cert->length = region.length; + + cert->certificate = mem_maybedup(mctx, region.base, region.length); + if (cert->certificate == NULL) { + return (ISC_R_NOMEMORY); + } + + cert->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_cert(ARGS_FREESTRUCT) { + dns_rdata_cert_t *cert = source; + + REQUIRE(cert != NULL); + REQUIRE(cert->common.rdtype == dns_rdatatype_cert); + + if (cert->mctx == NULL) { + return; + } + + if (cert->certificate != NULL) { + isc_mem_free(cert->mctx, cert->certificate); + } + cert->mctx = NULL; +} + +static isc_result_t +additionaldata_cert(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cert); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_cert(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_cert); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_cert(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_cert); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_cert(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_cert); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_cert(ARGS_COMPARE) { + return (compare_cert(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_CERT_37_C */ diff --git a/lib/dns/rdata/generic/cert_37.h b/lib/dns/rdata/generic/cert_37.h new file mode 100644 index 0000000..a905a2b --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*% RFC2538 */ +typedef struct dns_rdata_cert { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t type; + uint16_t key_tag; + uint8_t algorithm; + uint16_t length; + unsigned char *certificate; +} dns_rdata_cert_t; diff --git a/lib/dns/rdata/generic/cname_5.c b/lib/dns/rdata/generic/cname_5.c new file mode 100644 index 0000000..f3f9378 --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_CNAME_5_C +#define RDATA_GENERIC_CNAME_5_C + +#define RRTYPE_CNAME_ATTRIBUTES \ + (DNS_RDATATYPEATTR_EXCLUSIVE | DNS_RDATATYPEATTR_SINGLETON) + +static isc_result_t +fromtext_cname(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_cname); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_cname(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_cname); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_cname(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_cname); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_cname(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_cname); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_cname(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_cname); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_cname(ARGS_FROMSTRUCT) { + dns_rdata_cname_t *cname = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_cname); + REQUIRE(cname != NULL); + REQUIRE(cname->common.rdtype == type); + REQUIRE(cname->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&cname->cname, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_cname(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_cname_t *cname = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_cname); + REQUIRE(cname != NULL); + REQUIRE(rdata->length != 0); + + cname->common.rdclass = rdata->rdclass; + cname->common.rdtype = rdata->type; + ISC_LINK_INIT(&cname->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&cname->cname, NULL); + name_duporclone(&name, mctx, &cname->cname); + cname->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_cname(ARGS_FREESTRUCT) { + dns_rdata_cname_t *cname = source; + + REQUIRE(cname != NULL); + + if (cname->mctx == NULL) { + return; + } + + dns_name_free(&cname->cname, cname->mctx); + cname->mctx = NULL; +} + +static isc_result_t +additionaldata_cname(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_cname); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_cname(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_cname); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_cname(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_cname); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_cname(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_cname); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_cname(ARGS_COMPARE) { + return (compare_cname(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_CNAME_5_C */ diff --git a/lib/dns/rdata/generic/cname_5.h b/lib/dns/rdata/generic/cname_5.h new file mode 100644 index 0000000..1525fa0 --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_cname { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t cname; +} dns_rdata_cname_t; diff --git a/lib/dns/rdata/generic/csync_62.c b/lib/dns/rdata/generic/csync_62.c new file mode 100644 index 0000000..285f22b --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.c @@ -0,0 +1,274 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 7477 */ + +#ifndef RDATA_GENERIC_CSYNC_62_C +#define RDATA_GENERIC_CSYNC_62_C + +#define RRTYPE_CSYNC_ATTRIBUTES 0 + +static isc_result_t +fromtext_csync(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_csync); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* Serial. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* Flags. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* Type Map */ + return (typemap_fromtext(lexer, target, true)); +} + +static isc_result_t +totext_csync(ARGS_TOTEXT) { + unsigned long num; + char buf[sizeof("0123456789")]; /* Also TYPE65535 */ + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_csync); + REQUIRE(rdata->length >= 6); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + num = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + num = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu", num); + RETERR(str_totext(buf, target)); + + /* + * Don't leave a trailing space when there's no typemap present. + */ + if (sr.length > 0) { + RETERR(str_totext(" ", target)); + } + return (typemap_totext(&sr, NULL, target)); +} + +static isc_result_t +fromwire_csync(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_csync); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(options); + UNUSED(dctx); + + /* + * Serial + Flags + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 6) { + return (ISC_R_UNEXPECTEDEND); + } + + RETERR(mem_tobuffer(target, sr.base, 6)); + isc_buffer_forward(source, 6); + isc_region_consume(&sr, 6); + + RETERR(typemap_test(&sr, true)); + + RETERR(mem_tobuffer(target, sr.base, sr.length)); + isc_buffer_forward(source, sr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_csync(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_csync); + REQUIRE(rdata->length >= 6); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_csync(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_csync); + REQUIRE(rdata1->length >= 6); + REQUIRE(rdata2->length >= 6); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_csync(ARGS_FROMSTRUCT) { + dns_rdata_csync_t *csync = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_csync); + REQUIRE(csync != NULL); + REQUIRE(csync->common.rdtype == type); + REQUIRE(csync->common.rdclass == rdclass); + REQUIRE(csync->typebits != NULL || csync->len == 0); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint32_tobuffer(csync->serial, target)); + RETERR(uint16_tobuffer(csync->flags, target)); + + region.base = csync->typebits; + region.length = csync->len; + RETERR(typemap_test(®ion, true)); + return (mem_tobuffer(target, csync->typebits, csync->len)); +} + +static isc_result_t +tostruct_csync(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_csync_t *csync = target; + + REQUIRE(rdata->type == dns_rdatatype_csync); + REQUIRE(csync != NULL); + REQUIRE(rdata->length != 0); + + csync->common.rdclass = rdata->rdclass; + csync->common.rdtype = rdata->type; + ISC_LINK_INIT(&csync->common, link); + + dns_rdata_toregion(rdata, ®ion); + + csync->serial = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + csync->flags = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + csync->len = region.length; + csync->typebits = mem_maybedup(mctx, region.base, region.length); + if (csync->typebits == NULL) { + goto cleanup; + } + + csync->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + return (ISC_R_NOMEMORY); +} + +static void +freestruct_csync(ARGS_FREESTRUCT) { + dns_rdata_csync_t *csync = source; + + REQUIRE(csync != NULL); + REQUIRE(csync->common.rdtype == dns_rdatatype_csync); + + if (csync->mctx == NULL) { + return; + } + + if (csync->typebits != NULL) { + isc_mem_free(csync->mctx, csync->typebits); + } + csync->mctx = NULL; +} + +static isc_result_t +additionaldata_csync(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_csync); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_csync(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_csync); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_csync(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_csync); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_csync(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_csync); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_csync(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_csync); + REQUIRE(rdata1->length >= 6); + REQUIRE(rdata2->length >= 6); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} +#endif /* RDATA_GENERIC_CSYNC_62_C */ diff --git a/lib/dns/rdata/generic/csync_62.h b/lib/dns/rdata/generic/csync_62.h new file mode 100644 index 0000000..a7b3c1e --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC 7477 + */ + +typedef struct dns_rdata_csync { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint32_t serial; + uint16_t flags; + unsigned char *typebits; + uint16_t len; +} dns_rdata_csync_t; diff --git a/lib/dns/rdata/generic/dlv_32769.c b/lib/dns/rdata/generic/dlv_32769.c new file mode 100644 index 0000000..faf9500 --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC3658 */ + +#ifndef RDATA_GENERIC_DLV_32769_C +#define RDATA_GENERIC_DLV_32769_C + +#define RRTYPE_DLV_ATTRIBUTES 0 + +#include + +static isc_result_t +fromtext_dlv(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromtext_ds(CALL_FROMTEXT)); +} + +static isc_result_t +totext_dlv(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dlv); + + return (generic_totext_ds(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_dlv(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromwire_ds(CALL_FROMWIRE)); +} + +static isc_result_t +towire_dlv(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_dlv); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_dlv(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_dlv); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_dlv(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromstruct_ds(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_dlv(ARGS_TOSTRUCT) { + dns_rdata_dlv_t *dlv = target; + + REQUIRE(rdata->type == dns_rdatatype_dlv); + REQUIRE(dlv != NULL); + + dlv->common.rdclass = rdata->rdclass; + dlv->common.rdtype = rdata->type; + ISC_LINK_INIT(&dlv->common, link); + + return (generic_tostruct_ds(CALL_TOSTRUCT)); +} + +static void +freestruct_dlv(ARGS_FREESTRUCT) { + dns_rdata_dlv_t *dlv = source; + + REQUIRE(dlv != NULL); + REQUIRE(dlv->common.rdtype == dns_rdatatype_dlv); + + if (dlv->mctx == NULL) { + return; + } + + if (dlv->digest != NULL) { + isc_mem_free(dlv->mctx, dlv->digest); + } + dlv->mctx = NULL; +} + +static isc_result_t +additionaldata_dlv(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_dlv); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_dlv(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_dlv); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_dlv(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_dlv); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_dlv(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_dlv); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_dlv(ARGS_COMPARE) { + return (compare_dlv(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_DLV_32769_C */ diff --git a/lib/dns/rdata/generic/dlv_32769.h b/lib/dns/rdata/generic/dlv_32769.h new file mode 100644 index 0000000..ec3709f --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsext-delegation-signer-05.txt */ +#pragma once + +typedef struct dns_rdata_ds dns_rdata_dlv_t; diff --git a/lib/dns/rdata/generic/dname_39.c b/lib/dns/rdata/generic/dname_39.c new file mode 100644 index 0000000..2eb1dc8 --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2672 */ + +#ifndef RDATA_GENERIC_DNAME_39_C +#define RDATA_GENERIC_DNAME_39_C + +#define RRTYPE_DNAME_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON) + +static isc_result_t +fromtext_dname(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_dname); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_dname(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_dname); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_dname(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_dname); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_dname(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_dname); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_dname(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_dname); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_dname(ARGS_FROMSTRUCT) { + dns_rdata_dname_t *dname = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_dname); + REQUIRE(dname != NULL); + REQUIRE(dname->common.rdtype == type); + REQUIRE(dname->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&dname->dname, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_dname(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_dname_t *dname = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_dname); + REQUIRE(dname != NULL); + REQUIRE(rdata->length != 0); + + dname->common.rdclass = rdata->rdclass; + dname->common.rdtype = rdata->type; + ISC_LINK_INIT(&dname->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&dname->dname, NULL); + name_duporclone(&name, mctx, &dname->dname); + dname->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_dname(ARGS_FREESTRUCT) { + dns_rdata_dname_t *dname = source; + + REQUIRE(dname != NULL); + REQUIRE(dname->common.rdtype == dns_rdatatype_dname); + + if (dname->mctx == NULL) { + return; + } + + dns_name_free(&dname->dname, dname->mctx); + dname->mctx = NULL; +} + +static isc_result_t +additionaldata_dname(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_dname); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_dname(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_dname); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_dname(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_dname); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_dname(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_dname); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_dname(ARGS_COMPARE) { + return (compare_dname(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_DNAME_39_C */ diff --git a/lib/dns/rdata/generic/dname_39.h b/lib/dns/rdata/generic/dname_39.h new file mode 100644 index 0000000..14c71fb --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief per RFC2672 */ + +typedef struct dns_rdata_dname { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t dname; +} dns_rdata_dname_t; diff --git a/lib/dns/rdata/generic/dnskey_48.c b/lib/dns/rdata/generic/dnskey_48.c new file mode 100644 index 0000000..2df1e77 --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2535 */ + +#ifndef RDATA_GENERIC_DNSKEY_48_C +#define RDATA_GENERIC_DNSKEY_48_C + +#include + +#define RRTYPE_DNSKEY_ATTRIBUTES (DNS_RDATATYPEATTR_DNSSEC) + +static isc_result_t +fromtext_dnskey(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromtext_key(CALL_FROMTEXT)); +} + +static isc_result_t +totext_dnskey(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + return (generic_totext_key(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_dnskey(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromwire_key(CALL_FROMWIRE)); +} + +static isc_result_t +towire_dnskey(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_dnskey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_dnskey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_dnskey(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromstruct_key(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_dnskey(ARGS_TOSTRUCT) { + dns_rdata_dnskey_t *dnskey = target; + + REQUIRE(dnskey != NULL); + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + dnskey->common.rdclass = rdata->rdclass; + dnskey->common.rdtype = rdata->type; + ISC_LINK_INIT(&dnskey->common, link); + + return (generic_tostruct_key(CALL_TOSTRUCT)); +} + +static void +freestruct_dnskey(ARGS_FREESTRUCT) { + dns_rdata_dnskey_t *dnskey = (dns_rdata_dnskey_t *)source; + + REQUIRE(dnskey != NULL); + REQUIRE(dnskey->common.rdtype == dns_rdatatype_dnskey); + + generic_freestruct_key(source); +} + +static isc_result_t +additionaldata_dnskey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_dnskey(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_dnskey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_dnskey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_dnskey(ARGS_CHECKNAMES) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_dnskey(ARGS_COMPARE) { + /* + * Treat ALG 253 (private DNS) subtype name case sensitively. + */ + return (compare_dnskey(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_DNSKEY_48_C */ diff --git a/lib/dns/rdata/generic/dnskey_48.h b/lib/dns/rdata/generic/dnskey_48.h new file mode 100644 index 0000000..7a0bc6f --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief per RFC2535 + */ + +typedef struct dns_rdata_key dns_rdata_dnskey_t; diff --git a/lib/dns/rdata/generic/doa_259.c b/lib/dns/rdata/generic/doa_259.c new file mode 100644 index 0000000..45f2439 --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_DOA_259_C +#define RDATA_GENERIC_DOA_259_C + +#define RRTYPE_DOA_ATTRIBUTES (0) + +static isc_result_t +fromtext_doa(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_doa); + + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * DOA-ENTERPRISE + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * DOA-TYPE + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * DOA-LOCATION + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * DOA-MEDIA-TYPE + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + + /* + * DOA-DATA + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcmp(DNS_AS_STR(token), "-") == 0) { + return (ISC_R_SUCCESS); + } else { + isc_lex_ungettoken(lexer, &token); + return (isc_base64_tobuffer(lexer, target, -1)); + } +} + +static isc_result_t +totext_doa(ARGS_TOTEXT) { + char buf[sizeof("4294967295 ")]; + isc_region_t region; + uint32_t n; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_doa); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + + /* + * DOA-ENTERPRISE + */ + n = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * DOA-TYPE + */ + n = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * DOA-LOCATION + */ + n = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * DOA-MEDIA-TYPE + */ + RETERR(txt_totext(®ion, true, target)); + RETERR(str_totext(" ", target)); + + /* + * DOA-DATA + */ + if (region.length == 0) { + return (str_totext("-", target)); + } else { + return (isc_base64_totext(®ion, 60, "", target)); + } +} + +static isc_result_t +fromwire_doa(ARGS_FROMWIRE) { + isc_region_t region; + + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + REQUIRE(type == dns_rdatatype_doa); + + isc_buffer_activeregion(source, ®ion); + /* + * DOA-MEDIA-TYPE may be an empty (i.e., + * comprising of just the length octet) and DOA-DATA can have + * zero length. + */ + if (region.length < 4 + 4 + 1 + 1) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Check whether DOA-MEDIA-TYPE length is not malformed. + */ + if (region.base[9] > region.length - 10) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); +} + +static isc_result_t +towire_doa(ARGS_TOWIRE) { + isc_region_t region; + + UNUSED(cctx); + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_doa); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_doa(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->type == dns_rdatatype_doa); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_doa(ARGS_FROMSTRUCT) { + dns_rdata_doa_t *doa = source; + + REQUIRE(type == dns_rdatatype_doa); + REQUIRE(doa != NULL); + REQUIRE(doa->common.rdtype == dns_rdatatype_doa); + REQUIRE(doa->common.rdclass == rdclass); + + RETERR(uint32_tobuffer(doa->enterprise, target)); + RETERR(uint32_tobuffer(doa->type, target)); + RETERR(uint8_tobuffer(doa->location, target)); + RETERR(uint8_tobuffer(doa->mediatype_len, target)); + RETERR(mem_tobuffer(target, doa->mediatype, doa->mediatype_len)); + return (mem_tobuffer(target, doa->data, doa->data_len)); +} + +static isc_result_t +tostruct_doa(ARGS_TOSTRUCT) { + dns_rdata_doa_t *doa = target; + isc_region_t region; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_doa); + REQUIRE(doa != NULL); + REQUIRE(rdata->length != 0); + + doa->common.rdclass = rdata->rdclass; + doa->common.rdtype = rdata->type; + ISC_LINK_INIT(&doa->common, link); + + dns_rdata_toregion(rdata, ®ion); + + /* + * DOA-ENTERPRISE + */ + if (region.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + doa->enterprise = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + /* + * DOA-TYPE + */ + if (region.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + doa->type = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + /* + * DOA-LOCATION + */ + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + doa->location = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + /* + * DOA-MEDIA-TYPE + */ + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + doa->mediatype_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + INSIST(doa->mediatype_len <= region.length); + doa->mediatype = mem_maybedup(mctx, region.base, doa->mediatype_len); + if (doa->mediatype == NULL) { + goto cleanup; + } + isc_region_consume(®ion, doa->mediatype_len); + + /* + * DOA-DATA + */ + doa->data_len = region.length; + doa->data = NULL; + if (doa->data_len > 0) { + doa->data = mem_maybedup(mctx, region.base, doa->data_len); + if (doa->data == NULL) { + goto cleanup; + } + isc_region_consume(®ion, doa->data_len); + } + + doa->mctx = mctx; + + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL && doa->mediatype != NULL) { + isc_mem_free(mctx, doa->mediatype); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_doa(ARGS_FREESTRUCT) { + dns_rdata_doa_t *doa = source; + + REQUIRE(doa != NULL); + REQUIRE(doa->common.rdtype == dns_rdatatype_doa); + + if (doa->mctx == NULL) { + return; + } + + if (doa->mediatype != NULL) { + isc_mem_free(doa->mctx, doa->mediatype); + } + if (doa->data != NULL) { + isc_mem_free(doa->mctx, doa->data); + } + + doa->mctx = NULL; +} + +static isc_result_t +additionaldata_doa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_doa); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_doa(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_doa); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_doa(ARGS_CHECKOWNER) { + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + REQUIRE(type == dns_rdatatype_doa); + + return (true); +} + +static bool +checknames_doa(ARGS_CHECKNAMES) { + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + REQUIRE(rdata->type == dns_rdatatype_doa); + + return (true); +} + +static int +casecompare_doa(ARGS_COMPARE) { + return (compare_doa(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_DOA_259_C */ diff --git a/lib/dns/rdata/generic/doa_259.h b/lib/dns/rdata/generic/doa_259.h new file mode 100644 index 0000000..320bd7a --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_doa { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *mediatype; + unsigned char *data; + uint32_t enterprise; + uint32_t type; + uint16_t data_len; + uint8_t location; + uint8_t mediatype_len; +} dns_rdata_doa_t; diff --git a/lib/dns/rdata/generic/ds_43.c b/lib/dns/rdata/generic/ds_43.c new file mode 100644 index 0000000..482f32b --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.c @@ -0,0 +1,386 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC3658 */ + +#ifndef RDATA_GENERIC_DS_43_C +#define RDATA_GENERIC_DS_43_C + +#define RRTYPE_DS_ATTRIBUTES \ + (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \ + DNS_RDATATYPEATTR_ATPARENT) + +#include + +#include + +static isc_result_t +generic_fromtext_ds(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char c; + int length; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Key tag. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Digest type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_dsdigest_fromtext(&c, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Digest. + */ + switch (c) { + case DNS_DSDIGEST_SHA1: + length = ISC_SHA1_DIGESTLENGTH; + break; + case DNS_DSDIGEST_SHA256: + length = ISC_SHA256_DIGESTLENGTH; + break; + case DNS_DSDIGEST_SHA384: + length = ISC_SHA384_DIGESTLENGTH; + break; + default: + length = -2; + break; + } + return (isc_hex_tobuffer(lexer, target, length)); +} + +static isc_result_t +fromtext_ds(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromtext_ds(CALL_FROMTEXT)); +} + +static isc_result_t +generic_totext_ds(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000 ")]; + unsigned int n; + + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + /* + * Key tag. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Algorithm. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Digest type. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u", n); + RETERR(str_totext(buf, target)); + + /* + * Digest. + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) { + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_hex_totext(&sr, 0, "", target)); + } else { + RETERR(isc_hex_totext(&sr, tctx->width - 2, + tctx->linebreak, target)); + } + } else { + RETERR(str_totext("[omitted]", target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_ds(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_ds); + + return (generic_totext_ds(CALL_TOTEXT)); +} + +static isc_result_t +generic_fromwire_ds(ARGS_FROMWIRE) { + isc_region_t sr; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + + /* + * Check digest lengths if we know them. + */ + if (sr.length < 5 || + (sr.base[3] == DNS_DSDIGEST_SHA1 && + sr.length < 4 + ISC_SHA1_DIGESTLENGTH) || + (sr.base[3] == DNS_DSDIGEST_SHA256 && + sr.length < 4 + ISC_SHA256_DIGESTLENGTH) || + (sr.base[3] == DNS_DSDIGEST_SHA384 && + sr.length < 4 + ISC_SHA384_DIGESTLENGTH)) + { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Only copy digest lengths if we know them. + * If there is extra data dns_rdata_fromwire() will + * detect that. + */ + if (sr.base[3] == DNS_DSDIGEST_SHA1) { + sr.length = 4 + ISC_SHA1_DIGESTLENGTH; + } else if (sr.base[3] == DNS_DSDIGEST_SHA256) { + sr.length = 4 + ISC_SHA256_DIGESTLENGTH; + } else if (sr.base[3] == DNS_DSDIGEST_SHA384) { + sr.length = 4 + ISC_SHA384_DIGESTLENGTH; + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +fromwire_ds(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromwire_ds(CALL_FROMWIRE)); +} + +static isc_result_t +towire_ds(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_ds); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_ds(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ds); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +generic_fromstruct_ds(ARGS_FROMSTRUCT) { + dns_rdata_ds_t *ds = source; + + REQUIRE(ds != NULL); + REQUIRE(ds->common.rdtype == type); + REQUIRE(ds->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + switch (ds->digest_type) { + case DNS_DSDIGEST_SHA1: + REQUIRE(ds->length == ISC_SHA1_DIGESTLENGTH); + break; + case DNS_DSDIGEST_SHA256: + REQUIRE(ds->length == ISC_SHA256_DIGESTLENGTH); + break; + case DNS_DSDIGEST_SHA384: + REQUIRE(ds->length == ISC_SHA384_DIGESTLENGTH); + break; + } + + RETERR(uint16_tobuffer(ds->key_tag, target)); + RETERR(uint8_tobuffer(ds->algorithm, target)); + RETERR(uint8_tobuffer(ds->digest_type, target)); + + return (mem_tobuffer(target, ds->digest, ds->length)); +} + +static isc_result_t +fromstruct_ds(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromstruct_ds(CALL_FROMSTRUCT)); +} + +static isc_result_t +generic_tostruct_ds(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + isc_region_t region; + + REQUIRE(ds != NULL); + REQUIRE(rdata->length != 0); + REQUIRE(ds->common.rdtype == rdata->type); + REQUIRE(ds->common.rdclass == rdata->rdclass); + REQUIRE(!ISC_LINK_LINKED(&ds->common, link)); + + dns_rdata_toregion(rdata, ®ion); + + ds->key_tag = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + ds->algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + ds->digest_type = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + ds->length = region.length; + + ds->digest = mem_maybedup(mctx, region.base, region.length); + if (ds->digest == NULL) { + return (ISC_R_NOMEMORY); + } + + ds->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static isc_result_t +tostruct_ds(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + + REQUIRE(rdata->type == dns_rdatatype_ds); + REQUIRE(ds != NULL); + + ds->common.rdclass = rdata->rdclass; + ds->common.rdtype = rdata->type; + ISC_LINK_INIT(&ds->common, link); + + return (generic_tostruct_ds(CALL_TOSTRUCT)); +} + +static void +freestruct_ds(ARGS_FREESTRUCT) { + dns_rdata_ds_t *ds = source; + + REQUIRE(ds != NULL); + REQUIRE(ds->common.rdtype == dns_rdatatype_ds); + + if (ds->mctx == NULL) { + return; + } + + if (ds->digest != NULL) { + isc_mem_free(ds->mctx, ds->digest); + } + ds->mctx = NULL; +} + +static isc_result_t +additionaldata_ds(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ds(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_ds); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_ds(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ds); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_ds(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_ds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_ds(ARGS_COMPARE) { + return (compare_ds(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_DS_43_C */ diff --git a/lib/dns/rdata/generic/ds_43.h b/lib/dns/rdata/generic/ds_43.h new file mode 100644 index 0000000..57e8494 --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief per draft-ietf-dnsext-delegation-signer-05.txt */ +typedef struct dns_rdata_ds { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t key_tag; + dns_secalg_t algorithm; + dns_dsdigest_t digest_type; + uint16_t length; + unsigned char *digest; +} dns_rdata_ds_t; diff --git a/lib/dns/rdata/generic/eui48_108.c b/lib/dns/rdata/generic/eui48_108.c new file mode 100644 index 0000000..25603b2 --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_EUI48_108_C +#define RDATA_GENERIC_EUI48_108_C + +#include + +#define RRTYPE_EUI48_ATTRIBUTES (0) + +static isc_result_t +fromtext_eui48(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char eui48[6]; + unsigned int l0, l1, l2, l3, l4, l5; + int n; + + REQUIRE(type == dns_rdatatype_eui48); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + n = sscanf(DNS_AS_STR(token), "%2x-%2x-%2x-%2x-%2x-%2x", &l0, &l1, &l2, + &l3, &l4, &l5); + if (n != 6 || l0 > 255U || l1 > 255U || l2 > 255U || l3 > 255U || + l4 > 255U || l5 > 255U) + { + return (DNS_R_BADEUI); + } + + eui48[0] = l0; + eui48[1] = l1; + eui48[2] = l2; + eui48[3] = l3; + eui48[4] = l4; + eui48[5] = l5; + return (mem_tobuffer(target, eui48, sizeof(eui48))); +} + +static isc_result_t +totext_eui48(ARGS_TOTEXT) { + char buf[sizeof("xx-xx-xx-xx-xx-xx")]; + + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + UNUSED(tctx); + + (void)snprintf(buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x", + rdata->data[0], rdata->data[1], rdata->data[2], + rdata->data[3], rdata->data[4], rdata->data[5]); + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_eui48(ARGS_FROMWIRE) { + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_eui48); + + UNUSED(type); + UNUSED(options); + UNUSED(rdclass); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length != 6) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sregion.length); + return (mem_tobuffer(target, sregion.base, sregion.length)); +} + +static isc_result_t +towire_eui48(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_eui48(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_eui48); + REQUIRE(rdata1->length == 6); + REQUIRE(rdata2->length == 6); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_eui48(ARGS_FROMSTRUCT) { + dns_rdata_eui48_t *eui48 = source; + + REQUIRE(type == dns_rdatatype_eui48); + REQUIRE(eui48 != NULL); + REQUIRE(eui48->common.rdtype == type); + REQUIRE(eui48->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, eui48->eui48, sizeof(eui48->eui48))); +} + +static isc_result_t +tostruct_eui48(ARGS_TOSTRUCT) { + dns_rdata_eui48_t *eui48 = target; + + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(eui48 != NULL); + REQUIRE(rdata->length == 6); + + UNUSED(mctx); + + eui48->common.rdclass = rdata->rdclass; + eui48->common.rdtype = rdata->type; + ISC_LINK_INIT(&eui48->common, link); + + memmove(eui48->eui48, rdata->data, rdata->length); + return (ISC_R_SUCCESS); +} + +static void +freestruct_eui48(ARGS_FREESTRUCT) { + dns_rdata_eui48_t *eui48 = source; + + REQUIRE(eui48 != NULL); + REQUIRE(eui48->common.rdtype == dns_rdatatype_eui48); + + return; +} + +static isc_result_t +additionaldata_eui48(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_eui48(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_eui48(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_eui48); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_eui48(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_eui48(ARGS_COMPARE) { + return (compare_eui48(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_EUI48_108_C */ diff --git a/lib/dns/rdata/generic/eui48_108.h b/lib/dns/rdata/generic/eui48_108.h new file mode 100644 index 0000000..fb3cebd --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_eui48 { + dns_rdatacommon_t common; + unsigned char eui48[6]; +} dns_rdata_eui48_t; diff --git a/lib/dns/rdata/generic/eui64_109.c b/lib/dns/rdata/generic/eui64_109.c new file mode 100644 index 0000000..93978d2 --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.c @@ -0,0 +1,215 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_EUI64_109_C +#define RDATA_GENERIC_EUI64_109_C + +#include + +#define RRTYPE_EUI64_ATTRIBUTES (0) + +static isc_result_t +fromtext_eui64(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char eui64[8]; + unsigned int l0, l1, l2, l3, l4, l5, l6, l7; + int n; + + REQUIRE(type == dns_rdatatype_eui64); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + n = sscanf(DNS_AS_STR(token), "%2x-%2x-%2x-%2x-%2x-%2x-%2x-%2x", &l0, + &l1, &l2, &l3, &l4, &l5, &l6, &l7); + if (n != 8 || l0 > 255U || l1 > 255U || l2 > 255U || l3 > 255U || + l4 > 255U || l5 > 255U || l6 > 255U || l7 > 255U) + { + return (DNS_R_BADEUI); + } + + eui64[0] = l0; + eui64[1] = l1; + eui64[2] = l2; + eui64[3] = l3; + eui64[4] = l4; + eui64[5] = l5; + eui64[6] = l6; + eui64[7] = l7; + return (mem_tobuffer(target, eui64, sizeof(eui64))); +} + +static isc_result_t +totext_eui64(ARGS_TOTEXT) { + char buf[sizeof("xx-xx-xx-xx-xx-xx-xx-xx")]; + + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + UNUSED(tctx); + + (void)snprintf( + buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x", + rdata->data[0], rdata->data[1], rdata->data[2], rdata->data[3], + rdata->data[4], rdata->data[5], rdata->data[6], rdata->data[7]); + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_eui64(ARGS_FROMWIRE) { + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_eui64); + + UNUSED(type); + UNUSED(options); + UNUSED(rdclass); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length != 8) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sregion.length); + return (mem_tobuffer(target, sregion.base, sregion.length)); +} + +static isc_result_t +towire_eui64(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_eui64(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_eui64); + REQUIRE(rdata1->length == 8); + REQUIRE(rdata2->length == 8); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_eui64(ARGS_FROMSTRUCT) { + dns_rdata_eui64_t *eui64 = source; + + REQUIRE(type == dns_rdatatype_eui64); + REQUIRE(eui64 != NULL); + REQUIRE(eui64->common.rdtype == type); + REQUIRE(eui64->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, eui64->eui64, sizeof(eui64->eui64))); +} + +static isc_result_t +tostruct_eui64(ARGS_TOSTRUCT) { + dns_rdata_eui64_t *eui64 = target; + + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(eui64 != NULL); + REQUIRE(rdata->length == 8); + + UNUSED(mctx); + + eui64->common.rdclass = rdata->rdclass; + eui64->common.rdtype = rdata->type; + ISC_LINK_INIT(&eui64->common, link); + + memmove(eui64->eui64, rdata->data, rdata->length); + return (ISC_R_SUCCESS); +} + +static void +freestruct_eui64(ARGS_FREESTRUCT) { + dns_rdata_eui64_t *eui64 = source; + + REQUIRE(eui64 != NULL); + REQUIRE(eui64->common.rdtype == dns_rdatatype_eui64); + + return; +} + +static isc_result_t +additionaldata_eui64(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_eui64(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_eui64(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_eui64); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_eui64(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_eui64(ARGS_COMPARE) { + return (compare_eui64(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_EUI64_109_C */ diff --git a/lib/dns/rdata/generic/eui64_109.h b/lib/dns/rdata/generic/eui64_109.h new file mode 100644 index 0000000..c5f6d89 --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_eui64 { + dns_rdatacommon_t common; + unsigned char eui64[8]; +} dns_rdata_eui64_t; diff --git a/lib/dns/rdata/generic/gpos_27.c b/lib/dns/rdata/generic/gpos_27.c new file mode 100644 index 0000000..4f2f3fd --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1712 */ + +#ifndef RDATA_GENERIC_GPOS_27_C +#define RDATA_GENERIC_GPOS_27_C + +#define RRTYPE_GPOS_ATTRIBUTES (0) + +static isc_result_t +fromtext_gpos(ARGS_FROMTEXT) { + isc_token_t token; + int i; + + REQUIRE(type == dns_rdatatype_gpos); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + for (i = 0; i < 3; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_qstring, false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_gpos(ARGS_TOTEXT) { + isc_region_t region; + int i; + + REQUIRE(rdata->type == dns_rdatatype_gpos); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + + for (i = 0; i < 3; i++) { + RETERR(txt_totext(®ion, true, target)); + if (i != 2) { + RETERR(str_totext(" ", target)); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_gpos(ARGS_FROMWIRE) { + int i; + + REQUIRE(type == dns_rdatatype_gpos); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + for (i = 0; i < 3; i++) { + RETERR(txt_fromwire(source, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_gpos(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_gpos); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_gpos(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_gpos); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_gpos(ARGS_FROMSTRUCT) { + dns_rdata_gpos_t *gpos = source; + + REQUIRE(type == dns_rdatatype_gpos); + REQUIRE(gpos != NULL); + REQUIRE(gpos->common.rdtype == type); + REQUIRE(gpos->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(gpos->long_len, target)); + RETERR(mem_tobuffer(target, gpos->longitude, gpos->long_len)); + RETERR(uint8_tobuffer(gpos->lat_len, target)); + RETERR(mem_tobuffer(target, gpos->latitude, gpos->lat_len)); + RETERR(uint8_tobuffer(gpos->alt_len, target)); + return (mem_tobuffer(target, gpos->altitude, gpos->alt_len)); +} + +static isc_result_t +tostruct_gpos(ARGS_TOSTRUCT) { + dns_rdata_gpos_t *gpos = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_gpos); + REQUIRE(gpos != NULL); + REQUIRE(rdata->length != 0); + + gpos->common.rdclass = rdata->rdclass; + gpos->common.rdtype = rdata->type; + ISC_LINK_INIT(&gpos->common, link); + + dns_rdata_toregion(rdata, ®ion); + gpos->long_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + gpos->longitude = mem_maybedup(mctx, region.base, gpos->long_len); + if (gpos->longitude == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(®ion, gpos->long_len); + + gpos->lat_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + gpos->latitude = mem_maybedup(mctx, region.base, gpos->lat_len); + if (gpos->latitude == NULL) { + goto cleanup_longitude; + } + isc_region_consume(®ion, gpos->lat_len); + + gpos->alt_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + if (gpos->lat_len > 0) { + gpos->altitude = mem_maybedup(mctx, region.base, gpos->alt_len); + if (gpos->altitude == NULL) { + goto cleanup_latitude; + } + } else { + gpos->altitude = NULL; + } + + gpos->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup_latitude: + if (mctx != NULL && gpos->longitude != NULL) { + isc_mem_free(mctx, gpos->longitude); + } + +cleanup_longitude: + if (mctx != NULL && gpos->latitude != NULL) { + isc_mem_free(mctx, gpos->latitude); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_gpos(ARGS_FREESTRUCT) { + dns_rdata_gpos_t *gpos = source; + + REQUIRE(gpos != NULL); + REQUIRE(gpos->common.rdtype == dns_rdatatype_gpos); + + if (gpos->mctx == NULL) { + return; + } + + if (gpos->longitude != NULL) { + isc_mem_free(gpos->mctx, gpos->longitude); + } + if (gpos->latitude != NULL) { + isc_mem_free(gpos->mctx, gpos->latitude); + } + if (gpos->altitude != NULL) { + isc_mem_free(gpos->mctx, gpos->altitude); + } + gpos->mctx = NULL; +} + +static isc_result_t +additionaldata_gpos(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_gpos); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_gpos(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_gpos); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_gpos(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_gpos); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_gpos(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_gpos); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_gpos(ARGS_COMPARE) { + return (compare_gpos(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_GPOS_27_C */ diff --git a/lib/dns/rdata/generic/gpos_27.h b/lib/dns/rdata/generic/gpos_27.h new file mode 100644 index 0000000..b09a69f --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief per RFC1712 */ + +typedef struct dns_rdata_gpos { + dns_rdatacommon_t common; + isc_mem_t *mctx; + char *longitude; + char *latitude; + char *altitude; + uint8_t long_len; + uint8_t lat_len; + uint8_t alt_len; +} dns_rdata_gpos_t; diff --git a/lib/dns/rdata/generic/hinfo_13.c b/lib/dns/rdata/generic/hinfo_13.c new file mode 100644 index 0000000..d3978ed --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#define RRTYPE_HINFO_ATTRIBUTES (0) + +static isc_result_t +fromtext_hinfo(ARGS_FROMTEXT) { + isc_token_t token; + int i; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + REQUIRE(type == dns_rdatatype_hinfo); + + for (i = 0; i < 2; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_qstring, false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_hinfo(ARGS_TOTEXT) { + isc_region_t region; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + RETERR(txt_totext(®ion, true, target)); + RETERR(str_totext(" ", target)); + return (txt_totext(®ion, true, target)); +} + +static isc_result_t +fromwire_hinfo(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_hinfo); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + RETERR(txt_fromwire(source, target)); + return (txt_fromwire(source, target)); +} + +static isc_result_t +towire_hinfo(ARGS_TOWIRE) { + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + REQUIRE(rdata->length != 0); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_hinfo(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_hinfo); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_hinfo(ARGS_FROMSTRUCT) { + dns_rdata_hinfo_t *hinfo = source; + + REQUIRE(type == dns_rdatatype_hinfo); + REQUIRE(hinfo != NULL); + REQUIRE(hinfo->common.rdtype == type); + REQUIRE(hinfo->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(hinfo->cpu_len, target)); + RETERR(mem_tobuffer(target, hinfo->cpu, hinfo->cpu_len)); + RETERR(uint8_tobuffer(hinfo->os_len, target)); + return (mem_tobuffer(target, hinfo->os, hinfo->os_len)); +} + +static isc_result_t +tostruct_hinfo(ARGS_TOSTRUCT) { + dns_rdata_hinfo_t *hinfo = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + REQUIRE(hinfo != NULL); + REQUIRE(rdata->length != 0); + + hinfo->common.rdclass = rdata->rdclass; + hinfo->common.rdtype = rdata->type; + ISC_LINK_INIT(&hinfo->common, link); + + dns_rdata_toregion(rdata, ®ion); + hinfo->cpu_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + hinfo->cpu = mem_maybedup(mctx, region.base, hinfo->cpu_len); + if (hinfo->cpu == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(®ion, hinfo->cpu_len); + + hinfo->os_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + hinfo->os = mem_maybedup(mctx, region.base, hinfo->os_len); + if (hinfo->os == NULL) { + goto cleanup; + } + + hinfo->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL && hinfo->cpu != NULL) { + isc_mem_free(mctx, hinfo->cpu); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_hinfo(ARGS_FREESTRUCT) { + dns_rdata_hinfo_t *hinfo = source; + + REQUIRE(hinfo != NULL); + + if (hinfo->mctx == NULL) { + return; + } + + if (hinfo->cpu != NULL) { + isc_mem_free(hinfo->mctx, hinfo->cpu); + } + if (hinfo->os != NULL) { + isc_mem_free(hinfo->mctx, hinfo->os); + } + hinfo->mctx = NULL; +} + +static isc_result_t +additionaldata_hinfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_hinfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_hinfo(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_hinfo(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_hinfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_hinfo(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_hinfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_hinfo(ARGS_COMPARE) { + return (compare_hinfo(rdata1, rdata2)); +} diff --git a/lib/dns/rdata/generic/hinfo_13.h b/lib/dns/rdata/generic/hinfo_13.h new file mode 100644 index 0000000..a477d47 --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_hinfo { + dns_rdatacommon_t common; + isc_mem_t *mctx; + char *cpu; + char *os; + uint8_t cpu_len; + uint8_t os_len; +} dns_rdata_hinfo_t; diff --git a/lib/dns/rdata/generic/hip_55.c b/lib/dns/rdata/generic/hip_55.c new file mode 100644 index 0000000..e67df0e --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.c @@ -0,0 +1,523 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 5205 */ + +#pragma once + +#define RRTYPE_HIP_ATTRIBUTES (0) + +static isc_result_t +fromtext_hip(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + isc_buffer_t hit_len; + isc_buffer_t key_len; + unsigned char *start; + size_t len; + + REQUIRE(type == dns_rdatatype_hip); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Dummy HIT len. + */ + hit_len = *target; + RETERR(uint8_tobuffer(0, target)); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Dummy KEY len. + */ + key_len = *target; + RETERR(uint16_tobuffer(0, target)); + + /* + * HIT (base16). + */ + start = isc_buffer_used(target); + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(isc_hex_decodestring(DNS_AS_STR(token), target)); + + /* + * Fill in HIT len. + */ + len = (unsigned char *)isc_buffer_used(target) - start; + if (len > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer((uint32_t)len, &hit_len)); + + /* + * Public key (base64). + */ + start = isc_buffer_used(target); + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(isc_base64_decodestring(DNS_AS_STR(token), target)); + + /* + * Fill in KEY len. + */ + len = (unsigned char *)isc_buffer_used(target) - start; + if (len > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer((uint32_t)len, &key_len)); + + if (origin == NULL) { + origin = dns_rootname; + } + + /* + * Rendezvous Servers. + */ + dns_name_init(&name, NULL); + do { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, true)); + if (token.type != isc_tokentype_string) { + break; + } + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + } while (1); + + /* + * Let upper layer handle eol/eof. + */ + isc_lex_ungettoken(lexer, &token); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_hip(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + unsigned int length, key_len, hit_len; + unsigned char algorithm; + char buf[sizeof("225 ")]; + + REQUIRE(rdata->type == dns_rdatatype_hip); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + + hit_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + key_len = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( ", target)); + } + + /* + * Algorithm + */ + snprintf(buf, sizeof(buf), "%u ", algorithm); + RETERR(str_totext(buf, target)); + + /* + * HIT. + */ + INSIST(hit_len < region.length); + length = region.length; + region.length = hit_len; + RETERR(isc_hex_totext(®ion, 1, "", target)); + region.length = length - hit_len; + RETERR(str_totext(tctx->linebreak, target)); + + /* + * Public KEY. + */ + INSIST(key_len <= region.length); + length = region.length; + region.length = key_len; + RETERR(isc_base64_totext(®ion, 1, "", target)); + region.length = length - key_len; + if (region.length > 0) { + RETERR(str_totext(tctx->linebreak, target)); + } + + /* + * Rendezvous Servers. + */ + dns_name_init(&name, NULL); + while (region.length > 0) { + dns_name_fromregion(&name, ®ion); + + RETERR(dns_name_totext(&name, false, target)); + isc_region_consume(®ion, name.length); + if (region.length > 0) { + RETERR(str_totext(tctx->linebreak, target)); + } + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_hip(ARGS_FROMWIRE) { + isc_region_t region, rr; + dns_name_t name; + uint8_t hit_len; + uint16_t key_len; + size_t len; + + REQUIRE(type == dns_rdatatype_hip); + + UNUSED(type); + UNUSED(rdclass); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 4U) { + RETERR(DNS_R_FORMERR); + } + + rr = region; + hit_len = uint8_fromregion(®ion); + if (hit_len == 0) { + RETERR(DNS_R_FORMERR); + } + isc_region_consume(®ion, 2); /* hit length + algorithm */ + key_len = uint16_fromregion(®ion); + if (key_len == 0) { + RETERR(DNS_R_FORMERR); + } + isc_region_consume(®ion, 2); + len = hit_len + key_len; + if (len > region.length) { + RETERR(DNS_R_FORMERR); + } + + RETERR(mem_tobuffer(target, rr.base, 4 + len)); + isc_buffer_forward(source, 4 + len); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + while (isc_buffer_activelength(source) > 0) { + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_hip(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_hip); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_hip(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_hip); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_hip(ARGS_FROMSTRUCT) { + dns_rdata_hip_t *hip = source; + dns_rdata_hip_t myhip; + isc_result_t result; + + REQUIRE(type == dns_rdatatype_hip); + REQUIRE(hip != NULL); + REQUIRE(hip->common.rdtype == type); + REQUIRE(hip->common.rdclass == rdclass); + REQUIRE(hip->hit_len > 0 && hip->hit != NULL); + REQUIRE(hip->key_len > 0 && hip->key != NULL); + REQUIRE((hip->servers == NULL && hip->servers_len == 0) || + (hip->servers != NULL && hip->servers_len != 0)); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(hip->hit_len, target)); + RETERR(uint8_tobuffer(hip->algorithm, target)); + RETERR(uint16_tobuffer(hip->key_len, target)); + RETERR(mem_tobuffer(target, hip->hit, hip->hit_len)); + RETERR(mem_tobuffer(target, hip->key, hip->key_len)); + + myhip = *hip; + for (result = dns_rdata_hip_first(&myhip); result == ISC_R_SUCCESS; + result = dns_rdata_hip_next(&myhip)) + { + /* initialize the names */ + } + + return (mem_tobuffer(target, hip->servers, hip->servers_len)); +} + +static isc_result_t +tostruct_hip(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_hip_t *hip = target; + + REQUIRE(rdata->type == dns_rdatatype_hip); + REQUIRE(hip != NULL); + REQUIRE(rdata->length != 0); + + hip->common.rdclass = rdata->rdclass; + hip->common.rdtype = rdata->type; + ISC_LINK_INIT(&hip->common, link); + + dns_rdata_toregion(rdata, ®ion); + + hip->hit_len = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + hip->algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + hip->key_len = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + hip->hit = hip->key = hip->servers = NULL; + + hip->hit = mem_maybedup(mctx, region.base, hip->hit_len); + if (hip->hit == NULL) { + goto cleanup; + } + isc_region_consume(®ion, hip->hit_len); + + INSIST(hip->key_len <= region.length); + + hip->key = mem_maybedup(mctx, region.base, hip->key_len); + if (hip->key == NULL) { + goto cleanup; + } + isc_region_consume(®ion, hip->key_len); + + hip->servers_len = region.length; + if (hip->servers_len != 0) { + hip->servers = mem_maybedup(mctx, region.base, region.length); + if (hip->servers == NULL) { + goto cleanup; + } + } + + hip->offset = hip->servers_len; + hip->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (hip->hit != NULL) { + isc_mem_free(mctx, hip->hit); + } + if (hip->key != NULL) { + isc_mem_free(mctx, hip->key); + } + if (hip->servers != NULL) { + isc_mem_free(mctx, hip->servers); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_hip(ARGS_FREESTRUCT) { + dns_rdata_hip_t *hip = source; + + REQUIRE(hip != NULL); + + if (hip->mctx == NULL) { + return; + } + + isc_mem_free(hip->mctx, hip->hit); + isc_mem_free(hip->mctx, hip->key); + if (hip->servers != NULL) { + isc_mem_free(hip->mctx, hip->servers); + } + hip->mctx = NULL; +} + +static isc_result_t +additionaldata_hip(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_hip); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_hip(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_hip); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_hip(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_hip); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_hip(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_hip); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +isc_result_t +dns_rdata_hip_first(dns_rdata_hip_t *hip) { + if (hip->servers_len == 0) { + return (ISC_R_NOMORE); + } + hip->offset = 0; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdata_hip_next(dns_rdata_hip_t *hip) { + isc_region_t region; + dns_name_t name; + + if (hip->offset >= hip->servers_len) { + return (ISC_R_NOMORE); + } + + region.base = hip->servers + hip->offset; + region.length = hip->servers_len - hip->offset; + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + hip->offset += name.length; + INSIST(hip->offset <= hip->servers_len); + return (hip->offset < hip->servers_len ? ISC_R_SUCCESS : ISC_R_NOMORE); +} + +void +dns_rdata_hip_current(dns_rdata_hip_t *hip, dns_name_t *name) { + isc_region_t region; + + REQUIRE(hip->offset < hip->servers_len); + + region.base = hip->servers + hip->offset; + region.length = hip->servers_len - hip->offset; + dns_name_fromregion(name, ®ion); + + INSIST(name->length + hip->offset <= hip->servers_len); +} + +static int +casecompare_hip(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + uint8_t hit_len; + uint16_t key_len; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_hip); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + + INSIST(r1.length > 4); + INSIST(r2.length > 4); + order = memcmp(r1.base, r2.base, 4); + if (order != 0) { + return (order); + } + + hit_len = uint8_fromregion(&r1); + isc_region_consume(&r1, 2); /* hit length + algorithm */ + key_len = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); /* key length */ + isc_region_consume(&r2, 4); + + INSIST(r1.length >= (unsigned)(hit_len + key_len)); + INSIST(r2.length >= (unsigned)(hit_len + key_len)); + order = memcmp(r1.base, r2.base, hit_len + key_len); + if (order != 0) { + return (order); + } + isc_region_consume(&r1, hit_len + key_len); + isc_region_consume(&r2, hit_len + key_len); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + while (r1.length != 0 && r2.length != 0) { + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + } + return (isc_region_compare(&r1, &r2)); +} diff --git a/lib/dns/rdata/generic/hip_55.h b/lib/dns/rdata/generic/hip_55.h new file mode 100644 index 0000000..617e9c9 --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* RFC 5205 */ + +typedef struct dns_rdata_hip { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *hit; + unsigned char *key; + unsigned char *servers; + uint8_t algorithm; + uint8_t hit_len; + uint16_t key_len; + uint16_t servers_len; + /* Private */ + uint16_t offset; +} dns_rdata_hip_t; + +isc_result_t +dns_rdata_hip_first(dns_rdata_hip_t *); + +isc_result_t +dns_rdata_hip_next(dns_rdata_hip_t *); + +void +dns_rdata_hip_current(dns_rdata_hip_t *, dns_name_t *); diff --git a/lib/dns/rdata/generic/ipseckey_45.c b/lib/dns/rdata/generic/ipseckey_45.c new file mode 100644 index 0000000..a4232db --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.c @@ -0,0 +1,527 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_IPSECKEY_45_C +#define RDATA_GENERIC_IPSECKEY_45_C + +#include + +#include + +#define RRTYPE_IPSECKEY_ATTRIBUTES (0) + +static isc_result_t +fromtext_ipseckey(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + unsigned int gateway; + struct in_addr addr; + unsigned char addr6[16]; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ipseckey); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Precedence. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Gateway type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0x3U) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + gateway = token.value.as_ulong; + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Gateway. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + switch (gateway) { + case 0: + if (strcmp(DNS_AS_STR(token), ".") != 0) { + RETTOK(DNS_R_SYNTAX); + } + break; + + case 1: + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + break; + + case 2: + if (inet_pton(AF_INET6, DNS_AS_STR(token), addr6) != 1) { + RETTOK(DNS_R_BADAAAA); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 16) { + return (ISC_R_NOSPACE); + } + memmove(region.base, addr6, 16); + isc_buffer_add(target, 16); + break; + + case 3: + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + break; + } + + /* + * Public key. + */ + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_ipseckey(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + char buf[sizeof("255 ")]; + unsigned short num; + unsigned short gateway; + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + REQUIRE(rdata->length >= 3); + + dns_name_init(&name, NULL); + + if (rdata->data[1] > 3U) { + return (ISC_R_NOTIMPLEMENTED); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( ", target)); + } + + /* + * Precedence. + */ + dns_rdata_toregion(rdata, ®ion); + num = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u ", num); + RETERR(str_totext(buf, target)); + + /* + * Gateway type. + */ + gateway = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u ", gateway); + RETERR(str_totext(buf, target)); + + /* + * Algorithm. + */ + num = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + snprintf(buf, sizeof(buf), "%u ", num); + RETERR(str_totext(buf, target)); + + /* + * Gateway. + */ + switch (gateway) { + case 0: + RETERR(str_totext(".", target)); + break; + + case 1: + RETERR(inet_totext(AF_INET, tctx->flags, ®ion, target)); + isc_region_consume(®ion, 4); + break; + + case 2: + RETERR(inet_totext(AF_INET6, tctx->flags, ®ion, target)); + isc_region_consume(®ion, 16); + break; + + case 3: + dns_name_fromregion(&name, ®ion); + RETERR(dns_name_totext(&name, false, target)); + isc_region_consume(®ion, name_length(&name)); + break; + } + + /* + * Key. + */ + if (region.length > 0U) { + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(®ion, 60, "", target)); + } else { + RETERR(isc_base64_totext(®ion, tctx->width - 2, + tctx->linebreak, target)); + } + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_ipseckey(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ipseckey); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 3) { + return (ISC_R_UNEXPECTEDEND); + } + + switch (region.base[1]) { + case 0: + if (region.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 1: + if (region.length < 8) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 2: + if (region.length < 20) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 3: + RETERR(mem_tobuffer(target, region.base, 3)); + isc_buffer_forward(source, 3); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + isc_buffer_activeregion(source, ®ion); + isc_buffer_forward(source, region.length); + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + return (mem_tobuffer(target, region.base, region.length)); + + default: + return (ISC_R_NOTIMPLEMENTED); + } +} + +static isc_result_t +towire_ipseckey(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_ipseckey(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ipseckey); + REQUIRE(rdata1->length >= 3); + REQUIRE(rdata2->length >= 3); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_ipseckey(ARGS_FROMSTRUCT) { + dns_rdata_ipseckey_t *ipseckey = source; + isc_region_t region; + uint32_t n; + + REQUIRE(type == dns_rdatatype_ipseckey); + REQUIRE(ipseckey != NULL); + REQUIRE(ipseckey->common.rdtype == type); + REQUIRE(ipseckey->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + if (ipseckey->gateway_type > 3U) { + return (ISC_R_NOTIMPLEMENTED); + } + + RETERR(uint8_tobuffer(ipseckey->precedence, target)); + RETERR(uint8_tobuffer(ipseckey->gateway_type, target)); + RETERR(uint8_tobuffer(ipseckey->algorithm, target)); + + switch (ipseckey->gateway_type) { + case 0: + break; + + case 1: + n = ntohl(ipseckey->in_addr.s_addr); + RETERR(uint32_tobuffer(n, target)); + break; + + case 2: + RETERR(mem_tobuffer(target, ipseckey->in6_addr.s6_addr, 16)); + break; + + case 3: + dns_name_toregion(&ipseckey->gateway, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + break; + } + + return (mem_tobuffer(target, ipseckey->key, ipseckey->keylength)); +} + +static isc_result_t +tostruct_ipseckey(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_ipseckey_t *ipseckey = target; + dns_name_t name; + uint32_t n; + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + REQUIRE(ipseckey != NULL); + REQUIRE(rdata->length >= 3); + + if (rdata->data[1] > 3U) { + return (ISC_R_NOTIMPLEMENTED); + } + + ipseckey->common.rdclass = rdata->rdclass; + ipseckey->common.rdtype = rdata->type; + ISC_LINK_INIT(&ipseckey->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + + ipseckey->precedence = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + ipseckey->gateway_type = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + ipseckey->algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + + switch (ipseckey->gateway_type) { + case 0: + break; + + case 1: + n = uint32_fromregion(®ion); + ipseckey->in_addr.s_addr = htonl(n); + isc_region_consume(®ion, 4); + break; + + case 2: + memmove(ipseckey->in6_addr.s6_addr, region.base, 16); + isc_region_consume(®ion, 16); + break; + + case 3: + dns_name_init(&ipseckey->gateway, NULL); + dns_name_fromregion(&name, ®ion); + name_duporclone(&name, mctx, &ipseckey->gateway); + isc_region_consume(®ion, name_length(&name)); + break; + } + + ipseckey->keylength = region.length; + if (ipseckey->keylength != 0U) { + ipseckey->key = mem_maybedup(mctx, region.base, + ipseckey->keylength); + if (ipseckey->key == NULL) { + if (ipseckey->gateway_type == 3) { + dns_name_free(&ipseckey->gateway, + ipseckey->mctx); + } + return (ISC_R_NOMEMORY); + } + } else { + ipseckey->key = NULL; + } + + ipseckey->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_ipseckey(ARGS_FREESTRUCT) { + dns_rdata_ipseckey_t *ipseckey = source; + + REQUIRE(ipseckey != NULL); + REQUIRE(ipseckey->common.rdtype == dns_rdatatype_ipseckey); + + if (ipseckey->mctx == NULL) { + return; + } + + if (ipseckey->gateway_type == 3) { + dns_name_free(&ipseckey->gateway, ipseckey->mctx); + } + + if (ipseckey->key != NULL) { + isc_mem_free(ipseckey->mctx, ipseckey->key); + } + + ipseckey->mctx = NULL; +} + +static isc_result_t +additionaldata_ipseckey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ipseckey(ARGS_DIGEST) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + + dns_rdata_toregion(rdata, ®ion); + return ((digest)(arg, ®ion)); +} + +static bool +checkowner_ipseckey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ipseckey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_ipseckey(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_ipseckey(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ipseckey); + REQUIRE(rdata1->length >= 3); + REQUIRE(rdata2->length >= 3); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + if (memcmp(region1.base, region2.base, 3) != 0 || region1.base[1] != 3) + { + return (isc_region_compare(®ion1, ®ion2)); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + isc_region_consume(®ion1, 3); + isc_region_consume(®ion2, 3); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + return (isc_region_compare(®ion1, ®ion2)); +} + +#endif /* RDATA_GENERIC_IPSECKEY_45_C */ diff --git a/lib/dns/rdata/generic/ipseckey_45.h b/lib/dns/rdata/generic/ipseckey_45.h new file mode 100644 index 0000000..829e9b4 --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_ipseckey { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t precedence; + uint8_t gateway_type; + uint8_t algorithm; + struct in_addr in_addr; /* gateway type 1 */ + struct in6_addr in6_addr; /* gateway type 2 */ + dns_name_t gateway; /* gateway type 3 */ + unsigned char *key; + uint16_t keylength; +} dns_rdata_ipseckey_t; diff --git a/lib/dns/rdata/generic/isdn_20.c b/lib/dns/rdata/generic/isdn_20.c new file mode 100644 index 0000000..8218d06 --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1183 */ + +#ifndef RDATA_GENERIC_ISDN_20_C +#define RDATA_GENERIC_ISDN_20_C + +#define RRTYPE_ISDN_ATTRIBUTES (0) + +static isc_result_t +fromtext_isdn(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_isdn); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* ISDN-address */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + + /* sa: optional */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + true)); + if (token.type != isc_tokentype_string && + token.type != isc_tokentype_qstring) + { + isc_lex_ungettoken(lexer, &token); + return (ISC_R_SUCCESS); + } + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_isdn(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_isdn); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + RETERR(txt_totext(®ion, true, target)); + if (region.length == 0) { + return (ISC_R_SUCCESS); + } + RETERR(str_totext(" ", target)); + return (txt_totext(®ion, true, target)); +} + +static isc_result_t +fromwire_isdn(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_isdn); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + RETERR(txt_fromwire(source, target)); + if (buffer_empty(source)) { + return (ISC_R_SUCCESS); + } + return (txt_fromwire(source, target)); +} + +static isc_result_t +towire_isdn(ARGS_TOWIRE) { + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_isdn); + REQUIRE(rdata->length != 0); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_isdn(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_isdn); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_isdn(ARGS_FROMSTRUCT) { + dns_rdata_isdn_t *isdn = source; + + REQUIRE(type == dns_rdatatype_isdn); + REQUIRE(isdn != NULL); + REQUIRE(isdn->common.rdtype == type); + REQUIRE(isdn->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(isdn->isdn_len, target)); + RETERR(mem_tobuffer(target, isdn->isdn, isdn->isdn_len)); + if (isdn->subaddress == NULL) { + return (ISC_R_SUCCESS); + } + RETERR(uint8_tobuffer(isdn->subaddress_len, target)); + return (mem_tobuffer(target, isdn->subaddress, isdn->subaddress_len)); +} + +static isc_result_t +tostruct_isdn(ARGS_TOSTRUCT) { + dns_rdata_isdn_t *isdn = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_isdn); + REQUIRE(isdn != NULL); + REQUIRE(rdata->length != 0); + + isdn->common.rdclass = rdata->rdclass; + isdn->common.rdtype = rdata->type; + ISC_LINK_INIT(&isdn->common, link); + + dns_rdata_toregion(rdata, &r); + + isdn->isdn_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + isdn->isdn = mem_maybedup(mctx, r.base, isdn->isdn_len); + if (isdn->isdn == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(&r, isdn->isdn_len); + + if (r.length == 0) { + isdn->subaddress_len = 0; + isdn->subaddress = NULL; + } else { + isdn->subaddress_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + isdn->subaddress = mem_maybedup(mctx, r.base, + isdn->subaddress_len); + if (isdn->subaddress == NULL) { + goto cleanup; + } + } + + isdn->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL && isdn->isdn != NULL) { + isc_mem_free(mctx, isdn->isdn); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_isdn(ARGS_FREESTRUCT) { + dns_rdata_isdn_t *isdn = source; + + REQUIRE(isdn != NULL); + + if (isdn->mctx == NULL) { + return; + } + + if (isdn->isdn != NULL) { + isc_mem_free(isdn->mctx, isdn->isdn); + } + if (isdn->subaddress != NULL) { + isc_mem_free(isdn->mctx, isdn->subaddress); + } + isdn->mctx = NULL; +} + +static isc_result_t +additionaldata_isdn(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_isdn); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_isdn(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_isdn); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_isdn(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_isdn); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_isdn(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_isdn); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_isdn(ARGS_COMPARE) { + return (compare_isdn(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_ISDN_20_C */ diff --git a/lib/dns/rdata/generic/isdn_20.h b/lib/dns/rdata/generic/isdn_20.h new file mode 100644 index 0000000..d18aa63 --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1183 */ + +typedef struct dns_rdata_isdn { + dns_rdatacommon_t common; + isc_mem_t *mctx; + char *isdn; + char *subaddress; + uint8_t isdn_len; + uint8_t subaddress_len; +} dns_rdata_isdn_t; diff --git a/lib/dns/rdata/generic/key_25.c b/lib/dns/rdata/generic/key_25.c new file mode 100644 index 0000000..32c2db3 --- /dev/null +++ b/lib/dns/rdata/generic/key_25.c @@ -0,0 +1,469 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2535 */ + +#ifndef RDATA_GENERIC_KEY_25_C +#define RDATA_GENERIC_KEY_25_C + +#include + +#define RRTYPE_KEY_ATTRIBUTES \ + (DNS_RDATATYPEATTR_ATCNAME | DNS_RDATATYPEATTR_ZONECUTAUTH) + +/* + * RFC 2535 section 3.1.2 says that if bits 0-1 of the Flags field are + * both set, it means there is no key information and the RR stops after + * the algorithm octet. However, this only applies to KEY records, as + * indicated by the specifications of the RR types based on KEY: + * + * CDNSKEY - RFC 7344 + * DNSKEY - RFC 4034 + * RKEY - draft-reid-dnsext-rkey-00 + */ +static bool +generic_key_nokey(dns_rdatatype_t type, unsigned int flags) { + switch (type) { + case dns_rdatatype_cdnskey: + case dns_rdatatype_dnskey: + case dns_rdatatype_rkey: + return (false); + case dns_rdatatype_key: + default: + return ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY); + } +} + +static isc_result_t +generic_fromtext_key(ARGS_FROMTEXT) { + isc_token_t token; + dns_secalg_t alg; + dns_secproto_t proto; + dns_keyflags_t flags; + + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* flags */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_keyflags_fromtext(&flags, &token.value.as_textregion)); + if (type == dns_rdatatype_rkey && flags != 0U) { + RETTOK(DNS_R_FORMERR); + } + RETERR(uint16_tobuffer(flags, target)); + + /* protocol */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secproto_fromtext(&proto, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &proto, 1)); + + /* algorithm */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &alg, 1)); + + /* No Key? */ + if (generic_key_nokey(type, flags)) { + return (ISC_R_SUCCESS); + } + + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +generic_totext_key(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("[key id = 64000]")]; + unsigned int flags; + unsigned char algorithm; + char algbuf[DNS_NAME_FORMATSIZE]; + const char *keyinfo; + isc_region_t tmpr; + + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* flags */ + flags = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u", flags); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + if ((flags & DNS_KEYFLAG_KSK) != 0) { + if (flags & DNS_KEYFLAG_REVOKE) { + keyinfo = "revoked KSK"; + } else { + keyinfo = "KSK"; + } + } else { + keyinfo = "ZSK"; + } + + /* protocol */ + snprintf(buf, sizeof(buf), "%u", sr.base[0]); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* algorithm */ + algorithm = sr.base[0]; + snprintf(buf, sizeof(buf), "%u", algorithm); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + + /* No Key? */ + if (generic_key_nokey(rdata->type, flags)) { + return (ISC_R_SUCCESS); + } + + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0 && + algorithm == DNS_KEYALG_PRIVATEDNS) + { + dns_name_t name; + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &sr); + dns_name_format(&name, algbuf, sizeof(algbuf)); + } else { + dns_secalg_format((dns_secalg_t)algorithm, algbuf, + sizeof(algbuf)); + } + + /* key */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + + if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) { + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, + tctx->linebreak, target)); + } + } else { + dns_rdata_toregion(rdata, &tmpr); + snprintf(buf, sizeof(buf), "[key id = %u]", + dst_region_computeid(&tmpr)); + RETERR(str_totext(buf, target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) { + RETERR(str_totext(tctx->linebreak, target)); + } else if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" ", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(")", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) { + if (rdata->type == dns_rdatatype_dnskey || + rdata->type == dns_rdatatype_cdnskey) + { + RETERR(str_totext(" ; ", target)); + RETERR(str_totext(keyinfo, target)); + } + RETERR(str_totext("; alg = ", target)); + RETERR(str_totext(algbuf, target)); + RETERR(str_totext(" ; key id = ", target)); + dns_rdata_toregion(rdata, &tmpr); + snprintf(buf, sizeof(buf), "%u", dst_region_computeid(&tmpr)); + RETERR(str_totext(buf, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_fromwire_key(ARGS_FROMWIRE) { + unsigned char algorithm; + uint16_t flags; + isc_region_t sr; + + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + flags = (sr.base[0] << 8) | sr.base[1]; + + if (type == dns_rdatatype_rkey && flags != 0U) { + return (DNS_R_FORMERR); + } + + algorithm = sr.base[3]; + RETERR(mem_tobuffer(target, sr.base, 4)); + isc_region_consume(&sr, 4); + isc_buffer_forward(source, 4); + + if (generic_key_nokey(type, flags)) { + return (ISC_R_SUCCESS); + } + if (sr.length == 0) { + return (ISC_R_UNEXPECTEDEND); + } + + if (algorithm == DNS_KEYALG_PRIVATEDNS) { + dns_name_t name; + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + } + + isc_buffer_activeregion(source, &sr); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +fromtext_key(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromtext_key(CALL_FROMTEXT)); +} + +static isc_result_t +totext_key(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + return (generic_totext_key(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_key(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromwire_key(CALL_FROMWIRE)); +} + +static isc_result_t +towire_key(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_key(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_key); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +generic_fromstruct_key(ARGS_FROMSTRUCT) { + dns_rdata_key_t *key = source; + + REQUIRE(key != NULL); + REQUIRE(key->common.rdtype == type); + REQUIRE(key->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + if (type == dns_rdatatype_rkey) { + INSIST(key->flags == 0U); + } + + /* Flags */ + RETERR(uint16_tobuffer(key->flags, target)); + + /* Protocol */ + RETERR(uint8_tobuffer(key->protocol, target)); + + /* Algorithm */ + RETERR(uint8_tobuffer(key->algorithm, target)); + + /* Data */ + return (mem_tobuffer(target, key->data, key->datalen)); +} + +static isc_result_t +generic_tostruct_key(ARGS_TOSTRUCT) { + dns_rdata_key_t *key = target; + isc_region_t sr; + + REQUIRE(key != NULL); + REQUIRE(rdata->length != 0); + + REQUIRE(key != NULL); + REQUIRE(key->common.rdclass == rdata->rdclass); + REQUIRE(key->common.rdtype == rdata->type); + REQUIRE(!ISC_LINK_LINKED(&key->common, link)); + + dns_rdata_toregion(rdata, &sr); + + /* Flags */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + key->flags = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Protocol */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + key->protocol = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Algorithm */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + key->algorithm = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Data */ + key->datalen = sr.length; + key->data = mem_maybedup(mctx, sr.base, key->datalen); + if (key->data == NULL) { + return (ISC_R_NOMEMORY); + } + + key->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +generic_freestruct_key(ARGS_FREESTRUCT) { + dns_rdata_key_t *key = (dns_rdata_key_t *)source; + + REQUIRE(key != NULL); + + if (key->mctx == NULL) { + return; + } + + if (key->data != NULL) { + isc_mem_free(key->mctx, key->data); + } + key->mctx = NULL; +} + +static isc_result_t +fromstruct_key(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromstruct_key(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_key(ARGS_TOSTRUCT) { + dns_rdata_key_t *key = target; + + REQUIRE(key != NULL); + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + key->common.rdclass = rdata->rdclass; + key->common.rdtype = rdata->type; + ISC_LINK_INIT(&key->common, link); + + return (generic_tostruct_key(CALL_TOSTRUCT)); +} + +static void +freestruct_key(ARGS_FREESTRUCT) { + dns_rdata_key_t *key = (dns_rdata_key_t *)source; + + REQUIRE(key != NULL); + REQUIRE(key->common.rdtype == dns_rdatatype_key); + + generic_freestruct_key(source); +} + +static isc_result_t +additionaldata_key(ARGS_ADDLDATA) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_key(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_key(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_key); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_key(ARGS_CHECKNAMES) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_key(ARGS_COMPARE) { + return (compare_key(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_KEY_25_C */ diff --git a/lib/dns/rdata/generic/key_25.h b/lib/dns/rdata/generic/key_25.h new file mode 100644 index 0000000..4702434 --- /dev/null +++ b/lib/dns/rdata/generic/key_25.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2535 */ + +typedef struct dns_rdata_key { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t flags; + dns_secproto_t protocol; + dns_secalg_t algorithm; + uint16_t datalen; + unsigned char *data; +} dns_rdata_key_t; diff --git a/lib/dns/rdata/generic/keydata_65533.c b/lib/dns/rdata/generic/keydata_65533.c new file mode 100644 index 0000000..175afad --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.c @@ -0,0 +1,463 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef GENERIC_KEYDATA_65533_C +#define GENERIC_KEYDATA_65533_C 1 + +#include +#include + +#include + +#define RRTYPE_KEYDATA_ATTRIBUTES (0) + +static isc_result_t +fromtext_keydata(ARGS_FROMTEXT) { + isc_token_t token; + dns_secalg_t alg; + dns_secproto_t proto; + dns_keyflags_t flags; + uint32_t refresh, addhd, removehd; + + REQUIRE(type == dns_rdatatype_keydata); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* refresh timer */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &refresh)); + RETERR(uint32_tobuffer(refresh, target)); + + /* add hold-down */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &addhd)); + RETERR(uint32_tobuffer(addhd, target)); + + /* remove hold-down */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &removehd)); + RETERR(uint32_tobuffer(removehd, target)); + + /* flags */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_keyflags_fromtext(&flags, &token.value.as_textregion)); + RETERR(uint16_tobuffer(flags, target)); + + /* protocol */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secproto_fromtext(&proto, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &proto, 1)); + + /* algorithm */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &alg, 1)); + + /* Do we have a placeholder KEYDATA record? */ + if (flags == 0 && proto == 0 && alg == 0) { + return (ISC_R_SUCCESS); + } + + /* No Key? */ + if ((flags & 0xc000) == 0xc000) { + return (ISC_R_SUCCESS); + } + + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_keydata(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000")]; + unsigned int flags; + unsigned char proto, algorithm; + unsigned long refresh, add, deltime; + char algbuf[DNS_NAME_FORMATSIZE]; + const char *keyinfo; + + REQUIRE(rdata->type == dns_rdatatype_keydata); + + if ((tctx->flags & DNS_STYLEFLAG_KEYDATA) == 0 || rdata->length < 16) { + return (unknown_totext(rdata, tctx, target)); + } + + dns_rdata_toregion(rdata, &sr); + + /* refresh timer */ + refresh = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(refresh, target)); + RETERR(str_totext(" ", target)); + + /* add hold-down */ + add = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(add, target)); + RETERR(str_totext(" ", target)); + + /* remove hold-down */ + deltime = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(deltime, target)); + RETERR(str_totext(" ", target)); + + /* flags */ + flags = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u", flags); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + if ((flags & DNS_KEYFLAG_KSK) != 0) { + if ((flags & DNS_KEYFLAG_REVOKE) != 0) { + keyinfo = "revoked KSK"; + } else { + keyinfo = "KSK"; + } + } else { + keyinfo = "ZSK"; + } + + /* protocol */ + proto = sr.base[0]; + snprintf(buf, sizeof(buf), "%u", proto); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* algorithm */ + algorithm = sr.base[0]; + snprintf(buf, sizeof(buf), "%u", algorithm); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + + /* Do we have a placeholder KEYDATA record? */ + if (flags == 0 && proto == 0 && algorithm == 0) { + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) { + RETERR(str_totext(" ; placeholder", target)); + } + return (ISC_R_SUCCESS); + } + + /* No Key? */ + if ((flags & 0xc000) == 0xc000) { + return (ISC_R_SUCCESS); + } + + /* key */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) { + RETERR(str_totext(tctx->linebreak, target)); + } else if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" ", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(")", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0) { + isc_region_t tmpr; + char rbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char abuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char dbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_t t; + + RETERR(str_totext(" ; ", target)); + RETERR(str_totext(keyinfo, target)); + dns_secalg_format((dns_secalg_t)algorithm, algbuf, + sizeof(algbuf)); + RETERR(str_totext("; alg = ", target)); + RETERR(str_totext(algbuf, target)); + RETERR(str_totext("; key id = ", target)); + dns_rdata_toregion(rdata, &tmpr); + /* Skip over refresh, addhd, and removehd */ + isc_region_consume(&tmpr, 12); + snprintf(buf, sizeof(buf), "%u", dst_region_computeid(&tmpr)); + RETERR(str_totext(buf, target)); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + isc_stdtime_t now; + + isc_stdtime_get(&now); + + RETERR(str_totext(tctx->linebreak, target)); + RETERR(str_totext("; next refresh: ", target)); + isc_time_set(&t, refresh, 0); + isc_time_formathttptimestamp(&t, rbuf, sizeof(rbuf)); + RETERR(str_totext(rbuf, target)); + + if (add == 0U) { + RETERR(str_totext(tctx->linebreak, target)); + RETERR(str_totext("; no trust", target)); + } else { + RETERR(str_totext(tctx->linebreak, target)); + if (add < now) { + RETERR(str_totext("; trusted since: ", + target)); + } else { + RETERR(str_totext("; trust pending: ", + target)); + } + isc_time_set(&t, add, 0); + isc_time_formathttptimestamp(&t, abuf, + sizeof(abuf)); + RETERR(str_totext(abuf, target)); + } + + if (deltime != 0U) { + RETERR(str_totext(tctx->linebreak, target)); + RETERR(str_totext("; removal pending: ", + target)); + isc_time_set(&t, deltime, 0); + isc_time_formathttptimestamp(&t, dbuf, + sizeof(dbuf)); + RETERR(str_totext(dbuf, target)); + } + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_keydata(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_keydata); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_keydata(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_keydata); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_keydata(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_keydata); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_keydata(ARGS_FROMSTRUCT) { + dns_rdata_keydata_t *keydata = source; + + REQUIRE(type == dns_rdatatype_keydata); + REQUIRE(keydata != NULL); + REQUIRE(keydata->common.rdtype == type); + REQUIRE(keydata->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + /* Refresh timer */ + RETERR(uint32_tobuffer(keydata->refresh, target)); + + /* Add hold-down */ + RETERR(uint32_tobuffer(keydata->addhd, target)); + + /* Remove hold-down */ + RETERR(uint32_tobuffer(keydata->removehd, target)); + + /* Flags */ + RETERR(uint16_tobuffer(keydata->flags, target)); + + /* Protocol */ + RETERR(uint8_tobuffer(keydata->protocol, target)); + + /* Algorithm */ + RETERR(uint8_tobuffer(keydata->algorithm, target)); + + /* Data */ + return (mem_tobuffer(target, keydata->data, keydata->datalen)); +} + +static isc_result_t +tostruct_keydata(ARGS_TOSTRUCT) { + dns_rdata_keydata_t *keydata = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_keydata); + REQUIRE(keydata != NULL); + + keydata->common.rdclass = rdata->rdclass; + keydata->common.rdtype = rdata->type; + ISC_LINK_INIT(&keydata->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* Refresh timer */ + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->refresh = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* Add hold-down */ + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->addhd = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* Remove hold-down */ + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->removehd = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* Flags */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->flags = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Protocol */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->protocol = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Algorithm */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + keydata->algorithm = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Data */ + keydata->datalen = sr.length; + keydata->data = mem_maybedup(mctx, sr.base, keydata->datalen); + if (keydata->data == NULL) { + return (ISC_R_NOMEMORY); + } + + keydata->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_keydata(ARGS_FREESTRUCT) { + dns_rdata_keydata_t *keydata = (dns_rdata_keydata_t *)source; + + REQUIRE(keydata != NULL); + REQUIRE(keydata->common.rdtype == dns_rdatatype_keydata); + + if (keydata->mctx == NULL) { + return; + } + + if (keydata->data != NULL) { + isc_mem_free(keydata->mctx, keydata->data); + } + keydata->mctx = NULL; +} + +static isc_result_t +additionaldata_keydata(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_keydata); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_keydata(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_keydata); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_keydata(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_keydata); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_keydata(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_keydata); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_keydata(ARGS_COMPARE) { + return (compare_keydata(rdata1, rdata2)); +} + +#endif /* GENERIC_KEYDATA_65533_C */ diff --git a/lib/dns/rdata/generic/keydata_65533.h b/lib/dns/rdata/generic/keydata_65533.h new file mode 100644 index 0000000..598b31b --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_keydata { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint32_t refresh; /* Timer for refreshing data */ + uint32_t addhd; /* Hold-down timer for adding */ + uint32_t removehd; /* Hold-down timer for removing */ + uint16_t flags; /* Copy of DNSKEY_48 */ + dns_secproto_t protocol; + dns_secalg_t algorithm; + uint16_t datalen; + unsigned char *data; +} dns_rdata_keydata_t; diff --git a/lib/dns/rdata/generic/l32_105.c b/lib/dns/rdata/generic/l32_105.c new file mode 100644 index 0000000..7ab6518 --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_L32_105_C +#define RDATA_GENERIC_L32_105_C + +#include + +#include + +#define RRTYPE_L32_ATTRIBUTES (0) + +static isc_result_t +fromtext_l32(ARGS_FROMTEXT) { + isc_token_t token; + struct in_addr addr; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_l32); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_l32(ARGS_TOTEXT) { + isc_region_t region; + char buf[sizeof("65000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + return (inet_totext(AF_INET, tctx->flags, ®ion, target)); +} + +static isc_result_t +fromwire_l32(ARGS_FROMWIRE) { + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_l32); + + UNUSED(type); + UNUSED(options); + UNUSED(rdclass); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length != 6) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sregion.length); + return (mem_tobuffer(target, sregion.base, sregion.length)); +} + +static isc_result_t +towire_l32(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_l32(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_l32); + REQUIRE(rdata1->length == 6); + REQUIRE(rdata2->length == 6); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_l32(ARGS_FROMSTRUCT) { + dns_rdata_l32_t *l32 = source; + uint32_t n; + + REQUIRE(type == dns_rdatatype_l32); + REQUIRE(l32 != NULL); + REQUIRE(l32->common.rdtype == type); + REQUIRE(l32->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(l32->pref, target)); + n = ntohl(l32->l32.s_addr); + return (uint32_tobuffer(n, target)); +} + +static isc_result_t +tostruct_l32(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_l32_t *l32 = target; + uint32_t n; + + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(l32 != NULL); + REQUIRE(rdata->length == 6); + + UNUSED(mctx); + + l32->common.rdclass = rdata->rdclass; + l32->common.rdtype = rdata->type; + ISC_LINK_INIT(&l32->common, link); + + dns_rdata_toregion(rdata, ®ion); + l32->pref = uint16_fromregion(®ion); + n = uint32_fromregion(®ion); + l32->l32.s_addr = htonl(n); + return (ISC_R_SUCCESS); +} + +static void +freestruct_l32(ARGS_FREESTRUCT) { + dns_rdata_l32_t *l32 = source; + + REQUIRE(l32 != NULL); + REQUIRE(l32->common.rdtype == dns_rdatatype_l32); + + return; +} + +static isc_result_t +additionaldata_l32(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_l32(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_l32(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_l32); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_l32(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_l32(ARGS_COMPARE) { + return (compare_l32(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_L32_105_C */ diff --git a/lib/dns/rdata/generic/l32_105.h b/lib/dns/rdata/generic/l32_105.h new file mode 100644 index 0000000..58a76d2 --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_l32 { + dns_rdatacommon_t common; + uint16_t pref; + struct in_addr l32; +} dns_rdata_l32_t; diff --git a/lib/dns/rdata/generic/l64_106.c b/lib/dns/rdata/generic/l64_106.c new file mode 100644 index 0000000..ee28f2e --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_L64_106_C +#define RDATA_GENERIC_L64_106_C + +#include + +#include + +#define RRTYPE_L64_ATTRIBUTES (0) + +static isc_result_t +fromtext_l64(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char locator[NS_LOCATORSZ]; + + REQUIRE(type == dns_rdatatype_l64); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (locator_pton(DNS_AS_STR(token), locator) != 1) { + RETTOK(DNS_R_SYNTAX); + } + return (mem_tobuffer(target, locator, NS_LOCATORSZ)); +} + +static isc_result_t +totext_l64(ARGS_TOTEXT) { + isc_region_t region; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + snprintf(buf, sizeof(buf), "%x:%x:%x:%x", + region.base[0] << 8 | region.base[1], + region.base[2] << 8 | region.base[3], + region.base[4] << 8 | region.base[5], + region.base[6] << 8 | region.base[7]); + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_l64(ARGS_FROMWIRE) { + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_l64); + + UNUSED(type); + UNUSED(options); + UNUSED(rdclass); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length != 10) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sregion.length); + return (mem_tobuffer(target, sregion.base, sregion.length)); +} + +static isc_result_t +towire_l64(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_l64(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_l64); + REQUIRE(rdata1->length == 10); + REQUIRE(rdata2->length == 10); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_l64(ARGS_FROMSTRUCT) { + dns_rdata_l64_t *l64 = source; + + REQUIRE(type == dns_rdatatype_l64); + REQUIRE(l64 != NULL); + REQUIRE(l64->common.rdtype == type); + REQUIRE(l64->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(l64->pref, target)); + return (mem_tobuffer(target, l64->l64, sizeof(l64->l64))); +} + +static isc_result_t +tostruct_l64(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_l64_t *l64 = target; + + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(l64 != NULL); + REQUIRE(rdata->length == 10); + + UNUSED(mctx); + + l64->common.rdclass = rdata->rdclass; + l64->common.rdtype = rdata->type; + ISC_LINK_INIT(&l64->common, link); + + dns_rdata_toregion(rdata, ®ion); + l64->pref = uint16_fromregion(®ion); + memmove(l64->l64, region.base, region.length); + return (ISC_R_SUCCESS); +} + +static void +freestruct_l64(ARGS_FREESTRUCT) { + dns_rdata_l64_t *l64 = source; + + REQUIRE(l64 != NULL); + REQUIRE(l64->common.rdtype == dns_rdatatype_l64); + + return; +} + +static isc_result_t +additionaldata_l64(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_l64(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_l64(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_l64); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_l64(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_l64(ARGS_COMPARE) { + return (compare_l64(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_L64_106_C */ diff --git a/lib/dns/rdata/generic/l64_106.h b/lib/dns/rdata/generic/l64_106.h new file mode 100644 index 0000000..aa4d9b8 --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_l64 { + dns_rdatacommon_t common; + uint16_t pref; + unsigned char l64[8]; +} dns_rdata_l64_t; diff --git a/lib/dns/rdata/generic/loc_29.c b/lib/dns/rdata/generic/loc_29.c new file mode 100644 index 0000000..c05e144 --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.c @@ -0,0 +1,839 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1876 */ + +#ifndef RDATA_GENERIC_LOC_29_C +#define RDATA_GENERIC_LOC_29_C + +#define RRTYPE_LOC_ATTRIBUTES (0) + +static isc_result_t +loc_getdecimal(const char *str, unsigned long max, size_t precision, char units, + unsigned long *valuep) { + bool ok; + char *e; + size_t i; + long tmp; + unsigned long value; + + value = strtoul(str, &e, 10); + if (*e != 0 && *e != '.' && *e != units) { + return (DNS_R_SYNTAX); + } + if (value > max) { + return (ISC_R_RANGE); + } + ok = e != str; + if (*e == '.') { + e++; + for (i = 0; i < precision; i++) { + if (*e == 0 || *e == units) { + break; + } + if ((tmp = decvalue(*e++)) < 0) { + return (DNS_R_SYNTAX); + } + ok = true; + value *= 10; + value += tmp; + } + for (; i < precision; i++) { + value *= 10; + } + } else { + for (i = 0; i < precision; i++) { + value *= 10; + } + } + if (*e != 0 && *e == units) { + e++; + } + if (!ok || *e != 0) { + return (DNS_R_SYNTAX); + } + *valuep = value; + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getprecision(const char *str, unsigned char *valuep) { + unsigned long poweroften[8] = { 1, 10, 100, 1000, + 10000, 100000, 1000000, 10000000 }; + unsigned long m, cm; + bool ok; + char *e; + size_t i; + long tmp; + int man; + int exp; + + m = strtoul(str, &e, 10); + if (*e != 0 && *e != '.' && *e != 'm') { + return (DNS_R_SYNTAX); + } + if (m > 90000000) { + return (ISC_R_RANGE); + } + cm = 0; + ok = e != str; + if (*e == '.') { + e++; + for (i = 0; i < 2; i++) { + if (*e == 0 || *e == 'm') { + break; + } + if ((tmp = decvalue(*e++)) < 0) { + return (DNS_R_SYNTAX); + } + ok = true; + cm *= 10; + cm += tmp; + } + for (; i < 2; i++) { + cm *= 10; + } + } + if (*e == 'm') { + e++; + } + if (!ok || *e != 0) { + return (DNS_R_SYNTAX); + } + + /* + * We don't just multiply out as we will overflow. + */ + if (m > 0) { + for (exp = 0; exp < 7; exp++) { + if (m < poweroften[exp + 1]) { + break; + } + } + man = m / poweroften[exp]; + exp += 2; + } else if (cm >= 10) { + man = cm / 10; + exp = 1; + } else { + man = cm; + exp = 0; + } + *valuep = (man << 4) + exp; + return (ISC_R_SUCCESS); +} + +static isc_result_t +get_degrees(isc_lex_t *lexer, isc_token_t *token, unsigned long *d) { + RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number, + false)); + *d = token->value.as_ulong; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +check_coordinate(unsigned long d, unsigned long m, unsigned long s, + unsigned long maxd) { + if (d > maxd || m > 59U) { + return (ISC_R_RANGE); + } + if (d == maxd && (m != 0 || s != 0)) { + return (ISC_R_RANGE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +get_minutes(isc_lex_t *lexer, isc_token_t *token, unsigned long *m) { + RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_number, + false)); + + *m = token->value.as_ulong; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +get_seconds(isc_lex_t *lexer, isc_token_t *token, unsigned long *s) { + RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string, + false)); + RETERR(loc_getdecimal(DNS_AS_STR(*token), 59, 3, '\0', s)); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +get_direction(isc_lex_t *lexer, isc_token_t *token, const char *directions, + int *direction) { + RETERR(isc_lex_getmastertoken(lexer, token, isc_tokentype_string, + false)); + if (DNS_AS_STR(*token)[0] == directions[1] && + DNS_AS_STR(*token)[1] == 0) + { + *direction = DNS_AS_STR(*token)[0]; + return (ISC_R_SUCCESS); + } + + if (DNS_AS_STR(*token)[0] == directions[0] && + DNS_AS_STR(*token)[1] == 0) + { + *direction = DNS_AS_STR(*token)[0]; + return (ISC_R_SUCCESS); + } + + *direction = 0; + isc_lex_ungettoken(lexer, token); + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getcoordinate(isc_lex_t *lexer, unsigned long *dp, unsigned long *mp, + unsigned long *sp, const char *directions, int *directionp, + unsigned long maxd) { + isc_result_t result = ISC_R_SUCCESS; + isc_token_t token; + unsigned long d, m, s; + int direction = 0; + + m = 0; + s = 0; + + /* + * Degrees. + */ + RETERR(get_degrees(lexer, &token, &d)); + RETTOK(check_coordinate(d, m, s, maxd)); + + /* + * Minutes. + */ + RETERR(get_direction(lexer, &token, directions, &direction)); + if (direction > 0) { + goto done; + } + + RETERR(get_minutes(lexer, &token, &m)); + RETTOK(check_coordinate(d, m, s, maxd)); + + /* + * Seconds. + */ + RETERR(get_direction(lexer, &token, directions, &direction)); + if (direction > 0) { + goto done; + } + + result = get_seconds(lexer, &token, &s); + if (result == ISC_R_RANGE || result == DNS_R_SYNTAX) { + RETTOK(result); + } + RETERR(result); + RETTOK(check_coordinate(d, m, s, maxd)); + + /* + * Direction. + */ + RETERR(get_direction(lexer, &token, directions, &direction)); + if (direction == 0) { + RETERR(DNS_R_SYNTAX); + } +done: + + *directionp = direction; + *dp = d; + *mp = m; + *sp = s; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getlatitude(isc_lex_t *lexer, unsigned long *latitude) { + unsigned long d1 = 0, m1 = 0, s1 = 0; + int direction = 0; + + RETERR(loc_getcoordinate(lexer, &d1, &m1, &s1, "SN", &direction, 90U)); + + switch (direction) { + case 'N': + *latitude = 0x80000000 + (d1 * 3600 + m1 * 60) * 1000 + s1; + break; + case 'S': + *latitude = 0x80000000 - (d1 * 3600 + m1 * 60) * 1000 - s1; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getlongitude(isc_lex_t *lexer, unsigned long *longitude) { + unsigned long d2 = 0, m2 = 0, s2 = 0; + int direction = 0; + + RETERR(loc_getcoordinate(lexer, &d2, &m2, &s2, "WE", &direction, 180U)); + + switch (direction) { + case 'E': + *longitude = 0x80000000 + (d2 * 3600 + m2 * 60) * 1000 + s2; + break; + case 'W': + *longitude = 0x80000000 - (d2 * 3600 + m2 * 60) * 1000 - s2; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getaltitude(isc_lex_t *lexer, unsigned long *altitude) { + isc_token_t token; + unsigned long cm; + const char *str; + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + str = DNS_AS_STR(token); + if (DNS_AS_STR(token)[0] == '-') { + RETTOK(loc_getdecimal(str + 1, 100000, 2, 'm', &cm)); + if (cm > 10000000UL) { + RETTOK(ISC_R_RANGE); + } + *altitude = 10000000 - cm; + } else { + RETTOK(loc_getdecimal(str, 42849672, 2, 'm', &cm)); + if (cm > 4284967295UL) { + RETTOK(ISC_R_RANGE); + } + *altitude = 10000000 + cm; + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getoptionalprecision(isc_lex_t *lexer, unsigned char *valuep) { + isc_token_t token; + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + true)); + if (token.type == isc_tokentype_eol || token.type == isc_tokentype_eof) + { + isc_lex_ungettoken(lexer, &token); + return (ISC_R_NOMORE); + } + RETTOK(loc_getprecision(DNS_AS_STR(token), valuep)); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +loc_getsize(isc_lex_t *lexer, unsigned char *sizep) { + return (loc_getoptionalprecision(lexer, sizep)); +} + +static isc_result_t +loc_gethorizontalprecision(isc_lex_t *lexer, unsigned char *hpp) { + return (loc_getoptionalprecision(lexer, hpp)); +} + +static isc_result_t +loc_getverticalprecision(isc_lex_t *lexer, unsigned char *vpp) { + return (loc_getoptionalprecision(lexer, vpp)); +} + +/* The LOC record is expressed in a master file in the following format: + * + * LOC ( d1 [m1 [s1]] {"N"|"S"} d2 [m2 [s2]] + * {"E"|"W"} alt["m"] [siz["m"] [hp["m"] + * [vp["m"]]]] ) + * + * (The parentheses are used for multi-line data as specified in [RFC + * 1035] section 5.1.) + * + * where: + * + * d1: [0 .. 90] (degrees latitude) + * d2: [0 .. 180] (degrees longitude) + * m1, m2: [0 .. 59] (minutes latitude/longitude) + * s1, s2: [0 .. 59.999] (seconds latitude/longitude) + * alt: [-100000.00 .. 42849672.95] BY .01 (altitude in meters) + * siz, hp, vp: [0 .. 90000000.00] (size/precision in meters) + * + * If omitted, minutes and seconds default to zero, size defaults to 1m, + * horizontal precision defaults to 10000m, and vertical precision + * defaults to 10m. These defaults are chosen to represent typical + * ZIP/postal code area sizes, since it is often easy to find + * approximate geographical location by ZIP/postal code. + */ +static isc_result_t +fromtext_loc(ARGS_FROMTEXT) { + isc_result_t result = ISC_R_SUCCESS; + unsigned long latitude = 0; + unsigned long longitude = 0; + unsigned long altitude = 0; + unsigned char size = 0x12; /* Default: 1.00m */ + unsigned char hp = 0x16; /* Default: 10000.00 m */ + unsigned char vp = 0x13; /* Default: 10.00 m */ + unsigned char version = 0; + + REQUIRE(type == dns_rdatatype_loc); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(loc_getlatitude(lexer, &latitude)); + RETERR(loc_getlongitude(lexer, &longitude)); + RETERR(loc_getaltitude(lexer, &altitude)); + result = loc_getsize(lexer, &size); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + goto encode; + } + RETERR(result); + result = loc_gethorizontalprecision(lexer, &hp); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + goto encode; + } + RETERR(result); + result = loc_getverticalprecision(lexer, &vp); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + goto encode; + } + RETERR(result); +encode: + RETERR(mem_tobuffer(target, &version, 1)); + RETERR(mem_tobuffer(target, &size, 1)); + RETERR(mem_tobuffer(target, &hp, 1)); + RETERR(mem_tobuffer(target, &vp, 1)); + + RETERR(uint32_tobuffer(latitude, target)); + RETERR(uint32_tobuffer(longitude, target)); + RETERR(uint32_tobuffer(altitude, target)); + + return (result); +} + +static isc_result_t +totext_loc(ARGS_TOTEXT) { + int d1, m1, s1, fs1; + int d2, m2, s2, fs2; + unsigned long latitude; + unsigned long longitude; + unsigned long altitude; + bool north; + bool east; + bool below; + isc_region_t sr; + char sbuf[sizeof("90000000m")]; + char hbuf[sizeof("90000000m")]; + char vbuf[sizeof("90000000m")]; + /* "89 59 59.999 N 179 59 59.999 E " */ + /* "-42849672.95m 90000000m 90000000m 90000000m"; */ + char buf[8 * 6 + 12 * 1 + 2 * 10 + sizeof(sbuf) + sizeof(hbuf) + + sizeof(vbuf)]; + unsigned char size, hp, vp; + unsigned long poweroften[8] = { 1, 10, 100, 1000, + 10000, 100000, 1000000, 10000000 }; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_loc); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + if (sr.base[0] != 0) { + return (ISC_R_NOTIMPLEMENTED); + } + + REQUIRE(rdata->length == 16); + + size = sr.base[1]; + INSIST((size & 0x0f) < 10 && (size >> 4) < 10); + if ((size & 0x0f) > 1) { + snprintf(sbuf, sizeof(sbuf), "%lum", + (size >> 4) * poweroften[(size & 0x0f) - 2]); + } else { + snprintf(sbuf, sizeof(sbuf), "0.%02lum", + (size >> 4) * poweroften[(size & 0x0f)]); + } + hp = sr.base[2]; + INSIST((hp & 0x0f) < 10 && (hp >> 4) < 10); + if ((hp & 0x0f) > 1) { + snprintf(hbuf, sizeof(hbuf), "%lum", + (hp >> 4) * poweroften[(hp & 0x0f) - 2]); + } else { + snprintf(hbuf, sizeof(hbuf), "0.%02lum", + (hp >> 4) * poweroften[(hp & 0x0f)]); + } + vp = sr.base[3]; + INSIST((vp & 0x0f) < 10 && (vp >> 4) < 10); + if ((vp & 0x0f) > 1) { + snprintf(vbuf, sizeof(vbuf), "%lum", + (vp >> 4) * poweroften[(vp & 0x0f) - 2]); + } else { + snprintf(vbuf, sizeof(vbuf), "0.%02lum", + (vp >> 4) * poweroften[(vp & 0x0f)]); + } + isc_region_consume(&sr, 4); + + latitude = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + if (latitude >= 0x80000000) { + north = true; + latitude -= 0x80000000; + } else { + north = false; + latitude = 0x80000000 - latitude; + } + fs1 = (int)(latitude % 1000); + latitude /= 1000; + s1 = (int)(latitude % 60); + latitude /= 60; + m1 = (int)(latitude % 60); + latitude /= 60; + d1 = (int)latitude; + INSIST(latitude <= 90U); + + longitude = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + if (longitude >= 0x80000000) { + east = true; + longitude -= 0x80000000; + } else { + east = false; + longitude = 0x80000000 - longitude; + } + fs2 = (int)(longitude % 1000); + longitude /= 1000; + s2 = (int)(longitude % 60); + longitude /= 60; + m2 = (int)(longitude % 60); + longitude /= 60; + d2 = (int)longitude; + INSIST(longitude <= 180U); + + altitude = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + if (altitude < 10000000U) { + below = true; + altitude = 10000000 - altitude; + } else { + below = false; + altitude -= 10000000; + } + + snprintf(buf, sizeof(buf), + "%d %d %d.%03d %s %d %d %d.%03d %s %s%lu.%02lum %s %s %s", d1, + m1, s1, fs1, north ? "N" : "S", d2, m2, s2, fs2, + east ? "E" : "W", below ? "-" : "", altitude / 100, + altitude % 100, sbuf, hbuf, vbuf); + + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_loc(ARGS_FROMWIRE) { + isc_region_t sr; + unsigned char c; + unsigned long latitude; + unsigned long longitude; + + REQUIRE(type == dns_rdatatype_loc); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + if (sr.base[0] != 0) { + /* Treat as unknown. */ + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); + } + if (sr.length < 16) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Size. + */ + c = sr.base[1]; + if (c != 0) { + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || + ((c >> 4) & 0xf) == 0) + { + return (ISC_R_RANGE); + + /* + * Horizontal precision. + */ + } + } + + /* + * Horizontal precision. + */ + c = sr.base[2]; + if (c != 0) { + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || + ((c >> 4) & 0xf) == 0) + { + return (ISC_R_RANGE); + + /* + * Vertical precision. + */ + } + } + + /* + * Vertical precision. + */ + c = sr.base[3]; + if (c != 0) { + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || + ((c >> 4) & 0xf) == 0) + { + return (ISC_R_RANGE); + } + } + isc_region_consume(&sr, 4); + + /* + * Latitude. + */ + latitude = uint32_fromregion(&sr); + if (latitude < (0x80000000UL - 90 * 3600000) || + latitude > (0x80000000UL + 90 * 3600000)) + { + return (ISC_R_RANGE); + } + isc_region_consume(&sr, 4); + + /* + * Longitude. + */ + longitude = uint32_fromregion(&sr); + if (longitude < (0x80000000UL - 180 * 3600000) || + longitude > (0x80000000UL + 180 * 3600000)) + { + return (ISC_R_RANGE); + } + + /* + * Altitude. + * All values possible. + */ + + isc_buffer_activeregion(source, &sr); + isc_buffer_forward(source, 16); + return (mem_tobuffer(target, sr.base, 16)); +} + +static isc_result_t +towire_loc(ARGS_TOWIRE) { + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_loc); + REQUIRE(rdata->length != 0); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_loc(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_loc); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_loc(ARGS_FROMSTRUCT) { + dns_rdata_loc_t *loc = source; + uint8_t c; + + REQUIRE(type == dns_rdatatype_loc); + REQUIRE(loc != NULL); + REQUIRE(loc->common.rdtype == type); + REQUIRE(loc->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + if (loc->v.v0.version != 0) { + return (ISC_R_NOTIMPLEMENTED); + } + RETERR(uint8_tobuffer(loc->v.v0.version, target)); + + c = loc->v.v0.size; + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) { + return (ISC_R_RANGE); + } + RETERR(uint8_tobuffer(loc->v.v0.size, target)); + + c = loc->v.v0.horizontal; + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) { + return (ISC_R_RANGE); + } + RETERR(uint8_tobuffer(loc->v.v0.horizontal, target)); + + c = loc->v.v0.vertical; + if ((c & 0xf) > 9 || ((c >> 4) & 0xf) > 9 || ((c >> 4) & 0xf) == 0) { + return (ISC_R_RANGE); + } + RETERR(uint8_tobuffer(loc->v.v0.vertical, target)); + + if (loc->v.v0.latitude < (0x80000000UL - 90 * 3600000) || + loc->v.v0.latitude > (0x80000000UL + 90 * 3600000)) + { + return (ISC_R_RANGE); + } + RETERR(uint32_tobuffer(loc->v.v0.latitude, target)); + + if (loc->v.v0.longitude < (0x80000000UL - 180 * 3600000) || + loc->v.v0.longitude > (0x80000000UL + 180 * 3600000)) + { + return (ISC_R_RANGE); + } + RETERR(uint32_tobuffer(loc->v.v0.longitude, target)); + return (uint32_tobuffer(loc->v.v0.altitude, target)); +} + +static isc_result_t +tostruct_loc(ARGS_TOSTRUCT) { + dns_rdata_loc_t *loc = target; + isc_region_t r; + uint8_t version; + + REQUIRE(rdata->type == dns_rdatatype_loc); + REQUIRE(loc != NULL); + REQUIRE(rdata->length != 0); + + UNUSED(mctx); + + dns_rdata_toregion(rdata, &r); + version = uint8_fromregion(&r); + if (version != 0) { + return (ISC_R_NOTIMPLEMENTED); + } + + loc->common.rdclass = rdata->rdclass; + loc->common.rdtype = rdata->type; + ISC_LINK_INIT(&loc->common, link); + + loc->v.v0.version = version; + isc_region_consume(&r, 1); + loc->v.v0.size = uint8_fromregion(&r); + isc_region_consume(&r, 1); + loc->v.v0.horizontal = uint8_fromregion(&r); + isc_region_consume(&r, 1); + loc->v.v0.vertical = uint8_fromregion(&r); + isc_region_consume(&r, 1); + loc->v.v0.latitude = uint32_fromregion(&r); + isc_region_consume(&r, 4); + loc->v.v0.longitude = uint32_fromregion(&r); + isc_region_consume(&r, 4); + loc->v.v0.altitude = uint32_fromregion(&r); + isc_region_consume(&r, 4); + return (ISC_R_SUCCESS); +} + +static void +freestruct_loc(ARGS_FREESTRUCT) { + dns_rdata_loc_t *loc = source; + + REQUIRE(loc != NULL); + REQUIRE(loc->common.rdtype == dns_rdatatype_loc); + + UNUSED(source); + UNUSED(loc); +} + +static isc_result_t +additionaldata_loc(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_loc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_loc(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_loc); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_loc(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_loc); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_loc(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_loc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_loc(ARGS_COMPARE) { + return (compare_loc(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_LOC_29_C */ diff --git a/lib/dns/rdata/generic/loc_29.h b/lib/dns/rdata/generic/loc_29.h new file mode 100644 index 0000000..635563a --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1876 */ + +typedef struct dns_rdata_loc_0 { + uint8_t version; /* must be first and zero */ + uint8_t size; + uint8_t horizontal; + uint8_t vertical; + uint32_t latitude; + uint32_t longitude; + uint32_t altitude; +} dns_rdata_loc_0_t; + +typedef struct dns_rdata_loc { + dns_rdatacommon_t common; + union { + dns_rdata_loc_0_t v0; + } v; +} dns_rdata_loc_t; diff --git a/lib/dns/rdata/generic/lp_107.c b/lib/dns/rdata/generic/lp_107.c new file mode 100644 index 0000000..c4342c0 --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_LP_107_C +#define RDATA_GENERIC_LP_107_C + +#include + +#include + +#define RRTYPE_LP_ATTRIBUTES (0) + +static isc_result_t +fromtext_lp(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_lp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + return (dns_name_fromtext(&name, &buffer, origin, options, target)); +} + +static isc_result_t +totext_lp(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_lp); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_lp(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_lp); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sregion.base, 2)); + isc_buffer_forward(source, 2); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_lp(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_lp); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_lp(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_lp); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_lp(ARGS_FROMSTRUCT) { + dns_rdata_lp_t *lp = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_lp); + REQUIRE(lp != NULL); + REQUIRE(lp->common.rdtype == type); + REQUIRE(lp->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(lp->pref, target)); + dns_name_toregion(&lp->lp, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_lp(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_lp_t *lp = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_lp); + REQUIRE(lp != NULL); + REQUIRE(rdata->length != 0); + + lp->common.rdclass = rdata->rdclass; + lp->common.rdtype = rdata->type; + ISC_LINK_INIT(&lp->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + lp->pref = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + dns_name_init(&lp->lp, NULL); + name_duporclone(&name, mctx, &lp->lp); + lp->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_lp(ARGS_FREESTRUCT) { + dns_rdata_lp_t *lp = source; + + REQUIRE(lp != NULL); + REQUIRE(lp->common.rdtype == dns_rdatatype_lp); + + if (lp->mctx == NULL) { + return; + } + + dns_name_free(&lp->lp, lp->mctx); + lp->mctx = NULL; +} + +static isc_result_t +additionaldata_lp(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_lp); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + result = (add)(arg, &name, dns_rdatatype_l32, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + return ((add)(arg, &name, dns_rdatatype_l64, NULL)); +} + +static isc_result_t +digest_lp(ARGS_DIGEST) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_lp); + + dns_rdata_toregion(rdata, ®ion); + return ((digest)(arg, ®ion)); +} + +static bool +checkowner_lp(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_lp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(name); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_lp(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_lp); + + UNUSED(bad); + UNUSED(owner); + + return (true); +} + +static int +casecompare_lp(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_lp); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + order = memcmp(rdata1->data, rdata2->data, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +#endif /* RDATA_GENERIC_LP_107_C */ diff --git a/lib/dns/rdata/generic/lp_107.h b/lib/dns/rdata/generic/lp_107.h new file mode 100644 index 0000000..09031c4 --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_lp { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t pref; + dns_name_t lp; +} dns_rdata_lp_t; diff --git a/lib/dns/rdata/generic/mb_7.c b/lib/dns/rdata/generic/mb_7.c new file mode 100644 index 0000000..23e9e09 --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MB_7_C +#define RDATA_GENERIC_MB_7_C + +#define RRTYPE_MB_ATTRIBUTES (0) + +static isc_result_t +fromtext_mb(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_mb); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_mb(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_mb); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_mb(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_mb); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_mb(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mb); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_mb(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_mb); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_mb(ARGS_FROMSTRUCT) { + dns_rdata_mb_t *mb = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mb); + REQUIRE(mb != NULL); + REQUIRE(mb->common.rdtype == type); + REQUIRE(mb->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&mb->mb, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_mb(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_mb_t *mb = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mb); + REQUIRE(mb != NULL); + REQUIRE(rdata->length != 0); + + mb->common.rdclass = rdata->rdclass; + mb->common.rdtype = rdata->type; + ISC_LINK_INIT(&mb->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&mb->mb, NULL); + name_duporclone(&name, mctx, &mb->mb); + mb->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_mb(ARGS_FREESTRUCT) { + dns_rdata_mb_t *mb = source; + + REQUIRE(mb != NULL); + + if (mb->mctx == NULL) { + return; + } + + dns_name_free(&mb->mb, mb->mctx); + mb->mctx = NULL; +} + +static isc_result_t +additionaldata_mb(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mb); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_mb(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mb); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_mb(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_mb); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_ismailbox(name)); +} + +static bool +checknames_mb(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_mb); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_mb(ARGS_COMPARE) { + return (compare_mb(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MB_7_C */ diff --git a/lib/dns/rdata/generic/mb_7.h b/lib/dns/rdata/generic/mb_7.h new file mode 100644 index 0000000..187e657 --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_mb { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mb; +} dns_rdata_mb_t; diff --git a/lib/dns/rdata/generic/md_3.c b/lib/dns/rdata/generic/md_3.c new file mode 100644 index 0000000..38d48d3 --- /dev/null +++ b/lib/dns/rdata/generic/md_3.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MD_3_C +#define RDATA_GENERIC_MD_3_C + +#define RRTYPE_MD_ATTRIBUTES (0) + +static isc_result_t +fromtext_md(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_md); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_md(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_md); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_md(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_md); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_md(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_md); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_md(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_md); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_md(ARGS_FROMSTRUCT) { + dns_rdata_md_t *md = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_md); + REQUIRE(md != NULL); + REQUIRE(md->common.rdtype == type); + REQUIRE(md->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&md->md, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_md(ARGS_TOSTRUCT) { + dns_rdata_md_t *md = target; + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_md); + REQUIRE(md != NULL); + REQUIRE(rdata->length != 0); + + md->common.rdclass = rdata->rdclass; + md->common.rdtype = rdata->type; + ISC_LINK_INIT(&md->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, &r); + dns_name_fromregion(&name, &r); + dns_name_init(&md->md, NULL); + name_duporclone(&name, mctx, &md->md); + md->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_md(ARGS_FREESTRUCT) { + dns_rdata_md_t *md = source; + + REQUIRE(md != NULL); + REQUIRE(md->common.rdtype == dns_rdatatype_md); + + if (md->mctx == NULL) { + return; + } + + dns_name_free(&md->md, md->mctx); + md->mctx = NULL; +} + +static isc_result_t +additionaldata_md(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_md); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_md(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_md); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_md(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_md); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_md(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_md); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_md(ARGS_COMPARE) { + return (compare_md(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MD_3_C */ diff --git a/lib/dns/rdata/generic/md_3.h b/lib/dns/rdata/generic/md_3.h new file mode 100644 index 0000000..530e2cb --- /dev/null +++ b/lib/dns/rdata/generic/md_3.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_md { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t md; +} dns_rdata_md_t; diff --git a/lib/dns/rdata/generic/mf_4.c b/lib/dns/rdata/generic/mf_4.c new file mode 100644 index 0000000..3590767 --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MF_4_C +#define RDATA_GENERIC_MF_4_C + +#define RRTYPE_MF_ATTRIBUTES (0) + +static isc_result_t +fromtext_mf(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_mf); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_mf(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_mf); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_mf(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_mf); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_mf(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mf); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_mf(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_mf); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_mf(ARGS_FROMSTRUCT) { + dns_rdata_mf_t *mf = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mf); + REQUIRE(mf != NULL); + REQUIRE(mf->common.rdtype == type); + REQUIRE(mf->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&mf->mf, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_mf(ARGS_TOSTRUCT) { + dns_rdata_mf_t *mf = target; + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mf); + REQUIRE(mf != NULL); + REQUIRE(rdata->length != 0); + + mf->common.rdclass = rdata->rdclass; + mf->common.rdtype = rdata->type; + ISC_LINK_INIT(&mf->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, &r); + dns_name_fromregion(&name, &r); + dns_name_init(&mf->mf, NULL); + name_duporclone(&name, mctx, &mf->mf); + mf->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_mf(ARGS_FREESTRUCT) { + dns_rdata_mf_t *mf = source; + + REQUIRE(mf != NULL); + REQUIRE(mf->common.rdtype == dns_rdatatype_mf); + + if (mf->mctx == NULL) { + return; + } + dns_name_free(&mf->mf, mf->mctx); + mf->mctx = NULL; +} + +static isc_result_t +additionaldata_mf(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mf); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_mf(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mf); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_mf(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_mf); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_mf(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_mf); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_mf(ARGS_COMPARE) { + return (compare_mf(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MF_4_C */ diff --git a/lib/dns/rdata/generic/mf_4.h b/lib/dns/rdata/generic/mf_4.h new file mode 100644 index 0000000..eb1559a --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_mf { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mf; +} dns_rdata_mf_t; diff --git a/lib/dns/rdata/generic/mg_8.c b/lib/dns/rdata/generic/mg_8.c new file mode 100644 index 0000000..201a295 --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MG_8_C +#define RDATA_GENERIC_MG_8_C + +#define RRTYPE_MG_ATTRIBUTES (0) + +static isc_result_t +fromtext_mg(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_mg); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_mg(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_mg); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_mg(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_mg); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_mg(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mg); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_mg(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_mg); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_mg(ARGS_FROMSTRUCT) { + dns_rdata_mg_t *mg = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mg); + REQUIRE(mg != NULL); + REQUIRE(mg->common.rdtype == type); + REQUIRE(mg->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&mg->mg, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_mg(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_mg_t *mg = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mg); + REQUIRE(mg != NULL); + REQUIRE(rdata->length != 0); + + mg->common.rdclass = rdata->rdclass; + mg->common.rdtype = rdata->type; + ISC_LINK_INIT(&mg->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&mg->mg, NULL); + name_duporclone(&name, mctx, &mg->mg); + mg->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_mg(ARGS_FREESTRUCT) { + dns_rdata_mg_t *mg = source; + + REQUIRE(mg != NULL); + REQUIRE(mg->common.rdtype == dns_rdatatype_mg); + + if (mg->mctx == NULL) { + return; + } + dns_name_free(&mg->mg, mg->mctx); + mg->mctx = NULL; +} + +static isc_result_t +additionaldata_mg(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_mg); + + UNUSED(add); + UNUSED(arg); + UNUSED(rdata); + UNUSED(owner); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_mg(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mg); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_mg(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_mg); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_ismailbox(name)); +} + +static bool +checknames_mg(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_mg); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_mg(ARGS_COMPARE) { + return (compare_mg(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MG_8_C */ diff --git a/lib/dns/rdata/generic/mg_8.h b/lib/dns/rdata/generic/mg_8.h new file mode 100644 index 0000000..09fd206 --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_mg { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mg; +} dns_rdata_mg_t; diff --git a/lib/dns/rdata/generic/minfo_14.c b/lib/dns/rdata/generic/minfo_14.c new file mode 100644 index 0000000..38ff8bd --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MINFO_14_C +#define RDATA_GENERIC_MINFO_14_C + +#define RRTYPE_MINFO_ATTRIBUTES (0) + +static isc_result_t +fromtext_minfo(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + int i; + bool ok; + + REQUIRE(type == dns_rdatatype_minfo); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + if (origin == NULL) { + origin = dns_rootname; + } + + for (i = 0; i < 2; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ismailbox(&name); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_minfo(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t rmail; + dns_name_t email; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_minfo); + REQUIRE(rdata->length != 0); + + dns_name_init(&rmail, NULL); + dns_name_init(&email, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, rmail.length); + + dns_name_fromregion(&email, ®ion); + isc_region_consume(®ion, email.length); + + sub = name_prefix(&rmail, tctx->origin, &prefix); + + RETERR(dns_name_totext(&prefix, sub, target)); + + RETERR(str_totext(" ", target)); + + sub = name_prefix(&email, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_minfo(ARGS_FROMWIRE) { + dns_name_t rmail; + dns_name_t email; + + REQUIRE(type == dns_rdatatype_minfo); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&rmail, NULL); + dns_name_init(&email, NULL); + + RETERR(dns_name_fromwire(&rmail, source, dctx, options, target)); + return (dns_name_fromwire(&email, source, dctx, options, target)); +} + +static isc_result_t +towire_minfo(ARGS_TOWIRE) { + isc_region_t region; + dns_name_t rmail; + dns_name_t email; + dns_offsets_t roffsets; + dns_offsets_t eoffsets; + + REQUIRE(rdata->type == dns_rdatatype_minfo); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&rmail, roffsets); + dns_name_init(&email, eoffsets); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, name_length(&rmail)); + + RETERR(dns_name_towire(&rmail, cctx, target)); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, rmail.length); + + return (dns_name_towire(&rmail, cctx, target)); +} + +static int +compare_minfo(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_minfo); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + return (order); +} + +static isc_result_t +fromstruct_minfo(ARGS_FROMSTRUCT) { + dns_rdata_minfo_t *minfo = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_minfo); + REQUIRE(minfo != NULL); + REQUIRE(minfo->common.rdtype == type); + REQUIRE(minfo->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&minfo->rmailbox, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + dns_name_toregion(&minfo->emailbox, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_minfo(ARGS_TOSTRUCT) { + dns_rdata_minfo_t *minfo = target; + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_minfo); + REQUIRE(minfo != NULL); + REQUIRE(rdata->length != 0); + + minfo->common.rdclass = rdata->rdclass; + minfo->common.rdtype = rdata->type; + ISC_LINK_INIT(&minfo->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&minfo->rmailbox, NULL); + name_duporclone(&name, mctx, &minfo->rmailbox); + isc_region_consume(®ion, name_length(&name)); + + dns_name_fromregion(&name, ®ion); + dns_name_init(&minfo->emailbox, NULL); + name_duporclone(&name, mctx, &minfo->emailbox); + minfo->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_minfo(ARGS_FREESTRUCT) { + dns_rdata_minfo_t *minfo = source; + + REQUIRE(minfo != NULL); + REQUIRE(minfo->common.rdtype == dns_rdatatype_minfo); + + if (minfo->mctx == NULL) { + return; + } + + dns_name_free(&minfo->rmailbox, minfo->mctx); + dns_name_free(&minfo->emailbox, minfo->mctx); + minfo->mctx = NULL; +} + +static isc_result_t +additionaldata_minfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_minfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_minfo(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_minfo); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + result = dns_name_digest(&name, digest, arg); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_region_consume(&r, name_length(&name)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_minfo(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_minfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_minfo(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_minfo); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ismailbox(&name)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + isc_region_consume(®ion, name_length(&name)); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ismailbox(&name)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_minfo(ARGS_COMPARE) { + return (compare_minfo(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MINFO_14_C */ diff --git a/lib/dns/rdata/generic/minfo_14.h b/lib/dns/rdata/generic/minfo_14.h new file mode 100644 index 0000000..f2a530a --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_minfo { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t rmailbox; + dns_name_t emailbox; +} dns_rdata_minfo_t; diff --git a/lib/dns/rdata/generic/mr_9.c b/lib/dns/rdata/generic/mr_9.c new file mode 100644 index 0000000..4ea279b --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MR_9_C +#define RDATA_GENERIC_MR_9_C + +#define RRTYPE_MR_ATTRIBUTES (0) + +static isc_result_t +fromtext_mr(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_mr); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_mr(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_mr); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_mr(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_mr); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_mr(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mr); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_mr(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_mr); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_mr(ARGS_FROMSTRUCT) { + dns_rdata_mr_t *mr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mr); + REQUIRE(mr != NULL); + REQUIRE(mr->common.rdtype == type); + REQUIRE(mr->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&mr->mr, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_mr(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_mr_t *mr = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mr); + REQUIRE(mr != NULL); + REQUIRE(rdata->length != 0); + + mr->common.rdclass = rdata->rdclass; + mr->common.rdtype = rdata->type; + ISC_LINK_INIT(&mr->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&mr->mr, NULL); + name_duporclone(&name, mctx, &mr->mr); + mr->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_mr(ARGS_FREESTRUCT) { + dns_rdata_mr_t *mr = source; + + REQUIRE(mr != NULL); + REQUIRE(mr->common.rdtype == dns_rdatatype_mr); + + if (mr->mctx == NULL) { + return; + } + dns_name_free(&mr->mr, mr->mctx); + mr->mctx = NULL; +} + +static isc_result_t +additionaldata_mr(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_mr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_mr(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mr); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_mr(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_mr); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_mr(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_mr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_mr(ARGS_COMPARE) { + return (compare_mr(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MR_9_C */ diff --git a/lib/dns/rdata/generic/mr_9.h b/lib/dns/rdata/generic/mr_9.h new file mode 100644 index 0000000..e9a876e --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_mr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mr; +} dns_rdata_mr_t; diff --git a/lib/dns/rdata/generic/mx_15.c b/lib/dns/rdata/generic/mx_15.c new file mode 100644 index 0000000..9356f0d --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_MX_15_C +#define RDATA_GENERIC_MX_15_C + +#include + +#include + +#include + +#define RRTYPE_MX_ATTRIBUTES (0) + +static bool +check_mx(isc_token_t *token) { + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123.")]; + struct in_addr addr; + struct in6_addr addr6; + + if (strlcpy(tmp, DNS_AS_STR(*token), sizeof(tmp)) >= sizeof(tmp)) { + return (true); + } + + if (tmp[strlen(tmp) - 1] == '.') { + tmp[strlen(tmp) - 1] = '\0'; + } + if (inet_pton(AF_INET, tmp, &addr) == 1 || + inet_pton(AF_INET6, tmp, &addr6) == 1) + { + return (false); + } + + return (true); +} + +static isc_result_t +fromtext_mx(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool ok; + + REQUIRE(type == dns_rdatatype_mx); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + ok = true; + if ((options & DNS_RDATA_CHECKMX) != 0) { + ok = check_mx(&token); + } + if (!ok && (options & DNS_RDATA_CHECKMXFAIL) != 0) { + RETTOK(DNS_R_MXISADDRESS); + } + if (!ok && callbacks != NULL) { + warn_badmx(&token, lexer, callbacks); + } + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_mx(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_mx); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_mx(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_mx); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sregion.base, 2)); + isc_buffer_forward(source, 2); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_mx(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mx); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_rdata_toregion(rdata, ®ion); + RETERR(mem_tobuffer(target, region.base, 2)); + isc_region_consume(®ion, 2); + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_mx(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_mx); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + order = memcmp(rdata1->data, rdata2->data, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_mx(ARGS_FROMSTRUCT) { + dns_rdata_mx_t *mx = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mx); + REQUIRE(mx != NULL); + REQUIRE(mx->common.rdtype == type); + REQUIRE(mx->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(mx->pref, target)); + dns_name_toregion(&mx->mx, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_mx(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_mx_t *mx = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mx); + REQUIRE(mx != NULL); + REQUIRE(rdata->length != 0); + + mx->common.rdclass = rdata->rdclass; + mx->common.rdtype = rdata->type; + ISC_LINK_INIT(&mx->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + mx->pref = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + dns_name_init(&mx->mx, NULL); + name_duporclone(&name, mctx, &mx->mx); + mx->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_mx(ARGS_FREESTRUCT) { + dns_rdata_mx_t *mx = source; + + REQUIRE(mx != NULL); + REQUIRE(mx->common.rdtype == dns_rdatatype_mx); + + if (mx->mctx == NULL) { + return; + } + + dns_name_free(&mx->mx, mx->mctx); + mx->mctx = NULL; +} + +static unsigned char port25_offset[] = { 0, 3 }; +static unsigned char port25_ndata[] = "\003_25\004_tcp"; +static dns_name_t port25 = DNS_NAME_INITNONABSOLUTE(port25_ndata, + port25_offset); + +static isc_result_t +additionaldata_mx(ARGS_ADDLDATA) { + isc_result_t result; + dns_fixedname_t fixed; + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_mx); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + if (dns_name_equal(&name, dns_rootname)) { + return (ISC_R_SUCCESS); + } + + result = (add)(arg, &name, dns_rdatatype_a, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_fixedname_init(&fixed); + result = dns_name_concatenate(&port25, &name, + dns_fixedname_name(&fixed), NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa, + NULL)); +} + +static isc_result_t +digest_mx(ARGS_DIGEST) { + isc_region_t r1, r2; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mx); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 2); + r1.length = 2; + RETERR((digest)(arg, &r1)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_mx(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_mx); + + UNUSED(type); + UNUSED(rdclass); + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_mx(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_mx); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_mx(ARGS_COMPARE) { + return (compare_mx(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_MX_15_C */ diff --git a/lib/dns/rdata/generic/mx_15.h b/lib/dns/rdata/generic/mx_15.h new file mode 100644 index 0000000..f651591 --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_mx { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t pref; + dns_name_t mx; +} dns_rdata_mx_t; diff --git a/lib/dns/rdata/generic/naptr_35.c b/lib/dns/rdata/generic/naptr_35.c new file mode 100644 index 0000000..3db485a --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2915 */ + +#ifndef RDATA_GENERIC_NAPTR_35_C +#define RDATA_GENERIC_NAPTR_35_C + +#define RRTYPE_NAPTR_ATTRIBUTES (0) + +#include + +/* + * Check the wire format of the Regexp field. + * Don't allow embedded NUL's. + */ +static isc_result_t +txt_valid_regex(const unsigned char *txt) { + unsigned int nsub = 0; + char regex[256]; + char *cp; + bool flags = false; + bool replace = false; + unsigned char c; + unsigned char delim; + unsigned int len; + int n; + + len = *txt++; + if (len == 0U) { + return (ISC_R_SUCCESS); + } + + delim = *txt++; + len--; + + /* + * Digits, backslash and flags can't be delimiters. + */ + switch (delim) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '\\': + case 'i': + case 0: + return (DNS_R_SYNTAX); + } + + cp = regex; + while (len-- > 0) { + c = *txt++; + if (c == 0) { + return (DNS_R_SYNTAX); + } + if (c == delim && !replace) { + replace = true; + continue; + } else if (c == delim && !flags) { + flags = true; + continue; + } else if (c == delim) { + return (DNS_R_SYNTAX); + } + /* + * Flags are not escaped. + */ + if (flags) { + switch (c) { + case 'i': + continue; + default: + return (DNS_R_SYNTAX); + } + } + if (!replace) { + *cp++ = c; + } + if (c == '\\') { + if (len == 0) { + return (DNS_R_SYNTAX); + } + c = *txt++; + if (c == 0) { + return (DNS_R_SYNTAX); + } + len--; + if (replace) { + switch (c) { + case '0': + return (DNS_R_SYNTAX); + case '1': + if (nsub < 1) { + nsub = 1; + } + break; + case '2': + if (nsub < 2) { + nsub = 2; + } + break; + case '3': + if (nsub < 3) { + nsub = 3; + } + break; + case '4': + if (nsub < 4) { + nsub = 4; + } + break; + case '5': + if (nsub < 5) { + nsub = 5; + } + break; + case '6': + if (nsub < 6) { + nsub = 6; + } + break; + case '7': + if (nsub < 7) { + nsub = 7; + } + break; + case '8': + if (nsub < 8) { + nsub = 8; + } + break; + case '9': + if (nsub < 9) { + nsub = 9; + } + break; + } + } + if (!replace) { + *cp++ = c; + } + } + } + if (!flags) { + return (DNS_R_SYNTAX); + } + *cp = '\0'; + n = isc_regex_validate(regex); + if (n < 0 || nsub > (unsigned int)n) { + return (DNS_R_SYNTAX); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromtext_naptr(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + unsigned char *regex; + + REQUIRE(type == dns_rdatatype_naptr); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Order. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Preference. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Flags. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + + /* + * Service. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + + /* + * Regexp. + */ + regex = isc_buffer_used(target); + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + RETTOK(txt_valid_regex(regex)); + + /* + * Replacement. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_naptr(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_naptr); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + + /* + * Order. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Preference. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Flags. + */ + RETERR(txt_totext(®ion, true, target)); + RETERR(str_totext(" ", target)); + + /* + * Service. + */ + RETERR(txt_totext(®ion, true, target)); + RETERR(str_totext(" ", target)); + + /* + * Regexp. + */ + RETERR(txt_totext(®ion, true, target)); + RETERR(str_totext(" ", target)); + + /* + * Replacement. + */ + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_naptr(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sr; + unsigned char *regex; + + REQUIRE(type == dns_rdatatype_naptr); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + /* + * Order, preference. + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, 4)); + isc_buffer_forward(source, 4); + + /* + * Flags. + */ + RETERR(txt_fromwire(source, target)); + + /* + * Service. + */ + RETERR(txt_fromwire(source, target)); + + /* + * Regexp. + */ + regex = isc_buffer_used(target); + RETERR(txt_fromwire(source, target)); + RETERR(txt_valid_regex(regex)); + + /* + * Replacement. + */ + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_naptr(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_naptr); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + /* + * Order, preference. + */ + dns_rdata_toregion(rdata, &sr); + RETERR(mem_tobuffer(target, sr.base, 4)); + isc_region_consume(&sr, 4); + + /* + * Flags. + */ + RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1)); + isc_region_consume(&sr, sr.base[0] + 1); + + /* + * Service. + */ + RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1)); + isc_region_consume(&sr, sr.base[0] + 1); + + /* + * Regexp. + */ + RETERR(mem_tobuffer(target, sr.base, sr.base[0] + 1)); + isc_region_consume(&sr, sr.base[0] + 1); + + /* + * Replacement. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_naptr(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order, len; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_naptr); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + /* + * Order, preference. + */ + order = memcmp(region1.base, region2.base, 4); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(®ion1, 4); + isc_region_consume(®ion2, 4); + + /* + * Flags. + */ + len = ISC_MIN(region1.base[0], region2.base[0]); + order = memcmp(region1.base, region2.base, len + 1); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(®ion1, region1.base[0] + 1); + isc_region_consume(®ion2, region2.base[0] + 1); + + /* + * Service. + */ + len = ISC_MIN(region1.base[0], region2.base[0]); + order = memcmp(region1.base, region2.base, len + 1); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(®ion1, region1.base[0] + 1); + isc_region_consume(®ion2, region2.base[0] + 1); + + /* + * Regexp. + */ + len = ISC_MIN(region1.base[0], region2.base[0]); + order = memcmp(region1.base, region2.base, len + 1); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(®ion1, region1.base[0] + 1); + isc_region_consume(®ion2, region2.base[0] + 1); + + /* + * Replacement. + */ + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_naptr(ARGS_FROMSTRUCT) { + dns_rdata_naptr_t *naptr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_naptr); + REQUIRE(naptr != NULL); + REQUIRE(naptr->common.rdtype == type); + REQUIRE(naptr->common.rdclass == rdclass); + REQUIRE(naptr->flags != NULL || naptr->flags_len == 0); + REQUIRE(naptr->service != NULL || naptr->service_len == 0); + REQUIRE(naptr->regexp != NULL || naptr->regexp_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(naptr->order, target)); + RETERR(uint16_tobuffer(naptr->preference, target)); + RETERR(uint8_tobuffer(naptr->flags_len, target)); + RETERR(mem_tobuffer(target, naptr->flags, naptr->flags_len)); + RETERR(uint8_tobuffer(naptr->service_len, target)); + RETERR(mem_tobuffer(target, naptr->service, naptr->service_len)); + RETERR(uint8_tobuffer(naptr->regexp_len, target)); + RETERR(mem_tobuffer(target, naptr->regexp, naptr->regexp_len)); + dns_name_toregion(&naptr->replacement, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_naptr(ARGS_TOSTRUCT) { + dns_rdata_naptr_t *naptr = target; + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_naptr); + REQUIRE(naptr != NULL); + REQUIRE(rdata->length != 0); + + naptr->common.rdclass = rdata->rdclass; + naptr->common.rdtype = rdata->type; + ISC_LINK_INIT(&naptr->common, link); + + naptr->flags = NULL; + naptr->service = NULL; + naptr->regexp = NULL; + + dns_rdata_toregion(rdata, &r); + + naptr->order = uint16_fromregion(&r); + isc_region_consume(&r, 2); + + naptr->preference = uint16_fromregion(&r); + isc_region_consume(&r, 2); + + naptr->flags_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + INSIST(naptr->flags_len <= r.length); + naptr->flags = mem_maybedup(mctx, r.base, naptr->flags_len); + if (naptr->flags == NULL) { + goto cleanup; + } + isc_region_consume(&r, naptr->flags_len); + + naptr->service_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + INSIST(naptr->service_len <= r.length); + naptr->service = mem_maybedup(mctx, r.base, naptr->service_len); + if (naptr->service == NULL) { + goto cleanup; + } + isc_region_consume(&r, naptr->service_len); + + naptr->regexp_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + INSIST(naptr->regexp_len <= r.length); + naptr->regexp = mem_maybedup(mctx, r.base, naptr->regexp_len); + if (naptr->regexp == NULL) { + goto cleanup; + } + isc_region_consume(&r, naptr->regexp_len); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + dns_name_init(&naptr->replacement, NULL); + name_duporclone(&name, mctx, &naptr->replacement); + naptr->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL && naptr->flags != NULL) { + isc_mem_free(mctx, naptr->flags); + } + if (mctx != NULL && naptr->service != NULL) { + isc_mem_free(mctx, naptr->service); + } + if (mctx != NULL && naptr->regexp != NULL) { + isc_mem_free(mctx, naptr->regexp); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_naptr(ARGS_FREESTRUCT) { + dns_rdata_naptr_t *naptr = source; + + REQUIRE(naptr != NULL); + REQUIRE(naptr->common.rdtype == dns_rdatatype_naptr); + + if (naptr->mctx == NULL) { + return; + } + + if (naptr->flags != NULL) { + isc_mem_free(naptr->mctx, naptr->flags); + } + if (naptr->service != NULL) { + isc_mem_free(naptr->mctx, naptr->service); + } + if (naptr->regexp != NULL) { + isc_mem_free(naptr->mctx, naptr->regexp); + } + dns_name_free(&naptr->replacement, naptr->mctx); + naptr->mctx = NULL; +} + +static isc_result_t +additionaldata_naptr(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t sr; + dns_rdatatype_t atype; + unsigned int i, flagslen; + char *cp; + + REQUIRE(rdata->type == dns_rdatatype_naptr); + + UNUSED(owner); + + /* + * Order, preference. + */ + dns_rdata_toregion(rdata, &sr); + isc_region_consume(&sr, 4); + + /* + * Flags. + */ + atype = 0; + flagslen = sr.base[0]; + cp = (char *)&sr.base[1]; + for (i = 0; i < flagslen; i++, cp++) { + if (*cp == 'S' || *cp == 's') { + atype = dns_rdatatype_srv; + break; + } + if (*cp == 'A' || *cp == 'a') { + atype = dns_rdatatype_a; + break; + } + } + isc_region_consume(&sr, flagslen + 1); + + /* + * Service. + */ + isc_region_consume(&sr, sr.base[0] + 1); + + /* + * Regexp. + */ + isc_region_consume(&sr, sr.base[0] + 1); + + /* + * Replacement. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + + if (atype != 0) { + return ((add)(arg, &name, atype, NULL)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_naptr(ARGS_DIGEST) { + isc_region_t r1, r2; + unsigned int length, n; + isc_result_t result; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_naptr); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + length = 0; + + /* + * Order, preference. + */ + length += 4; + isc_region_consume(&r2, 4); + + /* + * Flags. + */ + n = r2.base[0] + 1; + length += n; + isc_region_consume(&r2, n); + + /* + * Service. + */ + n = r2.base[0] + 1; + length += n; + isc_region_consume(&r2, n); + + /* + * Regexp. + */ + n = r2.base[0] + 1; + length += n; + isc_region_consume(&r2, n); + + /* + * Digest the RR up to the replacement name. + */ + r1.length = length; + result = (digest)(arg, &r1); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Replacement. + */ + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_naptr(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_naptr); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_naptr(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_naptr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_naptr(ARGS_COMPARE) { + return (compare_naptr(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NAPTR_35_C */ diff --git a/lib/dns/rdata/generic/naptr_35.h b/lib/dns/rdata/generic/naptr_35.h new file mode 100644 index 0000000..f414ddd --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2915 */ + +typedef struct dns_rdata_naptr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t order; + uint16_t preference; + char *flags; + uint8_t flags_len; + char *service; + uint8_t service_len; + char *regexp; + uint8_t regexp_len; + dns_name_t replacement; +} dns_rdata_naptr_t; diff --git a/lib/dns/rdata/generic/nid_104.c b/lib/dns/rdata/generic/nid_104.c new file mode 100644 index 0000000..8a95813 --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_NID_104_C +#define RDATA_GENERIC_NID_104_C + +#include + +#include + +#define RRTYPE_NID_ATTRIBUTES (0) + +static isc_result_t +fromtext_nid(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char locator[NS_LOCATORSZ]; + + REQUIRE(type == dns_rdatatype_nid); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (locator_pton(DNS_AS_STR(token), locator) != 1) { + RETTOK(DNS_R_SYNTAX); + } + return (mem_tobuffer(target, locator, NS_LOCATORSZ)); +} + +static isc_result_t +totext_nid(ARGS_TOTEXT) { + isc_region_t region; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + snprintf(buf, sizeof(buf), "%x:%x:%x:%x", + region.base[0] << 8 | region.base[1], + region.base[2] << 8 | region.base[3], + region.base[4] << 8 | region.base[5], + region.base[6] << 8 | region.base[7]); + return (str_totext(buf, target)); +} + +static isc_result_t +fromwire_nid(ARGS_FROMWIRE) { + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_nid); + + UNUSED(type); + UNUSED(options); + UNUSED(rdclass); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length != 10) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sregion.length); + return (mem_tobuffer(target, sregion.base, sregion.length)); +} + +static isc_result_t +towire_nid(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length == 10); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_nid(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nid); + REQUIRE(rdata1->length == 10); + REQUIRE(rdata2->length == 10); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_nid(ARGS_FROMSTRUCT) { + dns_rdata_nid_t *nid = source; + + REQUIRE(type == dns_rdatatype_nid); + REQUIRE(nid != NULL); + REQUIRE(nid->common.rdtype == type); + REQUIRE(nid->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(nid->pref, target)); + return (mem_tobuffer(target, nid->nid, sizeof(nid->nid))); +} + +static isc_result_t +tostruct_nid(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nid_t *nid = target; + + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(nid != NULL); + REQUIRE(rdata->length == 10); + + UNUSED(mctx); + + nid->common.rdclass = rdata->rdclass; + nid->common.rdtype = rdata->type; + ISC_LINK_INIT(&nid->common, link); + + dns_rdata_toregion(rdata, ®ion); + nid->pref = uint16_fromregion(®ion); + memmove(nid->nid, region.base, region.length); + return (ISC_R_SUCCESS); +} + +static void +freestruct_nid(ARGS_FREESTRUCT) { + dns_rdata_nid_t *nid = source; + + REQUIRE(nid != NULL); + REQUIRE(nid->common.rdtype == dns_rdatatype_nid); + + return; +} + +static isc_result_t +additionaldata_nid(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_nid(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length == 10); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_nid(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nid); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_nid(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_nid(ARGS_COMPARE) { + return (compare_nid(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NID_104_C */ diff --git a/lib/dns/rdata/generic/nid_104.h b/lib/dns/rdata/generic/nid_104.h new file mode 100644 index 0000000..194cefa --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_nid { + dns_rdatacommon_t common; + uint16_t pref; + unsigned char nid[8]; +} dns_rdata_nid_t; diff --git a/lib/dns/rdata/generic/ninfo_56.c b/lib/dns/rdata/generic/ninfo_56.c new file mode 100644 index 0000000..8e0fae1 --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_NINFO_56_C +#define RDATA_GENERIC_NINFO_56_C + +#define RRTYPE_NINFO_ATTRIBUTES (0) + +static isc_result_t +fromtext_ninfo(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_ninfo); + + return (generic_fromtext_txt(CALL_FROMTEXT)); +} + +static isc_result_t +totext_ninfo(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + return (generic_totext_txt(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_ninfo(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_ninfo); + + return (generic_fromwire_txt(CALL_FROMWIRE)); +} + +static isc_result_t +towire_ninfo(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_ninfo(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ninfo); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_ninfo(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_ninfo); + + return (generic_fromstruct_txt(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_ninfo(ARGS_TOSTRUCT) { + dns_rdata_ninfo_t *ninfo = target; + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + REQUIRE(ninfo != NULL); + + ninfo->common.rdclass = rdata->rdclass; + ninfo->common.rdtype = rdata->type; + ISC_LINK_INIT(&ninfo->common, link); + + return (generic_tostruct_txt(CALL_TOSTRUCT)); +} + +static void +freestruct_ninfo(ARGS_FREESTRUCT) { + dns_rdata_ninfo_t *ninfo = source; + + REQUIRE(ninfo != NULL); + REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo); + + generic_freestruct_txt(source); +} + +static isc_result_t +additionaldata_ninfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ninfo(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_ninfo(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ninfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_ninfo(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_ninfo(ARGS_COMPARE) { + return (compare_ninfo(rdata1, rdata2)); +} + +isc_result_t +dns_rdata_ninfo_first(dns_rdata_ninfo_t *ninfo) { + REQUIRE(ninfo != NULL); + REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo); + + return (generic_txt_first(ninfo)); +} + +isc_result_t +dns_rdata_ninfo_next(dns_rdata_ninfo_t *ninfo) { + REQUIRE(ninfo != NULL); + REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo); + + return (generic_txt_next(ninfo)); +} + +isc_result_t +dns_rdata_ninfo_current(dns_rdata_ninfo_t *ninfo, + dns_rdata_ninfo_string_t *string) { + REQUIRE(ninfo != NULL); + REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo); + + return (generic_txt_current(ninfo, string)); +} +#endif /* RDATA_GENERIC_NINFO_56_C */ diff --git a/lib/dns/rdata/generic/ninfo_56.h b/lib/dns/rdata/generic/ninfo_56.h new file mode 100644 index 0000000..66cf948 --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_txt_string dns_rdata_ninfo_string_t; + +typedef struct dns_rdata_txt dns_rdata_ninfo_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ + +isc_result_t +dns_rdata_ninfo_first(dns_rdata_ninfo_t *); + +isc_result_t +dns_rdata_ninfo_next(dns_rdata_ninfo_t *); + +isc_result_t +dns_rdata_ninfo_current(dns_rdata_ninfo_t *, dns_rdata_ninfo_string_t *); diff --git a/lib/dns/rdata/generic/ns_2.c b/lib/dns/rdata/generic/ns_2.c new file mode 100644 index 0000000..9fa8dba --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_NS_2_C +#define RDATA_GENERIC_NS_2_C + +#define RRTYPE_NS_ATTRIBUTES (DNS_RDATATYPEATTR_ZONECUTAUTH) + +static isc_result_t +fromtext_ns(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool ok; + + REQUIRE(type == dns_rdatatype_ns); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_ns(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_ns); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_ns(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_ns); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_ns(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_ns); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_ns(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ns); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_ns(ARGS_FROMSTRUCT) { + dns_rdata_ns_t *ns = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ns); + REQUIRE(ns != NULL); + REQUIRE(ns->common.rdtype == type); + REQUIRE(ns->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&ns->name, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_ns(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_ns_t *ns = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ns); + REQUIRE(ns != NULL); + REQUIRE(rdata->length != 0); + + ns->common.rdclass = rdata->rdclass; + ns->common.rdtype = rdata->type; + ISC_LINK_INIT(&ns->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&ns->name, NULL); + name_duporclone(&name, mctx, &ns->name); + ns->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_ns(ARGS_FREESTRUCT) { + dns_rdata_ns_t *ns = source; + + REQUIRE(ns != NULL); + + if (ns->mctx == NULL) { + return; + } + + dns_name_free(&ns->name, ns->mctx); + ns->mctx = NULL; +} + +static isc_result_t +additionaldata_ns(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_ns); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_ns(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ns); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_ns(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ns); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_ns(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ns); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_ns(ARGS_COMPARE) { + return (compare_ns(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NS_2_C */ diff --git a/lib/dns/rdata/generic/ns_2.h b/lib/dns/rdata/generic/ns_2.h new file mode 100644 index 0000000..a9abd5d --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_ns { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t name; +} dns_rdata_ns_t; diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c new file mode 100644 index 0000000..556616b --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2004 Nominet, Ltd. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* RFC 5155 */ + +#ifndef RDATA_GENERIC_NSEC3_50_C +#define RDATA_GENERIC_NSEC3_50_C + +#include +#include + +#define RRTYPE_NSEC3_ATTRIBUTES DNS_RDATATYPEATTR_DNSSEC + +static isc_result_t +fromtext_nsec3(ARGS_FROMTEXT) { + isc_token_t token; + unsigned int flags; + unsigned char hashalg; + isc_buffer_t b; + unsigned char buf[256]; + + REQUIRE(type == dns_rdatatype_nsec3); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + UNUSED(origin); + UNUSED(options); + + /* Hash. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_hashalg_fromtext(&hashalg, &token.value.as_textregion)); + RETERR(uint8_tobuffer(hashalg, target)); + + /* Flags. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + flags = token.value.as_ulong; + if (flags > 255U) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(flags, target)); + + /* Iterations. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* salt */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (token.value.as_textregion.length > (255 * 2)) { + RETTOK(DNS_R_TEXTTOOLONG); + } + if (strcmp(DNS_AS_STR(token), "-") == 0) { + RETERR(uint8_tobuffer(0, target)); + } else { + RETERR(uint8_tobuffer(strlen(DNS_AS_STR(token)) / 2, target)); + RETERR(isc_hex_decodestring(DNS_AS_STR(token), target)); + } + + /* + * Next hash a single base32hex word. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + isc_buffer_init(&b, buf, sizeof(buf)); + RETTOK(isc_base32hexnp_decodestring(DNS_AS_STR(token), &b)); + if (isc_buffer_usedlength(&b) > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(isc_buffer_usedlength(&b), target)); + RETERR(mem_tobuffer(target, &buf, isc_buffer_usedlength(&b))); + + return (typemap_fromtext(lexer, target, true)); +} + +static isc_result_t +totext_nsec3(ARGS_TOTEXT) { + isc_region_t sr; + unsigned int i, j; + unsigned char hash; + unsigned char flags; + char buf[sizeof("TYPE65535")]; + uint32_t iterations; + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* Hash */ + hash = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", hash); + RETERR(str_totext(buf, target)); + + /* Flags */ + flags = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", flags); + RETERR(str_totext(buf, target)); + + /* Iterations */ + iterations = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%u ", iterations); + RETERR(str_totext(buf, target)); + + /* Salt */ + j = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + INSIST(j <= sr.length); + + if (j != 0) { + i = sr.length; + sr.length = j; + RETERR(isc_hex_totext(&sr, 1, "", target)); + sr.length = i - j; + } else { + RETERR(str_totext("-", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + + /* Next hash */ + j = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + INSIST(j <= sr.length); + + i = sr.length; + sr.length = j; + RETERR(isc_base32hexnp_totext(&sr, 1, "", target)); + sr.length = i - j; + + /* + * Don't leave a trailing space when there's no typemap present. + */ + if (((tctx->flags & DNS_STYLEFLAG_MULTILINE) == 0) && (sr.length > 0)) { + RETERR(str_totext(" ", target)); + } + RETERR(typemap_totext(&sr, tctx, target)); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_nsec3(ARGS_FROMWIRE) { + isc_region_t sr, rr; + unsigned int saltlen, hashlen; + + REQUIRE(type == dns_rdatatype_nsec3); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(options); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sr); + rr = sr; + + /* hash(1), flags(1), iteration(2), saltlen(1) */ + if (sr.length < 5U) { + RETERR(DNS_R_FORMERR); + } + saltlen = sr.base[4]; + isc_region_consume(&sr, 5); + + if (sr.length < saltlen) { + RETERR(DNS_R_FORMERR); + } + isc_region_consume(&sr, saltlen); + + if (sr.length < 1U) { + RETERR(DNS_R_FORMERR); + } + hashlen = sr.base[0]; + isc_region_consume(&sr, 1); + + if (hashlen < 1 || sr.length < hashlen) { + RETERR(DNS_R_FORMERR); + } + isc_region_consume(&sr, hashlen); + + RETERR(typemap_test(&sr, true)); + + RETERR(mem_tobuffer(target, rr.base, rr.length)); + isc_buffer_forward(source, rr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_nsec3(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_nsec3(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsec3); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_nsec3(ARGS_FROMSTRUCT) { + dns_rdata_nsec3_t *nsec3 = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsec3); + REQUIRE(nsec3 != NULL); + REQUIRE(nsec3->common.rdtype == type); + REQUIRE(nsec3->common.rdclass == rdclass); + REQUIRE(nsec3->typebits != NULL || nsec3->len == 0); + REQUIRE(nsec3->hash == dns_hash_sha1); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(nsec3->hash, target)); + RETERR(uint8_tobuffer(nsec3->flags, target)); + RETERR(uint16_tobuffer(nsec3->iterations, target)); + RETERR(uint8_tobuffer(nsec3->salt_length, target)); + RETERR(mem_tobuffer(target, nsec3->salt, nsec3->salt_length)); + RETERR(uint8_tobuffer(nsec3->next_length, target)); + RETERR(mem_tobuffer(target, nsec3->next, nsec3->next_length)); + + region.base = nsec3->typebits; + region.length = nsec3->len; + RETERR(typemap_test(®ion, true)); + return (mem_tobuffer(target, nsec3->typebits, nsec3->len)); +} + +static isc_result_t +tostruct_nsec3(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nsec3_t *nsec3 = target; + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + REQUIRE(nsec3 != NULL); + REQUIRE(rdata->length != 0); + + nsec3->common.rdclass = rdata->rdclass; + nsec3->common.rdtype = rdata->type; + ISC_LINK_INIT(&nsec3->common, link); + + region.base = rdata->data; + region.length = rdata->length; + nsec3->hash = uint8_consume_fromregion(®ion); + nsec3->flags = uint8_consume_fromregion(®ion); + nsec3->iterations = uint16_consume_fromregion(®ion); + + nsec3->salt_length = uint8_consume_fromregion(®ion); + INSIST(nsec3->salt_length <= region.length); + nsec3->salt = mem_maybedup(mctx, region.base, nsec3->salt_length); + if (nsec3->salt == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(®ion, nsec3->salt_length); + + nsec3->next_length = uint8_consume_fromregion(®ion); + INSIST(nsec3->next_length <= region.length); + nsec3->next = mem_maybedup(mctx, region.base, nsec3->next_length); + if (nsec3->next == NULL) { + goto cleanup; + } + isc_region_consume(®ion, nsec3->next_length); + + nsec3->len = region.length; + nsec3->typebits = mem_maybedup(mctx, region.base, region.length); + if (nsec3->typebits == NULL) { + goto cleanup; + } + + nsec3->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (nsec3->next != NULL) { + isc_mem_free(mctx, nsec3->next); + } + isc_mem_free(mctx, nsec3->salt); + return (ISC_R_NOMEMORY); +} + +static void +freestruct_nsec3(ARGS_FREESTRUCT) { + dns_rdata_nsec3_t *nsec3 = source; + + REQUIRE(nsec3 != NULL); + REQUIRE(nsec3->common.rdtype == dns_rdatatype_nsec3); + + if (nsec3->mctx == NULL) { + return; + } + + if (nsec3->salt != NULL) { + isc_mem_free(nsec3->mctx, nsec3->salt); + } + if (nsec3->next != NULL) { + isc_mem_free(nsec3->mctx, nsec3->next); + } + if (nsec3->typebits != NULL) { + isc_mem_free(nsec3->mctx, nsec3->typebits); + } + nsec3->mctx = NULL; +} + +static isc_result_t +additionaldata_nsec3(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_nsec3(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_nsec3(ARGS_CHECKOWNER) { + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + isc_buffer_t buffer; + dns_label_t label; + + REQUIRE(type == dns_rdatatype_nsec3); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + /* + * First label is a base32hex string without padding. + */ + dns_name_getlabel(name, 0, &label); + isc_region_consume(&label, 1); + isc_buffer_init(&buffer, owner, sizeof(owner)); + if (isc_base32hexnp_decoderegion(&label, &buffer) == ISC_R_SUCCESS) { + return (true); + } + + return (false); +} + +static bool +checknames_nsec3(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_nsec3(ARGS_COMPARE) { + return (compare_nsec3(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NSEC3_50_C */ diff --git a/lib/dns/rdata/generic/nsec3_50.h b/lib/dns/rdata/generic/nsec3_50.h new file mode 100644 index 0000000..3152c85 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC 5155 */ + +#include + +typedef struct dns_rdata_nsec3 { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_hash_t hash; + unsigned char flags; + dns_iterations_t iterations; + unsigned char salt_length; + unsigned char next_length; + uint16_t len; + unsigned char *salt; + unsigned char *next; + unsigned char *typebits; +} dns_rdata_nsec3_t; + +/* + * The corresponding NSEC3 interval is OPTOUT indicating possible + * insecure delegations. + */ +#define DNS_NSEC3FLAG_OPTOUT 0x01U + +/*% + * The following flags are used in the private-type record (implemented in + * lib/dns/private.c) which is used to store NSEC3PARAM data during the + * time when it is not legal to have an actual NSEC3PARAM record in the + * zone. They are defined here because the private-type record uses the + * same flags field for the OPTOUT flag above and for the private flags + * below. XXX: This should be considered for refactoring. + */ + +/*% + * Non-standard, private type only. + * + * Create a corresponding NSEC3 chain. + * Once the NSEC3 chain is complete this flag will be removed to signal + * that there is a complete chain. + * + * This flag is automatically set when a NSEC3PARAM record is added to + * the zone via UPDATE. + * + * NSEC3PARAM records containing this flag should never be published, + * but if they are, they should be ignored by RFC 5155 compliant + * nameservers. + */ +#define DNS_NSEC3FLAG_CREATE 0x80U + +/*% + * Non-standard, private type only. + * + * The corresponding NSEC3 set is to be removed once the NSEC chain + * has been generated. + * + * This flag is automatically set when the last active NSEC3PARAM record + * is removed from the zone via UPDATE. + * + * NSEC3PARAM records containing this flag should never be published, + * but if they are, they should be ignored by RFC 5155 compliant + * nameservers. + */ +#define DNS_NSEC3FLAG_REMOVE 0x40U + +/*% + * Non-standard, private type only. + * + * When set with the CREATE flag, a corresponding NSEC3 chain will be + * created when the zone becomes capable of supporting one (i.e., when it + * has a DNSKEY RRset containing at least one NSEC3-capable algorithm). + * Without this flag, NSEC3 chain creation would be attempted immediately, + * fail, and the private type record would be removed. With it, the NSEC3 + * parameters are stored until they can be used. When the zone has the + * necessary prerequisites for NSEC3, then the INITIAL flag can be cleared, + * and the record will be cleaned up normally. + * + * NSEC3PARAM records containing this flag should never be published, but + * if they are, they should be ignored by RFC 5155 compliant nameservers. + */ +#define DNS_NSEC3FLAG_INITIAL 0x20U + +/*% + * Non-standard, private type only. + * + * Prevent the creation of a NSEC chain before the last NSEC3 chain + * is removed. This will normally only be set when the zone is + * transitioning from secure with NSEC3 chains to insecure. + * + * NSEC3PARAM records containing this flag should never be published, + * but if they are, they should be ignored by RFC 5155 compliant + * nameservers. + */ +#define DNS_NSEC3FLAG_NONSEC 0x10U diff --git a/lib/dns/rdata/generic/nsec3param_51.c b/lib/dns/rdata/generic/nsec3param_51.c new file mode 100644 index 0000000..1690598 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2004 Nominet, Ltd. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND NOMINET DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* RFC 5155 */ + +#ifndef RDATA_GENERIC_NSEC3PARAM_51_C +#define RDATA_GENERIC_NSEC3PARAM_51_C + +#include +#include + +#define RRTYPE_NSEC3PARAM_ATTRIBUTES (DNS_RDATATYPEATTR_DNSSEC) + +static isc_result_t +fromtext_nsec3param(ARGS_FROMTEXT) { + isc_token_t token; + unsigned int flags = 0; + unsigned char hashalg; + + REQUIRE(type == dns_rdatatype_nsec3param); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + UNUSED(origin); + UNUSED(options); + + /* Hash. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_hashalg_fromtext(&hashalg, &token.value.as_textregion)); + RETERR(uint8_tobuffer(hashalg, target)); + + /* Flags. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + flags = token.value.as_ulong; + if (flags > 255U) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(flags, target)); + + /* Iterations. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* Salt. */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (token.value.as_textregion.length > (255 * 2)) { + RETTOK(DNS_R_TEXTTOOLONG); + } + if (strcmp(DNS_AS_STR(token), "-") == 0) { + RETERR(uint8_tobuffer(0, target)); + } else { + RETERR(uint8_tobuffer(strlen(DNS_AS_STR(token)) / 2, target)); + RETERR(isc_hex_decodestring(DNS_AS_STR(token), target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_nsec3param(ARGS_TOTEXT) { + isc_region_t sr; + unsigned int i, j; + unsigned char hash; + unsigned char flags; + char buf[sizeof("65535 ")]; + uint32_t iterations; + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + hash = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + flags = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + iterations = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + snprintf(buf, sizeof(buf), "%u ", hash); + RETERR(str_totext(buf, target)); + + snprintf(buf, sizeof(buf), "%u ", flags); + RETERR(str_totext(buf, target)); + + snprintf(buf, sizeof(buf), "%u ", iterations); + RETERR(str_totext(buf, target)); + + j = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + INSIST(j <= sr.length); + + if (j != 0) { + i = sr.length; + sr.length = j; + RETERR(isc_hex_totext(&sr, 1, "", target)); + sr.length = i - j; + } else { + RETERR(str_totext("-", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_nsec3param(ARGS_FROMWIRE) { + isc_region_t sr, rr; + unsigned int saltlen; + + REQUIRE(type == dns_rdatatype_nsec3param); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(options); + UNUSED(dctx); + + isc_buffer_activeregion(source, &sr); + rr = sr; + + /* hash(1), flags(1), iterations(2), saltlen(1) */ + if (sr.length < 5U) { + RETERR(DNS_R_FORMERR); + } + saltlen = sr.base[4]; + isc_region_consume(&sr, 5); + + if (sr.length != saltlen) { + RETERR(DNS_R_FORMERR); + } + isc_region_consume(&sr, saltlen); + RETERR(mem_tobuffer(target, rr.base, rr.length)); + isc_buffer_forward(source, rr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_nsec3param(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_nsec3param(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsec3param); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_nsec3param(ARGS_FROMSTRUCT) { + dns_rdata_nsec3param_t *nsec3param = source; + + REQUIRE(type == dns_rdatatype_nsec3param); + REQUIRE(nsec3param != NULL); + REQUIRE(nsec3param->common.rdtype == type); + REQUIRE(nsec3param->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(nsec3param->hash, target)); + RETERR(uint8_tobuffer(nsec3param->flags, target)); + RETERR(uint16_tobuffer(nsec3param->iterations, target)); + RETERR(uint8_tobuffer(nsec3param->salt_length, target)); + RETERR(mem_tobuffer(target, nsec3param->salt, nsec3param->salt_length)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +tostruct_nsec3param(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nsec3param_t *nsec3param = target; + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + REQUIRE(nsec3param != NULL); + REQUIRE(rdata->length != 0); + + nsec3param->common.rdclass = rdata->rdclass; + nsec3param->common.rdtype = rdata->type; + ISC_LINK_INIT(&nsec3param->common, link); + + region.base = rdata->data; + region.length = rdata->length; + nsec3param->hash = uint8_consume_fromregion(®ion); + nsec3param->flags = uint8_consume_fromregion(®ion); + nsec3param->iterations = uint16_consume_fromregion(®ion); + + nsec3param->salt_length = uint8_consume_fromregion(®ion); + INSIST(nsec3param->salt_length == region.length); + nsec3param->salt = mem_maybedup(mctx, region.base, + nsec3param->salt_length); + if (nsec3param->salt == NULL) { + return (ISC_R_NOMEMORY); + } + isc_region_consume(®ion, nsec3param->salt_length); + + nsec3param->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_nsec3param(ARGS_FREESTRUCT) { + dns_rdata_nsec3param_t *nsec3param = source; + + REQUIRE(nsec3param != NULL); + REQUIRE(nsec3param->common.rdtype == dns_rdatatype_nsec3param); + + if (nsec3param->mctx == NULL) { + return; + } + + if (nsec3param->salt != NULL) { + isc_mem_free(nsec3param->mctx, nsec3param->salt); + } + nsec3param->mctx = NULL; +} + +static isc_result_t +additionaldata_nsec3param(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_nsec3param(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_nsec3param(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nsec3param); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_nsec3param(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_nsec3param(ARGS_COMPARE) { + return (compare_nsec3param(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NSEC3PARAM_51_C */ diff --git a/lib/dns/rdata/generic/nsec3param_51.h b/lib/dns/rdata/generic/nsec3param_51.h new file mode 100644 index 0000000..9288a96 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC 5155 */ + +#include + +typedef struct dns_rdata_nsec3param { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_hash_t hash; + unsigned char flags; /* DNS_NSEC3FLAG_* */ + dns_iterations_t iterations; + unsigned char salt_length; + unsigned char *salt; +} dns_rdata_nsec3param_t; diff --git a/lib/dns/rdata/generic/nsec_47.c b/lib/dns/rdata/generic/nsec_47.c new file mode 100644 index 0000000..77700be --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 3845 */ + +#ifndef RDATA_GENERIC_NSEC_47_C +#define RDATA_GENERIC_NSEC_47_C + +/* + * The attributes do not include DNS_RDATATYPEATTR_SINGLETON + * because we must be able to handle a parent/child NSEC pair. + */ +#define RRTYPE_NSEC_ATTRIBUTES \ + (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \ + DNS_RDATATYPEATTR_ATCNAME) + +static isc_result_t +fromtext_nsec(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_nsec); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Next domain. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + return (typemap_fromtext(lexer, target, false)); +} + +static isc_result_t +totext_nsec(ARGS_TOTEXT) { + isc_region_t sr; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_nsec); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, &sr); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_totext(&name, false, target)); + /* + * Don't leave a trailing space when there's no typemap present. + */ + if (sr.length > 0) { + RETERR(str_totext(" ", target)); + } + return (typemap_totext(&sr, NULL, target)); +} + +static isc_result_t +fromwire_nsec(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_nsec); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + isc_buffer_activeregion(source, &sr); + RETERR(typemap_test(&sr, false)); + RETERR(mem_tobuffer(target, sr.base, sr.length)); + isc_buffer_forward(source, sr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_nsec(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_nsec); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, &sr); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_towire(&name, cctx, target)); + + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_nsec(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsec); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_nsec(ARGS_FROMSTRUCT) { + dns_rdata_nsec_t *nsec = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsec); + REQUIRE(nsec != NULL); + REQUIRE(nsec->common.rdtype == type); + REQUIRE(nsec->common.rdclass == rdclass); + REQUIRE(nsec->typebits != NULL || nsec->len == 0); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&nsec->next, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + + region.base = nsec->typebits; + region.length = nsec->len; + RETERR(typemap_test(®ion, false)); + return (mem_tobuffer(target, nsec->typebits, nsec->len)); +} + +static isc_result_t +tostruct_nsec(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nsec_t *nsec = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_nsec); + REQUIRE(nsec != NULL); + REQUIRE(rdata->length != 0); + + nsec->common.rdclass = rdata->rdclass; + nsec->common.rdtype = rdata->type; + ISC_LINK_INIT(&nsec->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&nsec->next, NULL); + name_duporclone(&name, mctx, &nsec->next); + + nsec->len = region.length; + nsec->typebits = mem_maybedup(mctx, region.base, region.length); + if (nsec->typebits == NULL) { + goto cleanup; + } + + nsec->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&nsec->next, mctx); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_nsec(ARGS_FREESTRUCT) { + dns_rdata_nsec_t *nsec = source; + + REQUIRE(nsec != NULL); + REQUIRE(nsec->common.rdtype == dns_rdatatype_nsec); + + if (nsec->mctx == NULL) { + return; + } + + dns_name_free(&nsec->next, nsec->mctx); + if (nsec->typebits != NULL) { + isc_mem_free(nsec->mctx, nsec->typebits); + } + nsec->mctx = NULL; +} + +static isc_result_t +additionaldata_nsec(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_nsec(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nsec); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_nsec(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nsec); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_nsec(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nsec); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_nsec(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsec); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + return (isc_region_compare(®ion1, ®ion2)); +} +#endif /* RDATA_GENERIC_NSEC_47_C */ diff --git a/lib/dns/rdata/generic/nsec_47.h b/lib/dns/rdata/generic/nsec_47.h new file mode 100644 index 0000000..eded7ac --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC 3845 */ + +typedef struct dns_rdata_nsec { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t next; + unsigned char *typebits; + uint16_t len; +} dns_rdata_nsec_t; diff --git a/lib/dns/rdata/generic/null_10.c b/lib/dns/rdata/generic/null_10.c new file mode 100644 index 0000000..4a019dd --- /dev/null +++ b/lib/dns/rdata/generic/null_10.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_NULL_10_C +#define RDATA_GENERIC_NULL_10_C + +#define RRTYPE_NULL_ATTRIBUTES (0) + +static isc_result_t +fromtext_null(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_null); + + UNUSED(rdclass); + UNUSED(type); + UNUSED(lexer); + UNUSED(origin); + UNUSED(options); + UNUSED(target); + UNUSED(callbacks); + + return (DNS_R_SYNTAX); +} + +static isc_result_t +totext_null(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_null); + + return (unknown_totext(rdata, tctx, target)); +} + +static isc_result_t +fromwire_null(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_null); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_null(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_null); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_null(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_null); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_null(ARGS_FROMSTRUCT) { + dns_rdata_null_t *null = source; + + REQUIRE(type == dns_rdatatype_null); + REQUIRE(null != NULL); + REQUIRE(null->common.rdtype == type); + REQUIRE(null->common.rdclass == rdclass); + REQUIRE(null->data != NULL || null->length == 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, null->data, null->length)); +} + +static isc_result_t +tostruct_null(ARGS_TOSTRUCT) { + dns_rdata_null_t *null = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_null); + REQUIRE(null != NULL); + + null->common.rdclass = rdata->rdclass; + null->common.rdtype = rdata->type; + ISC_LINK_INIT(&null->common, link); + + dns_rdata_toregion(rdata, &r); + null->length = r.length; + null->data = mem_maybedup(mctx, r.base, r.length); + if (null->data == NULL) { + return (ISC_R_NOMEMORY); + } + + null->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_null(ARGS_FREESTRUCT) { + dns_rdata_null_t *null = source; + + REQUIRE(null != NULL); + REQUIRE(null->common.rdtype == dns_rdatatype_null); + + if (null->mctx == NULL) { + return; + } + + if (null->data != NULL) { + isc_mem_free(null->mctx, null->data); + } + null->mctx = NULL; +} + +static isc_result_t +additionaldata_null(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_null); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_null(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_null); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_null(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_null); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_null(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_null); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_null(ARGS_COMPARE) { + return (compare_null(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_NULL_10_C */ diff --git a/lib/dns/rdata/generic/null_10.h b/lib/dns/rdata/generic/null_10.h new file mode 100644 index 0000000..d015b7d --- /dev/null +++ b/lib/dns/rdata/generic/null_10.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_null { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t length; + unsigned char *data; +} dns_rdata_null_t; diff --git a/lib/dns/rdata/generic/nxt_30.c b/lib/dns/rdata/generic/nxt_30.c new file mode 100644 index 0000000..93d2384 --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2535 */ + +#ifndef RDATA_GENERIC_NXT_30_C +#define RDATA_GENERIC_NXT_30_C + +/* + * The attributes do not include DNS_RDATATYPEATTR_SINGLETON + * because we must be able to handle a parent/child NXT pair. + */ +#define RRTYPE_NXT_ATTRIBUTES (0) + +static isc_result_t +fromtext_nxt(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + char *e; + unsigned char bm[8 * 1024]; /* 64k bits */ + dns_rdatatype_t covered; + dns_rdatatype_t maxcovered = 0; + bool first = true; + long n; + + REQUIRE(type == dns_rdatatype_nxt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Next domain. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + memset(bm, 0, sizeof(bm)); + do { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, true)); + if (token.type != isc_tokentype_string) { + break; + } + n = strtol(DNS_AS_STR(token), &e, 10); + if (e != DNS_AS_STR(token) && *e == '\0') { + covered = (dns_rdatatype_t)n; + } else if (dns_rdatatype_fromtext(&covered, + &token.value.as_textregion) == + DNS_R_UNKNOWN) + { + RETTOK(DNS_R_UNKNOWN); + } + /* + * NXT is only specified for types 1..127. + */ + if (covered < 1 || covered > 127) { + return (ISC_R_RANGE); + } + if (first || covered > maxcovered) { + maxcovered = covered; + } + first = false; + bm[covered / 8] |= (0x80 >> (covered % 8)); + } while (1); + isc_lex_ungettoken(lexer, &token); + if (first) { + return (ISC_R_SUCCESS); + } + n = (maxcovered + 8) / 8; + return (mem_tobuffer(target, bm, n)); +} + +static isc_result_t +totext_nxt(ARGS_TOTEXT) { + isc_region_t sr; + unsigned int i, j; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_nxt); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + dns_rdata_toregion(rdata, &sr); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + for (i = 0; i < sr.length; i++) { + if (sr.base[i] != 0) { + for (j = 0; j < 8; j++) { + if ((sr.base[i] & (0x80 >> j)) != 0) { + { + dns_rdatatype_t t = i * 8 + j; + RETERR(str_totext(" ", target)); + if (dns_rdatatype_isknown(t)) { + RETERR(dns_rdatatype_totext( + t, target)); + } else { + char buf[sizeof("6553" + "5")]; + snprintf(buf, + sizeof(buf), + "%u", t); + RETERR(str_totext( + buf, target)); + } + } + } + } + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_nxt(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_nxt); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + isc_buffer_activeregion(source, &sr); + if (sr.length > 0 && ((sr.base[0] & 0x80) != 0 || sr.length > 16 || + sr.base[sr.length - 1] == 0)) + { + return (DNS_R_BADBITMAP); + } + RETERR(mem_tobuffer(target, sr.base, sr.length)); + isc_buffer_forward(source, sr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_nxt(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_nxt); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, &sr); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_towire(&name, cctx, target)); + + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_nxt(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nxt); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_nxt(ARGS_FROMSTRUCT) { + dns_rdata_nxt_t *nxt = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nxt); + REQUIRE(nxt != NULL); + REQUIRE(nxt->common.rdtype == type); + REQUIRE(nxt->common.rdclass == rdclass); + REQUIRE(nxt->typebits != NULL || nxt->len == 0); + if (nxt->typebits != NULL && (nxt->typebits[0] & 0x80) == 0) { + REQUIRE(nxt->len <= 16); + REQUIRE(nxt->typebits[nxt->len - 1] != 0); + } + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&nxt->next, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + + return (mem_tobuffer(target, nxt->typebits, nxt->len)); +} + +static isc_result_t +tostruct_nxt(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nxt_t *nxt = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_nxt); + REQUIRE(nxt != NULL); + REQUIRE(rdata->length != 0); + + nxt->common.rdclass = rdata->rdclass; + nxt->common.rdtype = rdata->type; + ISC_LINK_INIT(&nxt->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&nxt->next, NULL); + name_duporclone(&name, mctx, &nxt->next); + + nxt->len = region.length; + nxt->typebits = mem_maybedup(mctx, region.base, region.length); + if (nxt->typebits == NULL) { + goto cleanup; + } + + nxt->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&nxt->next, mctx); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_nxt(ARGS_FREESTRUCT) { + dns_rdata_nxt_t *nxt = source; + + REQUIRE(nxt != NULL); + REQUIRE(nxt->common.rdtype == dns_rdatatype_nxt); + + if (nxt->mctx == NULL) { + return; + } + + dns_name_free(&nxt->next, nxt->mctx); + if (nxt->typebits != NULL) { + isc_mem_free(nxt->mctx, nxt->typebits); + } + nxt->mctx = NULL; +} + +static isc_result_t +additionaldata_nxt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nxt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_nxt(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_nxt); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + result = dns_name_digest(&name, digest, arg); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_region_consume(&r, name_length(&name)); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_nxt(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nxt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_nxt(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nxt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_nxt(ARGS_COMPARE) { + return (compare_nxt(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_NXT_30_C */ diff --git a/lib/dns/rdata/generic/nxt_30.h b/lib/dns/rdata/generic/nxt_30.h new file mode 100644 index 0000000..fb3e8b2 --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief RFC2535 */ + +typedef struct dns_rdata_nxt { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t next; + unsigned char *typebits; + uint16_t len; +} dns_rdata_nxt_t; diff --git a/lib/dns/rdata/generic/openpgpkey_61.c b/lib/dns/rdata/generic/openpgpkey_61.c new file mode 100644 index 0000000..d4f9fa5 --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_OPENPGPKEY_61_C +#define RDATA_GENERIC_OPENPGPKEY_61_C + +#define RRTYPE_OPENPGPKEY_ATTRIBUTES 0 + +static isc_result_t +fromtext_openpgpkey(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_openpgpkey); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + UNUSED(options); + UNUSED(origin); + + /* + * Keyring. + */ + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_openpgpkey(ARGS_TOTEXT) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* + * Keyring + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( ", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) { + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, + tctx->linebreak, target)); + } + } else { + RETERR(str_totext("[omitted]", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_openpgpkey(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_openpgpkey); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + /* + * Keyring. + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_openpgpkey(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_openpgpkey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_openpgpkey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_openpgpkey(ARGS_FROMSTRUCT) { + dns_rdata_openpgpkey_t *sig = source; + + REQUIRE(type == dns_rdatatype_openpgpkey); + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == type); + REQUIRE(sig->common.rdclass == rdclass); + REQUIRE(sig->keyring != NULL && sig->length != 0); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Keyring. + */ + return (mem_tobuffer(target, sig->keyring, sig->length)); +} + +static isc_result_t +tostruct_openpgpkey(ARGS_TOSTRUCT) { + isc_region_t sr; + dns_rdata_openpgpkey_t *sig = target; + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + REQUIRE(sig != NULL); + REQUIRE(rdata->length != 0); + + sig->common.rdclass = rdata->rdclass; + sig->common.rdtype = rdata->type; + ISC_LINK_INIT(&sig->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Keyring. + */ + sig->length = sr.length; + sig->keyring = mem_maybedup(mctx, sr.base, sig->length); + if (sig->keyring == NULL) { + goto cleanup; + } + + sig->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + return (ISC_R_NOMEMORY); +} + +static void +freestruct_openpgpkey(ARGS_FREESTRUCT) { + dns_rdata_openpgpkey_t *sig = (dns_rdata_openpgpkey_t *)source; + + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == dns_rdatatype_openpgpkey); + + if (sig->mctx == NULL) { + return; + } + + if (sig->keyring != NULL) { + isc_mem_free(sig->mctx, sig->keyring); + } + sig->mctx = NULL; +} + +static isc_result_t +additionaldata_openpgpkey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_openpgpkey(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_openpgpkey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_openpgpkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_openpgpkey(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_openpgpkey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_openpgpkey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + + return (isc_region_compare(&r1, &r2)); +} + +#endif /* RDATA_GENERIC_OPENPGPKEY_61_C */ diff --git a/lib/dns/rdata/generic/openpgpkey_61.h b/lib/dns/rdata/generic/openpgpkey_61.h new file mode 100644 index 0000000..702d343 --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_openpgpkey { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t length; + unsigned char *keyring; +} dns_rdata_openpgpkey_t; diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c new file mode 100644 index 0000000..e5e7168 --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.c @@ -0,0 +1,473 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2671 */ + +#ifndef RDATA_GENERIC_OPT_41_C +#define RDATA_GENERIC_OPT_41_C + +#define RRTYPE_OPT_ATTRIBUTES \ + (DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \ + DNS_RDATATYPEATTR_NOTQUESTION) + +#include + +static isc_result_t +fromtext_opt(ARGS_FROMTEXT) { + /* + * OPT records do not have a text format. + */ + + REQUIRE(type == dns_rdatatype_opt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(lexer); + UNUSED(origin); + UNUSED(options); + UNUSED(target); + UNUSED(callbacks); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +totext_opt(ARGS_TOTEXT) { + isc_region_t r; + isc_region_t or ; + uint16_t option; + uint16_t length; + char buf[sizeof("64000 64000")]; + + /* + * OPT records do not have a text format. + */ + + REQUIRE(rdata->type == dns_rdatatype_opt); + + dns_rdata_toregion(rdata, &r); + while (r.length > 0) { + option = uint16_fromregion(&r); + isc_region_consume(&r, 2); + length = uint16_fromregion(&r); + isc_region_consume(&r, 2); + snprintf(buf, sizeof(buf), "%u %u", option, length); + RETERR(str_totext(buf, target)); + INSIST(r.length >= length); + if (length > 0) { + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + or = r; + or.length = length; + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(& or, 60, "", target)); + } else { + RETERR(isc_base64_totext(& or, tctx->width - 2, + tctx->linebreak, + target)); + } + isc_region_consume(&r, length); + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + } + if (r.length > 0) { + RETERR(str_totext(" ", target)); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_opt(ARGS_FROMWIRE) { + isc_region_t sregion; + isc_region_t tregion; + uint16_t opt; + uint16_t length; + unsigned int total; + + REQUIRE(type == dns_rdatatype_opt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length == 0) { + return (ISC_R_SUCCESS); + } + total = 0; + while (sregion.length != 0) { + if (sregion.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + opt = uint16_fromregion(&sregion); + isc_region_consume(&sregion, 2); + length = uint16_fromregion(&sregion); + isc_region_consume(&sregion, 2); + total += 4; + if (sregion.length < length) { + return (ISC_R_UNEXPECTEDEND); + } + switch (opt) { + case DNS_OPT_LLQ: + if (length != 18U) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + case DNS_OPT_CLIENT_SUBNET: { + uint16_t family; + uint8_t addrlen; + uint8_t scope; + uint8_t addrbytes; + + if (length < 4) { + return (DNS_R_OPTERR); + } + family = uint16_fromregion(&sregion); + isc_region_consume(&sregion, 2); + addrlen = uint8_fromregion(&sregion); + isc_region_consume(&sregion, 1); + scope = uint8_fromregion(&sregion); + isc_region_consume(&sregion, 1); + + switch (family) { + case 0: + /* + * XXXMUKS: In queries and replies, if + * FAMILY is set to 0, SOURCE + * PREFIX-LENGTH and SCOPE PREFIX-LENGTH + * must be 0 and ADDRESS should not be + * present as the address and prefix + * lengths don't make sense because the + * family is unknown. + */ + if (addrlen != 0U || scope != 0U) { + return (DNS_R_OPTERR); + } + break; + case 1: + if (addrlen > 32U || scope > 32U) { + return (DNS_R_OPTERR); + } + break; + case 2: + if (addrlen > 128U || scope > 128U) { + return (DNS_R_OPTERR); + } + break; + default: + return (DNS_R_OPTERR); + } + addrbytes = (addrlen + 7) / 8; + if (addrbytes + 4 != length) { + return (DNS_R_OPTERR); + } + + if (addrbytes != 0U && (addrlen % 8) != 0) { + uint8_t bits = ~0U << (8 - (addrlen % 8)); + bits &= sregion.base[addrbytes - 1]; + if (bits != sregion.base[addrbytes - 1]) { + return (DNS_R_OPTERR); + } + } + isc_region_consume(&sregion, addrbytes); + break; + } + case DNS_OPT_EXPIRE: + /* + * Request has zero length. Response is 32 bits. + */ + if (length != 0 && length != 4) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + case DNS_OPT_COOKIE: + /* + * Client cookie alone has length 8. + * Client + server cookie is 8 + [8..32]. + */ + if (length != 8 && (length < 16 || length > 40)) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + case DNS_OPT_KEY_TAG: + if (length == 0 || (length % 2) != 0) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + case DNS_OPT_EDE: + if (length < 2) { + return (DNS_R_OPTERR); + } + /* UTF-8 Byte Order Mark is not permitted. RFC 5198 */ + if (isc_utf8_bom(sregion.base + 2, length - 2)) { + return (DNS_R_OPTERR); + } + /* + * The EXTRA-TEXT field is specified as UTF-8, and + * therefore must be validated for correctness + * according to RFC 3269 security considerations. + */ + if (!isc_utf8_valid(sregion.base + 2, length - 2)) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + case DNS_OPT_CLIENT_TAG: + FALLTHROUGH; + case DNS_OPT_SERVER_TAG: + if (length != 2) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; + default: + isc_region_consume(&sregion, length); + break; + } + total += length; + } + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (tregion.length < total) { + return (ISC_R_NOSPACE); + } + memmove(tregion.base, sregion.base, total); + isc_buffer_forward(source, total); + isc_buffer_add(target, total); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_opt(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_opt(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_opt); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_opt(ARGS_FROMSTRUCT) { + dns_rdata_opt_t *opt = source; + isc_region_t region; + uint16_t length; + + REQUIRE(type == dns_rdatatype_opt); + REQUIRE(opt != NULL); + REQUIRE(opt->common.rdtype == type); + REQUIRE(opt->common.rdclass == rdclass); + REQUIRE(opt->options != NULL || opt->length == 0); + + UNUSED(type); + UNUSED(rdclass); + + region.base = opt->options; + region.length = opt->length; + while (region.length >= 4) { + isc_region_consume(®ion, 2); /* opt */ + length = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + if (region.length < length) { + return (ISC_R_UNEXPECTEDEND); + } + isc_region_consume(®ion, length); + } + if (region.length != 0) { + return (ISC_R_UNEXPECTEDEND); + } + + return (mem_tobuffer(target, opt->options, opt->length)); +} + +static isc_result_t +tostruct_opt(ARGS_TOSTRUCT) { + dns_rdata_opt_t *opt = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_opt); + REQUIRE(opt != NULL); + + opt->common.rdclass = rdata->rdclass; + opt->common.rdtype = rdata->type; + ISC_LINK_INIT(&opt->common, link); + + dns_rdata_toregion(rdata, &r); + opt->length = r.length; + opt->options = mem_maybedup(mctx, r.base, r.length); + if (opt->options == NULL) { + return (ISC_R_NOMEMORY); + } + + opt->offset = 0; + opt->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_opt(ARGS_FREESTRUCT) { + dns_rdata_opt_t *opt = source; + + REQUIRE(opt != NULL); + REQUIRE(opt->common.rdtype == dns_rdatatype_opt); + + if (opt->mctx == NULL) { + return; + } + + if (opt->options != NULL) { + isc_mem_free(opt->mctx, opt->options); + } + opt->mctx = NULL; +} + +static isc_result_t +additionaldata_opt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_opt(ARGS_DIGEST) { + /* + * OPT records are not digested. + */ + + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static bool +checkowner_opt(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_opt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_equal(name, dns_rootname)); +} + +static bool +checknames_opt(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_opt(ARGS_COMPARE) { + return (compare_opt(rdata1, rdata2)); +} + +isc_result_t +dns_rdata_opt_first(dns_rdata_opt_t *opt) { + REQUIRE(opt != NULL); + REQUIRE(opt->common.rdtype == dns_rdatatype_opt); + REQUIRE(opt->options != NULL || opt->length == 0); + + if (opt->length == 0) { + return (ISC_R_NOMORE); + } + + opt->offset = 0; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdata_opt_next(dns_rdata_opt_t *opt) { + isc_region_t r; + uint16_t length; + + REQUIRE(opt != NULL); + REQUIRE(opt->common.rdtype == dns_rdatatype_opt); + REQUIRE(opt->options != NULL && opt->length != 0); + REQUIRE(opt->offset < opt->length); + + INSIST(opt->offset + 4 <= opt->length); + r.base = opt->options + opt->offset + 2; + r.length = opt->length - opt->offset - 2; + length = uint16_fromregion(&r); + INSIST(opt->offset + 4 + length <= opt->length); + opt->offset = opt->offset + 4 + length; + if (opt->offset == opt->length) { + return (ISC_R_NOMORE); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdata_opt_current(dns_rdata_opt_t *opt, dns_rdata_opt_opcode_t *opcode) { + isc_region_t r; + + REQUIRE(opt != NULL); + REQUIRE(opcode != NULL); + REQUIRE(opt->common.rdtype == dns_rdatatype_opt); + REQUIRE(opt->options != NULL); + REQUIRE(opt->offset < opt->length); + + INSIST(opt->offset + 4 <= opt->length); + r.base = opt->options + opt->offset; + r.length = opt->length - opt->offset; + + opcode->opcode = uint16_fromregion(&r); + isc_region_consume(&r, 2); + opcode->length = uint16_fromregion(&r); + isc_region_consume(&r, 2); + opcode->data = r.base; + INSIST(opt->offset + 4 + opcode->length <= opt->length); + + return (ISC_R_SUCCESS); +} + +#endif /* RDATA_GENERIC_OPT_41_C */ diff --git a/lib/dns/rdata/generic/opt_41.h b/lib/dns/rdata/generic/opt_41.h new file mode 100644 index 0000000..2198fde --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2671 */ + +typedef struct dns_rdata_opt_opcode { + uint16_t opcode; + uint16_t length; + unsigned char *data; +} dns_rdata_opt_opcode_t; + +typedef struct dns_rdata_opt { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *options; + uint16_t length; + /* private */ + uint16_t offset; +} dns_rdata_opt_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ + +isc_result_t +dns_rdata_opt_first(dns_rdata_opt_t *); + +isc_result_t +dns_rdata_opt_next(dns_rdata_opt_t *); + +isc_result_t +dns_rdata_opt_current(dns_rdata_opt_t *, dns_rdata_opt_opcode_t *); diff --git a/lib/dns/rdata/generic/proforma.c b/lib/dns/rdata/generic/proforma.c new file mode 100644 index 0000000..6315a6f --- /dev/null +++ b/lib/dns/rdata/generic/proforma.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_ #_ #_C +#define RDATA_GENERIC_ #_ #_C + +#define RRTYPE_ #_ATTRIBUTES(0) + +static isc_result_t fromtext_ #(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_proforma.c #); + REQUIRE(rdclass == #); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t totext_ #(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + REQUIRE(rdata->length != 0); /* XXX */ + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t fromwire_ #(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_proforma.c #); + REQUIRE(rdclass == #); + + /* NONE or GLOBAL14 */ + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t towire_ #(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + REQUIRE(rdata->length != 0); /* XXX */ + + /* NONE or GLOBAL14 */ + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + + return (ISC_R_NOTIMPLEMENTED); +} + +static int compare_ #(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata1->rdclass == #); + REQUIRE(rdata1->length != 0); /* XXX */ + REQUIRE(rdata2->length != 0); /* XXX */ + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t fromstruct_ #(ARGS_FROMSTRUCT) { + dns_rdata_ #_t *# = source; + + REQUIRE(type == dns_rdatatype_proforma.c #); + REQUIRE(rdclass == #); + REQUIRE(# != NULL); + REQUIRE(#->common.rdtype == dns_rdatatype_proforma.ctype); + REQUIRE(#->common.rdclass == rdclass); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t tostruct_ #(ARGS_TOSTRUCT) { + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + REQUIRE(rdata->length != 0); /* XXX */ + + return (ISC_R_NOTIMPLEMENTED); +} + +static void freestruct_ #(ARGS_FREESTRUCT) { + dns_rdata_ #_t *# = source; + + REQUIRE(# != NULL); + REQUIRE(#->common.rdtype == dns_rdatatype_proforma.c #); + REQUIRE(#->common.rdclass == #); +} + +static isc_result_t additionaldata_ #(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t digest_ #(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool checkowner_ #(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_proforma.c #); + REQUIRE(rdclass == #); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool checknames_ #(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata->rdclass == #); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int casecompare_ #(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == dns_rdatatype_proforma.crdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_proforma.c #); + REQUIRE(rdata1->rdclass == #); + REQUIRE(rdata1->length != 0); /* XXX */ + REQUIRE(rdata2->length != 0); /* XXX */ + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +#endif /* RDATA_GENERIC_#_#_C */ diff --git a/lib/dns/rdata/generic/proforma.h b/lib/dns/rdata/generic/proforma.h new file mode 100644 index 0000000..03962d1 --- /dev/null +++ b/lib/dns/rdata/generic/proforma.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_ #{ + dns_rdatacommon_t common; + isc_mem_t *mctx; /* if required */ + /* type & class specific elements */ +} +dns_rdata_ #_t; diff --git a/lib/dns/rdata/generic/ptr_12.c b/lib/dns/rdata/generic/ptr_12.c new file mode 100644 index 0000000..41e2e8e --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_PTR_12_C +#define RDATA_GENERIC_PTR_12_C + +#define RRTYPE_PTR_ATTRIBUTES (0) + +static isc_result_t +fromtext_ptr(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_ptr); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + if (rdclass == dns_rdataclass_in && + (options & DNS_RDATA_CHECKNAMES) != 0 && + (options & DNS_RDATA_CHECKREVERSE) != 0) + { + bool ok; + ok = dns_name_ishostname(&name, false); + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_ptr(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_ptr); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_ptr(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_ptr); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_ptr(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_ptr); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_ptr(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ptr); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_ptr(ARGS_FROMSTRUCT) { + dns_rdata_ptr_t *ptr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ptr); + REQUIRE(ptr != NULL); + REQUIRE(ptr->common.rdtype == type); + REQUIRE(ptr->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&ptr->ptr, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_ptr(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_ptr_t *ptr = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ptr); + REQUIRE(ptr != NULL); + REQUIRE(rdata->length != 0); + + ptr->common.rdclass = rdata->rdclass; + ptr->common.rdtype = rdata->type; + ISC_LINK_INIT(&ptr->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&ptr->ptr, NULL); + name_duporclone(&name, mctx, &ptr->ptr); + ptr->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_ptr(ARGS_FREESTRUCT) { + dns_rdata_ptr_t *ptr = source; + + REQUIRE(ptr != NULL); + REQUIRE(ptr->common.rdtype == dns_rdatatype_ptr); + + if (ptr->mctx == NULL) { + return; + } + + dns_name_free(&ptr->ptr, ptr->mctx); + ptr->mctx = NULL; +} + +static isc_result_t +additionaldata_ptr(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ptr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ptr(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ptr); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_ptr(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ptr); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA"; +static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 }; +static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data, + ip6_arpa_offsets); + +static unsigned char ip6_int_data[] = "\003IP6\003INT"; +static unsigned char ip6_int_offsets[] = { 0, 4, 8 }; +static const dns_name_t ip6_int = DNS_NAME_INITABSOLUTE(ip6_int_data, + ip6_int_offsets); + +static unsigned char in_addr_arpa_data[] = "\007IN-ADDR\004ARPA"; +static unsigned char in_addr_arpa_offsets[] = { 0, 8, 13 }; +static const dns_name_t in_addr_arpa = + DNS_NAME_INITABSOLUTE(in_addr_arpa_data, in_addr_arpa_offsets); + +static bool +checknames_ptr(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_ptr); + + if (rdata->rdclass != dns_rdataclass_in) { + return (true); + } + + if (dns_name_isdnssd(owner)) { + return (true); + } + + if (dns_name_issubdomain(owner, &in_addr_arpa) || + dns_name_issubdomain(owner, &ip6_arpa) || + dns_name_issubdomain(owner, &ip6_int)) + { + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + } + return (true); +} + +static int +casecompare_ptr(ARGS_COMPARE) { + return (compare_ptr(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_PTR_12_C */ diff --git a/lib/dns/rdata/generic/ptr_12.h b/lib/dns/rdata/generic/ptr_12.h new file mode 100644 index 0000000..8951eb3 --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_ptr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t ptr; +} dns_rdata_ptr_t; diff --git a/lib/dns/rdata/generic/rkey_57.c b/lib/dns/rdata/generic/rkey_57.c new file mode 100644 index 0000000..20cd0f0 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_RKEY_57_C +#define RDATA_GENERIC_RKEY_57_C + +#define RRTYPE_RKEY_ATTRIBUTES 0 + +static isc_result_t +fromtext_rkey(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromtext_key(CALL_FROMTEXT)); +} + +static isc_result_t +totext_rkey(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + return (generic_totext_key(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_rkey(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromwire_key(CALL_FROMWIRE)); +} + +static isc_result_t +towire_rkey(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_rkey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1 != NULL); + REQUIRE(rdata2 != NULL); + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_rkey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_rkey(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromstruct_key(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_rkey(ARGS_TOSTRUCT) { + dns_rdata_rkey_t *rkey = target; + + REQUIRE(rkey != NULL); + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + rkey->common.rdclass = rdata->rdclass; + rkey->common.rdtype = rdata->type; + ISC_LINK_INIT(&rkey->common, link); + + return (generic_tostruct_key(CALL_TOSTRUCT)); +} + +static void +freestruct_rkey(ARGS_FREESTRUCT) { + dns_rdata_rkey_t *rkey = (dns_rdata_rkey_t *)source; + + REQUIRE(rkey != NULL); + REQUIRE(rkey->common.rdtype == dns_rdatatype_rkey); + + generic_freestruct_key(source); +} + +static isc_result_t +additionaldata_rkey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_rkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_rkey(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_rkey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_rkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_rkey(ARGS_CHECKNAMES) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_rkey(ARGS_COMPARE) { + /* + * Treat ALG 253 (private DNS) subtype name case sensitively. + */ + return (compare_rkey(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_RKEY_57_C */ diff --git a/lib/dns/rdata/generic/rkey_57.h b/lib/dns/rdata/generic/rkey_57.h new file mode 100644 index 0000000..3538be0 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_key dns_rdata_rkey_t; diff --git a/lib/dns/rdata/generic/rp_17.c b/lib/dns/rdata/generic/rp_17.c new file mode 100644 index 0000000..62080c6 --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.c @@ -0,0 +1,310 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1183 */ + +#ifndef RDATA_GENERIC_RP_17_C +#define RDATA_GENERIC_RP_17_C + +#define RRTYPE_RP_ATTRIBUTES (0) + +static isc_result_t +fromtext_rp(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + int i; + bool ok; + + REQUIRE(type == dns_rdatatype_rp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + if (origin == NULL) { + origin = dns_rootname; + } + + for (i = 0; i < 2; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0 && i == 0) { + ok = dns_name_ismailbox(&name); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_rp(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t rmail; + dns_name_t email; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_rp); + REQUIRE(rdata->length != 0); + + dns_name_init(&rmail, NULL); + dns_name_init(&email, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, rmail.length); + + dns_name_fromregion(&email, ®ion); + isc_region_consume(®ion, email.length); + + sub = name_prefix(&rmail, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + RETERR(str_totext(" ", target)); + + sub = name_prefix(&email, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_rp(ARGS_FROMWIRE) { + dns_name_t rmail; + dns_name_t email; + + REQUIRE(type == dns_rdatatype_rp); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&rmail, NULL); + dns_name_init(&email, NULL); + + RETERR(dns_name_fromwire(&rmail, source, dctx, options, target)); + return (dns_name_fromwire(&email, source, dctx, options, target)); +} + +static isc_result_t +towire_rp(ARGS_TOWIRE) { + isc_region_t region; + dns_name_t rmail; + dns_name_t email; + dns_offsets_t roffsets; + dns_offsets_t eoffsets; + + REQUIRE(rdata->type == dns_rdatatype_rp); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_name_init(&rmail, roffsets); + dns_name_init(&email, eoffsets); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, rmail.length); + + RETERR(dns_name_towire(&rmail, cctx, target)); + + dns_name_fromregion(&rmail, ®ion); + isc_region_consume(®ion, rmail.length); + + return (dns_name_towire(&rmail, cctx, target)); +} + +static int +compare_rp(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_rp); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_rp(ARGS_FROMSTRUCT) { + dns_rdata_rp_t *rp = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_rp); + REQUIRE(rp != NULL); + REQUIRE(rp->common.rdtype == type); + REQUIRE(rp->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&rp->mail, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + dns_name_toregion(&rp->text, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_rp(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_rp_t *rp = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rp); + REQUIRE(rp != NULL); + REQUIRE(rdata->length != 0); + + rp->common.rdclass = rdata->rdclass; + rp->common.rdtype = rdata->type; + ISC_LINK_INIT(&rp->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&rp->mail, NULL); + name_duporclone(&name, mctx, &rp->mail); + isc_region_consume(®ion, name_length(&name)); + dns_name_fromregion(&name, ®ion); + dns_name_init(&rp->text, NULL); + name_duporclone(&name, mctx, &rp->text); + rp->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_rp(ARGS_FREESTRUCT) { + dns_rdata_rp_t *rp = source; + + REQUIRE(rp != NULL); + REQUIRE(rp->common.rdtype == dns_rdatatype_rp); + + if (rp->mctx == NULL) { + return; + } + + dns_name_free(&rp->mail, rp->mctx); + dns_name_free(&rp->text, rp->mctx); + rp->mctx = NULL; +} + +static isc_result_t +additionaldata_rp(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_rp); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_rp(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rp); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + + dns_name_fromregion(&name, &r); + RETERR(dns_name_digest(&name, digest, arg)); + isc_region_consume(&r, name_length(&name)); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_rp(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_rp); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_rp(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rp); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ismailbox(&name)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_rp(ARGS_COMPARE) { + return (compare_rp(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_RP_17_C */ diff --git a/lib/dns/rdata/generic/rp_17.h b/lib/dns/rdata/generic/rp_17.h new file mode 100644 index 0000000..d27ffb9 --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1183 */ + +typedef struct dns_rdata_rp { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mail; + dns_name_t text; +} dns_rdata_rp_t; diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c new file mode 100644 index 0000000..553c9cf --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.c @@ -0,0 +1,640 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2535 */ + +#ifndef RDATA_GENERIC_RRSIG_46_C +#define RDATA_GENERIC_RRSIG_46_C + +#define RRTYPE_RRSIG_ATTRIBUTES \ + (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH | \ + DNS_RDATATYPEATTR_ATCNAME) + +static isc_result_t +fromtext_rrsig(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char c; + long i; + dns_rdatatype_t covered; + char *e; + isc_result_t result; + dns_name_t name; + isc_buffer_t buffer; + uint32_t time_signed, time_expire; + + REQUIRE(type == dns_rdatatype_rrsig); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Type covered. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + i = strtol(DNS_AS_STR(token), &e, 10); + if (i < 0 || i > 65535) { + RETTOK(ISC_R_RANGE); + } + if (*e != 0) { + RETTOK(result); + } + covered = (dns_rdatatype_t)i; + } + RETERR(uint16_tobuffer(covered, target)); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Labels. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + c = (unsigned char)token.value.as_ulong; + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Original ttl. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * Signature expiration. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' && + *DNS_AS_STR(token) != '+') + { + char *end; + unsigned long u; + uint64_t u64; + + u64 = u = strtoul(DNS_AS_STR(token), &end, 10); + if (u == ULONG_MAX || *end != 0) { + RETTOK(DNS_R_SYNTAX); + } + if (u64 > 0xffffffffUL) { + RETTOK(ISC_R_RANGE); + } + time_expire = u; + } else { + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire)); + } + RETERR(uint32_tobuffer(time_expire, target)); + + /* + * Time signed. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strlen(DNS_AS_STR(token)) <= 10U && *DNS_AS_STR(token) != '-' && + *DNS_AS_STR(token) != '+') + { + char *end; + unsigned long u; + uint64_t u64; + + u64 = u = strtoul(DNS_AS_STR(token), &end, 10); + if (u == ULONG_MAX || *end != 0) { + RETTOK(DNS_R_SYNTAX); + } + if (u64 > 0xffffffffUL) { + RETTOK(ISC_R_RANGE); + } + time_signed = u; + } else { + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed)); + } + RETERR(uint32_tobuffer(time_signed, target)); + + /* + * Key footprint. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Signer. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + /* + * Sig. + */ + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_rrsig(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("4294967295")]; /* Also TYPE65000. */ + dns_rdatatype_t covered; + unsigned long ttl; + unsigned long when; + unsigned long exp; + unsigned long foot; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* + * Type covered. + */ + covered = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + /* + * XXXAG We should have something like dns_rdatatype_isknown() + * that does the right thing with type 0. + */ + if (dns_rdatatype_isknown(covered) && covered != 0) { + RETERR(dns_rdatatype_totext(covered, target)); + } else { + snprintf(buf, sizeof(buf), "TYPE%u", covered); + RETERR(str_totext(buf, target)); + } + RETERR(str_totext(" ", target)); + + /* + * Algorithm. + */ + snprintf(buf, sizeof(buf), "%u", sr.base[0]); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Labels. + */ + snprintf(buf, sizeof(buf), "%u", sr.base[0]); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Ttl. + */ + ttl = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu", ttl); + RETERR(str_totext(buf, target)); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + + /* + * Sig exp. + */ + exp = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(exp, target)); + RETERR(str_totext(" ", target)); + + /* + * Time signed. + */ + when = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(when, target)); + RETERR(str_totext(" ", target)); + + /* + * Footprint. + */ + foot = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu", foot); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Signer. + */ + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_totext(&name, false, target)); + + /* + * Sig. + */ + RETERR(str_totext(tctx->linebreak, target)); + if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) { + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, + tctx->linebreak, target)); + } + } else { + RETERR(str_totext("[omitted]", target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_rrsig(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_rrsig); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + isc_buffer_activeregion(source, &sr); + /* + * type covered: 2 + * algorithm: 1 + * labels: 1 + * original ttl: 4 + * signature expiration: 4 + * time signed: 4 + * key footprint: 2 + */ + if (sr.length < 18) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, 18); + RETERR(mem_tobuffer(target, sr.base, 18)); + + /* + * Signer. + */ + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + /* + * Sig. + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 1) { + return (DNS_R_FORMERR); + } + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_rrsig(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_rdata_toregion(rdata, &sr); + /* + * type covered: 2 + * algorithm: 1 + * labels: 1 + * original ttl: 4 + * signature expiration: 4 + * time signed: 4 + * key footprint: 2 + */ + RETERR(mem_tobuffer(target, sr.base, 18)); + isc_region_consume(&sr, 18); + + /* + * Signer. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_towire(&name, cctx, target)); + + /* + * Signature. + */ + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_rrsig(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_rrsig); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_rrsig(ARGS_FROMSTRUCT) { + dns_rdata_rrsig_t *sig = source; + + REQUIRE(type == dns_rdatatype_rrsig); + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == type); + REQUIRE(sig->common.rdclass == rdclass); + REQUIRE(sig->signature != NULL || sig->siglen == 0); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Type covered. + */ + RETERR(uint16_tobuffer(sig->covered, target)); + + /* + * Algorithm. + */ + RETERR(uint8_tobuffer(sig->algorithm, target)); + + /* + * Labels. + */ + RETERR(uint8_tobuffer(sig->labels, target)); + + /* + * Original TTL. + */ + RETERR(uint32_tobuffer(sig->originalttl, target)); + + /* + * Expire time. + */ + RETERR(uint32_tobuffer(sig->timeexpire, target)); + + /* + * Time signed. + */ + RETERR(uint32_tobuffer(sig->timesigned, target)); + + /* + * Key ID. + */ + RETERR(uint16_tobuffer(sig->keyid, target)); + + /* + * Signer name. + */ + RETERR(name_tobuffer(&sig->signer, target)); + + /* + * Signature. + */ + return (mem_tobuffer(target, sig->signature, sig->siglen)); +} + +static isc_result_t +tostruct_rrsig(ARGS_TOSTRUCT) { + isc_region_t sr; + dns_rdata_rrsig_t *sig = target; + dns_name_t signer; + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + REQUIRE(sig != NULL); + REQUIRE(rdata->length != 0); + + sig->common.rdclass = rdata->rdclass; + sig->common.rdtype = rdata->type; + ISC_LINK_INIT(&sig->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Type covered. + */ + sig->covered = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Algorithm. + */ + sig->algorithm = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Labels. + */ + sig->labels = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Original TTL. + */ + sig->originalttl = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Expire time. + */ + sig->timeexpire = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Time signed. + */ + sig->timesigned = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Key ID. + */ + sig->keyid = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + dns_name_init(&signer, NULL); + dns_name_fromregion(&signer, &sr); + dns_name_init(&sig->signer, NULL); + name_duporclone(&signer, mctx, &sig->signer); + isc_region_consume(&sr, name_length(&sig->signer)); + + /* + * Signature. + */ + sig->siglen = sr.length; + sig->signature = mem_maybedup(mctx, sr.base, sig->siglen); + if (sig->signature == NULL) { + goto cleanup; + } + + sig->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&sig->signer, mctx); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_rrsig(ARGS_FREESTRUCT) { + dns_rdata_rrsig_t *sig = (dns_rdata_rrsig_t *)source; + + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == dns_rdatatype_rrsig); + + if (sig->mctx == NULL) { + return; + } + + dns_name_free(&sig->signer, sig->mctx); + if (sig->signature != NULL) { + isc_mem_free(sig->mctx, sig->signature); + } + sig->mctx = NULL; +} + +static isc_result_t +additionaldata_rrsig(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_rrsig(ARGS_DIGEST) { + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static dns_rdatatype_t +covers_rrsig(dns_rdata_t *rdata) { + dns_rdatatype_t type; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + dns_rdata_toregion(rdata, &r); + type = uint16_fromregion(&r); + + return (type); +} + +static bool +checkowner_rrsig(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_rrsig); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_rrsig(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_rrsig(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_rrsig); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + + INSIST(r1.length > 18); + INSIST(r2.length > 18); + r1.length = 18; + r2.length = 18; + order = isc_region_compare(&r1, &r2); + if (order != 0) { + return (order); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + isc_region_consume(&r1, 18); + isc_region_consume(&r2, 18); + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + + return (isc_region_compare(&r1, &r2)); +} + +#endif /* RDATA_GENERIC_RRSIG_46_C */ diff --git a/lib/dns/rdata/generic/rrsig_46.h b/lib/dns/rdata/generic/rrsig_46.h new file mode 100644 index 0000000..0869c2d --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2535 */ +typedef struct dns_rdata_rrsig { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_rdatatype_t covered; + dns_secalg_t algorithm; + uint8_t labels; + uint32_t originalttl; + uint32_t timeexpire; + uint32_t timesigned; + uint16_t keyid; + dns_name_t signer; + uint16_t siglen; + unsigned char *signature; +} dns_rdata_rrsig_t; diff --git a/lib/dns/rdata/generic/rt_21.c b/lib/dns/rdata/generic/rt_21.c new file mode 100644 index 0000000..10a003f --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1183 */ + +#ifndef RDATA_GENERIC_RT_21_C +#define RDATA_GENERIC_RT_21_C + +#define RRTYPE_RT_ATTRIBUTES (0) + +static isc_result_t +fromtext_rt(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool ok; + + REQUIRE(type == dns_rdatatype_rt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_rt(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_rt); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_rt(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(type == dns_rdatatype_rt); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (tregion.length < 2) { + return (ISC_R_NOSPACE); + } + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + memmove(tregion.base, sregion.base, 2); + isc_buffer_forward(source, 2); + isc_buffer_add(target, 2); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_rt(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + isc_region_t tr; + + REQUIRE(rdata->type == dns_rdatatype_rt); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + isc_buffer_availableregion(target, &tr); + dns_rdata_toregion(rdata, ®ion); + if (tr.length < 2) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, region.base, 2); + isc_region_consume(®ion, 2); + isc_buffer_add(target, 2); + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_rt(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_rt); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + order = memcmp(rdata1->data, rdata2->data, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_rt(ARGS_FROMSTRUCT) { + dns_rdata_rt_t *rt = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_rt); + REQUIRE(rt != NULL); + REQUIRE(rt->common.rdtype == type); + REQUIRE(rt->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(rt->preference, target)); + dns_name_toregion(&rt->host, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_rt(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_rt_t *rt = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rt); + REQUIRE(rt != NULL); + REQUIRE(rdata->length != 0); + + rt->common.rdclass = rdata->rdclass; + rt->common.rdtype = rdata->type; + ISC_LINK_INIT(&rt->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + rt->preference = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + dns_name_init(&rt->host, NULL); + name_duporclone(&name, mctx, &rt->host); + + rt->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_rt(ARGS_FREESTRUCT) { + dns_rdata_rt_t *rt = source; + + REQUIRE(rt != NULL); + REQUIRE(rt->common.rdtype == dns_rdatatype_rt); + + if (rt->mctx == NULL) { + return; + } + + dns_name_free(&rt->host, rt->mctx); + rt->mctx = NULL; +} + +static isc_result_t +additionaldata_rt(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_rt); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + result = (add)(arg, &name, dns_rdatatype_x25, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = (add)(arg, &name, dns_rdatatype_isdn, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_rt(ARGS_DIGEST) { + isc_region_t r1, r2; + isc_result_t result; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rt); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 2); + r1.length = 2; + result = (digest)(arg, &r1); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_rt(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_rt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_rt(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_rt); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_rt(ARGS_COMPARE) { + return (compare_rt(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_RT_21_C */ diff --git a/lib/dns/rdata/generic/rt_21.h b/lib/dns/rdata/generic/rt_21.h new file mode 100644 index 0000000..7df33d1 --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1183 */ + +typedef struct dns_rdata_rt { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t preference; + dns_name_t host; +} dns_rdata_rt_t; diff --git a/lib/dns/rdata/generic/sig_24.c b/lib/dns/rdata/generic/sig_24.c new file mode 100644 index 0000000..85075c3 --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.c @@ -0,0 +1,591 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2535 */ + +#ifndef RDATA_GENERIC_SIG_24_C +#define RDATA_GENERIC_SIG_24_C + +#define RRTYPE_SIG_ATTRIBUTES (0) + +static isc_result_t +fromtext_sig(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char c; + long i; + dns_rdatatype_t covered; + char *e; + isc_result_t result; + dns_name_t name; + isc_buffer_t buffer; + uint32_t time_signed, time_expire; + + REQUIRE(type == dns_rdatatype_sig); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Type covered. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + result = dns_rdatatype_fromtext(&covered, &token.value.as_textregion); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + i = strtol(DNS_AS_STR(token), &e, 10); + if (i < 0 || i > 65535) { + RETTOK(ISC_R_RANGE); + } + if (*e != 0) { + RETTOK(result); + } + covered = (dns_rdatatype_t)i; + } + RETERR(uint16_tobuffer(covered, target)); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion)); + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Labels. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + c = (unsigned char)token.value.as_ulong; + RETERR(mem_tobuffer(target, &c, 1)); + + /* + * Original ttl. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * Signature expiration. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_expire)); + RETERR(uint32_tobuffer(time_expire, target)); + + /* + * Time signed. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + RETTOK(dns_time32_fromtext(DNS_AS_STR(token), &time_signed)); + RETERR(uint32_tobuffer(time_signed, target)); + + /* + * Key footprint. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Signer. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + /* + * Sig. + */ + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_sig(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("4294967295")]; + dns_rdatatype_t covered; + unsigned long ttl; + unsigned long when; + unsigned long exp; + unsigned long foot; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_sig); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* + * Type covered. + */ + covered = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + /* + * XXXAG We should have something like dns_rdatatype_isknown() + * that does the right thing with type 0. + */ + if (dns_rdatatype_isknown(covered) && covered != 0) { + RETERR(dns_rdatatype_totext(covered, target)); + } else { + snprintf(buf, sizeof(buf), "%u", covered); + RETERR(str_totext(buf, target)); + } + RETERR(str_totext(" ", target)); + + /* + * Algorithm. + */ + snprintf(buf, sizeof(buf), "%u", sr.base[0]); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Labels. + */ + snprintf(buf, sizeof(buf), "%u", sr.base[0]); + isc_region_consume(&sr, 1); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Ttl. + */ + ttl = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu", ttl); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Sig exp. + */ + exp = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(exp, target)); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + + /* + * Time signed. + */ + when = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + RETERR(dns_time32_totext(when, target)); + RETERR(str_totext(" ", target)); + + /* + * Footprint. + */ + foot = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu", foot); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Signer. + */ + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + /* + * Sig. + */ + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_sig(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_sig); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + isc_buffer_activeregion(source, &sr); + /* + * type covered: 2 + * algorithm: 1 + * labels: 1 + * original ttl: 4 + * signature expiration: 4 + * time signed: 4 + * key footprint: 2 + */ + if (sr.length < 18) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, 18); + RETERR(mem_tobuffer(target, sr.base, 18)); + + /* + * Signer. + */ + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + /* + * Sig. + */ + isc_buffer_activeregion(source, &sr); + if (sr.length == 0) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_sig(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_sig); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_rdata_toregion(rdata, &sr); + /* + * type covered: 2 + * algorithm: 1 + * labels: 1 + * original ttl: 4 + * signature expiration: 4 + * time signed: 4 + * key footprint: 2 + */ + RETERR(mem_tobuffer(target, sr.base, 18)); + isc_region_consume(&sr, 18); + + /* + * Signer. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + isc_region_consume(&sr, name_length(&name)); + RETERR(dns_name_towire(&name, cctx, target)); + + /* + * Signature. + */ + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_sig(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_sig); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + + INSIST(r1.length > 18); + INSIST(r2.length > 18); + r1.length = 18; + r2.length = 18; + order = isc_region_compare(&r1, &r2); + if (order != 0) { + return (order); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + isc_region_consume(&r1, 18); + isc_region_consume(&r2, 18); + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_sig(ARGS_FROMSTRUCT) { + dns_rdata_sig_t *sig = source; + + REQUIRE(type == dns_rdatatype_sig); + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == type); + REQUIRE(sig->common.rdclass == rdclass); + REQUIRE(sig->signature != NULL || sig->siglen == 0); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Type covered. + */ + RETERR(uint16_tobuffer(sig->covered, target)); + + /* + * Algorithm. + */ + RETERR(uint8_tobuffer(sig->algorithm, target)); + + /* + * Labels. + */ + RETERR(uint8_tobuffer(sig->labels, target)); + + /* + * Original TTL. + */ + RETERR(uint32_tobuffer(sig->originalttl, target)); + + /* + * Expire time. + */ + RETERR(uint32_tobuffer(sig->timeexpire, target)); + + /* + * Time signed. + */ + RETERR(uint32_tobuffer(sig->timesigned, target)); + + /* + * Key ID. + */ + RETERR(uint16_tobuffer(sig->keyid, target)); + + /* + * Signer name. + */ + RETERR(name_tobuffer(&sig->signer, target)); + + /* + * Signature. + */ + return (mem_tobuffer(target, sig->signature, sig->siglen)); +} + +static isc_result_t +tostruct_sig(ARGS_TOSTRUCT) { + isc_region_t sr; + dns_rdata_sig_t *sig = target; + dns_name_t signer; + + REQUIRE(rdata->type == dns_rdatatype_sig); + REQUIRE(sig != NULL); + REQUIRE(rdata->length != 0); + + sig->common.rdclass = rdata->rdclass; + sig->common.rdtype = rdata->type; + ISC_LINK_INIT(&sig->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Type covered. + */ + sig->covered = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Algorithm. + */ + sig->algorithm = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Labels. + */ + sig->labels = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* + * Original TTL. + */ + sig->originalttl = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Expire time. + */ + sig->timeexpire = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Time signed. + */ + sig->timesigned = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Key ID. + */ + sig->keyid = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + dns_name_init(&signer, NULL); + dns_name_fromregion(&signer, &sr); + dns_name_init(&sig->signer, NULL); + name_duporclone(&signer, mctx, &sig->signer); + isc_region_consume(&sr, name_length(&sig->signer)); + + /* + * Signature. + */ + sig->siglen = sr.length; + sig->signature = mem_maybedup(mctx, sr.base, sig->siglen); + if (sig->signature == NULL) { + goto cleanup; + } + + sig->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&sig->signer, mctx); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_sig(ARGS_FREESTRUCT) { + dns_rdata_sig_t *sig = (dns_rdata_sig_t *)source; + + REQUIRE(sig != NULL); + REQUIRE(sig->common.rdtype == dns_rdatatype_sig); + + if (sig->mctx == NULL) { + return; + } + + dns_name_free(&sig->signer, sig->mctx); + if (sig->signature != NULL) { + isc_mem_free(sig->mctx, sig->signature); + } + sig->mctx = NULL; +} + +static isc_result_t +additionaldata_sig(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_sig(ARGS_DIGEST) { + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static dns_rdatatype_t +covers_sig(dns_rdata_t *rdata) { + dns_rdatatype_t type; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_sig); + + dns_rdata_toregion(rdata, &r); + type = uint16_fromregion(&r); + + return (type); +} + +static bool +checkowner_sig(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_sig); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_sig(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_sig(ARGS_COMPARE) { + return (compare_sig(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_SIG_24_C */ diff --git a/lib/dns/rdata/generic/sig_24.h b/lib/dns/rdata/generic/sig_24.h new file mode 100644 index 0000000..40bad22 --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2535 */ + +typedef struct dns_rdata_sig_t { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_rdatatype_t covered; + dns_secalg_t algorithm; + uint8_t labels; + uint32_t originalttl; + uint32_t timeexpire; + uint32_t timesigned; + uint16_t keyid; + dns_name_t signer; + uint16_t siglen; + unsigned char *signature; +} dns_rdata_sig_t; diff --git a/lib/dns/rdata/generic/sink_40.c b/lib/dns/rdata/generic/sink_40.c new file mode 100644 index 0000000..8d76212 --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_SINK_40_C +#define RDATA_GENERIC_SINK_40_C + +#include + +#define RRTYPE_SINK_ATTRIBUTES (0) + +static isc_result_t +fromtext_sink(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_sink); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* meaning */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* coding */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* subcoding */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + return (isc_base64_tobuffer(lexer, target, -1)); +} + +static isc_result_t +totext_sink(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("255 255 255")]; + uint8_t meaning, coding, subcoding; + + REQUIRE(rdata->type == dns_rdatatype_sink); + REQUIRE(rdata->length >= 3); + + dns_rdata_toregion(rdata, &sr); + + /* Meaning, Coding and Subcoding */ + meaning = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + coding = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + subcoding = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u %u %u", meaning, coding, subcoding); + RETERR(str_totext(buf, target)); + + if (sr.length == 0U) { + return (ISC_R_SUCCESS); + } + + /* data */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + + RETERR(str_totext(tctx->linebreak, target)); + + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_sink(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_sink); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 3) { + return (ISC_R_UNEXPECTEDEND); + } + + RETERR(mem_tobuffer(target, sr.base, sr.length)); + isc_buffer_forward(source, sr.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_sink(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_sink); + REQUIRE(rdata->length >= 3); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_sink(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_sink); + REQUIRE(rdata1->length >= 3); + REQUIRE(rdata2->length >= 3); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_sink(ARGS_FROMSTRUCT) { + dns_rdata_sink_t *sink = source; + + REQUIRE(type == dns_rdatatype_sink); + REQUIRE(sink != NULL); + REQUIRE(sink->common.rdtype == type); + REQUIRE(sink->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + /* Meaning */ + RETERR(uint8_tobuffer(sink->meaning, target)); + + /* Coding */ + RETERR(uint8_tobuffer(sink->coding, target)); + + /* Subcoding */ + RETERR(uint8_tobuffer(sink->subcoding, target)); + + /* Data */ + return (mem_tobuffer(target, sink->data, sink->datalen)); +} + +static isc_result_t +tostruct_sink(ARGS_TOSTRUCT) { + dns_rdata_sink_t *sink = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_sink); + REQUIRE(sink != NULL); + REQUIRE(rdata->length >= 3); + + sink->common.rdclass = rdata->rdclass; + sink->common.rdtype = rdata->type; + ISC_LINK_INIT(&sink->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* Meaning */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + sink->meaning = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Coding */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + sink->coding = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Subcoding */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + sink->subcoding = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + + /* Data */ + sink->datalen = sr.length; + sink->data = mem_maybedup(mctx, sr.base, sink->datalen); + if (sink->data == NULL) { + return (ISC_R_NOMEMORY); + } + + sink->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_sink(ARGS_FREESTRUCT) { + dns_rdata_sink_t *sink = (dns_rdata_sink_t *)source; + + REQUIRE(sink != NULL); + REQUIRE(sink->common.rdtype == dns_rdatatype_sink); + + if (sink->mctx == NULL) { + return; + } + + if (sink->data != NULL) { + isc_mem_free(sink->mctx, sink->data); + } + sink->mctx = NULL; +} + +static isc_result_t +additionaldata_sink(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sink); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_sink(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_sink); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_sink(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_sink); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_sink(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_sink); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_sink(ARGS_COMPARE) { + return (compare_sink(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_SINK_40_C */ diff --git a/lib/dns/rdata/generic/sink_40.h b/lib/dns/rdata/generic/sink_40.h new file mode 100644 index 0000000..e2615fd --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_sink_t { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t meaning; + uint8_t coding; + uint8_t subcoding; + uint16_t datalen; + unsigned char *data; +} dns_rdata_sink_t; diff --git a/lib/dns/rdata/generic/smimea_53.c b/lib/dns/rdata/generic/smimea_53.c new file mode 100644 index 0000000..ce154a0 --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_SMIMEA_53_C +#define RDATA_GENERIC_SMIMEA_53_C + +#define RRTYPE_SMIMEA_ATTRIBUTES 0 + +static isc_result_t +fromtext_smimea(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromtext_tlsa(CALL_FROMTEXT)); +} + +static isc_result_t +totext_smimea(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_smimea); + + return (generic_totext_tlsa(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_smimea(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromwire_tlsa(CALL_FROMWIRE)); +} + +static isc_result_t +towire_smimea(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_smimea); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_smimea(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_smimea); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_smimea(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromstruct_tlsa(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_smimea(ARGS_TOSTRUCT) { + dns_rdata_smimea_t *smimea = target; + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_smimea); + REQUIRE(smimea != NULL); + + smimea->common.rdclass = rdata->rdclass; + smimea->common.rdtype = rdata->type; + ISC_LINK_INIT(&smimea->common, link); + + return (generic_tostruct_tlsa(CALL_TOSTRUCT)); +} + +static void +freestruct_smimea(ARGS_FREESTRUCT) { + dns_rdata_smimea_t *smimea = source; + + REQUIRE(smimea != NULL); + REQUIRE(smimea->common.rdtype == dns_rdatatype_smimea); + + generic_freestruct_tlsa(source); +} + +static isc_result_t +additionaldata_smimea(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_smimea); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_smimea(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_smimea); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_smimea(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_smimea); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_smimea(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_smimea); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_smimea(ARGS_COMPARE) { + return (compare_smimea(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_SMIMEA_53_C */ diff --git a/lib/dns/rdata/generic/smimea_53.h b/lib/dns/rdata/generic/smimea_53.h new file mode 100644 index 0000000..108eb20 --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_tlsa dns_rdata_smimea_t; diff --git a/lib/dns/rdata/generic/soa_6.c b/lib/dns/rdata/generic/soa_6.c new file mode 100644 index 0000000..24b5278 --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_SOA_6_C +#define RDATA_GENERIC_SOA_6_C + +#define RRTYPE_SOA_ATTRIBUTES (DNS_RDATATYPEATTR_SINGLETON) + +static isc_result_t +fromtext_soa(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + int i; + uint32_t n; + bool ok; + + REQUIRE(type == dns_rdatatype_soa); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + if (origin == NULL) { + origin = dns_rootname; + } + + for (i = 0; i < 2; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + switch (i) { + case 0: + ok = dns_name_ishostname(&name, false); + break; + case 1: + ok = dns_name_ismailbox(&name); + break; + } + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + } + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + for (i = 0; i < 4; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + RETTOK(dns_counter_fromtext(&token.value.as_textregion, &n)); + RETERR(uint32_tobuffer(n, target)); + } + + return (ISC_R_SUCCESS); +} + +static const char *soa_fieldnames[5] = { "serial", "refresh", "retry", "expire", + "minimum" }; + +static isc_result_t +totext_soa(ARGS_TOTEXT) { + isc_region_t dregion; + dns_name_t mname; + dns_name_t rname; + dns_name_t prefix; + bool sub; + int i; + bool multiline; + bool comm; + + REQUIRE(rdata->type == dns_rdatatype_soa); + REQUIRE(rdata->length != 0); + + multiline = ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0); + if (multiline) { + comm = ((tctx->flags & DNS_STYLEFLAG_RRCOMMENT) != 0); + } else { + comm = false; + } + + dns_name_init(&mname, NULL); + dns_name_init(&rname, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, &dregion); + + dns_name_fromregion(&mname, &dregion); + isc_region_consume(&dregion, name_length(&mname)); + + dns_name_fromregion(&rname, &dregion); + isc_region_consume(&dregion, name_length(&rname)); + + sub = name_prefix(&mname, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + RETERR(str_totext(" ", target)); + + sub = name_prefix(&rname, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + if (multiline) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + + for (i = 0; i < 5; i++) { + char buf[sizeof("0123456789 ; ")]; + unsigned long num; + num = uint32_fromregion(&dregion); + isc_region_consume(&dregion, 4); + snprintf(buf, sizeof(buf), comm ? "%-10lu ; " : "%lu", num); + RETERR(str_totext(buf, target)); + if (comm) { + RETERR(str_totext(soa_fieldnames[i], target)); + /* Print times in week/day/hour/minute/second form */ + if (i >= 1) { + RETERR(str_totext(" (", target)); + RETERR(dns_ttl_totext(num, true, true, target)); + RETERR(str_totext(")", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + } else if (i < 4) { + RETERR(str_totext(tctx->linebreak, target)); + } + } + + if (multiline) { + RETERR(str_totext(")", target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_soa(ARGS_FROMWIRE) { + dns_name_t mname; + dns_name_t rname; + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(type == dns_rdatatype_soa); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&mname, NULL); + dns_name_init(&rname, NULL); + + RETERR(dns_name_fromwire(&mname, source, dctx, options, target)); + RETERR(dns_name_fromwire(&rname, source, dctx, options, target)); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + + if (sregion.length < 20) { + return (ISC_R_UNEXPECTEDEND); + } + if (tregion.length < 20) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 20); + isc_buffer_forward(source, 20); + isc_buffer_add(target, 20); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_soa(ARGS_TOWIRE) { + isc_region_t sregion; + isc_region_t tregion; + dns_name_t mname; + dns_name_t rname; + dns_offsets_t moffsets; + dns_offsets_t roffsets; + + REQUIRE(rdata->type == dns_rdatatype_soa); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + + dns_name_init(&mname, moffsets); + dns_name_init(&rname, roffsets); + + dns_rdata_toregion(rdata, &sregion); + + dns_name_fromregion(&mname, &sregion); + isc_region_consume(&sregion, name_length(&mname)); + RETERR(dns_name_towire(&mname, cctx, target)); + + dns_name_fromregion(&rname, &sregion); + isc_region_consume(&sregion, name_length(&rname)); + RETERR(dns_name_towire(&rname, cctx, target)); + + isc_buffer_availableregion(target, &tregion); + if (tregion.length < 20) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 20); + isc_buffer_add(target, 20); + return (ISC_R_SUCCESS); +} + +static int +compare_soa(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_soa); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_soa(ARGS_FROMSTRUCT) { + dns_rdata_soa_t *soa = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_soa); + REQUIRE(soa != NULL); + REQUIRE(soa->common.rdtype == type); + REQUIRE(soa->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&soa->origin, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + dns_name_toregion(&soa->contact, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + RETERR(uint32_tobuffer(soa->serial, target)); + RETERR(uint32_tobuffer(soa->refresh, target)); + RETERR(uint32_tobuffer(soa->retry, target)); + RETERR(uint32_tobuffer(soa->expire, target)); + return (uint32_tobuffer(soa->minimum, target)); +} + +static isc_result_t +tostruct_soa(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_soa_t *soa = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_soa); + REQUIRE(soa != NULL); + REQUIRE(rdata->length != 0); + + soa->common.rdclass = rdata->rdclass; + soa->common.rdtype = rdata->type; + ISC_LINK_INIT(&soa->common, link); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&soa->origin, NULL); + name_duporclone(&name, mctx, &soa->origin); + + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&soa->contact, NULL); + name_duporclone(&name, mctx, &soa->contact); + + soa->serial = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + soa->refresh = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + soa->retry = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + soa->expire = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + + soa->minimum = uint32_fromregion(®ion); + + soa->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_soa(ARGS_FREESTRUCT) { + dns_rdata_soa_t *soa = source; + + REQUIRE(soa != NULL); + REQUIRE(soa->common.rdtype == dns_rdatatype_soa); + + if (soa->mctx == NULL) { + return; + } + + dns_name_free(&soa->origin, soa->mctx); + dns_name_free(&soa->contact, soa->mctx); + soa->mctx = NULL; +} + +static isc_result_t +additionaldata_soa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_soa); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_soa(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_soa); + + dns_rdata_toregion(rdata, &r); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + RETERR(dns_name_digest(&name, digest, arg)); + isc_region_consume(&r, name_length(&name)); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + RETERR(dns_name_digest(&name, digest, arg)); + isc_region_consume(&r, name_length(&name)); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_soa(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_soa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_soa(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_soa); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + isc_region_consume(®ion, name_length(&name)); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ismailbox(&name)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_soa(ARGS_COMPARE) { + return (compare_soa(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_SOA_6_C */ diff --git a/lib/dns/rdata/generic/soa_6.h b/lib/dns/rdata/generic/soa_6.h new file mode 100644 index 0000000..c6071bc --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_soa { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t origin; + dns_name_t contact; + uint32_t serial; /*%< host order */ + uint32_t refresh; /*%< host order */ + uint32_t retry; /*%< host order */ + uint32_t expire; /*%< host order */ + uint32_t minimum; /*%< host order */ +} dns_rdata_soa_t; diff --git a/lib/dns/rdata/generic/spf_99.c b/lib/dns/rdata/generic/spf_99.c new file mode 100644 index 0000000..20f939b --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_SPF_99_C +#define RDATA_GENERIC_SPF_99_C + +#define RRTYPE_SPF_ATTRIBUTES (0) + +static isc_result_t +fromtext_spf(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_spf); + + return (generic_fromtext_txt(CALL_FROMTEXT)); +} + +static isc_result_t +totext_spf(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_spf); + + return (generic_totext_txt(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_spf(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_spf); + + return (generic_fromwire_txt(CALL_FROMWIRE)); +} + +static isc_result_t +towire_spf(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_spf(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_spf); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_spf(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_spf); + + return (generic_fromstruct_txt(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_spf(ARGS_TOSTRUCT) { + dns_rdata_spf_t *spf = target; + + REQUIRE(spf != NULL); + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_spf); + + spf->common.rdclass = rdata->rdclass; + spf->common.rdtype = rdata->type; + ISC_LINK_INIT(&spf->common, link); + + return (generic_tostruct_txt(CALL_TOSTRUCT)); +} + +static void +freestruct_spf(ARGS_FREESTRUCT) { + dns_rdata_spf_t *spf = source; + + REQUIRE(spf != NULL); + REQUIRE(spf->common.rdtype == dns_rdatatype_spf); + + generic_freestruct_txt(source); +} + +static isc_result_t +additionaldata_spf(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_spf(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_spf); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_spf(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_spf); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_spf(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_spf(ARGS_COMPARE) { + return (compare_spf(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_SPF_99_C */ diff --git a/lib/dns/rdata/generic/spf_99.h b/lib/dns/rdata/generic/spf_99.h new file mode 100644 index 0000000..f0963ed --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_spf_string { + uint8_t length; + unsigned char *data; +} dns_rdata_spf_string_t; + +typedef struct dns_rdata_spf { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *txt; + uint16_t txt_len; + /* private */ + uint16_t offset; +} dns_rdata_spf_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ diff --git a/lib/dns/rdata/generic/sshfp_44.c b/lib/dns/rdata/generic/sshfp_44.c new file mode 100644 index 0000000..7f1060a --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.c @@ -0,0 +1,297 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 4255 */ + +#ifndef RDATA_GENERIC_SSHFP_44_C +#define RDATA_GENERIC_SSHFP_44_C + +#define RRTYPE_SSHFP_ATTRIBUTES (0) + +static isc_result_t +fromtext_sshfp(ARGS_FROMTEXT) { + isc_token_t token; + int len = -1; + + REQUIRE(type == dns_rdatatype_sshfp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Digest type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Enforce known digest lengths. + */ + switch (token.value.as_ulong) { + case 1: + len = ISC_SHA1_DIGESTLENGTH; + break; + case 2: + len = ISC_SHA256_DIGESTLENGTH; + break; + default: + break; + } + + /* + * Digest. + */ + return (isc_hex_tobuffer(lexer, target, len)); +} + +static isc_result_t +totext_sshfp(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000 ")]; + unsigned int n; + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + /* + * Algorithm. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Digest type. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u", n); + RETERR(str_totext(buf, target)); + + if (sr.length == 0U) { + return (ISC_R_SUCCESS); + } + + /* + * Digest. + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_hex_totext(&sr, 0, "", target)); + } else { + RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_sshfp(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_sshfp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + + if ((sr.base[1] == 1 && sr.length != ISC_SHA1_DIGESTLENGTH + 2) || + (sr.base[1] == 2 && sr.length != ISC_SHA256_DIGESTLENGTH + 2)) + { + return (DNS_R_FORMERR); + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_sshfp(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_sshfp(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_sshfp); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_sshfp(ARGS_FROMSTRUCT) { + dns_rdata_sshfp_t *sshfp = source; + + REQUIRE(type == dns_rdatatype_sshfp); + REQUIRE(sshfp != NULL); + REQUIRE(sshfp->common.rdtype == type); + REQUIRE(sshfp->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(sshfp->algorithm, target)); + RETERR(uint8_tobuffer(sshfp->digest_type, target)); + + return (mem_tobuffer(target, sshfp->digest, sshfp->length)); +} + +static isc_result_t +tostruct_sshfp(ARGS_TOSTRUCT) { + dns_rdata_sshfp_t *sshfp = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + REQUIRE(sshfp != NULL); + REQUIRE(rdata->length != 0); + + sshfp->common.rdclass = rdata->rdclass; + sshfp->common.rdtype = rdata->type; + ISC_LINK_INIT(&sshfp->common, link); + + dns_rdata_toregion(rdata, ®ion); + + sshfp->algorithm = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + sshfp->digest_type = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + sshfp->length = region.length; + + sshfp->digest = mem_maybedup(mctx, region.base, region.length); + if (sshfp->digest == NULL) { + return (ISC_R_NOMEMORY); + } + + sshfp->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_sshfp(ARGS_FREESTRUCT) { + dns_rdata_sshfp_t *sshfp = source; + + REQUIRE(sshfp != NULL); + REQUIRE(sshfp->common.rdtype == dns_rdatatype_sshfp); + + if (sshfp->mctx == NULL) { + return; + } + + if (sshfp->digest != NULL) { + isc_mem_free(sshfp->mctx, sshfp->digest); + } + sshfp->mctx = NULL; +} + +static isc_result_t +additionaldata_sshfp(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sshfp); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_sshfp(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_sshfp(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_sshfp); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_sshfp(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_sshfp); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_sshfp(ARGS_COMPARE) { + return (compare_sshfp(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_SSHFP_44_C */ diff --git a/lib/dns/rdata/generic/sshfp_44.h b/lib/dns/rdata/generic/sshfp_44.h new file mode 100644 index 0000000..a19806f --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! + * \brief Per RFC 4255 */ + +#pragma once + +typedef struct dns_rdata_sshfp { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t algorithm; + uint8_t digest_type; + uint16_t length; + unsigned char *digest; +} dns_rdata_sshfp_t; diff --git a/lib/dns/rdata/generic/ta_32768.c b/lib/dns/rdata/generic/ta_32768.c new file mode 100644 index 0000000..4a0688c --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* http://www.watson.org/~weiler/INI1999-19.pdf */ + +#ifndef RDATA_GENERIC_TA_32768_C +#define RDATA_GENERIC_TA_32768_C + +#define RRTYPE_TA_ATTRIBUTES 0 + +static isc_result_t +fromtext_ta(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromtext_ds(CALL_FROMTEXT)); +} + +static isc_result_t +totext_ta(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_ta); + + return (generic_totext_ds(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_ta(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromwire_ds(CALL_FROMWIRE)); +} + +static isc_result_t +towire_ta(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_ta); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_ta(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_ta); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_ta(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromstruct_ds(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_ta(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + + REQUIRE(rdata->type == dns_rdatatype_ta); + REQUIRE(ds != NULL); + + /* + * Checked by generic_tostruct_ds(). + */ + ds->common.rdclass = rdata->rdclass; + ds->common.rdtype = rdata->type; + ISC_LINK_INIT(&ds->common, link); + + return (generic_tostruct_ds(CALL_TOSTRUCT)); +} + +static void +freestruct_ta(ARGS_FREESTRUCT) { + dns_rdata_ta_t *ds = source; + + REQUIRE(ds != NULL); + REQUIRE(ds->common.rdtype == dns_rdatatype_ta); + + if (ds->mctx == NULL) { + return; + } + + if (ds->digest != NULL) { + isc_mem_free(ds->mctx, ds->digest); + } + ds->mctx = NULL; +} + +static isc_result_t +additionaldata_ta(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ta); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_ta(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_ta); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_ta(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_ta); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_ta(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_ta); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_ta(ARGS_COMPARE) { + return (compare_ta(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_TA_32768_C */ diff --git a/lib/dns/rdata/generic/ta_32768.h b/lib/dns/rdata/generic/ta_32768.h new file mode 100644 index 0000000..e9111c5 --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* + * TA records are identical to DS records. + */ +typedef struct dns_rdata_ds dns_rdata_ta_t; diff --git a/lib/dns/rdata/generic/talink_58.c b/lib/dns/rdata/generic/talink_58.c new file mode 100644 index 0000000..49ad8df --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_TALINK_58_C +#define RDATA_GENERIC_TALINK_58_C + +#define RRTYPE_TALINK_ATTRIBUTES 0 + +static isc_result_t +fromtext_talink(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + int i; + + REQUIRE(type == dns_rdatatype_talink); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + if (origin == NULL) { + origin = dns_rootname; + } + + for (i = 0; i < 2; i++) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, + target)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_talink(ARGS_TOTEXT) { + isc_region_t dregion; + dns_name_t prev; + dns_name_t next; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_talink); + REQUIRE(rdata->length != 0); + + dns_name_init(&prev, NULL); + dns_name_init(&next, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, &dregion); + + dns_name_fromregion(&prev, &dregion); + isc_region_consume(&dregion, name_length(&prev)); + + dns_name_fromregion(&next, &dregion); + isc_region_consume(&dregion, name_length(&next)); + + sub = name_prefix(&prev, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + RETERR(str_totext(" ", target)); + + sub = name_prefix(&next, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_talink(ARGS_FROMWIRE) { + dns_name_t prev; + dns_name_t next; + + REQUIRE(type == dns_rdatatype_talink); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&prev, NULL); + dns_name_init(&next, NULL); + + RETERR(dns_name_fromwire(&prev, source, dctx, options, target)); + return (dns_name_fromwire(&next, source, dctx, options, target)); +} + +static isc_result_t +towire_talink(ARGS_TOWIRE) { + isc_region_t sregion; + dns_name_t prev; + dns_name_t next; + dns_offsets_t moffsets; + dns_offsets_t roffsets; + + REQUIRE(rdata->type == dns_rdatatype_talink); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + + dns_name_init(&prev, moffsets); + dns_name_init(&next, roffsets); + + dns_rdata_toregion(rdata, &sregion); + + dns_name_fromregion(&prev, &sregion); + isc_region_consume(&sregion, name_length(&prev)); + RETERR(dns_name_towire(&prev, cctx, target)); + + dns_name_fromregion(&next, &sregion); + isc_region_consume(&sregion, name_length(&next)); + return (dns_name_towire(&next, cctx, target)); +} + +static int +compare_talink(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_talink); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_talink(ARGS_FROMSTRUCT) { + dns_rdata_talink_t *talink = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_talink); + REQUIRE(talink != NULL); + REQUIRE(talink->common.rdtype == type); + REQUIRE(talink->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&talink->prev, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + dns_name_toregion(&talink->next, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_talink(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_talink_t *talink = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_talink); + REQUIRE(talink != NULL); + REQUIRE(rdata->length != 0); + + talink->common.rdclass = rdata->rdclass; + talink->common.rdtype = rdata->type; + ISC_LINK_INIT(&talink->common, link); + + dns_rdata_toregion(rdata, ®ion); + + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&talink->prev, NULL); + name_duporclone(&name, mctx, &talink->prev); + + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&talink->next, NULL); + name_duporclone(&name, mctx, &talink->next); + + talink->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_talink(ARGS_FREESTRUCT) { + dns_rdata_talink_t *talink = source; + + REQUIRE(talink != NULL); + REQUIRE(talink->common.rdtype == dns_rdatatype_talink); + + if (talink->mctx == NULL) { + return; + } + + dns_name_free(&talink->prev, talink->mctx); + dns_name_free(&talink->next, talink->mctx); + talink->mctx = NULL; +} + +static isc_result_t +additionaldata_talink(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_talink); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_talink(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_talink); + + dns_rdata_toregion(rdata, &r); + return ((digest)(arg, &r)); +} + +static bool +checkowner_talink(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_talink); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_talink(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_talink); + + UNUSED(bad); + UNUSED(owner); + + return (true); +} + +static int +casecompare_talink(ARGS_COMPARE) { + return (compare_talink(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_TALINK_58_C */ diff --git a/lib/dns/rdata/generic/talink_58.h b/lib/dns/rdata/generic/talink_58.h new file mode 100644 index 0000000..5a7f075 --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * http://www.iana.org/assignments/dns-parameters/TALINK/talink-completed-template + */ + +#pragma once + +typedef struct dns_rdata_talink { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t prev; + dns_name_t next; +} dns_rdata_talink_t; diff --git a/lib/dns/rdata/generic/tkey_249.c b/lib/dns/rdata/generic/tkey_249.c new file mode 100644 index 0000000..3605a34 --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.c @@ -0,0 +1,581 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsext-tkey-01.txt */ + +#ifndef RDATA_GENERIC_TKEY_249_C +#define RDATA_GENERIC_TKEY_249_C + +#define RRTYPE_TKEY_ATTRIBUTES (DNS_RDATATYPEATTR_META) + +static isc_result_t +fromtext_tkey(ARGS_FROMTEXT) { + isc_token_t token; + dns_rcode_t rcode; + dns_name_t name; + isc_buffer_t buffer; + long i; + char *e; + + REQUIRE(type == dns_rdatatype_tkey); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Algorithm. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + /* + * Inception. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * Expiration. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * Mode. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Error. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (dns_tsigrcode_fromtext(&rcode, &token.value.as_textregion) != + ISC_R_SUCCESS) + { + i = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0) { + RETTOK(DNS_R_UNKNOWN); + } + if (i < 0 || i > 0xffff) { + RETTOK(ISC_R_RANGE); + } + rcode = (dns_rcode_t)i; + } + RETERR(uint16_tobuffer(rcode, target)); + + /* + * Key Size. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Key Data. + */ + RETERR(isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong)); + + /* + * Other Size. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Other Data. + */ + return (isc_base64_tobuffer(lexer, target, (int)token.value.as_ulong)); +} + +static isc_result_t +totext_tkey(ARGS_TOTEXT) { + isc_region_t sr, dr; + char buf[sizeof("4294967295 ")]; + unsigned long n; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_tkey); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + + /* + * Algorithm. + */ + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + dns_name_fromregion(&name, &sr); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + RETERR(str_totext(" ", target)); + isc_region_consume(&sr, name_length(&name)); + + /* + * Inception. + */ + n = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu ", n); + RETERR(str_totext(buf, target)); + + /* + * Expiration. + */ + n = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu ", n); + RETERR(str_totext(buf, target)); + + /* + * Mode. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu ", n); + RETERR(str_totext(buf, target)); + + /* + * Error. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + if (dns_tsigrcode_totext((dns_rcode_t)n, target) == ISC_R_SUCCESS) { + RETERR(str_totext(" ", target)); + } else { + snprintf(buf, sizeof(buf), "%lu ", n); + RETERR(str_totext(buf, target)); + } + + /* + * Key Size. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu", n); + RETERR(str_totext(buf, target)); + + /* + * Key Data. + */ + REQUIRE(n <= sr.length); + dr = sr; + dr.length = n; + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&dr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&dr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" ) ", target)); + } else { + RETERR(str_totext(" ", target)); + } + isc_region_consume(&sr, n); + + /* + * Other Size. + */ + n = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + snprintf(buf, sizeof(buf), "%lu", n); + RETERR(str_totext(buf, target)); + + /* + * Other Data. + */ + REQUIRE(n <= sr.length); + if (n != 0U) { + dr = sr; + dr.length = n; + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&dr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&dr, tctx->width - 2, + tctx->linebreak, target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_tkey(ARGS_FROMWIRE) { + isc_region_t sr; + unsigned long n; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_tkey); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + /* + * Algorithm. + */ + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + /* + * Inception: 4 + * Expiration: 4 + * Mode: 2 + * Error: 2 + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 12) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, 12)); + isc_region_consume(&sr, 12); + isc_buffer_forward(source, 12); + + /* + * Key Length + Key Data. + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + n = uint16_fromregion(&sr); + if (sr.length < n + 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, n + 2)); + isc_region_consume(&sr, n + 2); + isc_buffer_forward(source, n + 2); + + /* + * Other Length + Other Data. + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + n = uint16_fromregion(&sr); + if (sr.length < n + 2) { + return (ISC_R_UNEXPECTEDEND); + } + isc_buffer_forward(source, n + 2); + return (mem_tobuffer(target, sr.base, n + 2)); +} + +static isc_result_t +towire_tkey(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + + REQUIRE(rdata->type == dns_rdatatype_tkey); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + /* + * Algorithm. + */ + dns_rdata_toregion(rdata, &sr); + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + RETERR(dns_name_towire(&name, cctx, target)); + isc_region_consume(&sr, name_length(&name)); + + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_tkey(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + dns_name_t name1; + dns_name_t name2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_tkey); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + /* + * Algorithm. + */ + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_name_fromregion(&name1, &r1); + dns_name_fromregion(&name2, &r2); + if ((order = dns_name_rdatacompare(&name1, &name2)) != 0) { + return (order); + } + isc_region_consume(&r1, name_length(&name1)); + isc_region_consume(&r2, name_length(&name2)); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_tkey(ARGS_FROMSTRUCT) { + dns_rdata_tkey_t *tkey = source; + + REQUIRE(type == dns_rdatatype_tkey); + REQUIRE(tkey != NULL); + REQUIRE(tkey->common.rdtype == type); + REQUIRE(tkey->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Algorithm Name. + */ + RETERR(name_tobuffer(&tkey->algorithm, target)); + + /* + * Inception: 32 bits. + */ + RETERR(uint32_tobuffer(tkey->inception, target)); + + /* + * Expire: 32 bits. + */ + RETERR(uint32_tobuffer(tkey->expire, target)); + + /* + * Mode: 16 bits. + */ + RETERR(uint16_tobuffer(tkey->mode, target)); + + /* + * Error: 16 bits. + */ + RETERR(uint16_tobuffer(tkey->error, target)); + + /* + * Key size: 16 bits. + */ + RETERR(uint16_tobuffer(tkey->keylen, target)); + + /* + * Key. + */ + RETERR(mem_tobuffer(target, tkey->key, tkey->keylen)); + + /* + * Other size: 16 bits. + */ + RETERR(uint16_tobuffer(tkey->otherlen, target)); + + /* + * Other data. + */ + return (mem_tobuffer(target, tkey->other, tkey->otherlen)); +} + +static isc_result_t +tostruct_tkey(ARGS_TOSTRUCT) { + dns_rdata_tkey_t *tkey = target; + dns_name_t alg; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_tkey); + REQUIRE(tkey != NULL); + REQUIRE(rdata->length != 0); + + tkey->common.rdclass = rdata->rdclass; + tkey->common.rdtype = rdata->type; + ISC_LINK_INIT(&tkey->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Algorithm Name. + */ + dns_name_init(&alg, NULL); + dns_name_fromregion(&alg, &sr); + dns_name_init(&tkey->algorithm, NULL); + name_duporclone(&alg, mctx, &tkey->algorithm); + isc_region_consume(&sr, name_length(&tkey->algorithm)); + + /* + * Inception. + */ + tkey->inception = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Expire. + */ + tkey->expire = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + + /* + * Mode. + */ + tkey->mode = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Error. + */ + tkey->error = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Key size. + */ + tkey->keylen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Key. + */ + INSIST(tkey->keylen + 2U <= sr.length); + tkey->key = mem_maybedup(mctx, sr.base, tkey->keylen); + if (tkey->key == NULL) { + goto cleanup; + } + isc_region_consume(&sr, tkey->keylen); + + /* + * Other size. + */ + tkey->otherlen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Other. + */ + INSIST(tkey->otherlen <= sr.length); + tkey->other = mem_maybedup(mctx, sr.base, tkey->otherlen); + if (tkey->other == NULL) { + goto cleanup; + } + + tkey->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&tkey->algorithm, mctx); + } + if (mctx != NULL && tkey->key != NULL) { + isc_mem_free(mctx, tkey->key); + } + return (ISC_R_NOMEMORY); +} + +static void +freestruct_tkey(ARGS_FREESTRUCT) { + dns_rdata_tkey_t *tkey = (dns_rdata_tkey_t *)source; + + REQUIRE(tkey != NULL); + + if (tkey->mctx == NULL) { + return; + } + + dns_name_free(&tkey->algorithm, tkey->mctx); + if (tkey->key != NULL) { + isc_mem_free(tkey->mctx, tkey->key); + } + if (tkey->other != NULL) { + isc_mem_free(tkey->mctx, tkey->other); + } + tkey->mctx = NULL; +} + +static isc_result_t +additionaldata_tkey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_tkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_tkey(ARGS_DIGEST) { + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_tkey); + + return (ISC_R_NOTIMPLEMENTED); +} + +static bool +checkowner_tkey(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_tkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_tkey(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_tkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_tkey(ARGS_COMPARE) { + return (compare_tkey(rdata1, rdata2)); +} +#endif /* RDATA_GENERIC_TKEY_249_C */ diff --git a/lib/dns/rdata/generic/tkey_249.h b/lib/dns/rdata/generic/tkey_249.h new file mode 100644 index 0000000..8edd074 --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per draft-ietf-dnsind-tkey-00.txt */ + +typedef struct dns_rdata_tkey { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t algorithm; + uint32_t inception; + uint32_t expire; + uint16_t mode; + uint16_t error; + uint16_t keylen; + unsigned char *key; + uint16_t otherlen; + unsigned char *other; +} dns_rdata_tkey_t; diff --git a/lib/dns/rdata/generic/tlsa_52.c b/lib/dns/rdata/generic/tlsa_52.c new file mode 100644 index 0000000..8489dd9 --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* rfc6698.txt */ + +#ifndef RDATA_GENERIC_TLSA_52_C +#define RDATA_GENERIC_TLSA_52_C + +#define RRTYPE_TLSA_ATTRIBUTES 0 + +static isc_result_t +generic_fromtext_tlsa(ARGS_FROMTEXT) { + isc_token_t token; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Certificate Usage. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Selector. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Matching type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Certificate Association Data. + */ + return (isc_hex_tobuffer(lexer, target, -2)); +} + +static isc_result_t +generic_totext_tlsa(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000 ")]; + unsigned int n; + + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + /* + * Certificate Usage. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Selector. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u ", n); + RETERR(str_totext(buf, target)); + + /* + * Matching type. + */ + n = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u", n); + RETERR(str_totext(buf, target)); + + /* + * Certificate Association Data. + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_hex_totext(&sr, 0, "", target)); + } else { + RETERR(isc_hex_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_fromwire_tlsa(ARGS_FROMWIRE) { + isc_region_t sr; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + + /* Usage(1), Selector(1), Type(1), Data(1+) */ + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +fromtext_tlsa(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromtext_tlsa(CALL_FROMTEXT)); +} + +static isc_result_t +totext_tlsa(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + return (generic_totext_tlsa(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_tlsa(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromwire_tlsa(CALL_FROMWIRE)); +} + +static isc_result_t +towire_tlsa(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_tlsa(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_tlsa); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +generic_fromstruct_tlsa(ARGS_FROMSTRUCT) { + dns_rdata_tlsa_t *tlsa = source; + + REQUIRE(tlsa != NULL); + REQUIRE(tlsa->common.rdtype == type); + REQUIRE(tlsa->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint8_tobuffer(tlsa->usage, target)); + RETERR(uint8_tobuffer(tlsa->selector, target)); + RETERR(uint8_tobuffer(tlsa->match, target)); + + return (mem_tobuffer(target, tlsa->data, tlsa->length)); +} + +static isc_result_t +generic_tostruct_tlsa(ARGS_TOSTRUCT) { + dns_rdata_tlsa_t *tlsa = target; + isc_region_t region; + + REQUIRE(tlsa != NULL); + REQUIRE(rdata->length != 0); + + REQUIRE(tlsa != NULL); + REQUIRE(tlsa->common.rdclass == rdata->rdclass); + REQUIRE(tlsa->common.rdtype == rdata->type); + REQUIRE(!ISC_LINK_LINKED(&tlsa->common, link)); + + dns_rdata_toregion(rdata, ®ion); + + tlsa->usage = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + tlsa->selector = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + tlsa->match = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + tlsa->length = region.length; + + tlsa->data = mem_maybedup(mctx, region.base, region.length); + if (tlsa->data == NULL) { + return (ISC_R_NOMEMORY); + } + + tlsa->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +generic_freestruct_tlsa(ARGS_FREESTRUCT) { + dns_rdata_tlsa_t *tlsa = source; + + REQUIRE(tlsa != NULL); + + if (tlsa->mctx == NULL) { + return; + } + + if (tlsa->data != NULL) { + isc_mem_free(tlsa->mctx, tlsa->data); + } + tlsa->mctx = NULL; +} + +static isc_result_t +fromstruct_tlsa(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromstruct_tlsa(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_tlsa(ARGS_TOSTRUCT) { + dns_rdata_tlsa_t *tlsa = target; + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + REQUIRE(tlsa != NULL); + + tlsa->common.rdclass = rdata->rdclass; + tlsa->common.rdtype = rdata->type; + ISC_LINK_INIT(&tlsa->common, link); + + return (generic_tostruct_tlsa(CALL_TOSTRUCT)); +} + +static void +freestruct_tlsa(ARGS_FREESTRUCT) { + dns_rdata_tlsa_t *tlsa = source; + + REQUIRE(tlsa != NULL); + REQUIRE(tlsa->common.rdtype == dns_rdatatype_tlsa); + + generic_freestruct_tlsa(source); +} + +static isc_result_t +additionaldata_tlsa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_tlsa(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_tlsa(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_tlsa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_tlsa(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_tlsa(ARGS_COMPARE) { + return (compare_tlsa(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_TLSA_52_C */ diff --git a/lib/dns/rdata/generic/tlsa_52.h b/lib/dns/rdata/generic/tlsa_52.h new file mode 100644 index 0000000..fd75f95 --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief per rfc6698.txt + */ +typedef struct dns_rdata_tlsa { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint8_t usage; + uint8_t selector; + uint8_t match; + uint16_t length; + unsigned char *data; +} dns_rdata_tlsa_t; diff --git a/lib/dns/rdata/generic/txt_16.c b/lib/dns/rdata/generic/txt_16.c new file mode 100644 index 0000000..75f359e --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.c @@ -0,0 +1,360 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_GENERIC_TXT_16_C +#define RDATA_GENERIC_TXT_16_C + +#define RRTYPE_TXT_ATTRIBUTES (0) + +static isc_result_t +generic_fromtext_txt(ARGS_FROMTEXT) { + isc_token_t token; + int strings; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + strings = 0; + if ((options & DNS_RDATA_UNKNOWNESCAPE) != 0) { + isc_textregion_t r; + DE_CONST("#", r.base); + r.length = 1; + RETERR(txt_fromtext(&r, target)); + strings++; + } + for (;;) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_qstring, true)); + if (token.type != isc_tokentype_qstring && + token.type != isc_tokentype_string) + { + break; + } + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + strings++; + } + /* Let upper layer handle eol/eof. */ + isc_lex_ungettoken(lexer, &token); + return (strings == 0 ? ISC_R_UNEXPECTEDEND : ISC_R_SUCCESS); +} + +static isc_result_t +generic_totext_txt(ARGS_TOTEXT) { + isc_region_t region; + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + + while (region.length > 0) { + RETERR(txt_totext(®ion, true, target)); + if (region.length > 0) { + RETERR(str_totext(" ", target)); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_fromwire_txt(ARGS_FROMWIRE) { + isc_result_t result; + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + do { + result = txt_fromwire(source, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + } while (!buffer_empty(source)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromtext_txt(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromtext_txt(CALL_FROMTEXT)); +} + +static isc_result_t +totext_txt(ARGS_TOTEXT) { + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_txt); + + return (generic_totext_txt(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_txt(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromwire_txt(CALL_FROMWIRE)); +} + +static isc_result_t +towire_txt(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_txt(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_txt); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +generic_fromstruct_txt(ARGS_FROMSTRUCT) { + dns_rdata_txt_t *txt = source; + isc_region_t region; + uint8_t length; + + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdtype == type); + REQUIRE(txt->common.rdclass == rdclass); + REQUIRE(txt->txt != NULL && txt->txt_len != 0); + + UNUSED(type); + UNUSED(rdclass); + + region.base = txt->txt; + region.length = txt->txt_len; + while (region.length > 0) { + length = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + if (region.length < length) { + return (ISC_R_UNEXPECTEDEND); + } + isc_region_consume(®ion, length); + } + + return (mem_tobuffer(target, txt->txt, txt->txt_len)); +} + +static isc_result_t +generic_tostruct_txt(ARGS_TOSTRUCT) { + dns_rdata_txt_t *txt = target; + isc_region_t r; + + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdclass == rdata->rdclass); + REQUIRE(txt->common.rdtype == rdata->type); + REQUIRE(!ISC_LINK_LINKED(&txt->common, link)); + + dns_rdata_toregion(rdata, &r); + txt->txt_len = r.length; + txt->txt = mem_maybedup(mctx, r.base, r.length); + if (txt->txt == NULL) { + return (ISC_R_NOMEMORY); + } + + txt->offset = 0; + txt->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +generic_freestruct_txt(ARGS_FREESTRUCT) { + dns_rdata_txt_t *txt = source; + + REQUIRE(txt != NULL); + + if (txt->mctx == NULL) { + return; + } + + if (txt->txt != NULL) { + isc_mem_free(txt->mctx, txt->txt); + } + txt->mctx = NULL; +} + +static isc_result_t +fromstruct_txt(ARGS_FROMSTRUCT) { + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromstruct_txt(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_txt(ARGS_TOSTRUCT) { + dns_rdata_txt_t *txt = target; + + REQUIRE(rdata->type == dns_rdatatype_txt); + REQUIRE(txt != NULL); + + txt->common.rdclass = rdata->rdclass; + txt->common.rdtype = rdata->type; + ISC_LINK_INIT(&txt->common, link); + + return (generic_tostruct_txt(CALL_TOSTRUCT)); +} + +static void +freestruct_txt(ARGS_FREESTRUCT) { + dns_rdata_txt_t *txt = source; + + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_txt); + + generic_freestruct_txt(source); +} + +static isc_result_t +additionaldata_txt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_txt(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_txt); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_txt(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_txt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_txt(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_txt(ARGS_COMPARE) { + return (compare_txt(rdata1, rdata2)); +} + +static isc_result_t +generic_txt_first(dns_rdata_txt_t *txt) { + REQUIRE(txt != NULL); + REQUIRE(txt->txt != NULL || txt->txt_len == 0); + + if (txt->txt_len == 0) { + return (ISC_R_NOMORE); + } + + txt->offset = 0; + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_txt_next(dns_rdata_txt_t *txt) { + isc_region_t r; + uint8_t length; + + REQUIRE(txt != NULL); + REQUIRE(txt->txt != NULL && txt->txt_len != 0); + + INSIST(txt->offset + 1 <= txt->txt_len); + r.base = txt->txt + txt->offset; + r.length = txt->txt_len - txt->offset; + length = uint8_fromregion(&r); + INSIST(txt->offset + 1 + length <= txt->txt_len); + txt->offset = txt->offset + 1 + length; + if (txt->offset == txt->txt_len) { + return (ISC_R_NOMORE); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) { + isc_region_t r; + + REQUIRE(txt != NULL); + REQUIRE(string != NULL); + REQUIRE(txt->txt != NULL); + REQUIRE(txt->offset < txt->txt_len); + + INSIST(txt->offset + 1 <= txt->txt_len); + r.base = txt->txt + txt->offset; + r.length = txt->txt_len - txt->offset; + + string->length = uint8_fromregion(&r); + isc_region_consume(&r, 1); + string->data = r.base; + INSIST(txt->offset + 1 + string->length <= txt->txt_len); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdata_txt_first(dns_rdata_txt_t *txt) { + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_txt); + + return (generic_txt_first(txt)); +} + +isc_result_t +dns_rdata_txt_next(dns_rdata_txt_t *txt) { + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_txt); + + return (generic_txt_next(txt)); +} + +isc_result_t +dns_rdata_txt_current(dns_rdata_txt_t *txt, dns_rdata_txt_string_t *string) { + REQUIRE(txt != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_txt); + + return (generic_txt_current(txt, string)); +} +#endif /* RDATA_GENERIC_TXT_16_C */ diff --git a/lib/dns/rdata/generic/txt_16.h b/lib/dns/rdata/generic/txt_16.h new file mode 100644 index 0000000..3e4c58a --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_txt_string { + uint8_t length; + unsigned char *data; +} dns_rdata_txt_string_t; + +typedef struct dns_rdata_txt { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *txt; + uint16_t txt_len; + /* private */ + uint16_t offset; +} dns_rdata_txt_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ + +isc_result_t +dns_rdata_txt_first(dns_rdata_txt_t *); + +isc_result_t +dns_rdata_txt_next(dns_rdata_txt_t *); + +isc_result_t +dns_rdata_txt_current(dns_rdata_txt_t *, dns_rdata_txt_string_t *); diff --git a/lib/dns/rdata/generic/uri_256.c b/lib/dns/rdata/generic/uri_256.c new file mode 100644 index 0000000..4618823 --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef GENERIC_URI_256_C +#define GENERIC_URI_256_C 1 + +#define RRTYPE_URI_ATTRIBUTES (0) + +static isc_result_t +fromtext_uri(ARGS_FROMTEXT) { + isc_token_t token; + + REQUIRE(type == dns_rdatatype_uri); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Priority + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Weight + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Target URI + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + if (token.type != isc_tokentype_qstring) { + RETTOK(DNS_R_SYNTAX); + } + RETTOK(multitxt_fromtext(&token.value.as_textregion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_uri(ARGS_TOTEXT) { + isc_region_t region; + unsigned short priority, weight; + char buf[sizeof("65000 ")]; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_uri); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + + /* + * Priority + */ + priority = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u ", priority); + RETERR(str_totext(buf, target)); + + /* + * Weight + */ + weight = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u ", weight); + RETERR(str_totext(buf, target)); + + /* + * Target URI + */ + RETERR(multitxt_totext(®ion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_uri(ARGS_FROMWIRE) { + isc_region_t region; + + REQUIRE(type == dns_rdatatype_uri); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + /* + * Priority, weight + */ + isc_buffer_activeregion(source, ®ion); + if (region.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Priority, weight and target URI + */ + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); +} + +static isc_result_t +towire_uri(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_uri); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, ®ion); + return (mem_tobuffer(target, region.base, region.length)); +} + +static int +compare_uri(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_uri); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + + /* + * Priority + */ + order = memcmp(r1.base, r2.base, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(&r1, 2); + isc_region_consume(&r2, 2); + + /* + * Weight + */ + order = memcmp(r1.base, r2.base, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + isc_region_consume(&r1, 2); + isc_region_consume(&r2, 2); + + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_uri(ARGS_FROMSTRUCT) { + dns_rdata_uri_t *uri = source; + + REQUIRE(type == dns_rdatatype_uri); + REQUIRE(uri != NULL); + REQUIRE(uri->common.rdtype == type); + REQUIRE(uri->common.rdclass == rdclass); + REQUIRE(uri->target != NULL && uri->tgt_len != 0); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Priority + */ + RETERR(uint16_tobuffer(uri->priority, target)); + + /* + * Weight + */ + RETERR(uint16_tobuffer(uri->weight, target)); + + /* + * Target URI + */ + return (mem_tobuffer(target, uri->target, uri->tgt_len)); +} + +static isc_result_t +tostruct_uri(ARGS_TOSTRUCT) { + dns_rdata_uri_t *uri = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_uri); + REQUIRE(uri != NULL); + REQUIRE(rdata->length != 0); + + uri->common.rdclass = rdata->rdclass; + uri->common.rdtype = rdata->type; + ISC_LINK_INIT(&uri->common, link); + + dns_rdata_toregion(rdata, &sr); + + /* + * Priority + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + uri->priority = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Weight + */ + if (sr.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + uri->weight = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* + * Target URI + */ + uri->tgt_len = sr.length; + uri->target = mem_maybedup(mctx, sr.base, sr.length); + if (uri->target == NULL) { + return (ISC_R_NOMEMORY); + } + + uri->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_uri(ARGS_FREESTRUCT) { + dns_rdata_uri_t *uri = (dns_rdata_uri_t *)source; + + REQUIRE(uri != NULL); + REQUIRE(uri->common.rdtype == dns_rdatatype_uri); + + if (uri->mctx == NULL) { + return; + } + + if (uri->target != NULL) { + isc_mem_free(uri->mctx, uri->target); + } + uri->mctx = NULL; +} + +static isc_result_t +additionaldata_uri(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_uri); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_uri(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_uri); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_uri(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_uri); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_uri(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_uri); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_uri(ARGS_COMPARE) { + return (compare_uri(rdata1, rdata2)); +} + +#endif /* GENERIC_URI_256_C */ diff --git a/lib/dns/rdata/generic/uri_256.h b/lib/dns/rdata/generic/uri_256.h new file mode 100644 index 0000000..be99e95 --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_uri { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t priority; + uint16_t weight; + unsigned char *target; + uint16_t tgt_len; +} dns_rdata_uri_t; diff --git a/lib/dns/rdata/generic/x25_19.c b/lib/dns/rdata/generic/x25_19.c new file mode 100644 index 0000000..a0fe705 --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1183 */ + +#ifndef RDATA_GENERIC_X25_19_C +#define RDATA_GENERIC_X25_19_C + +#define RRTYPE_X25_ATTRIBUTES (0) + +static isc_result_t +fromtext_x25(ARGS_FROMTEXT) { + isc_token_t token; + unsigned int i; + + REQUIRE(type == dns_rdatatype_x25); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + if (token.value.as_textregion.length < 4) { + RETTOK(DNS_R_SYNTAX); + } + for (i = 0; i < token.value.as_textregion.length; i++) { + if (!isdigit((unsigned char)token.value.as_textregion.base[i])) + { + RETTOK(ISC_R_RANGE); + } + } + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_x25(ARGS_TOTEXT) { + isc_region_t region; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_x25); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + return (txt_totext(®ion, true, target)); +} + +static isc_result_t +fromwire_x25(ARGS_FROMWIRE) { + isc_region_t sr; + unsigned int i; + + REQUIRE(type == dns_rdatatype_x25); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 5 || sr.base[0] != (sr.length - 1)) { + return (DNS_R_FORMERR); + } + for (i = 1; i < sr.length; i++) { + if (sr.base[i] < 0x30 || sr.base[i] > 0x39) { + return (DNS_R_FORMERR); + } + } + return (txt_fromwire(source, target)); +} + +static isc_result_t +towire_x25(ARGS_TOWIRE) { + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_x25); + REQUIRE(rdata->length != 0); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_x25(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_x25); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_x25(ARGS_FROMSTRUCT) { + dns_rdata_x25_t *x25 = source; + uint8_t i; + + REQUIRE(type == dns_rdatatype_x25); + REQUIRE(x25 != NULL); + REQUIRE(x25->common.rdtype == type); + REQUIRE(x25->common.rdclass == rdclass); + REQUIRE(x25->x25 != NULL && x25->x25_len != 0); + + UNUSED(type); + UNUSED(rdclass); + + if (x25->x25_len < 4) { + return (ISC_R_RANGE); + } + + for (i = 0; i < x25->x25_len; i++) { + if (!isdigit((unsigned char)x25->x25[i])) { + return (ISC_R_RANGE); + } + } + + RETERR(uint8_tobuffer(x25->x25_len, target)); + return (mem_tobuffer(target, x25->x25, x25->x25_len)); +} + +static isc_result_t +tostruct_x25(ARGS_TOSTRUCT) { + dns_rdata_x25_t *x25 = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_x25); + REQUIRE(x25 != NULL); + REQUIRE(rdata->length != 0); + + x25->common.rdclass = rdata->rdclass; + x25->common.rdtype = rdata->type; + ISC_LINK_INIT(&x25->common, link); + + dns_rdata_toregion(rdata, &r); + x25->x25_len = uint8_fromregion(&r); + isc_region_consume(&r, 1); + x25->x25 = mem_maybedup(mctx, r.base, x25->x25_len); + if (x25->x25 == NULL) { + return (ISC_R_NOMEMORY); + } + + x25->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_x25(ARGS_FREESTRUCT) { + dns_rdata_x25_t *x25 = source; + + REQUIRE(x25 != NULL); + REQUIRE(x25->common.rdtype == dns_rdatatype_x25); + + if (x25->mctx == NULL) { + return; + } + + if (x25->x25 != NULL) { + isc_mem_free(x25->mctx, x25->x25); + } + x25->mctx = NULL; +} + +static isc_result_t +additionaldata_x25(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_x25); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_x25(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_x25); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_x25(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_x25); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_x25(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_x25); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_x25(ARGS_COMPARE) { + return (compare_x25(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_X25_19_C */ diff --git a/lib/dns/rdata/generic/x25_19.h b/lib/dns/rdata/generic/x25_19.h new file mode 100644 index 0000000..74a5ee1 --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1183 */ + +typedef struct dns_rdata_x25 { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *x25; + uint8_t x25_len; +} dns_rdata_x25_t; diff --git a/lib/dns/rdata/generic/zonemd_63.c b/lib/dns/rdata/generic/zonemd_63.c new file mode 100644 index 0000000..1bf9573 --- /dev/null +++ b/lib/dns/rdata/generic/zonemd_63.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 8976 */ + +#ifndef RDATA_GENERIC_ZONEMD_63_C +#define RDATA_GENERIC_ZONEMD_63_C + +#define RRTYPE_ZONEMD_ATTRIBUTES 0 + +static isc_result_t +fromtext_zonemd(ARGS_FROMTEXT) { + isc_token_t token; + int digest_type, length; + isc_buffer_t save; + isc_result_t result; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + /* + * Zone Serial. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint32_tobuffer(token.value.as_ulong, target)); + + /* + * Digest Scheme. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + RETERR(uint8_tobuffer(token.value.as_ulong, target)); + + /* + * Digest Type. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + digest_type = token.value.as_ulong; + RETERR(uint8_tobuffer(digest_type, target)); + + /* + * Digest. + */ + switch (digest_type) { + case DNS_ZONEMD_DIGEST_SHA384: + length = ISC_SHA384_DIGESTLENGTH; + break; + case DNS_ZONEMD_DIGEST_SHA512: + length = ISC_SHA512_DIGESTLENGTH; + break; + default: + length = -2; + break; + } + + save = *target; + result = isc_hex_tobuffer(lexer, target, length); + /* Minimum length of digest is 12 octets. */ + if (isc_buffer_usedlength(target) - isc_buffer_usedlength(&save) < 12) { + return (ISC_R_UNEXPECTEDEND); + } + return (result); +} + +static isc_result_t +totext_zonemd(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("0123456789")]; + unsigned long num; + + REQUIRE(rdata->length > 6); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + + /* + * Zone Serial. + */ + num = uint32_fromregion(&sr); + isc_region_consume(&sr, 4); + snprintf(buf, sizeof(buf), "%lu", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + /* + * Digest scheme. + */ + num = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%lu", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + /* + * Digest type. + */ + num = uint8_fromregion(&sr); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%lu", num); + RETERR(str_totext(buf, target)); + + /* + * Digest. + */ + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" (", target)); + } + RETERR(str_totext(tctx->linebreak, target)); + if ((tctx->flags & DNS_STYLEFLAG_NOCRYPTO) == 0) { + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_hex_totext(&sr, 0, "", target)); + } else { + RETERR(isc_hex_totext(&sr, tctx->width - 2, + tctx->linebreak, target)); + } + } else { + RETERR(str_totext("[omitted]", target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_zonemd(ARGS_FROMWIRE) { + isc_region_t sr; + size_t digestlen = 0; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + + /* + * If we do not recognize the digest type, ensure that the digest + * meets minimum length (12). + * + * If we do recognize the digest type, ensure that the digest is of the + * correct length. + */ + if (sr.length < 18) { + return (ISC_R_UNEXPECTEDEND); + } + + switch (sr.base[5]) { + case DNS_ZONEMD_DIGEST_SHA384: + digestlen = ISC_SHA384_DIGESTLENGTH; + break; + case DNS_ZONEMD_DIGEST_SHA512: + digestlen = ISC_SHA512_DIGESTLENGTH; + break; + default: + break; + } + + if (digestlen != 0 && sr.length < 6 + digestlen) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Only specify the number of octets to consume if we recognize the + * digest type. + * + * If there is extra data, dns_rdata_fromwire() will detect that. + */ + if (digestlen != 0) { + sr.length = 6 + digestlen; + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_zonemd(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_zonemd); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_zonemd(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_zonemd); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_zonemd(ARGS_FROMSTRUCT) { + dns_rdata_zonemd_t *zonemd = source; + + REQUIRE(zonemd != NULL); + REQUIRE(zonemd->common.rdtype == type); + REQUIRE(zonemd->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + switch (zonemd->digest_type) { + case DNS_ZONEMD_DIGEST_SHA384: + REQUIRE(zonemd->length == ISC_SHA384_DIGESTLENGTH); + break; + case DNS_ZONEMD_DIGEST_SHA512: + REQUIRE(zonemd->length == ISC_SHA512_DIGESTLENGTH); + break; + } + + RETERR(uint32_tobuffer(zonemd->serial, target)); + RETERR(uint8_tobuffer(zonemd->scheme, target)); + RETERR(uint8_tobuffer(zonemd->digest_type, target)); + + return (mem_tobuffer(target, zonemd->digest, zonemd->length)); +} + +static isc_result_t +tostruct_zonemd(ARGS_TOSTRUCT) { + dns_rdata_zonemd_t *zonemd = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_zonemd); + REQUIRE(zonemd != NULL); + REQUIRE(rdata->length != 0); + + zonemd->common.rdclass = rdata->rdclass; + zonemd->common.rdtype = rdata->type; + ISC_LINK_INIT(&zonemd->common, link); + + dns_rdata_toregion(rdata, ®ion); + + zonemd->serial = uint32_fromregion(®ion); + isc_region_consume(®ion, 4); + zonemd->scheme = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + zonemd->digest_type = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + zonemd->length = region.length; + + zonemd->digest = mem_maybedup(mctx, region.base, region.length); + if (zonemd->digest == NULL) { + return (ISC_R_NOMEMORY); + } + + zonemd->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_zonemd(ARGS_FREESTRUCT) { + dns_rdata_zonemd_t *zonemd = source; + + REQUIRE(zonemd != NULL); + REQUIRE(zonemd->common.rdtype == dns_rdatatype_zonemd); + + if (zonemd->mctx == NULL) { + return; + } + + if (zonemd->digest != NULL) { + isc_mem_free(zonemd->mctx, zonemd->digest); + } + zonemd->mctx = NULL; +} + +static isc_result_t +additionaldata_zonemd(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_zonemd); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_zonemd(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_zonemd); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_zonemd(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_zonemd); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_zonemd(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_zonemd); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_zonemd(ARGS_COMPARE) { + return (compare_zonemd(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_ZONEMD_63_C */ diff --git a/lib/dns/rdata/generic/zonemd_63.h b/lib/dns/rdata/generic/zonemd_63.h new file mode 100644 index 0000000..5856b0d --- /dev/null +++ b/lib/dns/rdata/generic/zonemd_63.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/* Known digest type(s). */ +#define DNS_ZONEMD_DIGEST_SHA384 (1) +#define DNS_ZONEMD_DIGEST_SHA512 (2) + +/* + * \brief per RFC 8976 + */ +typedef struct dns_rdata_zonemd { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint32_t serial; + uint8_t scheme; + uint8_t digest_type; + unsigned char *digest; + uint16_t length; +} dns_rdata_zonemd_t; diff --git a/lib/dns/rdata/hs_4/a_1.c b/lib/dns/rdata/hs_4/a_1.c new file mode 100644 index 0000000..26246b7 --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +#define RRTYPE_A_ATTRIBUTES (0) + +static isc_result_t +fromtext_hs_a(ARGS_FROMTEXT) { + isc_token_t token; + struct in_addr addr; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_hs); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_hs_a(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + REQUIRE(rdata->length == 4); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + return (inet_totext(AF_INET, tctx->flags, ®ion, target)); +} + +static isc_result_t +fromwire_hs_a(ARGS_FROMWIRE) { + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_hs); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (sregion.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + if (tregion.length < 4) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 4); + isc_buffer_forward(source, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_hs_a(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + REQUIRE(rdata->length == 4); + + UNUSED(cctx); + + isc_buffer_availableregion(target, ®ion); + if (region.length < rdata->length) { + return (ISC_R_NOSPACE); + } + memmove(region.base, rdata->data, rdata->length); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static int +compare_hs_a(ARGS_COMPARE) { + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_a); + REQUIRE(rdata1->rdclass == dns_rdataclass_hs); + REQUIRE(rdata1->length == 4); + REQUIRE(rdata2->length == 4); + + order = memcmp(rdata1->data, rdata2->data, 4); + if (order != 0) { + order = (order < 0) ? -1 : 1; + } + + return (order); +} + +static isc_result_t +fromstruct_hs_a(ARGS_FROMSTRUCT) { + dns_rdata_hs_a_t *a = source; + uint32_t n; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_hs); + REQUIRE(a != NULL); + REQUIRE(a->common.rdtype == type); + REQUIRE(a->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + n = ntohl(a->in_addr.s_addr); + + return (uint32_tobuffer(n, target)); +} + +static isc_result_t +tostruct_hs_a(ARGS_TOSTRUCT) { + dns_rdata_hs_a_t *a = target; + uint32_t n; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + REQUIRE(rdata->length == 4); + REQUIRE(a != NULL); + + UNUSED(mctx); + + a->common.rdclass = rdata->rdclass; + a->common.rdtype = rdata->type; + ISC_LINK_INIT(&a->common, link); + + dns_rdata_toregion(rdata, ®ion); + n = uint32_fromregion(®ion); + a->in_addr.s_addr = htonl(n); + + return (ISC_R_SUCCESS); +} + +static void +freestruct_hs_a(ARGS_FREESTRUCT) { + UNUSED(source); + + REQUIRE(source != NULL); +} + +static isc_result_t +additionaldata_hs_a(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_hs_a(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_hs_a(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_hs); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_hs_a(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_hs); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_hs_a(ARGS_COMPARE) { + return (compare_hs_a(rdata1, rdata2)); +} diff --git a/lib/dns/rdata/hs_4/a_1.h b/lib/dns/rdata/hs_4/a_1.h new file mode 100644 index 0000000..f57d547 --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_hs_a { + dns_rdatacommon_t common; + struct in_addr in_addr; +} dns_rdata_hs_a_t; diff --git a/lib/dns/rdata/in_1/a6_38.c b/lib/dns/rdata/in_1/a6_38.c new file mode 100644 index 0000000..80941d6 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.c @@ -0,0 +1,487 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2874 */ + +#ifndef RDATA_IN_1_A6_28_C +#define RDATA_IN_1_A6_28_C + +#include + +#define RRTYPE_A6_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_a6(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char addr[16]; + unsigned char prefixlen; + unsigned char octets; + unsigned char mask; + dns_name_t name; + isc_buffer_t buffer; + bool ok; + + REQUIRE(type == dns_rdatatype_a6); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Prefix length. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 128U) { + RETTOK(ISC_R_RANGE); + } + + prefixlen = (unsigned char)token.value.as_ulong; + RETERR(mem_tobuffer(target, &prefixlen, 1)); + + /* + * Suffix. + */ + if (prefixlen != 128) { + /* + * Prefix 0..127. + */ + octets = prefixlen / 8; + /* + * Octets 0..15. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, false)); + if (inet_pton(AF_INET6, DNS_AS_STR(token), addr) != 1) { + RETTOK(DNS_R_BADAAAA); + } + mask = 0xff >> (prefixlen % 8); + addr[octets] &= mask; + RETERR(mem_tobuffer(target, &addr[octets], 16 - octets)); + } + + if (prefixlen == 0) { + return (ISC_R_SUCCESS); + } + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_a6(ARGS_TOTEXT) { + isc_region_t sr, ar; + unsigned char addr[16]; + unsigned char prefixlen; + unsigned char octets; + unsigned char mask; + char buf[sizeof("128")]; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + prefixlen = sr.base[0]; + INSIST(prefixlen <= 128); + isc_region_consume(&sr, 1); + snprintf(buf, sizeof(buf), "%u", prefixlen); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + if (prefixlen != 128) { + octets = prefixlen / 8; + memset(addr, 0, sizeof(addr)); + memmove(&addr[octets], sr.base, 16 - octets); + mask = 0xff >> (prefixlen % 8); + addr[octets] &= mask; + ar.base = addr; + ar.length = sizeof(addr); + RETERR(inet_totext(AF_INET6, tctx->flags, &ar, target)); + isc_region_consume(&sr, 16 - octets); + } + + if (prefixlen == 0) { + return (ISC_R_SUCCESS); + } + + RETERR(str_totext(" ", target)); + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + dns_name_fromregion(&name, &sr); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_in_a6(ARGS_FROMWIRE) { + isc_region_t sr; + unsigned char prefixlen; + unsigned char octets; + unsigned char mask; + dns_name_t name; + + REQUIRE(type == dns_rdatatype_a6); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + isc_buffer_activeregion(source, &sr); + /* + * Prefix length. + */ + if (sr.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + prefixlen = sr.base[0]; + if (prefixlen > 128) { + return (ISC_R_RANGE); + } + isc_region_consume(&sr, 1); + RETERR(mem_tobuffer(target, &prefixlen, 1)); + isc_buffer_forward(source, 1); + + /* + * Suffix. + */ + if (prefixlen != 128) { + octets = 16 - prefixlen / 8; + if (sr.length < octets) { + return (ISC_R_UNEXPECTEDEND); + } + mask = 0xff >> (prefixlen % 8); + if ((sr.base[0] & ~mask) != 0) { + return (DNS_R_FORMERR); + } + RETERR(mem_tobuffer(target, sr.base, octets)); + isc_buffer_forward(source, octets); + } + + if (prefixlen == 0) { + return (ISC_R_SUCCESS); + } + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_in_a6(ARGS_TOWIRE) { + isc_region_t sr; + dns_name_t name; + dns_offsets_t offsets; + unsigned char prefixlen; + unsigned char octets; + + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_rdata_toregion(rdata, &sr); + prefixlen = sr.base[0]; + INSIST(prefixlen <= 128); + + octets = 1 + 16 - prefixlen / 8; + RETERR(mem_tobuffer(target, sr.base, octets)); + isc_region_consume(&sr, octets); + + if (prefixlen == 0) { + return (ISC_R_SUCCESS); + } + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_in_a6(ARGS_COMPARE) { + int order; + unsigned char prefixlen1, prefixlen2; + unsigned char octets; + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_a6); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + prefixlen1 = region1.base[0]; + prefixlen2 = region2.base[0]; + isc_region_consume(®ion1, 1); + isc_region_consume(®ion2, 1); + if (prefixlen1 < prefixlen2) { + return (-1); + } else if (prefixlen1 > prefixlen2) { + return (1); + } + /* + * Prefix lengths are equal. + */ + octets = 16 - prefixlen1 / 8; + + if (octets > 0) { + order = memcmp(region1.base, region2.base, octets); + if (order < 0) { + return (-1); + } else if (order > 0) { + return (1); + } + /* + * Address suffixes are equal. + */ + if (prefixlen1 == 0) { + return (order); + } + isc_region_consume(®ion1, octets); + isc_region_consume(®ion2, octets); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_in_a6(ARGS_FROMSTRUCT) { + dns_rdata_in_a6_t *a6 = source; + isc_region_t region; + int octets; + uint8_t bits; + uint8_t first; + uint8_t mask; + + REQUIRE(type == dns_rdatatype_a6); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(a6 != NULL); + REQUIRE(a6->common.rdtype == type); + REQUIRE(a6->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + if (a6->prefixlen > 128) { + return (ISC_R_RANGE); + } + + RETERR(uint8_tobuffer(a6->prefixlen, target)); + + /* Suffix */ + if (a6->prefixlen != 128) { + octets = 16 - a6->prefixlen / 8; + bits = a6->prefixlen % 8; + if (bits != 0) { + mask = 0xffU >> bits; + first = a6->in6_addr.s6_addr[16 - octets] & mask; + RETERR(uint8_tobuffer(first, target)); + octets--; + } + if (octets > 0) { + RETERR(mem_tobuffer(target, + a6->in6_addr.s6_addr + 16 - octets, + octets)); + } + } + + if (a6->prefixlen == 0) { + return (ISC_R_SUCCESS); + } + dns_name_toregion(&a6->prefix, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_in_a6(ARGS_TOSTRUCT) { + dns_rdata_in_a6_t *a6 = target; + unsigned char octets; + dns_name_t name; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(a6 != NULL); + REQUIRE(rdata->length != 0); + + a6->common.rdclass = rdata->rdclass; + a6->common.rdtype = rdata->type; + ISC_LINK_INIT(&a6->common, link); + + dns_rdata_toregion(rdata, &r); + + a6->prefixlen = uint8_fromregion(&r); + isc_region_consume(&r, 1); + memset(a6->in6_addr.s6_addr, 0, sizeof(a6->in6_addr.s6_addr)); + + /* + * Suffix. + */ + if (a6->prefixlen != 128) { + octets = 16 - a6->prefixlen / 8; + INSIST(r.length >= octets); + memmove(a6->in6_addr.s6_addr + 16 - octets, r.base, octets); + isc_region_consume(&r, octets); + } + + /* + * Prefix. + */ + dns_name_init(&a6->prefix, NULL); + if (a6->prefixlen != 0) { + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + name_duporclone(&name, mctx, &a6->prefix); + } + a6->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_a6(ARGS_FREESTRUCT) { + dns_rdata_in_a6_t *a6 = source; + + REQUIRE(a6 != NULL); + REQUIRE(a6->common.rdclass == dns_rdataclass_in); + REQUIRE(a6->common.rdtype == dns_rdatatype_a6); + + if (a6->mctx == NULL) { + return; + } + + if (dns_name_dynamic(&a6->prefix)) { + dns_name_free(&a6->prefix, a6->mctx); + } + a6->mctx = NULL; +} + +static isc_result_t +additionaldata_in_a6(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_a6(ARGS_DIGEST) { + isc_region_t r1, r2; + unsigned char prefixlen, octets; + isc_result_t result; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + prefixlen = r1.base[0]; + octets = 1 + 16 - prefixlen / 8; + + r1.length = octets; + result = (digest)(arg, &r1); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (prefixlen == 0) { + return (ISC_R_SUCCESS); + } + + isc_region_consume(&r2, octets); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_in_a6(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_a6); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_in_a6(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + unsigned int prefixlen; + + REQUIRE(rdata->type == dns_rdatatype_a6); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + prefixlen = uint8_fromregion(®ion); + if (prefixlen == 0) { + return (true); + } + isc_region_consume(®ion, 1 + 16 - prefixlen / 8); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_in_a6(ARGS_COMPARE) { + return (compare_in_a6(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_A6_38_C */ diff --git a/lib/dns/rdata/in_1/a6_38.h b/lib/dns/rdata/in_1/a6_38.h new file mode 100644 index 0000000..7c09cc5 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2874 */ + +typedef struct dns_rdata_in_a6 { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t prefix; + uint8_t prefixlen; + struct in6_addr in6_addr; +} dns_rdata_in_a6_t; diff --git a/lib/dns/rdata/in_1/a_1.c b/lib/dns/rdata/in_1/a_1.c new file mode 100644 index 0000000..cf5549d --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_IN_1_A_1_C +#define RDATA_IN_1_A_1_C + +#include + +#include + +#define RRTYPE_A_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_a(ARGS_FROMTEXT) { + isc_token_t token; + struct in_addr addr; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_a(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 4); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + return (inet_totext(AF_INET, tctx->flags, ®ion, target)); +} + +static isc_result_t +fromwire_in_a(ARGS_FROMWIRE) { + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (sregion.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + if (tregion.length < 4) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 4); + isc_buffer_forward(source, 4); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_a(ARGS_TOWIRE) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 4); + + UNUSED(cctx); + + isc_buffer_availableregion(target, ®ion); + if (region.length < rdata->length) { + return (ISC_R_NOSPACE); + } + memmove(region.base, rdata->data, rdata->length); + isc_buffer_add(target, 4); + return (ISC_R_SUCCESS); +} + +static int +compare_in_a(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_a); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length == 4); + REQUIRE(rdata2->length == 4); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_a(ARGS_FROMSTRUCT) { + dns_rdata_in_a_t *a = source; + uint32_t n; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(a != NULL); + REQUIRE(a->common.rdtype == type); + REQUIRE(a->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + n = ntohl(a->in_addr.s_addr); + + return (uint32_tobuffer(n, target)); +} + +static isc_result_t +tostruct_in_a(ARGS_TOSTRUCT) { + dns_rdata_in_a_t *a = target; + uint32_t n; + isc_region_t region; + + REQUIRE(a != NULL); + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 4); + + UNUSED(mctx); + + a->common.rdclass = rdata->rdclass; + a->common.rdtype = rdata->type; + ISC_LINK_INIT(&a->common, link); + + dns_rdata_toregion(rdata, ®ion); + n = uint32_fromregion(®ion); + a->in_addr.s_addr = htonl(n); + + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_a(ARGS_FREESTRUCT) { + dns_rdata_in_a_t *a = source; + + REQUIRE(a != NULL); + REQUIRE(a->common.rdtype == dns_rdatatype_a); + REQUIRE(a->common.rdclass == dns_rdataclass_in); + + UNUSED(a); +} + +static isc_result_t +additionaldata_in_a(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_a(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_a(ARGS_CHECKOWNER) { + dns_name_t prefix, suffix; + unsigned int labels, i; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + labels = dns_name_countlabels(name); + if (labels > 2U) { + /* + * Handle Active Directory gc._msdcs. name. + */ + dns_name_init(&prefix, NULL); + dns_name_init(&suffix, NULL); + dns_name_split(name, labels - 2, &prefix, &suffix); + if (dns_name_equal(&gc_msdcs, &prefix) && + dns_name_ishostname(&suffix, false)) + { + return (true); + } + + /* + * Handle SPF exists targets when the seperating label is: + * - "_spf" RFC7208, section 5.7 + * - "_spf_verify" RFC7208, Appendix D1 + * - "_spf_rate" RFC7208, Appendix D1 + */ + for (i = 0; i < labels - 2; i++) { + dns_label_t label; + dns_name_getlabel(name, i, &label); + if ((label.length == 5 && + strncasecmp((char *)label.base, "\x04_spf", 5) == + 0) || + (label.length == 12 && + strncasecmp((char *)label.base, "\x0b_spf_verify", + 12) == 0) || + (label.length == 10 && + strncasecmp((char *)label.base, "\x09_spf_rate", + 10) == 0)) + { + return (true); + } + } + } + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_in_a(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_a); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_a(ARGS_COMPARE) { + return (compare_in_a(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_A_1_C */ diff --git a/lib/dns/rdata/in_1/a_1.h b/lib/dns/rdata/in_1/a_1.h new file mode 100644 index 0000000..38ffd8f --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_in_a { + dns_rdatacommon_t common; + struct in_addr in_addr; +} dns_rdata_in_a_t; diff --git a/lib/dns/rdata/in_1/aaaa_28.c b/lib/dns/rdata/in_1/aaaa_28.c new file mode 100644 index 0000000..e23bd47 --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1886 */ + +#ifndef RDATA_IN_1_AAAA_28_C +#define RDATA_IN_1_AAAA_28_C + +#include + +#define RRTYPE_AAAA_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_aaaa(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char addr[16]; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_aaaa); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (inet_pton(AF_INET6, DNS_AS_STR(token), addr) != 1) { + RETTOK(DNS_R_BADAAAA); + } + isc_buffer_availableregion(target, ®ion); + if (region.length < 16) { + return (ISC_R_NOSPACE); + } + memmove(region.base, addr, 16); + isc_buffer_add(target, 16); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_aaaa(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 16); + + if ((tctx->flags & DNS_STYLEFLAG_EXPANDAAAA) != 0) { + char buf[5 * 8]; + const char *sep = ""; + int i, n; + unsigned int len = 0; + + for (i = 0; i < 16; i += 2) { + INSIST(len < sizeof(buf)); + n = snprintf(buf + len, sizeof(buf) - len, "%s%02x%02x", + sep, rdata->data[i], rdata->data[i + 1]); + if (n < 0) { + return (ISC_R_FAILURE); + } + len += n; + sep = ":"; + } + return (str_totext(buf, target)); + } + dns_rdata_toregion(rdata, ®ion); + return (inet_totext(AF_INET6, tctx->flags, ®ion, target)); +} + +static isc_result_t +fromwire_in_aaaa(ARGS_FROMWIRE) { + isc_region_t sregion; + isc_region_t tregion; + + REQUIRE(type == dns_rdatatype_aaaa); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, &sregion); + isc_buffer_availableregion(target, &tregion); + if (sregion.length < 16) { + return (ISC_R_UNEXPECTEDEND); + } + if (tregion.length < 16) { + return (ISC_R_NOSPACE); + } + + memmove(tregion.base, sregion.base, 16); + isc_buffer_forward(source, 16); + isc_buffer_add(target, 16); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_aaaa(ARGS_TOWIRE) { + isc_region_t region; + + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 16); + + isc_buffer_availableregion(target, ®ion); + if (region.length < rdata->length) { + return (ISC_R_NOSPACE); + } + memmove(region.base, rdata->data, rdata->length); + isc_buffer_add(target, 16); + return (ISC_R_SUCCESS); +} + +static int +compare_in_aaaa(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_aaaa); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length == 16); + REQUIRE(rdata2->length == 16); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_aaaa(ARGS_FROMSTRUCT) { + dns_rdata_in_aaaa_t *aaaa = source; + + REQUIRE(type == dns_rdatatype_aaaa); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(aaaa != NULL); + REQUIRE(aaaa->common.rdtype == type); + REQUIRE(aaaa->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, aaaa->in6_addr.s6_addr, 16)); +} + +static isc_result_t +tostruct_in_aaaa(ARGS_TOSTRUCT) { + dns_rdata_in_aaaa_t *aaaa = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(aaaa != NULL); + REQUIRE(rdata->length == 16); + + UNUSED(mctx); + + aaaa->common.rdclass = rdata->rdclass; + aaaa->common.rdtype = rdata->type; + ISC_LINK_INIT(&aaaa->common, link); + + dns_rdata_toregion(rdata, &r); + INSIST(r.length == 16); + memmove(aaaa->in6_addr.s6_addr, r.base, 16); + + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_aaaa(ARGS_FREESTRUCT) { + dns_rdata_in_aaaa_t *aaaa = source; + + REQUIRE(aaaa != NULL); + REQUIRE(aaaa->common.rdclass == dns_rdataclass_in); + REQUIRE(aaaa->common.rdtype == dns_rdatatype_aaaa); + + UNUSED(aaaa); +} + +static isc_result_t +additionaldata_in_aaaa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_aaaa(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_aaaa(ARGS_CHECKOWNER) { + dns_name_t prefix, suffix; + + REQUIRE(type == dns_rdatatype_aaaa); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Handle Active Directory gc._msdcs. name. + */ + if (dns_name_countlabels(name) > 2U) { + dns_name_init(&prefix, NULL); + dns_name_init(&suffix, NULL); + dns_name_split(name, dns_name_countlabels(name) - 2, &prefix, + &suffix); + if (dns_name_equal(&gc_msdcs, &prefix) && + dns_name_ishostname(&suffix, false)) + { + return (true); + } + } + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_in_aaaa(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_aaaa(ARGS_COMPARE) { + return (compare_in_aaaa(rdata1, rdata2)); +} +#endif /* RDATA_IN_1_AAAA_28_C */ diff --git a/lib/dns/rdata/in_1/aaaa_28.h b/lib/dns/rdata/in_1/aaaa_28.h new file mode 100644 index 0000000..915cc0f --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1886 */ + +typedef struct dns_rdata_in_aaaa { + dns_rdatacommon_t common; + struct in6_addr in6_addr; +} dns_rdata_in_aaaa_t; diff --git a/lib/dns/rdata/in_1/apl_42.c b/lib/dns/rdata/in_1/apl_42.c new file mode 100644 index 0000000..03593ab --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.c @@ -0,0 +1,483 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC3123 */ + +#ifndef RDATA_IN_1_APL_42_C +#define RDATA_IN_1_APL_42_C + +#define RRTYPE_APL_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_apl(ARGS_FROMTEXT) { + isc_token_t token; + unsigned char addr[16]; + unsigned long afi; + uint8_t prefix; + uint8_t len; + bool neg; + char *cp, *ap, *slash; + int n; + + REQUIRE(type == dns_rdatatype_apl); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + do { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, true)); + if (token.type != isc_tokentype_string) { + break; + } + + cp = DNS_AS_STR(token); + neg = (*cp == '!'); + if (neg) { + cp++; + } + afi = strtoul(cp, &ap, 10); + if (*ap++ != ':' || cp == ap) { + RETTOK(DNS_R_SYNTAX); + } + if (afi > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + slash = strchr(ap, '/'); + if (slash == NULL || slash == ap) { + RETTOK(DNS_R_SYNTAX); + } + RETTOK(isc_parse_uint8(&prefix, slash + 1, 10)); + switch (afi) { + case 1: + *slash = '\0'; + n = inet_pton(AF_INET, ap, addr); + *slash = '/'; + if (n != 1) { + RETTOK(DNS_R_BADDOTTEDQUAD); + } + if (prefix > 32) { + RETTOK(ISC_R_RANGE); + } + for (len = 4; len > 0; len--) { + if (addr[len - 1] != 0) { + break; + } + } + break; + + case 2: + *slash = '\0'; + n = inet_pton(AF_INET6, ap, addr); + *slash = '/'; + if (n != 1) { + RETTOK(DNS_R_BADAAAA); + } + if (prefix > 128) { + RETTOK(ISC_R_RANGE); + } + for (len = 16; len > 0; len--) { + if (addr[len - 1] != 0) { + break; + } + } + break; + + default: + RETTOK(ISC_R_NOTIMPLEMENTED); + } + RETERR(uint16_tobuffer(afi, target)); + RETERR(uint8_tobuffer(prefix, target)); + RETERR(uint8_tobuffer(len | ((neg) ? 0x80 : 0), target)); + RETERR(mem_tobuffer(target, addr, len)); + } while (1); + + /* + * Let upper layer handle eol/eof. + */ + isc_lex_ungettoken(lexer, &token); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_apl(ARGS_TOTEXT) { + isc_region_t sr; + isc_region_t ir; + uint16_t afi; + uint8_t prefix; + uint8_t len; + bool neg; + unsigned char buf[16]; + char txt[sizeof(" !64000:")]; + const char *sep = ""; + int n; + + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, &sr); + ir.base = buf; + ir.length = sizeof(buf); + + while (sr.length > 0) { + INSIST(sr.length >= 4); + afi = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + prefix = *sr.base; + isc_region_consume(&sr, 1); + len = (*sr.base & 0x7f); + neg = (*sr.base & 0x80); + isc_region_consume(&sr, 1); + INSIST(len <= sr.length); + n = snprintf(txt, sizeof(txt), "%s%s%u:", sep, neg ? "!" : "", + afi); + INSIST(n < (int)sizeof(txt)); + RETERR(str_totext(txt, target)); + switch (afi) { + case 1: + INSIST(len <= 4); + INSIST(prefix <= 32); + memset(buf, 0, sizeof(buf)); + memmove(buf, sr.base, len); + RETERR(inet_totext(AF_INET, tctx->flags, &ir, target)); + break; + + case 2: + INSIST(len <= 16); + INSIST(prefix <= 128); + memset(buf, 0, sizeof(buf)); + memmove(buf, sr.base, len); + RETERR(inet_totext(AF_INET6, tctx->flags, &ir, target)); + break; + + default: + return (ISC_R_NOTIMPLEMENTED); + } + n = snprintf(txt, sizeof(txt), "/%u", prefix); + INSIST(n < (int)sizeof(txt)); + RETERR(str_totext(txt, target)); + isc_region_consume(&sr, len); + sep = " "; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_apl(ARGS_FROMWIRE) { + isc_region_t sr, sr2; + isc_region_t tr; + uint16_t afi; + uint8_t prefix; + uint8_t len; + + REQUIRE(type == dns_rdatatype_apl); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + isc_buffer_availableregion(target, &tr); + if (sr.length > tr.length) { + return (ISC_R_NOSPACE); + } + sr2 = sr; + + /* Zero or more items */ + while (sr.length > 0) { + if (sr.length < 4) { + return (ISC_R_UNEXPECTEDEND); + } + afi = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + prefix = *sr.base; + isc_region_consume(&sr, 1); + len = (*sr.base & 0x7f); + isc_region_consume(&sr, 1); + if (len > sr.length) { + return (ISC_R_UNEXPECTEDEND); + } + switch (afi) { + case 1: + if (prefix > 32 || len > 4) { + return (ISC_R_RANGE); + } + break; + case 2: + if (prefix > 128 || len > 16) { + return (ISC_R_RANGE); + } + } + if (len > 0 && sr.base[len - 1] == 0) { + return (DNS_R_FORMERR); + } + isc_region_consume(&sr, len); + } + isc_buffer_forward(source, sr2.length); + return (mem_tobuffer(target, sr2.base, sr2.length)); +} + +static isc_result_t +towire_in_apl(ARGS_TOWIRE) { + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_in_apl(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_apl); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_apl(ARGS_FROMSTRUCT) { + dns_rdata_in_apl_t *apl = source; + isc_buffer_t b; + + REQUIRE(type == dns_rdatatype_apl); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(apl != NULL); + REQUIRE(apl->common.rdtype == type); + REQUIRE(apl->common.rdclass == rdclass); + REQUIRE(apl->apl != NULL || apl->apl_len == 0); + + isc_buffer_init(&b, apl->apl, apl->apl_len); + isc_buffer_add(&b, apl->apl_len); + isc_buffer_setactive(&b, apl->apl_len); + return (fromwire_in_apl(rdclass, type, &b, NULL, false, target)); +} + +static isc_result_t +tostruct_in_apl(ARGS_TOSTRUCT) { + dns_rdata_in_apl_t *apl = target; + isc_region_t r; + + REQUIRE(apl != NULL); + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + apl->common.rdclass = rdata->rdclass; + apl->common.rdtype = rdata->type; + ISC_LINK_INIT(&apl->common, link); + + dns_rdata_toregion(rdata, &r); + apl->apl_len = r.length; + apl->apl = mem_maybedup(mctx, r.base, r.length); + if (apl->apl == NULL) { + return (ISC_R_NOMEMORY); + } + + apl->offset = 0; + apl->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_apl(ARGS_FREESTRUCT) { + dns_rdata_in_apl_t *apl = source; + + REQUIRE(apl != NULL); + REQUIRE(apl->common.rdtype == dns_rdatatype_apl); + REQUIRE(apl->common.rdclass == dns_rdataclass_in); + + if (apl->mctx == NULL) { + return; + } + if (apl->apl != NULL) { + isc_mem_free(apl->mctx, apl->apl); + } + apl->mctx = NULL; +} + +isc_result_t +dns_rdata_apl_first(dns_rdata_in_apl_t *apl) { + uint32_t length; + + REQUIRE(apl != NULL); + REQUIRE(apl->common.rdtype == dns_rdatatype_apl); + REQUIRE(apl->common.rdclass == dns_rdataclass_in); + REQUIRE(apl->apl != NULL || apl->apl_len == 0); + + /* + * If no APL return ISC_R_NOMORE. + */ + if (apl->apl == NULL) { + return (ISC_R_NOMORE); + } + + /* + * Sanity check data. + */ + INSIST(apl->apl_len > 3U); + length = apl->apl[apl->offset + 3] & 0x7f; + INSIST(4 + length <= apl->apl_len); + + apl->offset = 0; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdata_apl_next(dns_rdata_in_apl_t *apl) { + uint32_t length; + + REQUIRE(apl != NULL); + REQUIRE(apl->common.rdtype == dns_rdatatype_apl); + REQUIRE(apl->common.rdclass == dns_rdataclass_in); + REQUIRE(apl->apl != NULL || apl->apl_len == 0); + + /* + * No APL or have already reached the end return ISC_R_NOMORE. + */ + if (apl->apl == NULL || apl->offset == apl->apl_len) { + return (ISC_R_NOMORE); + } + + /* + * Sanity check data. + */ + INSIST(apl->offset < apl->apl_len); + INSIST(apl->apl_len > 3U); + INSIST(apl->offset <= apl->apl_len - 4U); + length = apl->apl[apl->offset + 3] & 0x7f; + /* + * 16 to 32 bits promotion as 'length' is 32 bits so there is + * no overflow problems. + */ + INSIST(4 + length + apl->offset <= apl->apl_len); + + apl->offset += 4 + length; + return ((apl->offset < apl->apl_len) ? ISC_R_SUCCESS : ISC_R_NOMORE); +} + +isc_result_t +dns_rdata_apl_current(dns_rdata_in_apl_t *apl, dns_rdata_apl_ent_t *ent) { + uint32_t length; + + REQUIRE(apl != NULL); + REQUIRE(apl->common.rdtype == dns_rdatatype_apl); + REQUIRE(apl->common.rdclass == dns_rdataclass_in); + REQUIRE(ent != NULL); + REQUIRE(apl->apl != NULL || apl->apl_len == 0); + REQUIRE(apl->offset <= apl->apl_len); + + if (apl->offset == apl->apl_len) { + return (ISC_R_NOMORE); + } + + /* + * Sanity check data. + */ + INSIST(apl->apl_len > 3U); + INSIST(apl->offset <= apl->apl_len - 4U); + length = (apl->apl[apl->offset + 3] & 0x7f); + /* + * 16 to 32 bits promotion as 'length' is 32 bits so there is + * no overflow problems. + */ + INSIST(4 + length + apl->offset <= apl->apl_len); + + ent->family = (apl->apl[apl->offset] << 8) + apl->apl[apl->offset + 1]; + ent->prefix = apl->apl[apl->offset + 2]; + ent->length = length; + ent->negative = (apl->apl[apl->offset + 3] & 0x80); + if (ent->length != 0) { + ent->data = &apl->apl[apl->offset + 4]; + } else { + ent->data = NULL; + } + return (ISC_R_SUCCESS); +} + +unsigned int +dns_rdata_apl_count(const dns_rdata_in_apl_t *apl) { + return (apl->apl_len); +} + +static isc_result_t +additionaldata_in_apl(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_apl(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_apl(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_apl); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_apl(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_apl); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_apl(ARGS_COMPARE) { + return (compare_in_apl(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_APL_42_C */ diff --git a/lib/dns/rdata/in_1/apl_42.h b/lib/dns/rdata/in_1/apl_42.h new file mode 100644 index 0000000..0a3f7ac --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_apl_ent { + bool negative; + uint16_t family; + uint8_t prefix; + uint8_t length; + unsigned char *data; +} dns_rdata_apl_ent_t; + +typedef struct dns_rdata_in_apl { + dns_rdatacommon_t common; + isc_mem_t *mctx; + /* type & class specific elements */ + unsigned char *apl; + uint16_t apl_len; + /* private */ + uint16_t offset; +} dns_rdata_in_apl_t; + +/* + * ISC_LANG_BEGINDECLS and ISC_LANG_ENDDECLS are already done + * via rdatastructpre.h and rdatastructsuf.h. + */ + +isc_result_t +dns_rdata_apl_first(dns_rdata_in_apl_t *); + +isc_result_t +dns_rdata_apl_next(dns_rdata_in_apl_t *); + +isc_result_t +dns_rdata_apl_current(dns_rdata_in_apl_t *, dns_rdata_apl_ent_t *); + +unsigned int +dns_rdata_apl_count(const dns_rdata_in_apl_t *apl); diff --git a/lib/dns/rdata/in_1/atma_34.c b/lib/dns/rdata/in_1/atma_34.c new file mode 100644 index 0000000..b1a207a --- /dev/null +++ b/lib/dns/rdata/in_1/atma_34.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* http://www.broadband-forum.org/ftp/pub/approved-specs/af-dans-0152.000.pdf */ + +#ifndef RDATA_IN_1_ATMA_22_C +#define RDATA_IN_1_ATMA_22_C + +#define RRTYPE_ATMA_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_atma(ARGS_FROMTEXT) { + isc_token_t token; + isc_textregion_t *sr; + int n; + bool valid = false; + bool lastwasperiod = true; /* leading periods not allowed */ + int digits = 0; + unsigned char c = 0; + + REQUIRE(type == dns_rdatatype_atma); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + /* ATM End System Address (AESA) format or E.164 */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + sr = &token.value.as_textregion; + if (sr->length < 1) { + RETTOK(ISC_R_UNEXPECTEDEND); + } + + if (sr->base[0] != '+') { + /* + * Format 0: ATM End System Address (AESA) format. + */ + c = 0; + RETERR(mem_tobuffer(target, &c, 1)); + while (sr->length > 0) { + if (sr->base[0] == '.') { + if (lastwasperiod) { + RETTOK(DNS_R_SYNTAX); + } + isc_textregion_consume(sr, 1); + lastwasperiod = true; + continue; + } + if ((n = hexvalue(sr->base[0])) == -1) { + RETTOK(DNS_R_SYNTAX); + } + c <<= 4; + c += n; + if (++digits == 2) { + RETERR(mem_tobuffer(target, &c, 1)); + valid = true; + digits = 0; + c = 0; + } + isc_textregion_consume(sr, 1); + lastwasperiod = false; + } + if (digits != 0 || !valid || lastwasperiod) { + RETTOK(ISC_R_UNEXPECTEDEND); + } + } else { + /* + * Format 1: E.164. + */ + c = 1; + RETERR(mem_tobuffer(target, &c, 1)); + isc_textregion_consume(sr, 1); + while (sr->length > 0) { + if (sr->base[0] == '.') { + if (lastwasperiod) { + RETTOK(DNS_R_SYNTAX); + } + isc_textregion_consume(sr, 1); + lastwasperiod = true; + continue; + } + if (!isdigit((unsigned char)sr->base[0])) { + RETTOK(DNS_R_SYNTAX); + } + RETERR(mem_tobuffer(target, sr->base, 1)); + isc_textregion_consume(sr, 1); + lastwasperiod = false; + } + if (lastwasperiod) { + RETTOK(ISC_R_UNEXPECTEDEND); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_atma(ARGS_TOTEXT) { + isc_region_t region; + char buf[sizeof("xx")]; + + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + INSIST(region.length > 1); + switch (region.base[0]) { + case 0: + isc_region_consume(®ion, 1); + while (region.length != 0) { + snprintf(buf, sizeof(buf), "%02x", region.base[0]); + isc_region_consume(®ion, 1); + RETERR(str_totext(buf, target)); + } + break; + case 1: + RETERR(str_totext("+", target)); + isc_region_consume(®ion, 1); + RETERR(mem_tobuffer(target, region.base, region.length)); + break; + default: + return (ISC_R_NOTIMPLEMENTED); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_atma(ARGS_FROMWIRE) { + isc_region_t region; + + REQUIRE(type == dns_rdatatype_atma); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + if (region.base[0] == 1) { + unsigned int i; + for (i = 1; i < region.length; i++) { + if (!isdigit((unsigned char)region.base[i])) { + return (DNS_R_FORMERR); + } + } + } + RETERR(mem_tobuffer(target, region.base, region.length)); + isc_buffer_forward(source, region.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_atma(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_in_atma(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_atma); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_atma(ARGS_FROMSTRUCT) { + dns_rdata_in_atma_t *atma = source; + + REQUIRE(type == dns_rdatatype_atma); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(atma != NULL); + REQUIRE(atma->common.rdtype == type); + REQUIRE(atma->common.rdclass == rdclass); + REQUIRE(atma->atma != NULL || atma->atma_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(mem_tobuffer(target, &atma->format, 1)); + return (mem_tobuffer(target, atma->atma, atma->atma_len)); +} + +static isc_result_t +tostruct_in_atma(ARGS_TOSTRUCT) { + dns_rdata_in_atma_t *atma = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(atma != NULL); + REQUIRE(rdata->length != 0); + + atma->common.rdclass = rdata->rdclass; + atma->common.rdtype = rdata->type; + ISC_LINK_INIT(&atma->common, link); + + dns_rdata_toregion(rdata, &r); + atma->format = r.base[0]; + isc_region_consume(&r, 1); + atma->atma_len = r.length; + atma->atma = mem_maybedup(mctx, r.base, r.length); + if (atma->atma == NULL) { + return (ISC_R_NOMEMORY); + } + + atma->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_atma(ARGS_FREESTRUCT) { + dns_rdata_in_atma_t *atma = source; + + REQUIRE(atma != NULL); + REQUIRE(atma->common.rdclass == dns_rdataclass_in); + REQUIRE(atma->common.rdtype == dns_rdatatype_atma); + + if (atma->mctx == NULL) { + return; + } + + if (atma->atma != NULL) { + isc_mem_free(atma->mctx, atma->atma); + } + atma->mctx = NULL; +} + +static isc_result_t +additionaldata_in_atma(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_atma(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_atma(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_atma); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_atma(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_atma); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_atma(ARGS_COMPARE) { + return (compare_in_atma(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_atma_22_C */ diff --git a/lib/dns/rdata/in_1/atma_34.h b/lib/dns/rdata/in_1/atma_34.h new file mode 100644 index 0000000..412b428 --- /dev/null +++ b/lib/dns/rdata/in_1/atma_34.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1706 */ + +typedef struct dns_rdata_in_atma { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char format; + unsigned char *atma; + uint16_t atma_len; +} dns_rdata_in_atma_t; diff --git a/lib/dns/rdata/in_1/dhcid_49.c b/lib/dns/rdata/in_1/dhcid_49.c new file mode 100644 index 0000000..b3f664b --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC 4701 */ + +#ifndef RDATA_IN_1_DHCID_49_C +#define RDATA_IN_1_DHCID_49_C 1 + +#define RRTYPE_DHCID_ATTRIBUTES 0 + +static isc_result_t +fromtext_in_dhcid(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_dhcid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + return (isc_base64_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_in_dhcid(ARGS_TOTEXT) { + isc_region_t sr, sr2; + /* " ; 64000 255 64000" */ + char buf[5 + 3 * 11 + 1]; + + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + sr2 = sr; + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( " /*)*/, target)); + } + if (tctx->width == 0) { /* No splitting */ + RETERR(isc_base64_totext(&sr, 60, "", target)); + } else { + RETERR(isc_base64_totext(&sr, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(/* ( */ " )", target)); + if (rdata->length > 2) { + snprintf(buf, sizeof(buf), " ; %u %u %u", + sr2.base[0] * 256U + sr2.base[1], sr2.base[2], + rdata->length - 3U); + RETERR(str_totext(buf, target)); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_dhcid(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_dhcid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length == 0) { + return (ISC_R_UNEXPECTEDEND); + } + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static isc_result_t +towire_in_dhcid(ARGS_TOWIRE) { + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_in_dhcid(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_dhcid); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_dhcid(ARGS_FROMSTRUCT) { + dns_rdata_in_dhcid_t *dhcid = source; + + REQUIRE(type == dns_rdatatype_dhcid); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(dhcid != NULL); + REQUIRE(dhcid->common.rdtype == type); + REQUIRE(dhcid->common.rdclass == rdclass); + REQUIRE(dhcid->length != 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, dhcid->dhcid, dhcid->length)); +} + +static isc_result_t +tostruct_in_dhcid(ARGS_TOSTRUCT) { + dns_rdata_in_dhcid_t *dhcid = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(dhcid != NULL); + REQUIRE(rdata->length != 0); + + dhcid->common.rdclass = rdata->rdclass; + dhcid->common.rdtype = rdata->type; + ISC_LINK_INIT(&dhcid->common, link); + + dns_rdata_toregion(rdata, ®ion); + + dhcid->dhcid = mem_maybedup(mctx, region.base, region.length); + if (dhcid->dhcid == NULL) { + return (ISC_R_NOMEMORY); + } + + dhcid->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_dhcid(ARGS_FREESTRUCT) { + dns_rdata_in_dhcid_t *dhcid = source; + + REQUIRE(dhcid != NULL); + REQUIRE(dhcid->common.rdtype == dns_rdatatype_dhcid); + REQUIRE(dhcid->common.rdclass == dns_rdataclass_in); + + if (dhcid->mctx == NULL) { + return; + } + + if (dhcid->dhcid != NULL) { + isc_mem_free(dhcid->mctx, dhcid->dhcid); + } + dhcid->mctx = NULL; +} + +static isc_result_t +additionaldata_in_dhcid(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_dhcid(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_dhcid(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_dhcid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_dhcid(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_dhcid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_dhcid(ARGS_COMPARE) { + return (compare_in_dhcid(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_DHCID_49_C */ diff --git a/lib/dns/rdata/in_1/dhcid_49.h b/lib/dns/rdata/in_1/dhcid_49.h new file mode 100644 index 0000000..338476e --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#pragma once + +typedef struct dns_rdata_in_dhcid { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *dhcid; + unsigned int length; +} dns_rdata_in_dhcid_t; diff --git a/lib/dns/rdata/in_1/eid_31.c b/lib/dns/rdata/in_1/eid_31.c new file mode 100644 index 0000000..e5046ca --- /dev/null +++ b/lib/dns/rdata/in_1/eid_31.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */ + +#ifndef RDATA_IN_1_EID_31_C +#define RDATA_IN_1_EID_31_C + +#define RRTYPE_EID_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_eid(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_eid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + return (isc_hex_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_in_eid(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( ", target)); + } + if (tctx->width == 0) { + RETERR(isc_hex_totext(®ion, 60, "", target)); + } else { + RETERR(isc_hex_totext(®ion, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_eid(ARGS_FROMWIRE) { + isc_region_t region; + + REQUIRE(type == dns_rdatatype_eid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + + RETERR(mem_tobuffer(target, region.base, region.length)); + isc_buffer_forward(source, region.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_eid(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_in_eid(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_eid); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_eid(ARGS_FROMSTRUCT) { + dns_rdata_in_eid_t *eid = source; + + REQUIRE(type == dns_rdatatype_eid); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(eid != NULL); + REQUIRE(eid->common.rdtype == type); + REQUIRE(eid->common.rdclass == rdclass); + REQUIRE(eid->eid != NULL || eid->eid_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, eid->eid, eid->eid_len)); +} + +static isc_result_t +tostruct_in_eid(ARGS_TOSTRUCT) { + dns_rdata_in_eid_t *eid = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(eid != NULL); + REQUIRE(rdata->length != 0); + + eid->common.rdclass = rdata->rdclass; + eid->common.rdtype = rdata->type; + ISC_LINK_INIT(&eid->common, link); + + dns_rdata_toregion(rdata, &r); + eid->eid_len = r.length; + eid->eid = mem_maybedup(mctx, r.base, r.length); + if (eid->eid == NULL) { + return (ISC_R_NOMEMORY); + } + + eid->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_eid(ARGS_FREESTRUCT) { + dns_rdata_in_eid_t *eid = source; + + REQUIRE(eid != NULL); + REQUIRE(eid->common.rdclass == dns_rdataclass_in); + REQUIRE(eid->common.rdtype == dns_rdatatype_eid); + + if (eid->mctx == NULL) { + return; + } + + if (eid->eid != NULL) { + isc_mem_free(eid->mctx, eid->eid); + } + eid->mctx = NULL; +} + +static isc_result_t +additionaldata_in_eid(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_eid(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_eid(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_eid); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_eid(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_eid); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_eid(ARGS_COMPARE) { + return (compare_in_eid(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_EID_31_C */ diff --git a/lib/dns/rdata/in_1/eid_31.h b/lib/dns/rdata/in_1/eid_31.h new file mode 100644 index 0000000..d32bd3e --- /dev/null +++ b/lib/dns/rdata/in_1/eid_31.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + */ + +typedef struct dns_rdata_in_eid { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *eid; + uint16_t eid_len; +} dns_rdata_in_eid_t; diff --git a/lib/dns/rdata/in_1/https_65.c b/lib/dns/rdata/in_1/https_65.c new file mode 100644 index 0000000..af1ad7f --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-svcb-https-02 */ + +#pragma once + +#define RRTYPE_HTTPS_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL) + +/* + * Most of these functions refer to equivalent functions for SVCB, + * since wire and presentation formats are identical. + */ + +static isc_result_t +fromtext_in_https(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromtext_in_svcb(CALL_FROMTEXT)); +} + +static isc_result_t +totext_in_https(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + return (generic_totext_in_svcb(CALL_TOTEXT)); +} + +static isc_result_t +fromwire_in_https(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromwire_in_svcb(CALL_FROMWIRE)); +} + +static isc_result_t +towire_in_https(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->length != 0); + + return (generic_towire_in_svcb(CALL_TOWIRE)); +} + +static int +compare_in_https(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_https); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +fromstruct_in_https(ARGS_FROMSTRUCT) { + dns_rdata_in_https_t *https = source; + + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == type); + REQUIRE(https->common.rdclass == rdclass); + + return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT)); +} + +static isc_result_t +tostruct_in_https(ARGS_TOSTRUCT) { + dns_rdata_in_https_t *https = target; + + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(https != NULL); + REQUIRE(rdata->length != 0); + + return (generic_tostruct_in_svcb(CALL_TOSTRUCT)); +} + +static void +freestruct_in_https(ARGS_FREESTRUCT) { + dns_rdata_in_https_t *https = source; + + REQUIRE(https != NULL); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + + generic_freestruct_in_svcb(CALL_FREESTRUCT); +} + +static isc_result_t +additionaldata_in_https(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_additionaldata_in_svcb(CALL_ADDLDATA)); +} + +static isc_result_t +digest_in_https(ARGS_DIGEST) { + isc_region_t region1; + + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, ®ion1); + return ((digest)(arg, ®ion1)); +} + +static bool +checkowner_in_https(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_https); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_https(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_https); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_checknames_in_svcb(CALL_CHECKNAMES)); +} + +static int +casecompare_in_https(ARGS_COMPARE) { + return (compare_in_https(rdata1, rdata2)); +} + +isc_result_t +dns_rdata_in_https_first(dns_rdata_in_https_t *https) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_first(https)); +} + +isc_result_t +dns_rdata_in_https_next(dns_rdata_in_https_t *https) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_next(https)); +} + +void +dns_rdata_in_https_current(dns_rdata_in_https_t *https, isc_region_t *region) { + REQUIRE(https != NULL); + REQUIRE(https->common.rdtype == dns_rdatatype_https); + REQUIRE(https->common.rdclass == dns_rdataclass_in); + REQUIRE(region != NULL); + + generic_rdata_in_svcb_current(https, region); +} diff --git a/lib/dns/rdata/in_1/https_65.h b/lib/dns/rdata/in_1/https_65.h new file mode 100644 index 0000000..589f983 --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per draft-ietf-dnsop-svcb-https-02 + */ + +/* + * Wire and presentation formats for HTTPS are identical to SVCB. + */ +typedef struct dns_rdata_in_svcb dns_rdata_in_https_t; + +isc_result_t +dns_rdata_in_https_first(dns_rdata_in_https_t *); + +isc_result_t +dns_rdata_in_https_next(dns_rdata_in_https_t *); + +void +dns_rdata_in_https_current(dns_rdata_in_https_t *, isc_region_t *); diff --git a/lib/dns/rdata/in_1/kx_36.c b/lib/dns/rdata/in_1/kx_36.c new file mode 100644 index 0000000..ce48db1 --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2230 */ + +#ifndef RDATA_IN_1_KX_36_C +#define RDATA_IN_1_KX_36_C + +#define RRTYPE_KX_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_kx(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_kx); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_kx(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + + RETERR(str_totext(" ", target)); + + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_in_kx(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_kx); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + isc_buffer_activeregion(source, &sregion); + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sregion.base, 2)); + isc_buffer_forward(source, 2); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_in_kx(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_rdata_toregion(rdata, ®ion); + RETERR(mem_tobuffer(target, region.base, 2)); + isc_region_consume(®ion, 2); + + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_in_kx(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_kx); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + order = memcmp(rdata1->data, rdata2->data, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_in_kx(ARGS_FROMSTRUCT) { + dns_rdata_in_kx_t *kx = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_kx); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(kx != NULL); + REQUIRE(kx->common.rdtype == type); + REQUIRE(kx->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(kx->preference, target)); + dns_name_toregion(&kx->exchange, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_in_kx(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_in_kx_t *kx = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(kx != NULL); + REQUIRE(rdata->length != 0); + + kx->common.rdclass = rdata->rdclass; + kx->common.rdtype = rdata->type; + ISC_LINK_INIT(&kx->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + + kx->preference = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + dns_name_fromregion(&name, ®ion); + dns_name_init(&kx->exchange, NULL); + name_duporclone(&name, mctx, &kx->exchange); + kx->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_kx(ARGS_FREESTRUCT) { + dns_rdata_in_kx_t *kx = source; + + REQUIRE(kx != NULL); + REQUIRE(kx->common.rdclass == dns_rdataclass_in); + REQUIRE(kx->common.rdtype == dns_rdatatype_kx); + + if (kx->mctx == NULL) { + return; + } + + dns_name_free(&kx->exchange, kx->mctx); + kx->mctx = NULL; +} + +static isc_result_t +additionaldata_in_kx(ARGS_ADDLDATA) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +digest_in_kx(ARGS_DIGEST) { + isc_region_t r1, r2; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 2); + r1.length = 2; + RETERR((digest)(arg, &r1)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_in_kx(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_kx); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_kx(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_kx); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_kx(ARGS_COMPARE) { + return (compare_in_kx(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_KX_36_C */ diff --git a/lib/dns/rdata/in_1/kx_36.h b/lib/dns/rdata/in_1/kx_36.h new file mode 100644 index 0000000..9e2243b --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2230 */ + +typedef struct dns_rdata_in_kx { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t preference; + dns_name_t exchange; +} dns_rdata_in_kx_t; diff --git a/lib/dns/rdata/in_1/nimloc_32.c b/lib/dns/rdata/in_1/nimloc_32.c new file mode 100644 index 0000000..6a9cdb4 --- /dev/null +++ b/lib/dns/rdata/in_1/nimloc_32.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt */ + +#ifndef RDATA_IN_1_NIMLOC_32_C +#define RDATA_IN_1_NIMLOC_32_C + +#define RRTYPE_NIMLOC_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_nimloc(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_nimloc); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + return (isc_hex_tobuffer(lexer, target, -2)); +} + +static isc_result_t +totext_in_nimloc(ARGS_TOTEXT) { + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, ®ion); + + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext("( ", target)); + } + if (tctx->width == 0) { + RETERR(isc_hex_totext(®ion, 60, "", target)); + } else { + RETERR(isc_hex_totext(®ion, tctx->width - 2, tctx->linebreak, + target)); + } + if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) { + RETERR(str_totext(" )", target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_nimloc(ARGS_FROMWIRE) { + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nimloc); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + + RETERR(mem_tobuffer(target, region.base, region.length)); + isc_buffer_forward(source, region.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_nimloc(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_in_nimloc(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nimloc); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_nimloc(ARGS_FROMSTRUCT) { + dns_rdata_in_nimloc_t *nimloc = source; + + REQUIRE(type == dns_rdatatype_nimloc); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(nimloc != NULL); + REQUIRE(nimloc->common.rdtype == type); + REQUIRE(nimloc->common.rdclass == rdclass); + REQUIRE(nimloc->nimloc != NULL || nimloc->nimloc_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, nimloc->nimloc, nimloc->nimloc_len)); +} + +static isc_result_t +tostruct_in_nimloc(ARGS_TOSTRUCT) { + dns_rdata_in_nimloc_t *nimloc = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(nimloc != NULL); + REQUIRE(rdata->length != 0); + + nimloc->common.rdclass = rdata->rdclass; + nimloc->common.rdtype = rdata->type; + ISC_LINK_INIT(&nimloc->common, link); + + dns_rdata_toregion(rdata, &r); + nimloc->nimloc_len = r.length; + nimloc->nimloc = mem_maybedup(mctx, r.base, r.length); + if (nimloc->nimloc == NULL) { + return (ISC_R_NOMEMORY); + } + + nimloc->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_nimloc(ARGS_FREESTRUCT) { + dns_rdata_in_nimloc_t *nimloc = source; + + REQUIRE(nimloc != NULL); + REQUIRE(nimloc->common.rdclass == dns_rdataclass_in); + REQUIRE(nimloc->common.rdtype == dns_rdatatype_nimloc); + + if (nimloc->mctx == NULL) { + return; + } + + if (nimloc->nimloc != NULL) { + isc_mem_free(nimloc->mctx, nimloc->nimloc); + } + nimloc->mctx = NULL; +} + +static isc_result_t +additionaldata_in_nimloc(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_nimloc(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_nimloc(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nimloc); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_nimloc(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nimloc); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_nimloc(ARGS_COMPARE) { + return (compare_in_nimloc(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_NIMLOC_32_C */ diff --git a/lib/dns/rdata/in_1/nimloc_32.h b/lib/dns/rdata/in_1/nimloc_32.h new file mode 100644 index 0000000..faa9196 --- /dev/null +++ b/lib/dns/rdata/in_1/nimloc_32.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + */ + +typedef struct dns_rdata_in_nimloc { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *nimloc; + uint16_t nimloc_len; +} dns_rdata_in_nimloc_t; diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.c b/lib/dns/rdata/in_1/nsap-ptr_23.c new file mode 100644 index 0000000..9620fed --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1348. Obsoleted in RFC 1706 - use PTR instead. */ + +#ifndef RDATA_IN_1_NSAP_PTR_23_C +#define RDATA_IN_1_NSAP_PTR_23_C + +#define RRTYPE_NSAP_PTR_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_nsap_ptr(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_nsap_ptr); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_nsap_ptr(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + sub = name_prefix(&name, tctx->origin, &prefix); + + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_in_nsap_ptr(ARGS_FROMWIRE) { + dns_name_t name; + + REQUIRE(type == dns_rdatatype_nsap_ptr); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_in_nsap_ptr(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_in_nsap_ptr(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_in_nsap_ptr(ARGS_FROMSTRUCT) { + dns_rdata_in_nsap_ptr_t *nsap_ptr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsap_ptr); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(nsap_ptr != NULL); + REQUIRE(nsap_ptr->common.rdtype == type); + REQUIRE(nsap_ptr->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + dns_name_toregion(&nsap_ptr->owner, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_in_nsap_ptr(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_in_nsap_ptr_t *nsap_ptr = target; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(nsap_ptr != NULL); + REQUIRE(rdata->length != 0); + + nsap_ptr->common.rdclass = rdata->rdclass; + nsap_ptr->common.rdtype = rdata->type; + ISC_LINK_INIT(&nsap_ptr->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + dns_name_init(&nsap_ptr->owner, NULL); + name_duporclone(&name, mctx, &nsap_ptr->owner); + nsap_ptr->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_nsap_ptr(ARGS_FREESTRUCT) { + dns_rdata_in_nsap_ptr_t *nsap_ptr = source; + + REQUIRE(nsap_ptr != NULL); + REQUIRE(nsap_ptr->common.rdclass == dns_rdataclass_in); + REQUIRE(nsap_ptr->common.rdtype == dns_rdatatype_nsap_ptr); + + if (nsap_ptr->mctx == NULL) { + return; + } + + dns_name_free(&nsap_ptr->owner, nsap_ptr->mctx); + nsap_ptr->mctx = NULL; +} + +static isc_result_t +additionaldata_in_nsap_ptr(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_nsap_ptr(ARGS_DIGEST) { + isc_region_t r; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_in_nsap_ptr(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nsap_ptr); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_nsap_ptr(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nsap_ptr); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_nsap_ptr(ARGS_COMPARE) { + return (compare_in_nsap_ptr(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_NSAP_PTR_23_C */ diff --git a/lib/dns/rdata/in_1/nsap-ptr_23.h b/lib/dns/rdata/in_1/nsap-ptr_23.h new file mode 100644 index 0000000..45e5b16 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1348. Obsoleted in RFC 1706 - use PTR instead. */ + +typedef struct dns_rdata_in_nsap_ptr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t owner; +} dns_rdata_in_nsap_ptr_t; diff --git a/lib/dns/rdata/in_1/nsap_22.c b/lib/dns/rdata/in_1/nsap_22.c new file mode 100644 index 0000000..fc92014 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC1706 */ + +#ifndef RDATA_IN_1_NSAP_22_C +#define RDATA_IN_1_NSAP_22_C + +#define RRTYPE_NSAP_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_nsap(ARGS_FROMTEXT) { + isc_token_t token; + isc_textregion_t *sr; + int n; + bool valid = false; + int digits = 0; + unsigned char c = 0; + + REQUIRE(type == dns_rdatatype_nsap); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + /* 0x */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + sr = &token.value.as_textregion; + if (sr->length < 2) { + RETTOK(ISC_R_UNEXPECTEDEND); + } + if (sr->base[0] != '0' || (sr->base[1] != 'x' && sr->base[1] != 'X')) { + RETTOK(DNS_R_SYNTAX); + } + isc_textregion_consume(sr, 2); + while (sr->length > 0) { + if (sr->base[0] == '.') { + isc_textregion_consume(sr, 1); + continue; + } + if ((n = hexvalue(sr->base[0])) == -1) { + RETTOK(DNS_R_SYNTAX); + } + c <<= 4; + c += n; + if (++digits == 2) { + RETERR(mem_tobuffer(target, &c, 1)); + valid = true; + digits = 0; + c = 0; + } + isc_textregion_consume(sr, 1); + } + if (digits != 0 || !valid) { + RETTOK(ISC_R_UNEXPECTEDEND); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_nsap(ARGS_TOTEXT) { + isc_region_t region; + char buf[sizeof("xx")]; + + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(tctx); + + dns_rdata_toregion(rdata, ®ion); + RETERR(str_totext("0x", target)); + while (region.length != 0) { + snprintf(buf, sizeof(buf), "%02x", region.base[0]); + isc_region_consume(®ion, 1); + RETERR(str_totext(buf, target)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_nsap(ARGS_FROMWIRE) { + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsap); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, ®ion); + if (region.length < 1) { + return (ISC_R_UNEXPECTEDEND); + } + + RETERR(mem_tobuffer(target, region.base, region.length)); + isc_buffer_forward(source, region.length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_nsap(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static int +compare_in_nsap(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_nsap); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_nsap(ARGS_FROMSTRUCT) { + dns_rdata_in_nsap_t *nsap = source; + + REQUIRE(type == dns_rdatatype_nsap); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(nsap != NULL); + REQUIRE(nsap->common.rdtype == type); + REQUIRE(nsap->common.rdclass == rdclass); + REQUIRE(nsap->nsap != NULL || nsap->nsap_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, nsap->nsap, nsap->nsap_len)); +} + +static isc_result_t +tostruct_in_nsap(ARGS_TOSTRUCT) { + dns_rdata_in_nsap_t *nsap = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(nsap != NULL); + REQUIRE(rdata->length != 0); + + nsap->common.rdclass = rdata->rdclass; + nsap->common.rdtype = rdata->type; + ISC_LINK_INIT(&nsap->common, link); + + dns_rdata_toregion(rdata, &r); + nsap->nsap_len = r.length; + nsap->nsap = mem_maybedup(mctx, r.base, r.length); + if (nsap->nsap == NULL) { + return (ISC_R_NOMEMORY); + } + + nsap->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_nsap(ARGS_FREESTRUCT) { + dns_rdata_in_nsap_t *nsap = source; + + REQUIRE(nsap != NULL); + REQUIRE(nsap->common.rdclass == dns_rdataclass_in); + REQUIRE(nsap->common.rdtype == dns_rdatatype_nsap); + + if (nsap->mctx == NULL) { + return; + } + + if (nsap->nsap != NULL) { + isc_mem_free(nsap->mctx, nsap->nsap); + } + nsap->mctx = NULL; +} + +static isc_result_t +additionaldata_in_nsap(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_nsap(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_nsap(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_nsap); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_nsap(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_nsap); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_nsap(ARGS_COMPARE) { + return (compare_in_nsap(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_NSAP_22_C */ diff --git a/lib/dns/rdata/in_1/nsap_22.h b/lib/dns/rdata/in_1/nsap_22.h new file mode 100644 index 0000000..fd5803c --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC1706 */ + +typedef struct dns_rdata_in_nsap { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *nsap; + uint16_t nsap_len; +} dns_rdata_in_nsap_t; diff --git a/lib/dns/rdata/in_1/px_26.c b/lib/dns/rdata/in_1/px_26.c new file mode 100644 index 0000000..441c3c4 --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.c @@ -0,0 +1,372 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2163 */ + +#ifndef RDATA_IN_1_PX_26_C +#define RDATA_IN_1_PX_26_C + +#define RRTYPE_PX_ATTRIBUTES (0) + +static isc_result_t +fromtext_in_px(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + + REQUIRE(type == dns_rdatatype_px); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + if (origin == NULL) { + origin = dns_rootname; + } + + /* + * Preference. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * MAP822. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + + /* + * MAPX400. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_px(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + /* + * Preference. + */ + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * MAP822. + */ + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + isc_region_consume(®ion, name_length(&name)); + RETERR(dns_name_totext(&prefix, sub, target)); + RETERR(str_totext(" ", target)); + + /* + * MAPX400. + */ + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_in_px(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sregion; + + REQUIRE(type == dns_rdatatype_px); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + /* + * Preference. + */ + isc_buffer_activeregion(source, &sregion); + if (sregion.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sregion.base, 2)); + isc_buffer_forward(source, 2); + + /* + * MAP822. + */ + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + /* + * MAPX400. + */ + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_in_px(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + /* + * Preference. + */ + dns_rdata_toregion(rdata, ®ion); + RETERR(mem_tobuffer(target, region.base, 2)); + isc_region_consume(®ion, 2); + + /* + * MAP822. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + RETERR(dns_name_towire(&name, cctx, target)); + isc_region_consume(®ion, name_length(&name)); + + /* + * MAPX400. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_in_px(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_px); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + order = memcmp(rdata1->data, rdata2->data, 2); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 2); + isc_region_consume(®ion2, 2); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + order = dns_name_rdatacompare(&name1, &name2); + if (order != 0) { + return (order); + } + + isc_region_consume(®ion1, name_length(&name1)); + isc_region_consume(®ion2, name_length(&name2)); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_in_px(ARGS_FROMSTRUCT) { + dns_rdata_in_px_t *px = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_px); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(px != NULL); + REQUIRE(px->common.rdtype == type); + REQUIRE(px->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(px->preference, target)); + dns_name_toregion(&px->map822, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + dns_name_toregion(&px->mapx400, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_in_px(ARGS_TOSTRUCT) { + dns_rdata_in_px_t *px = target; + dns_name_t name; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(px != NULL); + REQUIRE(rdata->length != 0); + + px->common.rdclass = rdata->rdclass; + px->common.rdtype = rdata->type; + ISC_LINK_INIT(&px->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + + px->preference = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + dns_name_fromregion(&name, ®ion); + + dns_name_init(&px->map822, NULL); + name_duporclone(&name, mctx, &px->map822); + isc_region_consume(®ion, name_length(&px->map822)); + + dns_name_init(&px->mapx400, NULL); + name_duporclone(&name, mctx, &px->mapx400); + + px->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_px(ARGS_FREESTRUCT) { + dns_rdata_in_px_t *px = source; + + REQUIRE(px != NULL); + REQUIRE(px->common.rdclass == dns_rdataclass_in); + REQUIRE(px->common.rdtype == dns_rdatatype_px); + + if (px->mctx == NULL) { + return; + } + + dns_name_free(&px->map822, px->mctx); + dns_name_free(&px->mapx400, px->mctx); + px->mctx = NULL; +} + +static isc_result_t +additionaldata_in_px(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_px(ARGS_DIGEST) { + isc_region_t r1, r2; + dns_name_t name; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 2); + r1.length = 2; + result = (digest)(arg, &r1); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + result = dns_name_digest(&name, digest, arg); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_region_consume(&r2, name_length(&name)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_in_px(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_px); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_px(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_px); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_px(ARGS_COMPARE) { + return (compare_in_px(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_PX_26_C */ diff --git a/lib/dns/rdata/in_1/px_26.h b/lib/dns/rdata/in_1/px_26.h new file mode 100644 index 0000000..36a1c0e --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2163 */ + +typedef struct dns_rdata_in_px { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t preference; + dns_name_t map822; + dns_name_t mapx400; +} dns_rdata_in_px_t; diff --git a/lib/dns/rdata/in_1/srv_33.c b/lib/dns/rdata/in_1/srv_33.c new file mode 100644 index 0000000..6818b03 --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* RFC2782 */ + +#ifndef RDATA_IN_1_SRV_33_C +#define RDATA_IN_1_SRV_33_C + +#define RRTYPE_SRV_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL) + +static isc_result_t +fromtext_in_srv(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool ok; + + REQUIRE(type == dns_rdatatype_srv); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * Priority. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Weight. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Port. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + /* + * Target. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + ok = true; + if ((options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_srv(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("64000")]; + unsigned short num; + + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + /* + * Priority. + */ + dns_rdata_toregion(rdata, ®ion); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Weight. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Port. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + snprintf(buf, sizeof(buf), "%u", num); + RETERR(str_totext(buf, target)); + RETERR(str_totext(" ", target)); + + /* + * Target. + */ + dns_name_fromregion(&name, ®ion); + sub = name_prefix(&name, tctx->origin, &prefix); + return (dns_name_totext(&prefix, sub, target)); +} + +static isc_result_t +fromwire_in_srv(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_srv); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + /* + * Priority, weight, port. + */ + isc_buffer_activeregion(source, &sr); + if (sr.length < 6) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, sr.base, 6)); + isc_buffer_forward(source, 6); + + /* + * Target. + */ + return (dns_name_fromwire(&name, source, dctx, options, target)); +} + +static isc_result_t +towire_in_srv(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + /* + * Priority, weight, port. + */ + dns_rdata_toregion(rdata, &sr); + RETERR(mem_tobuffer(target, sr.base, 6)); + isc_region_consume(&sr, 6); + + /* + * Target. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, &sr); + return (dns_name_towire(&name, cctx, target)); +} + +static int +compare_in_srv(ARGS_COMPARE) { + dns_name_t name1; + dns_name_t name2; + isc_region_t region1; + isc_region_t region2; + int order; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_srv); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + /* + * Priority, weight, port. + */ + order = memcmp(rdata1->data, rdata2->data, 6); + if (order != 0) { + return (order < 0 ? -1 : 1); + } + + /* + * Target. + */ + dns_name_init(&name1, NULL); + dns_name_init(&name2, NULL); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + isc_region_consume(®ion1, 6); + isc_region_consume(®ion2, 6); + + dns_name_fromregion(&name1, ®ion1); + dns_name_fromregion(&name2, ®ion2); + + return (dns_name_rdatacompare(&name1, &name2)); +} + +static isc_result_t +fromstruct_in_srv(ARGS_FROMSTRUCT) { + dns_rdata_in_srv_t *srv = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_srv); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(srv != NULL); + REQUIRE(srv->common.rdtype == type); + REQUIRE(srv->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(srv->priority, target)); + RETERR(uint16_tobuffer(srv->weight, target)); + RETERR(uint16_tobuffer(srv->port, target)); + dns_name_toregion(&srv->target, ®ion); + return (isc_buffer_copyregion(target, ®ion)); +} + +static isc_result_t +tostruct_in_srv(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_in_srv_t *srv = target; + dns_name_t name; + + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(srv != NULL); + REQUIRE(rdata->length != 0); + + srv->common.rdclass = rdata->rdclass; + srv->common.rdtype = rdata->type; + ISC_LINK_INIT(&srv->common, link); + + dns_name_init(&name, NULL); + dns_rdata_toregion(rdata, ®ion); + srv->priority = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + srv->weight = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + srv->port = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + dns_name_init(&srv->target, NULL); + name_duporclone(&name, mctx, &srv->target); + srv->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_srv(ARGS_FREESTRUCT) { + dns_rdata_in_srv_t *srv = source; + + REQUIRE(srv != NULL); + REQUIRE(srv->common.rdclass == dns_rdataclass_in); + REQUIRE(srv->common.rdtype == dns_rdatatype_srv); + + if (srv->mctx == NULL) { + return; + } + + dns_name_free(&srv->target, srv->mctx); + srv->mctx = NULL; +} + +static isc_result_t +additionaldata_in_srv(ARGS_ADDLDATA) { + char buf[sizeof("_65000._tcp")]; + dns_fixedname_t fixed; + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + uint16_t port; + isc_result_t result; + + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(owner); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 4); + port = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + dns_name_fromregion(&name, ®ion); + + if (dns_name_equal(&name, dns_rootname)) { + return (ISC_R_SUCCESS); + } + + result = (add)(arg, &name, dns_rdatatype_a, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_fixedname_init(&fixed); + snprintf(buf, sizeof(buf), "_%u._tcp", port); + result = dns_name_fromstring2(dns_fixedname_name(&fixed), buf, NULL, 0, + NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + result = dns_name_concatenate(dns_fixedname_name(&fixed), &name, + dns_fixedname_name(&fixed), NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + return ((add)(arg, dns_fixedname_name(&fixed), dns_rdatatype_tlsa, + NULL)); +} + +static isc_result_t +digest_in_srv(ARGS_DIGEST) { + isc_region_t r1, r2; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r1); + r2 = r1; + isc_region_consume(&r2, 6); + r1.length = 6; + RETERR((digest)(arg, &r1)); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, &r2); + return (dns_name_digest(&name, digest, arg)); +} + +static bool +checkowner_in_srv(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_srv); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +checknames_in_srv(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + + REQUIRE(rdata->type == dns_rdatatype_srv); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + isc_region_consume(®ion, 6); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static int +casecompare_in_srv(ARGS_COMPARE) { + return (compare_in_srv(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_SRV_33_C */ diff --git a/lib/dns/rdata/in_1/srv_33.h b/lib/dns/rdata/in_1/srv_33.h new file mode 100644 index 0000000..fbfa92c --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per RFC2782 */ + +typedef struct dns_rdata_in_srv { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t priority; + uint16_t weight; + uint16_t port; + dns_name_t target; +} dns_rdata_in_srv_t; diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c new file mode 100644 index 0000000..0d9da89 --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.c @@ -0,0 +1,1338 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* draft-ietf-dnsop-svcb-https-02 */ + +#ifndef RDATA_IN_1_SVCB_64_C +#define RDATA_IN_1_SVCB_64_C + +#define RRTYPE_SVCB_ATTRIBUTES (DNS_RDATATYPEATTR_FOLLOWADDITIONAL) + +#define SVCB_MAN_KEY 0 +#define SVCB_ALPN_KEY 1 +#define SVCB_NO_DEFAULT_ALPN_KEY 2 +#define MAX_CNAMES 16 /* See ns/query.c MAX_RESTARTS */ + +/* + * Service Binding Parameter Registry + */ +enum encoding { + sbpr_text, + sbpr_port, + sbpr_ipv4s, + sbpr_ipv6s, + sbpr_base64, + sbpr_empty, + sbpr_alpn, + sbpr_keylist, + sbpr_dohpath +}; +static const struct { + const char *name; /* Restricted to lowercase LDH by registry. */ + unsigned int value; + enum encoding encoding; + bool initial; /* Part of the first defined set of encodings. */ +} sbpr[] = { + { "mandatory", 0, sbpr_keylist, true }, + { "alpn", 1, sbpr_alpn, true }, + { "no-default-alpn", 2, sbpr_empty, true }, + { "port", 3, sbpr_port, true }, + { "ipv4hint", 4, sbpr_ipv4s, true }, + { "ech", 5, sbpr_base64, true }, + { "ipv6hint", 6, sbpr_ipv6s, true }, + { "dohpath", 7, sbpr_dohpath, false }, +}; + +static isc_result_t +alpn_fromtxt(isc_textregion_t *source, isc_buffer_t *target) { + isc_textregion_t source0 = *source; + do { + RETERR(commatxt_fromtext(&source0, true, target)); + } while (source0.length != 0); + return (ISC_R_SUCCESS); +} + +static int +svckeycmp(const void *a1, const void *a2) { + const unsigned char *u1 = a1, *u2 = a2; + if (*u1 != *u2) { + return (*u1 - *u2); + } + return (*(++u1) - *(++u2)); +} + +static isc_result_t +svcsortkeylist(isc_buffer_t *target, unsigned int used) { + isc_region_t region; + + isc_buffer_usedregion(target, ®ion); + isc_region_consume(®ion, used); + INSIST(region.length > 0U); + qsort(region.base, region.length / 2, 2, svckeycmp); + /* Reject duplicates. */ + while (region.length >= 4) { + if (region.base[0] == region.base[2] && + region.base[1] == region.base[3]) + { + return (DNS_R_SYNTAX); + } + isc_region_consume(®ion, 2); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +svcb_validate(uint16_t key, isc_region_t *region) { + size_t i; + + for (i = 0; i < ARRAY_SIZE(sbpr); i++) { + if (sbpr[i].value == key) { + switch (sbpr[i].encoding) { + case sbpr_port: + if (region->length != 2) { + return (DNS_R_FORMERR); + } + break; + case sbpr_ipv4s: + if ((region->length % 4) != 0 || + region->length == 0) + { + return (DNS_R_FORMERR); + } + break; + case sbpr_ipv6s: + if ((region->length % 16) != 0 || + region->length == 0) + { + return (DNS_R_FORMERR); + } + break; + case sbpr_alpn: { + if (region->length == 0) { + return (DNS_R_FORMERR); + } + while (region->length != 0) { + size_t l = *region->base + 1; + if (l == 1U || l > region->length) { + return (DNS_R_FORMERR); + } + isc_region_consume(region, l); + } + break; + } + case sbpr_keylist: { + if ((region->length % 2) != 0 || + region->length == 0) + { + return (DNS_R_FORMERR); + } + /* In order? */ + while (region->length >= 4) { + if (region->base[0] > region->base[2] || + (region->base[0] == + region->base[2] && + region->base[1] >= + region->base[3])) + { + return (DNS_R_FORMERR); + } + isc_region_consume(region, 2); + } + break; + } + case sbpr_text: + case sbpr_base64: + break; + case sbpr_dohpath: + /* + * Minimum valid dohpath is "/{?dns}" as + * it MUST be relative (leading "/") and + * MUST contain "{?dns}". + */ + if (region->length < 7) { + return (DNS_R_FORMERR); + } + /* MUST be relative */ + if (region->base[0] != '/') { + return (DNS_R_FORMERR); + } + /* MUST be UTF8 */ + if (!isc_utf8_valid(region->base, + region->length)) + { + return (DNS_R_FORMERR); + } + /* MUST contain "{?dns}" */ + if (strnstr((char *)region->base, "{?dns}", + region->length) == NULL) + { + return (DNS_R_FORMERR); + } + break; + case sbpr_empty: + if (region->length != 0) { + return (DNS_R_FORMERR); + } + break; + } + } + } + return (ISC_R_SUCCESS); +} + +/* + * Parse keyname from region. + */ +static isc_result_t +svc_keyfromregion(isc_textregion_t *region, char sep, uint16_t *value, + isc_buffer_t *target) { + char *e = NULL; + size_t i; + unsigned long ul; + + /* Look for known key names. */ + for (i = 0; i < ARRAY_SIZE(sbpr); i++) { + size_t len = strlen(sbpr[i].name); + if (strncasecmp(region->base, sbpr[i].name, len) != 0 || + (region->base[len] != 0 && region->base[len] != sep)) + { + continue; + } + isc_textregion_consume(region, len); + ul = sbpr[i].value; + goto finish; + } + /* Handle keyXXXXX form. */ + if (strncmp(region->base, "key", 3) != 0) { + return (DNS_R_SYNTAX); + } + isc_textregion_consume(region, 3); + /* Disallow [+-]XXXXX which is allowed by strtoul. */ + if (region->length == 0 || *region->base == '-' || *region->base == '+') + { + return (DNS_R_SYNTAX); + } + /* No zero padding. */ + if (region->length > 1 && *region->base == '0' && + region->base[1] != sep) + { + return (DNS_R_SYNTAX); + } + ul = strtoul(region->base, &e, 10); + /* Valid number? */ + if (e == region->base || (*e != sep && *e != 0)) { + return (DNS_R_SYNTAX); + } + if (ul > 0xffff) { + return (ISC_R_RANGE); + } + isc_textregion_consume(region, e - region->base); +finish: + if (sep == ',' && region->length == 1) { + return (DNS_R_SYNTAX); + } + /* Consume separator. */ + if (region->length != 0) { + isc_textregion_consume(region, 1); + } + RETERR(uint16_tobuffer(ul, target)); + if (value != NULL) { + *value = ul; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) { + char *e = NULL; + char abuf[16]; + char tbuf[sizeof("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:255.255.255.255,")]; + isc_buffer_t sb; + isc_region_t keyregion; + size_t len; + uint16_t key; + unsigned int i; + unsigned int used; + unsigned long ul; + + for (i = 0; i < ARRAY_SIZE(sbpr); i++) { + len = strlen(sbpr[i].name); + if (strncmp(region->base, sbpr[i].name, len) != 0 || + (region->base[len] != 0 && region->base[len] != '=')) + { + continue; + } + + if (region->base[len] == '=') { + len++; + } + + RETERR(uint16_tobuffer(sbpr[i].value, target)); + isc_textregion_consume(region, len); + + sb = *target; + RETERR(uint16_tobuffer(0, target)); /* length */ + + switch (sbpr[i].encoding) { + case sbpr_text: + case sbpr_dohpath: + RETERR(multitxt_fromtext(region, target)); + break; + case sbpr_alpn: + RETERR(alpn_fromtxt(region, target)); + break; + case sbpr_port: + if (!isdigit((unsigned char)*region->base)) { + return (DNS_R_SYNTAX); + } + ul = strtoul(region->base, &e, 10); + if (*e != '\0') { + return (DNS_R_SYNTAX); + } + if (ul > 0xffff) { + return (ISC_R_RANGE); + } + RETERR(uint16_tobuffer(ul, target)); + break; + case sbpr_ipv4s: + do { + snprintf(tbuf, sizeof(tbuf), "%*s", + (int)(region->length), region->base); + e = strchr(tbuf, ','); + if (e != NULL) { + *e++ = 0; + isc_textregion_consume(region, + e - tbuf); + } + if (inet_pton(AF_INET, tbuf, abuf) != 1) { + return (DNS_R_SYNTAX); + } + mem_tobuffer(target, abuf, 4); + } while (e != NULL); + break; + case sbpr_ipv6s: + do { + snprintf(tbuf, sizeof(tbuf), "%*s", + (int)(region->length), region->base); + e = strchr(tbuf, ','); + if (e != NULL) { + *e++ = 0; + isc_textregion_consume(region, + e - tbuf); + } + if (inet_pton(AF_INET6, tbuf, abuf) != 1) { + return (DNS_R_SYNTAX); + } + mem_tobuffer(target, abuf, 16); + } while (e != NULL); + break; + case sbpr_base64: + RETERR(isc_base64_decodestring(region->base, target)); + break; + case sbpr_empty: + if (region->length != 0) { + return (DNS_R_SYNTAX); + } + break; + case sbpr_keylist: + if (region->length == 0) { + return (DNS_R_SYNTAX); + } + used = isc_buffer_usedlength(target); + while (region->length != 0) { + RETERR(svc_keyfromregion(region, ',', NULL, + target)); + } + RETERR(svcsortkeylist(target, used)); + break; + default: + UNREACHABLE(); + } + + len = isc_buffer_usedlength(target) - + isc_buffer_usedlength(&sb) - 2; + RETERR(uint16_tobuffer(len, &sb)); /* length */ + switch (sbpr[i].encoding) { + case sbpr_dohpath: + /* + * Apply constraints not applied by multitxt_fromtext. + */ + keyregion.base = isc_buffer_used(&sb); + keyregion.length = isc_buffer_usedlength(target) - + isc_buffer_usedlength(&sb); + RETERR(svcb_validate(sbpr[i].value, &keyregion)); + break; + default: + break; + } + return (ISC_R_SUCCESS); + } + + RETERR(svc_keyfromregion(region, '=', &key, target)); + if (region->length == 0) { + RETERR(uint16_tobuffer(0, target)); /* length */ + /* Sanity check keyXXXXX form. */ + keyregion.base = isc_buffer_used(target); + keyregion.length = 0; + return (svcb_validate(key, &keyregion)); + } + sb = *target; + RETERR(uint16_tobuffer(0, target)); /* dummy length */ + RETERR(multitxt_fromtext(region, target)); + len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2; + RETERR(uint16_tobuffer(len, &sb)); /* length */ + /* Sanity check keyXXXXX form. */ + keyregion.base = isc_buffer_used(&sb); + keyregion.length = len; + return (svcb_validate(key, &keyregion)); +} + +static const char * +svcparamkey(unsigned short value, enum encoding *encoding, char *buf, + size_t len) { + size_t i; + int n; + + for (i = 0; i < ARRAY_SIZE(sbpr); i++) { + if (sbpr[i].value == value && sbpr[i].initial) { + *encoding = sbpr[i].encoding; + return (sbpr[i].name); + } + } + n = snprintf(buf, len, "key%u", value); + INSIST(n > 0 && (unsigned)n < len); + *encoding = sbpr_text; + return (buf); +} + +static isc_result_t +svcsortkeys(isc_buffer_t *target, unsigned int used) { + isc_region_t r1, r2, man = { .base = NULL, .length = 0 }; + unsigned char buf[1024]; + uint16_t mankey = 0; + bool have_alpn = false; + + if (isc_buffer_usedlength(target) == used) { + return (ISC_R_SUCCESS); + } + + /* + * Get the parameters into r1. + */ + isc_buffer_usedregion(target, &r1); + isc_region_consume(&r1, used); + + while (1) { + uint16_t key1, len1, key2, len2; + unsigned char *base1, *base2; + + r2 = r1; + + /* + * Get the first parameter. + */ + base1 = r1.base; + key1 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + len1 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + isc_region_consume(&r1, len1); + + /* + * Was there only one key left? + */ + if (r1.length == 0) { + if (mankey != 0) { + /* Is this the last mandatory key? */ + if (key1 != mankey || man.length != 0) { + return (DNS_R_INCONSISTENTRR); + } + } else if (key1 == SVCB_MAN_KEY) { + /* Lone mandatory field. */ + return (DNS_R_DISALLOWED); + } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && + !have_alpn) + { + /* Missing required ALPN field. */ + return (DNS_R_DISALLOWED); + } + return (ISC_R_SUCCESS); + } + + /* + * Find the smallest parameter. + */ + while (r1.length != 0) { + base2 = r1.base; + key2 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + len2 = uint16_fromregion(&r1); + isc_region_consume(&r1, 2); + isc_region_consume(&r1, len2); + if (key2 == key1) { + return (DNS_R_DUPLICATE); + } + if (key2 < key1) { + base1 = base2; + key1 = key2; + len1 = len2; + } + } + + /* + * Do we need to move the smallest parameter to the start? + */ + if (base1 != r2.base) { + size_t offset = 0; + size_t bytes = len1 + 4; + size_t length = base1 - r2.base; + + /* + * Move the smallest parameter to the start. + */ + while (bytes > 0) { + size_t count; + + if (bytes > sizeof(buf)) { + count = sizeof(buf); + } else { + count = bytes; + } + memmove(buf, base1, count); + memmove(r2.base + offset + count, + r2.base + offset, length); + memmove(r2.base + offset, buf, count); + base1 += count; + bytes -= count; + offset += count; + } + } + + /* + * Check ALPN is present when NO-DEFAULT-ALPN is set. + */ + if (key1 == SVCB_ALPN_KEY) { + have_alpn = true; + } else if (key1 == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) { + /* Missing required ALPN field. */ + return (DNS_R_DISALLOWED); + } + + /* + * Check key against mandatory key list. + */ + if (mankey != 0) { + if (key1 > mankey) { + return (DNS_R_INCONSISTENTRR); + } + if (key1 == mankey) { + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + } else { + mankey = 0; + } + } + } + + /* + * Is this the mandatory key? + */ + if (key1 == SVCB_MAN_KEY) { + man = r2; + man.length = len1 + 4; + isc_region_consume(&man, 4); + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + if (mankey == SVCB_MAN_KEY) { + return (DNS_R_DISALLOWED); + } + } else { + return (DNS_R_SYNTAX); + } + } + + /* + * Consume the smallest parameter. + */ + isc_region_consume(&r2, len1 + 4); + r1 = r2; + } +} + +static isc_result_t +generic_fromtext_in_svcb(ARGS_FROMTEXT) { + isc_token_t token; + dns_name_t name; + isc_buffer_t buffer; + bool alias; + bool ok = true; + unsigned int used; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + /* + * SvcPriority. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 0xffffU) { + RETTOK(ISC_R_RANGE); + } + RETERR(uint16_tobuffer(token.value.as_ulong, target)); + + alias = token.value.as_ulong == 0; + + /* + * TargetName. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring, + false)); + dns_name_init(&name, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } + RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); + if (!alias && (options & DNS_RDATA_CHECKNAMES) != 0) { + ok = dns_name_ishostname(&name, false); + } + if (!ok && (options & DNS_RDATA_CHECKNAMESFAIL) != 0) { + RETTOK(DNS_R_BADNAME); + } + if (!ok && callbacks != NULL) { + warn_badname(&name, lexer, callbacks); + } + + if (alias) { + return (ISC_R_SUCCESS); + } + + /* + * SvcParams + */ + used = isc_buffer_usedlength(target); + while (1) { + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_qvpair, true)); + if (token.type == isc_tokentype_eol || + token.type == isc_tokentype_eof) + { + isc_lex_ungettoken(lexer, &token); + return (svcsortkeys(target, used)); + } + + if (token.type != isc_tokentype_string && /* key only */ + token.type != isc_tokentype_qvpair && + token.type != isc_tokentype_vpair) + { + RETTOK(DNS_R_SYNTAX); + } + RETTOK(svc_fromtext(&token.value.as_textregion, target)); + } +} + +static isc_result_t +fromtext_in_svcb(ARGS_FROMTEXT) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + UNUSED(type); + UNUSED(rdclass); + UNUSED(callbacks); + + return (generic_fromtext_in_svcb(CALL_FROMTEXT)); +} + +static isc_result_t +generic_totext_in_svcb(ARGS_TOTEXT) { + isc_region_t region; + dns_name_t name; + dns_name_t prefix; + bool sub; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")]; + unsigned short num; + int n; + + REQUIRE(rdata->length != 0); + + dns_name_init(&name, NULL); + dns_name_init(&prefix, NULL); + + dns_rdata_toregion(rdata, ®ion); + + /* + * SvcPriority. + */ + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + n = snprintf(buf, sizeof(buf), "%u ", num); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + RETERR(str_totext(buf, target)); + + /* + * TargetName. + */ + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + sub = name_prefix(&name, tctx->origin, &prefix); + RETERR(dns_name_totext(&prefix, sub, target)); + + while (region.length > 0) { + isc_region_t r; + enum encoding encoding; + + RETERR(str_totext(" ", target)); + + INSIST(region.length >= 2); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + RETERR(str_totext(svcparamkey(num, &encoding, buf, sizeof(buf)), + target)); + + INSIST(region.length >= 2); + num = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + INSIST(region.length >= num); + r = region; + r.length = num; + isc_region_consume(®ion, num); + if (num == 0) { + continue; + } + if (encoding != sbpr_empty) { + RETERR(str_totext("=", target)); + } + switch (encoding) { + case sbpr_text: + RETERR(multitxt_totext(&r, target)); + break; + case sbpr_port: + num = uint16_fromregion(&r); + isc_region_consume(&r, 2); + n = snprintf(buf, sizeof(buf), "%u", num); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + RETERR(str_totext(buf, target)); + INSIST(r.length == 0U); + break; + case sbpr_ipv4s: + while (r.length > 0U) { + INSIST(r.length >= 4U); + inet_ntop(AF_INET, r.base, buf, sizeof(buf)); + RETERR(str_totext(buf, target)); + isc_region_consume(&r, 4); + if (r.length != 0U) { + RETERR(str_totext(",", target)); + } + } + break; + case sbpr_ipv6s: + while (r.length > 0U) { + INSIST(r.length >= 16U); + inet_ntop(AF_INET6, r.base, buf, sizeof(buf)); + RETERR(str_totext(buf, target)); + isc_region_consume(&r, 16); + if (r.length != 0U) { + RETERR(str_totext(",", target)); + } + } + break; + case sbpr_base64: + RETERR(isc_base64_totext(&r, 0, "", target)); + break; + case sbpr_alpn: + INSIST(r.length != 0U); + RETERR(str_totext("\"", target)); + while (r.length != 0) { + commatxt_totext(&r, false, true, target); + if (r.length != 0) { + RETERR(str_totext(",", target)); + } + } + RETERR(str_totext("\"", target)); + break; + case sbpr_empty: + INSIST(r.length == 0U); + break; + case sbpr_keylist: + while (r.length > 0) { + num = uint16_fromregion(&r); + isc_region_consume(&r, 2); + RETERR(str_totext(svcparamkey(num, &encoding, + buf, sizeof(buf)), + target)); + if (r.length != 0) { + RETERR(str_totext(",", target)); + } + } + break; + default: + UNREACHABLE(); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +totext_in_svcb(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + return (generic_totext_in_svcb(CALL_TOTEXT)); +} + +static isc_result_t +generic_fromwire_in_svcb(ARGS_FROMWIRE) { + dns_name_t name; + isc_region_t region, man = { .base = NULL, .length = 0 }; + bool alias, first = true, have_alpn = false; + uint16_t lastkey = 0, mankey = 0; + + UNUSED(type); + UNUSED(rdclass); + + dns_decompress_setmethods(dctx, DNS_COMPRESS_NONE); + + dns_name_init(&name, NULL); + + /* + * SvcPriority. + */ + isc_buffer_activeregion(source, ®ion); + if (region.length < 2) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + alias = uint16_fromregion(®ion) == 0; + isc_buffer_forward(source, 2); + + /* + * TargetName. + */ + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + + if (alias) { + return (ISC_R_SUCCESS); + } + + /* + * SvcParams. + */ + isc_buffer_activeregion(source, ®ion); + while (region.length > 0U) { + isc_region_t keyregion; + uint16_t key, len; + + /* + * SvcParamKey + */ + if (region.length < 2U) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + key = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + /* + * Keys must be unique and in order. + */ + if (!first && key <= lastkey) { + return (DNS_R_FORMERR); + } + + /* + * Check mandatory keys. + */ + if (mankey != 0) { + /* Missing mandatory key? */ + if (key > mankey) { + return (DNS_R_FORMERR); + } + if (key == mankey) { + /* Get next mandatory key. */ + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + } else { + mankey = 0; + } + } + } + + /* + * Check alpn present when no-default-alpn is set. + */ + if (key == SVCB_ALPN_KEY) { + have_alpn = true; + } else if (key == SVCB_NO_DEFAULT_ALPN_KEY && !have_alpn) { + return (DNS_R_FORMERR); + } + + first = false; + lastkey = key; + + /* + * SvcParamValue length. + */ + if (region.length < 2U) { + return (ISC_R_UNEXPECTEDEND); + } + RETERR(mem_tobuffer(target, region.base, 2)); + len = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + /* + * SvcParamValue. + */ + if (region.length < len) { + return (ISC_R_UNEXPECTEDEND); + } + + /* + * Remember manatory key. + */ + if (key == SVCB_MAN_KEY) { + man = region; + man.length = len; + /* Get first mandatory key */ + if (man.length >= 2) { + mankey = uint16_fromregion(&man); + isc_region_consume(&man, 2); + if (mankey == SVCB_MAN_KEY) { + return (DNS_R_FORMERR); + } + } else { + return (DNS_R_FORMERR); + } + } + keyregion = region; + keyregion.length = len; + RETERR(svcb_validate(key, &keyregion)); + RETERR(mem_tobuffer(target, region.base, len)); + isc_region_consume(®ion, len); + isc_buffer_forward(source, len + 4); + } + + /* + * Do we have an outstanding mandatory key? + */ + if (mankey != 0) { + return (DNS_R_FORMERR); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_svcb(ARGS_FROMWIRE) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + + return (generic_fromwire_in_svcb(CALL_FROMWIRE)); +} + +static isc_result_t +generic_towire_in_svcb(ARGS_TOWIRE) { + dns_name_t name; + dns_offsets_t offsets; + isc_region_t region; + + REQUIRE(rdata->length != 0); + + dns_compress_setmethods(cctx, DNS_COMPRESS_NONE); + + /* + * SvcPriority. + */ + dns_rdata_toregion(rdata, ®ion); + RETERR(mem_tobuffer(target, region.base, 2)); + isc_region_consume(®ion, 2); + + /* + * TargetName. + */ + dns_name_init(&name, offsets); + dns_name_fromregion(&name, ®ion); + RETERR(dns_name_towire(&name, cctx, target)); + isc_region_consume(®ion, name_length(&name)); + + /* + * SvcParams. + */ + return (mem_tobuffer(target, region.base, region.length)); +} + +static isc_result_t +towire_in_svcb(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->length != 0); + + return (generic_towire_in_svcb(CALL_TOWIRE)); +} + +static int +compare_in_svcb(ARGS_COMPARE) { + isc_region_t region1; + isc_region_t region2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_svcb); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, ®ion1); + dns_rdata_toregion(rdata2, ®ion2); + + return (isc_region_compare(®ion1, ®ion2)); +} + +static isc_result_t +generic_fromstruct_in_svcb(ARGS_FROMSTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + isc_region_t region; + + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == type); + REQUIRE(svcb->common.rdclass == rdclass); + + UNUSED(type); + UNUSED(rdclass); + + RETERR(uint16_tobuffer(svcb->priority, target)); + dns_name_toregion(&svcb->svcdomain, ®ion); + RETERR(isc_buffer_copyregion(target, ®ion)); + + return (mem_tobuffer(target, svcb->svc, svcb->svclen)); +} + +static isc_result_t +fromstruct_in_svcb(ARGS_FROMSTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == type); + REQUIRE(svcb->common.rdclass == rdclass); + + return (generic_fromstruct_in_svcb(CALL_FROMSTRUCT)); +} + +static isc_result_t +generic_tostruct_in_svcb(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_in_svcb_t *svcb = target; + dns_name_t name; + + REQUIRE(svcb != NULL); + REQUIRE(rdata->length != 0); + + svcb->common.rdclass = rdata->rdclass; + svcb->common.rdtype = rdata->type; + ISC_LINK_INIT(&svcb->common, link); + + dns_rdata_toregion(rdata, ®ion); + + svcb->priority = uint16_fromregion(®ion); + isc_region_consume(®ion, 2); + + dns_name_init(&svcb->svcdomain, NULL); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + + name_duporclone(&name, mctx, &svcb->svcdomain); + svcb->svclen = region.length; + svcb->svc = mem_maybedup(mctx, region.base, region.length); + + if (svcb->svc == NULL) { + if (mctx != NULL) { + dns_name_free(&svcb->svcdomain, svcb->mctx); + } + return (ISC_R_NOMEMORY); + } + + svcb->offset = 0; + svcb->mctx = mctx; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +tostruct_in_svcb(ARGS_TOSTRUCT) { + dns_rdata_in_svcb_t *svcb = target; + + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(svcb != NULL); + REQUIRE(rdata->length != 0); + + return (generic_tostruct_in_svcb(CALL_TOSTRUCT)); +} + +static void +generic_freestruct_in_svcb(ARGS_FREESTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(svcb != NULL); + + if (svcb->mctx == NULL) { + return; + } + + dns_name_free(&svcb->svcdomain, svcb->mctx); + isc_mem_free(svcb->mctx, svcb->svc); + svcb->mctx = NULL; +} + +static void +freestruct_in_svcb(ARGS_FREESTRUCT) { + dns_rdata_in_svcb_t *svcb = source; + + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + + generic_freestruct_in_svcb(CALL_FREESTRUCT); +} + +static isc_result_t +generic_additionaldata_in_svcb(ARGS_ADDLDATA) { + bool alias, done = false; + dns_fixedname_t fixed; + dns_name_t name, *fname = NULL; + dns_offsets_t offsets; + dns_rdataset_t rdataset; + isc_region_t region; + unsigned int cnames = 0; + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + alias = uint16_fromregion(®ion) == 0; + isc_region_consume(®ion, 2); + + dns_name_fromregion(&name, ®ion); + + if (dns_name_equal(&name, dns_rootname)) { + /* + * "." only means owner name in service form. + */ + if (alias || dns_name_equal(owner, dns_rootname) || + !dns_name_ishostname(owner, false)) + { + return (ISC_R_SUCCESS); + } + /* Only lookup address records */ + return ((add)(arg, owner, dns_rdatatype_a, NULL)); + } + + /* + * Follow CNAME chains when processing HTTPS and SVCB records. + */ + dns_rdataset_init(&rdataset); + fname = dns_fixedname_initname(&fixed); + do { + RETERR((add)(arg, &name, dns_rdatatype_cname, &rdataset)); + if (dns_rdataset_isassociated(&rdataset)) { + isc_result_t result; + result = dns_rdataset_first(&rdataset); + if (result == ISC_R_SUCCESS) { + dns_rdata_t current = DNS_RDATA_INIT; + dns_rdata_cname_t cname; + + dns_rdataset_current(&rdataset, ¤t); + + result = dns_rdata_tostruct(¤t, &cname, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_copy(&cname.cname, fname); + dns_name_clone(fname, &name); + } else { + done = true; + } + dns_rdataset_disassociate(&rdataset); + } else { + done = true; + } + /* + * Stop following a potentially infinite CNAME chain. + */ + if (!done && cnames++ > MAX_CNAMES) { + return (ISC_R_SUCCESS); + } + } while (!done); + + /* + * Look up HTTPS/SVCB records when processing the alias form. + */ + if (alias) { + RETERR((add)(arg, &name, rdata->type, &rdataset)); + /* + * Don't return A or AAAA if this is not the last element + * in the HTTP / SVCB chain. + */ + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + return (ISC_R_SUCCESS); + } + } + return ((add)(arg, &name, dns_rdatatype_a, NULL)); +} + +static isc_result_t +additionaldata_in_svcb(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_additionaldata_in_svcb(CALL_ADDLDATA)); +} + +static isc_result_t +digest_in_svcb(ARGS_DIGEST) { + isc_region_t region1; + + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, ®ion1); + return ((digest)(arg, ®ion1)); +} + +static bool +checkowner_in_svcb(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_svcb); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static bool +generic_checknames_in_svcb(ARGS_CHECKNAMES) { + isc_region_t region; + dns_name_t name; + bool alias; + + UNUSED(owner); + + dns_rdata_toregion(rdata, ®ion); + INSIST(region.length > 1); + alias = uint16_fromregion(®ion) == 0; + isc_region_consume(®ion, 2); + dns_name_init(&name, NULL); + dns_name_fromregion(&name, ®ion); + if (!alias && !dns_name_ishostname(&name, false)) { + if (bad != NULL) { + dns_name_clone(&name, bad); + } + return (false); + } + return (true); +} + +static bool +checknames_in_svcb(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_svcb); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + return (generic_checknames_in_svcb(CALL_CHECKNAMES)); +} + +static int +casecompare_in_svcb(ARGS_COMPARE) { + return (compare_in_svcb(rdata1, rdata2)); +} + +static isc_result_t +generic_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) { + if (svcb->svclen == 0) { + return (ISC_R_NOMORE); + } + svcb->offset = 0; + return (ISC_R_SUCCESS); +} + +static isc_result_t +generic_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) { + isc_region_t region; + size_t len; + + if (svcb->offset >= svcb->svclen) { + return (ISC_R_NOMORE); + } + + region.base = svcb->svc + svcb->offset; + region.length = svcb->svclen - svcb->offset; + INSIST(region.length >= 4); + isc_region_consume(®ion, 2); + len = uint16_fromregion(®ion); + INSIST(region.length >= len + 2); + svcb->offset += len + 4; + return (svcb->offset >= svcb->svclen ? ISC_R_NOMORE : ISC_R_SUCCESS); +} + +static void +generic_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) { + size_t len; + + INSIST(svcb->offset <= svcb->svclen); + + region->base = svcb->svc + svcb->offset; + region->length = svcb->svclen - svcb->offset; + INSIST(region->length >= 4); + isc_region_consume(region, 2); + len = uint16_fromregion(region); + INSIST(region->length >= len + 2); + region->base = svcb->svc + svcb->offset; + region->length = len + 4; +} + +isc_result_t +dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *svcb) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_first(svcb)); +} + +isc_result_t +dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *svcb) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + + return (generic_rdata_in_svcb_next(svcb)); +} + +void +dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *svcb, isc_region_t *region) { + REQUIRE(svcb != NULL); + REQUIRE(svcb->common.rdtype == dns_rdatatype_svcb); + REQUIRE(svcb->common.rdclass == dns_rdataclass_in); + REQUIRE(region != NULL); + + generic_rdata_in_svcb_current(svcb, region); +} + +#endif /* RDATA_IN_1_SVCB_64_C */ diff --git a/lib/dns/rdata/in_1/svcb_64.h b/lib/dns/rdata/in_1/svcb_64.h new file mode 100644 index 0000000..c861716 --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! + * \brief Per draft-ietf-dnsop-svcb-https-02 + */ + +typedef struct dns_rdata_in_svcb { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t priority; + dns_name_t svcdomain; + unsigned char *svc; + uint16_t svclen; + uint16_t offset; +} dns_rdata_in_svcb_t; + +isc_result_t +dns_rdata_in_svcb_first(dns_rdata_in_svcb_t *); + +isc_result_t +dns_rdata_in_svcb_next(dns_rdata_in_svcb_t *); + +void +dns_rdata_in_svcb_current(dns_rdata_in_svcb_t *, isc_region_t *); diff --git a/lib/dns/rdata/in_1/wks_11.c b/lib/dns/rdata/in_1/wks_11.c new file mode 100644 index 0000000..7252065 --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef RDATA_IN_1_WKS_11_C +#define RDATA_IN_1_WKS_11_C + +#include +#include + +#include +#include +#include + +#define RRTYPE_WKS_ATTRIBUTES (0) + +static isc_mutex_t wks_lock; + +static void +init_lock(void) { + isc_mutex_init(&wks_lock); +} + +static bool +mygetprotobyname(const char *name, long *proto) { + struct protoent *pe; + + LOCK(&wks_lock); + pe = getprotobyname(name); + if (pe != NULL) { + *proto = pe->p_proto; + } + UNLOCK(&wks_lock); + return (pe != NULL); +} + +static bool +mygetservbyname(const char *name, const char *proto, long *port) { + struct servent *se; + + LOCK(&wks_lock); + se = getservbyname(name, proto); + if (se != NULL) { + *port = ntohs(se->s_port); + } + UNLOCK(&wks_lock); + return (se != NULL); +} + +static isc_result_t +fromtext_in_wks(ARGS_FROMTEXT) { + static isc_once_t once = ISC_ONCE_INIT; + isc_token_t token; + isc_region_t region; + struct in_addr addr; + char *e = NULL; + long proto; + unsigned char bm[8 * 1024]; /* 64k bits */ + long port; + long maxport = -1; + const char *ps = NULL; + unsigned int n; + char service[32]; + int i; + isc_result_t result; + + REQUIRE(type == dns_rdatatype_wks); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(origin); + UNUSED(options); + UNUSED(rdclass); + UNUSED(callbacks); + + RUNTIME_CHECK(isc_once_do(&once, init_lock) == ISC_R_SUCCESS); + + /* + * IPv4 dotted quad. + */ + CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + isc_buffer_availableregion(target, ®ion); + if (inet_pton(AF_INET, DNS_AS_STR(token), &addr) != 1) { + CHECKTOK(DNS_R_BADDOTTEDQUAD); + } + if (region.length < 4) { + return (ISC_R_NOSPACE); + } + memmove(region.base, &addr, 4); + isc_buffer_add(target, 4); + + /* + * Protocol. + */ + CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + proto = strtol(DNS_AS_STR(token), &e, 10); + if (*e != '\0' && !mygetprotobyname(DNS_AS_STR(token), &proto)) { + CHECKTOK(DNS_R_UNKNOWNPROTO); + } + + if (proto < 0 || proto > 0xff) { + CHECKTOK(ISC_R_RANGE); + } + + if (proto == IPPROTO_TCP) { + ps = "tcp"; + } else if (proto == IPPROTO_UDP) { + ps = "udp"; + } + + CHECK(uint8_tobuffer(proto, target)); + + memset(bm, 0, sizeof(bm)); + do { + CHECK(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, true)); + if (token.type != isc_tokentype_string) { + break; + } + + /* + * Lowercase the service string as some getservbyname() are + * case sensitive and the database is usually in lowercase. + */ + strlcpy(service, DNS_AS_STR(token), sizeof(service)); + for (i = strlen(service) - 1; i >= 0; i--) { + if (isupper(service[i] & 0xff)) { + service[i] = tolower(service[i] & 0xff); + } + } + + port = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && !mygetservbyname(service, ps, &port) && + !mygetservbyname(DNS_AS_STR(token), ps, &port)) + { + CHECKTOK(DNS_R_UNKNOWNSERVICE); + } + if (port < 0 || port > 0xffff) { + CHECKTOK(ISC_R_RANGE); + } + if (port > maxport) { + maxport = port; + } + bm[port / 8] |= (0x80 >> (port % 8)); + } while (1); + + /* + * Let upper layer handle eol/eof. + */ + isc_lex_ungettoken(lexer, &token); + + n = (maxport + 8) / 8; + result = mem_tobuffer(target, bm, n); + +cleanup: + return (result); +} + +static isc_result_t +totext_in_wks(ARGS_TOTEXT) { + isc_region_t sr; + unsigned short proto; + char buf[sizeof("65535")]; + unsigned int i, j; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length >= 5); + + dns_rdata_toregion(rdata, &sr); + RETERR(inet_totext(AF_INET, tctx->flags, &sr, target)); + isc_region_consume(&sr, 4); + + proto = uint8_fromregion(&sr); + snprintf(buf, sizeof(buf), "%u", proto); + RETERR(str_totext(" ", target)); + RETERR(str_totext(buf, target)); + isc_region_consume(&sr, 1); + + INSIST(sr.length <= 8 * 1024); + for (i = 0; i < sr.length; i++) { + if (sr.base[i] != 0) { + for (j = 0; j < 8; j++) { + if ((sr.base[i] & (0x80 >> j)) != 0) { + { + snprintf(buf, sizeof(buf), "%u", + i * 8 + j); + RETERR(str_totext(" ", target)); + RETERR(str_totext(buf, target)); + } + } + } + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fromwire_in_wks(ARGS_FROMWIRE) { + isc_region_t sr; + isc_region_t tr; + + REQUIRE(type == dns_rdatatype_wks); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(dctx); + UNUSED(options); + UNUSED(rdclass); + + isc_buffer_activeregion(source, &sr); + isc_buffer_availableregion(target, &tr); + + if (sr.length < 5) { + return (ISC_R_UNEXPECTEDEND); + } + if (sr.length > 8 * 1024 + 5) { + return (DNS_R_EXTRADATA); + } + if (sr.length > 5 && sr.base[sr.length - 1] == 0) { + return (DNS_R_FORMERR); + } + if (tr.length < sr.length) { + return (ISC_R_NOSPACE); + } + + memmove(tr.base, sr.base, sr.length); + isc_buffer_add(target, sr.length); + isc_buffer_forward(source, sr.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +towire_in_wks(ARGS_TOWIRE) { + isc_region_t sr; + + UNUSED(cctx); + + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + dns_rdata_toregion(rdata, &sr); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static int +compare_in_wks(ARGS_COMPARE) { + isc_region_t r1; + isc_region_t r2; + + REQUIRE(rdata1->type == rdata2->type); + REQUIRE(rdata1->rdclass == rdata2->rdclass); + REQUIRE(rdata1->type == dns_rdatatype_wks); + REQUIRE(rdata1->rdclass == dns_rdataclass_in); + REQUIRE(rdata1->length != 0); + REQUIRE(rdata2->length != 0); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static isc_result_t +fromstruct_in_wks(ARGS_FROMSTRUCT) { + dns_rdata_in_wks_t *wks = source; + uint32_t a; + + REQUIRE(type == dns_rdatatype_wks); + REQUIRE(rdclass == dns_rdataclass_in); + REQUIRE(wks != NULL); + REQUIRE(wks->common.rdtype == type); + REQUIRE(wks->common.rdclass == rdclass); + REQUIRE((wks->map != NULL && wks->map_len <= 8 * 1024) || + wks->map_len == 0); + + UNUSED(type); + UNUSED(rdclass); + + a = ntohl(wks->in_addr.s_addr); + RETERR(uint32_tobuffer(a, target)); + RETERR(uint8_tobuffer(wks->protocol, target)); + return (mem_tobuffer(target, wks->map, wks->map_len)); +} + +static isc_result_t +tostruct_in_wks(ARGS_TOSTRUCT) { + dns_rdata_in_wks_t *wks = target; + uint32_t n; + isc_region_t region; + + REQUIRE(wks != NULL); + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length != 0); + + wks->common.rdclass = rdata->rdclass; + wks->common.rdtype = rdata->type; + ISC_LINK_INIT(&wks->common, link); + + dns_rdata_toregion(rdata, ®ion); + n = uint32_fromregion(®ion); + wks->in_addr.s_addr = htonl(n); + isc_region_consume(®ion, 4); + wks->protocol = uint8_fromregion(®ion); + isc_region_consume(®ion, 1); + wks->map_len = region.length; + wks->map = mem_maybedup(mctx, region.base, region.length); + if (wks->map == NULL) { + return (ISC_R_NOMEMORY); + } + wks->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static void +freestruct_in_wks(ARGS_FREESTRUCT) { + dns_rdata_in_wks_t *wks = source; + + REQUIRE(wks != NULL); + REQUIRE(wks->common.rdtype == dns_rdatatype_wks); + REQUIRE(wks->common.rdclass == dns_rdataclass_in); + + if (wks->mctx == NULL) { + return; + } + + if (wks->map != NULL) { + isc_mem_free(wks->mctx, wks->map); + } + wks->mctx = NULL; +} + +static isc_result_t +additionaldata_in_wks(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +digest_in_wks(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static bool +checkowner_in_wks(ARGS_CHECKOWNER) { + REQUIRE(type == dns_rdatatype_wks); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + return (dns_name_ishostname(name, wildcard)); +} + +static bool +checknames_in_wks(ARGS_CHECKNAMES) { + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static int +casecompare_in_wks(ARGS_COMPARE) { + return (compare_in_wks(rdata1, rdata2)); +} + +#endif /* RDATA_IN_1_WKS_11_C */ diff --git a/lib/dns/rdata/in_1/wks_11.h b/lib/dns/rdata/in_1/wks_11.h new file mode 100644 index 0000000..8863725 --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +typedef struct dns_rdata_in_wks { + dns_rdatacommon_t common; + isc_mem_t *mctx; + struct in_addr in_addr; + uint16_t protocol; + unsigned char *map; + uint16_t map_len; +} dns_rdata_in_wks_t; diff --git a/lib/dns/rdata/rdatastructpre.h b/lib/dns/rdata/rdatastructpre.h new file mode 100644 index 0000000..4fe31b1 --- /dev/null +++ b/lib/dns/rdata/rdatastructpre.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +typedef struct dns_rdatacommon { + dns_rdataclass_t rdclass; + dns_rdatatype_t rdtype; + ISC_LINK(struct dns_rdatacommon) link; +} dns_rdatacommon_t; + +#define DNS_RDATACOMMON_INIT(_data, _rdtype, _rdclass) \ + do { \ + (_data)->common.rdtype = (_rdtype); \ + (_data)->common.rdclass = (_rdclass); \ + ISC_LINK_INIT(&(_data)->common, link); \ + } while (0) diff --git a/lib/dns/rdata/rdatastructsuf.h b/lib/dns/rdata/rdatastructsuf.h new file mode 100644 index 0000000..c3ec424 --- /dev/null +++ b/lib/dns/rdata/rdatastructsuf.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c new file mode 100644 index 0000000..a2643c7 --- /dev/null +++ b/lib/dns/rdatalist.c @@ -0,0 +1,448 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "rdatalist_p.h" + +static dns_rdatasetmethods_t methods = { + isc__rdatalist_disassociate, + isc__rdatalist_first, + isc__rdatalist_next, + isc__rdatalist_current, + isc__rdatalist_clone, + isc__rdatalist_count, + isc__rdatalist_addnoqname, + isc__rdatalist_getnoqname, + isc__rdatalist_addclosest, + isc__rdatalist_getclosest, + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + isc__rdatalist_setownercase, + isc__rdatalist_getownercase, + NULL /* addglue */ +}; + +void +dns_rdatalist_init(dns_rdatalist_t *rdatalist) { + REQUIRE(rdatalist != NULL); + + /* + * Initialize rdatalist. + */ + + rdatalist->rdclass = 0; + rdatalist->type = 0; + rdatalist->covers = 0; + rdatalist->ttl = 0; + ISC_LIST_INIT(rdatalist->rdata); + ISC_LINK_INIT(rdatalist, link); + memset(rdatalist->upper, 0xeb, sizeof(rdatalist->upper)); + /* + * Clear upper set bit. + */ + rdatalist->upper[0] &= ~0x01; +} + +isc_result_t +dns_rdatalist_tordataset(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset) { + /* + * Make 'rdataset' refer to the rdata in 'rdatalist'. + */ + + REQUIRE(rdatalist != NULL); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + /* Check if dns_rdatalist_init has was called. */ + REQUIRE(rdatalist->upper[0] == 0xea); + + rdataset->methods = &methods; + rdataset->rdclass = rdatalist->rdclass; + rdataset->type = rdatalist->type; + rdataset->covers = rdatalist->covers; + rdataset->ttl = rdatalist->ttl; + rdataset->trust = 0; + rdataset->private1 = rdatalist; + rdataset->private2 = NULL; + rdataset->private3 = NULL; + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdatalist_fromrdataset(dns_rdataset_t *rdataset, + dns_rdatalist_t **rdatalist) { + REQUIRE(rdatalist != NULL && rdataset != NULL); + *rdatalist = rdataset->private1; + + return (ISC_R_SUCCESS); +} + +void +isc__rdatalist_disassociate(dns_rdataset_t *rdataset) { + UNUSED(rdataset); +} + +isc_result_t +isc__rdatalist_first(dns_rdataset_t *rdataset) { + dns_rdatalist_t *rdatalist; + + rdatalist = rdataset->private1; + rdataset->private2 = ISC_LIST_HEAD(rdatalist->rdata); + + if (rdataset->private2 == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc__rdatalist_next(dns_rdataset_t *rdataset) { + dns_rdata_t *rdata; + + REQUIRE(rdataset != NULL); + + rdata = rdataset->private2; + if (rdata == NULL) { + return (ISC_R_NOMORE); + } + + rdataset->private2 = ISC_LIST_NEXT(rdata, link); + + if (rdataset->private2 == NULL) { + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +void +isc__rdatalist_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + dns_rdata_t *list_rdata; + + REQUIRE(rdataset != NULL); + + list_rdata = rdataset->private2; + INSIST(list_rdata != NULL); + + dns_rdata_clone(list_rdata, rdata); +} + +void +isc__rdatalist_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + REQUIRE(source != NULL); + REQUIRE(target != NULL); + + *target = *source; + + /* + * Reset iterator state. + */ + target->private2 = NULL; +} + +unsigned int +isc__rdatalist_count(dns_rdataset_t *rdataset) { + dns_rdatalist_t *rdatalist; + dns_rdata_t *rdata; + unsigned int count; + + REQUIRE(rdataset != NULL); + + rdatalist = rdataset->private1; + + count = 0; + for (rdata = ISC_LIST_HEAD(rdatalist->rdata); rdata != NULL; + rdata = ISC_LIST_NEXT(rdata, link)) + { + count++; + } + + return (count); +} + +isc_result_t +isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) { + dns_rdataset_t *neg = NULL; + dns_rdataset_t *negsig = NULL; + dns_rdataset_t *rdset; + dns_ttl_t ttl; + + REQUIRE(rdataset != NULL); + + for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { + if (rdset->rdclass != rdataset->rdclass) { + continue; + } + if (rdset->type == dns_rdatatype_nsec || + rdset->type == dns_rdatatype_nsec3) + { + neg = rdset; + } + } + if (neg == NULL) { + return (ISC_R_NOTFOUND); + } + + for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { + if (rdset->type == dns_rdatatype_rrsig && + rdset->covers == neg->type) + { + negsig = rdset; + } + } + + if (negsig == NULL) { + return (ISC_R_NOTFOUND); + } + /* + * Minimise ttl. + */ + ttl = rdataset->ttl; + if (neg->ttl < ttl) { + ttl = neg->ttl; + } + if (negsig->ttl < ttl) { + ttl = negsig->ttl; + } + rdataset->ttl = neg->ttl = negsig->ttl = ttl; + rdataset->attributes |= DNS_RDATASETATTR_NOQNAME; + rdataset->private6 = name; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc__rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig) { + dns_rdataclass_t rdclass; + dns_rdataset_t *tneg = NULL; + dns_rdataset_t *tnegsig = NULL; + const dns_name_t *noqname; + + REQUIRE(rdataset != NULL); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0); + + rdclass = rdataset->rdclass; + noqname = rdataset->private6; + + (void)dns_name_dynamic(noqname); /* Sanity Check. */ + + for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->rdclass != rdclass) { + continue; + } + if (rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3) + { + tneg = rdataset; + } + } + if (tneg == NULL) { + return (ISC_R_NOTFOUND); + } + + for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == tneg->type) + { + tnegsig = rdataset; + } + } + if (tnegsig == NULL) { + return (ISC_R_NOTFOUND); + } + + dns_name_clone(noqname, name); + dns_rdataset_clone(tneg, neg); + dns_rdataset_clone(tnegsig, negsig); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc__rdatalist_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name) { + dns_rdataset_t *neg = NULL; + dns_rdataset_t *negsig = NULL; + dns_rdataset_t *rdset; + dns_ttl_t ttl; + + REQUIRE(rdataset != NULL); + + for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { + if (rdset->rdclass != rdataset->rdclass) { + continue; + } + if (rdset->type == dns_rdatatype_nsec || + rdset->type == dns_rdatatype_nsec3) + { + neg = rdset; + } + } + if (neg == NULL) { + return (ISC_R_NOTFOUND); + } + + for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { + if (rdset->type == dns_rdatatype_rrsig && + rdset->covers == neg->type) + { + negsig = rdset; + } + } + + if (negsig == NULL) { + return (ISC_R_NOTFOUND); + } + /* + * Minimise ttl. + */ + ttl = rdataset->ttl; + if (neg->ttl < ttl) { + ttl = neg->ttl; + } + if (negsig->ttl < ttl) { + ttl = negsig->ttl; + } + rdataset->ttl = neg->ttl = negsig->ttl = ttl; + rdataset->attributes |= DNS_RDATASETATTR_CLOSEST; + rdataset->private7 = name; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc__rdatalist_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig) { + dns_rdataclass_t rdclass; + dns_rdataset_t *tneg = NULL; + dns_rdataset_t *tnegsig = NULL; + const dns_name_t *closest; + + REQUIRE(rdataset != NULL); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0); + + rdclass = rdataset->rdclass; + closest = rdataset->private7; + + (void)dns_name_dynamic(closest); /* Sanity Check. */ + + for (rdataset = ISC_LIST_HEAD(closest->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->rdclass != rdclass) { + continue; + } + if (rdataset->type == dns_rdatatype_nsec || + rdataset->type == dns_rdatatype_nsec3) + { + tneg = rdataset; + } + } + if (tneg == NULL) { + return (ISC_R_NOTFOUND); + } + + for (rdataset = ISC_LIST_HEAD(closest->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == tneg->type) + { + tnegsig = rdataset; + } + } + if (tnegsig == NULL) { + return (ISC_R_NOTFOUND); + } + + dns_name_clone(closest, name); + dns_rdataset_clone(tneg, neg); + dns_rdataset_clone(tnegsig, negsig); + return (ISC_R_SUCCESS); +} + +void +isc__rdatalist_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { + dns_rdatalist_t *rdatalist; + unsigned int i; + + /* + * We do not need to worry about label lengths as they are all + * less than or equal to 63. + */ + rdatalist = rdataset->private1; + memset(rdatalist->upper, 0, sizeof(rdatalist->upper)); + for (i = 1; i < name->length; i++) { + if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a) { + rdatalist->upper[i / 8] |= 1 << (i % 8); + /* + * Record that upper has been set. + */ + } + } + /* + * Record that upper has been set. + */ + rdatalist->upper[0] |= 0x01; +} + +void +isc__rdatalist_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) { + dns_rdatalist_t *rdatalist; + unsigned int i; + + rdatalist = rdataset->private1; + if ((rdatalist->upper[0] & 0x01) == 0) { + return; + } + for (i = 0; i < name->length; i++) { + /* + * Set the case bit if it does not match the recorded bit. + */ + if (name->ndata[i] >= 0x61 && name->ndata[i] <= 0x7a && + (rdatalist->upper[i / 8] & (1 << (i % 8))) != 0) + { + name->ndata[i] &= ~0x20; /* clear the lower case bit */ + } else if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a && + (rdatalist->upper[i / 8] & (1 << (i % 8))) == 0) + { + name->ndata[i] |= 0x20; /* set the lower case bit */ + } + } +} diff --git a/lib/dns/rdatalist_p.h b/lib/dns/rdatalist_p.h new file mode 100644 index 0000000..f27a084 --- /dev/null +++ b/lib/dns/rdatalist_p.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file */ + +#include + +#include + +ISC_LANG_BEGINDECLS + +void +isc__rdatalist_disassociate(dns_rdataset_t *rdatasetp); + +isc_result_t +isc__rdatalist_first(dns_rdataset_t *rdataset); + +isc_result_t +isc__rdatalist_next(dns_rdataset_t *rdataset); + +void +isc__rdatalist_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata); + +void +isc__rdatalist_clone(dns_rdataset_t *source, dns_rdataset_t *target); + +unsigned int +isc__rdatalist_count(dns_rdataset_t *rdataset); + +isc_result_t +isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name); + +isc_result_t +isc__rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); + +isc_result_t +isc__rdatalist_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name); + +isc_result_t +isc__rdatalist_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig); + +void +isc__rdatalist_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name); + +void +isc__rdatalist_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c new file mode 100644 index 0000000..4d48203 --- /dev/null +++ b/lib/dns/rdataset.c @@ -0,0 +1,749 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static const char *trustnames[] = { + "none", "pending-additional", + "pending-answer", "additional", + "glue", "answer", + "authauthority", "authanswer", + "secure", "local" /* aka ultimate */ +}; + +const char * +dns_trust_totext(dns_trust_t trust) { + if (trust >= sizeof(trustnames) / sizeof(*trustnames)) { + return ("bad"); + } + return (trustnames[trust]); +} + +void +dns_rdataset_init(dns_rdataset_t *rdataset) { + /* + * Make 'rdataset' a valid, disassociated rdataset. + */ + + REQUIRE(rdataset != NULL); + + rdataset->magic = DNS_RDATASET_MAGIC; + rdataset->methods = NULL; + ISC_LINK_INIT(rdataset, link); + rdataset->rdclass = 0; + rdataset->type = 0; + rdataset->ttl = 0; + rdataset->trust = 0; + rdataset->covers = 0; + rdataset->attributes = 0; + rdataset->count = DNS_RDATASET_COUNT_UNDEFINED; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + rdataset->private3 = NULL; + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + rdataset->private6 = NULL; + rdataset->private7 = NULL; + rdataset->resign = 0; +} + +void +dns_rdataset_invalidate(dns_rdataset_t *rdataset) { + /* + * Invalidate 'rdataset'. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods == NULL); + + rdataset->magic = 0; + ISC_LINK_INIT(rdataset, link); + rdataset->rdclass = 0; + rdataset->type = 0; + rdataset->ttl = 0; + rdataset->trust = 0; + rdataset->covers = 0; + rdataset->attributes = 0; + rdataset->count = DNS_RDATASET_COUNT_UNDEFINED; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + rdataset->private3 = NULL; + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; +} + +void +dns_rdataset_disassociate(dns_rdataset_t *rdataset) { + /* + * Disassociate 'rdataset' from its rdata, allowing it to be reused. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + (rdataset->methods->disassociate)(rdataset); + rdataset->methods = NULL; + ISC_LINK_INIT(rdataset, link); + rdataset->rdclass = 0; + rdataset->type = 0; + rdataset->ttl = 0; + rdataset->trust = 0; + rdataset->covers = 0; + rdataset->attributes = 0; + rdataset->count = DNS_RDATASET_COUNT_UNDEFINED; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + rdataset->private3 = NULL; + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + rdataset->private6 = NULL; +} + +bool +dns_rdataset_isassociated(dns_rdataset_t *rdataset) { + /* + * Is 'rdataset' associated? + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + if (rdataset->methods != NULL) { + return (true); + } + + return (false); +} + +static void +question_disassociate(dns_rdataset_t *rdataset) { + UNUSED(rdataset); +} + +static isc_result_t +question_cursor(dns_rdataset_t *rdataset) { + UNUSED(rdataset); + + return (ISC_R_NOMORE); +} + +static void +question_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + /* + * This routine should never be called. + */ + UNUSED(rdataset); + UNUSED(rdata); + + REQUIRE(0); +} + +static void +question_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + *target = *source; +} + +static unsigned int +question_count(dns_rdataset_t *rdataset) { + /* + * This routine should never be called. + */ + UNUSED(rdataset); + REQUIRE(0); + + return (0); +} + +static dns_rdatasetmethods_t question_methods = { + question_disassociate, + question_cursor, + question_cursor, + question_current, + question_clone, + question_count, + NULL, /* addnoqname */ + NULL, /* getnoqname */ + NULL, /* addclosest */ + NULL, /* getclosest */ + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL, /* getownercase */ + NULL /* addglue */ +}; + +void +dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass, + dns_rdatatype_t type) { + /* + * Make 'rdataset' a valid, associated, question rdataset, with a + * question class of 'rdclass' and type 'type'. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods == NULL); + + rdataset->methods = &question_methods; + rdataset->rdclass = rdclass; + rdataset->type = type; + rdataset->attributes |= DNS_RDATASETATTR_QUESTION; +} + +unsigned int +dns_rdataset_count(dns_rdataset_t *rdataset) { + /* + * Return the number of records in 'rdataset'. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + return ((rdataset->methods->count)(rdataset)); +} + +void +dns_rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + /* + * Make 'target' refer to the same rdataset as 'source'. + */ + + REQUIRE(DNS_RDATASET_VALID(source)); + REQUIRE(source->methods != NULL); + REQUIRE(DNS_RDATASET_VALID(target)); + REQUIRE(target->methods == NULL); + + (source->methods->clone)(source, target); +} + +isc_result_t +dns_rdataset_first(dns_rdataset_t *rdataset) { + /* + * Move the rdata cursor to the first rdata in the rdataset (if any). + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + return ((rdataset->methods->first)(rdataset)); +} + +isc_result_t +dns_rdataset_next(dns_rdataset_t *rdataset) { + /* + * Move the rdata cursor to the next rdata in the rdataset (if any). + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + return ((rdataset->methods->next)(rdataset)); +} + +void +dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + /* + * Make 'rdata' refer to the current rdata. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + (rdataset->methods->current)(rdataset, rdata); +} + +#define MAX_SHUFFLE 32 +#define WANT_FIXED(r) (((r)->attributes & DNS_RDATASETATTR_FIXEDORDER) != 0) +#define WANT_RANDOM(r) (((r)->attributes & DNS_RDATASETATTR_RANDOMIZE) != 0) +#define WANT_CYCLIC(r) (((r)->attributes & DNS_RDATASETATTR_CYCLIC) != 0) + +struct towire_sort { + int key; + dns_rdata_t *rdata; +}; + +static int +towire_compare(const void *av, const void *bv) { + const struct towire_sort *a = (const struct towire_sort *)av; + const struct towire_sort *b = (const struct towire_sort *)bv; + return (a->key - b->key); +} + +static void +swap_rdata(dns_rdata_t *in, unsigned int a, unsigned int b) { + dns_rdata_t rdata = in[a]; + in[a] = in[b]; + in[b] = rdata; +} + +static isc_result_t +towiresorted(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_compress_t *cctx, isc_buffer_t *target, + dns_rdatasetorderfunc_t order, const void *order_arg, bool partial, + unsigned int options, unsigned int *countp, void **state) { + isc_region_t r; + isc_result_t result; + unsigned int i, count = 0, added; + isc_buffer_t savedbuffer, rdlen, rrbuffer; + unsigned int headlen; + bool question = false; + bool shuffle = false, sort = false; + bool want_random, want_cyclic; + dns_rdata_t in_fixed[MAX_SHUFFLE]; + dns_rdata_t *in = in_fixed; + struct towire_sort out_fixed[MAX_SHUFFLE]; + struct towire_sort *out = out_fixed; + dns_fixedname_t fixed; + dns_name_t *name; + uint16_t offset; + + UNUSED(state); + + /* + * Convert 'rdataset' to wire format, compressing names as specified + * in cctx, and storing the result in 'target'. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + REQUIRE(countp != NULL); + REQUIRE(cctx != NULL && cctx->mctx != NULL); + + want_random = WANT_RANDOM(rdataset); + want_cyclic = WANT_CYCLIC(rdataset); + + if ((rdataset->attributes & DNS_RDATASETATTR_QUESTION) != 0) { + question = true; + count = 1; + result = dns_rdataset_first(rdataset); + INSIST(result == ISC_R_NOMORE); + } else if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + /* + * This is a negative caching rdataset. + */ + unsigned int ncache_opts = 0; + if ((options & DNS_RDATASETTOWIRE_OMITDNSSEC) != 0) { + ncache_opts |= DNS_NCACHETOWIRE_OMITDNSSEC; + } + return (dns_ncache_towire(rdataset, cctx, target, ncache_opts, + countp)); + } else { + count = (rdataset->methods->count)(rdataset); + result = dns_rdataset_first(rdataset); + if (result == ISC_R_NOMORE) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* + * Do we want to sort and/or shuffle this answer? + */ + if (!question && count > 1 && rdataset->type != dns_rdatatype_rrsig) { + if (order != NULL) { + sort = true; + } + if (want_random || want_cyclic) { + shuffle = true; + } + } + + if ((shuffle || sort)) { + if (count > MAX_SHUFFLE) { + in = isc_mem_get(cctx->mctx, count * sizeof(*in)); + out = isc_mem_get(cctx->mctx, count * sizeof(*out)); + if (in == NULL || out == NULL) { + shuffle = sort = false; + } + } + } + + if ((shuffle || sort)) { + uint32_t seed = 0; + unsigned int j = 0; + + /* + * First we get handles to all of the rdata. + */ + i = 0; + do { + INSIST(i < count); + dns_rdata_init(&in[i]); + dns_rdataset_current(rdataset, &in[i]); + i++; + result = dns_rdataset_next(rdataset); + } while (result == ISC_R_SUCCESS); + if (result != ISC_R_NOMORE) { + goto cleanup; + } + INSIST(i == count); + + if (want_random) { + seed = isc_random32(); + } + + if (want_cyclic && + (rdataset->count != DNS_RDATASET_COUNT_UNDEFINED)) + { + j = rdataset->count % count; + } + + for (i = 0; i < count; i++) { + if (want_random) { + swap_rdata(in, j, j + seed % (count - j)); + } + + out[i].key = (sort) ? (*order)(&in[j], order_arg) : 0; + out[i].rdata = &in[j]; + if (++j == count) { + j = 0; + } + } + /* + * Sortlist order. + */ + if (sort) { + qsort(out, count, sizeof(out[0]), towire_compare); + } + } + + savedbuffer = *target; + i = 0; + added = 0; + + name = dns_fixedname_initname(&fixed); + dns_name_copy(owner_name, name); + dns_rdataset_getownercase(rdataset, name); + offset = 0xffff; + + name->attributes |= owner_name->attributes & DNS_NAMEATTR_NOCOMPRESS; + + do { + /* + * Copy out the name, type, class, ttl. + */ + + rrbuffer = *target; + dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14); + result = dns_name_towire2(name, cctx, target, &offset); + if (result != ISC_R_SUCCESS) { + goto rollback; + } + headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t); + if (!question) { + headlen += sizeof(dns_ttl_t) + 2; + } /* XXX 2 for rdata len + */ + isc_buffer_availableregion(target, &r); + if (r.length < headlen) { + result = ISC_R_NOSPACE; + goto rollback; + } + isc_buffer_putuint16(target, rdataset->type); + isc_buffer_putuint16(target, rdataset->rdclass); + if (!question) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + isc_buffer_putuint32(target, rdataset->ttl); + + /* + * Save space for rdlen. + */ + rdlen = *target; + isc_buffer_add(target, 2); + + /* + * Copy out the rdata + */ + if (shuffle || sort) { + rdata = *(out[i].rdata); + } else { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + } + result = dns_rdata_towire(&rdata, cctx, target); + if (result != ISC_R_SUCCESS) { + goto rollback; + } + INSIST((target->used >= rdlen.used + 2) && + (target->used - rdlen.used - 2 < 65536)); + isc_buffer_putuint16( + &rdlen, + (uint16_t)(target->used - rdlen.used - 2)); + added++; + } + + if (shuffle || sort) { + i++; + if (i == count) { + result = ISC_R_NOMORE; + } else { + result = ISC_R_SUCCESS; + } + } else { + result = dns_rdataset_next(rdataset); + } + } while (result == ISC_R_SUCCESS); + + if (result != ISC_R_NOMORE) { + goto rollback; + } + + *countp += count; + + result = ISC_R_SUCCESS; + goto cleanup; + +rollback: + if (partial && result == ISC_R_NOSPACE) { + INSIST(rrbuffer.used < 65536); + dns_compress_rollback(cctx, (uint16_t)rrbuffer.used); + *countp += added; + *target = rrbuffer; + goto cleanup; + } + INSIST(savedbuffer.used < 65536); + dns_compress_rollback(cctx, (uint16_t)savedbuffer.used); + *countp = 0; + *target = savedbuffer; + +cleanup: + if (out != NULL && out != out_fixed) { + isc_mem_put(cctx->mctx, out, count * sizeof(*out)); + } + if (in != NULL && in != in_fixed) { + isc_mem_put(cctx->mctx, in, count * sizeof(*in)); + } + return (result); +} + +isc_result_t +dns_rdataset_towiresorted(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, dns_compress_t *cctx, + isc_buffer_t *target, dns_rdatasetorderfunc_t order, + const void *order_arg, unsigned int options, + unsigned int *countp) { + return (towiresorted(rdataset, owner_name, cctx, target, order, + order_arg, false, options, countp, NULL)); +} + +isc_result_t +dns_rdataset_towirepartial(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, dns_compress_t *cctx, + isc_buffer_t *target, dns_rdatasetorderfunc_t order, + const void *order_arg, unsigned int options, + unsigned int *countp, void **state) { + REQUIRE(state == NULL); /* XXX remove when implemented */ + return (towiresorted(rdataset, owner_name, cctx, target, order, + order_arg, true, options, countp, state)); +} + +isc_result_t +dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name, + dns_compress_t *cctx, isc_buffer_t *target, + unsigned int options, unsigned int *countp) { + return (towiresorted(rdataset, owner_name, cctx, target, NULL, NULL, + false, options, countp, NULL)); +} + +isc_result_t +dns_rdataset_additionaldata(dns_rdataset_t *rdataset, + const dns_name_t *owner_name, + dns_additionaldatafunc_t add, void *arg) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + + /* + * For each rdata in rdataset, call 'add' for each name and type in the + * rdata which is subject to additional section processing. + */ + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_QUESTION) == 0); + + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + + do { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_additionaldata(&rdata, owner_name, add, arg); + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_next(rdataset); + } + dns_rdata_reset(&rdata); + } while (result == ISC_R_SUCCESS); + + if (result != ISC_R_NOMORE) { + return (result); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + if (rdataset->methods->addnoqname == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((rdataset->methods->addnoqname)(rdataset, name)); +} + +isc_result_t +dns_rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->getnoqname == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((rdataset->methods->getnoqname)(rdataset, name, neg, negsig)); +} + +isc_result_t +dns_rdataset_addclosest(dns_rdataset_t *rdataset, const dns_name_t *name) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + if (rdataset->methods->addclosest == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((rdataset->methods->addclosest)(rdataset, name)); +} + +isc_result_t +dns_rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns_rdataset_t *neg, dns_rdataset_t *negsig) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->getclosest == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((rdataset->methods->getclosest)(rdataset, name, neg, negsig)); +} + +void +dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->settrust != NULL) { + (rdataset->methods->settrust)(rdataset, trust); + } else { + rdataset->trust = trust; + } +} + +void +dns_rdataset_expire(dns_rdataset_t *rdataset) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->expire != NULL) { + (rdataset->methods->expire)(rdataset); + } +} + +void +dns_rdataset_clearprefetch(dns_rdataset_t *rdataset) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->clearprefetch != NULL) { + (rdataset->methods->clearprefetch)(rdataset); + } +} + +void +dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->setownercase != NULL) { + (rdataset->methods->setownercase)(rdataset, name); + } +} + +void +dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (rdataset->methods->getownercase != NULL) { + (rdataset->methods->getownercase)(rdataset, name); + } +} + +void +dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_rdata_rrsig_t *rrsig, isc_stdtime_t now, + bool acceptexpired) { + uint32_t ttl = 0; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(DNS_RDATASET_VALID(sigrdataset)); + REQUIRE(rrsig != NULL); + + /* + * If we accept expired RRsets keep them for no more than 120 seconds. + */ + if (acceptexpired && + (isc_serial_le(rrsig->timeexpire, ((now + 120) & 0xffffffff)) || + isc_serial_le(rrsig->timeexpire, now))) + { + ttl = 120; + } else if (isc_serial_ge(rrsig->timeexpire, now)) { + ttl = rrsig->timeexpire - now; + } + + ttl = ISC_MIN(ISC_MIN(rdataset->ttl, sigrdataset->ttl), + ISC_MIN(rrsig->originalttl, ttl)); + rdataset->ttl = ttl; + sigrdataset->ttl = ttl; +} + +isc_result_t +dns_rdataset_addglue(dns_rdataset_t *rdataset, dns_dbversion_t *version, + dns_message_t *msg) { + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + REQUIRE(rdataset->type == dns_rdatatype_ns); + + if (rdataset->methods->addglue == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + return ((rdataset->methods->addglue)(rdataset, version, msg)); +} diff --git a/lib/dns/rdatasetiter.c b/lib/dns/rdatasetiter.c new file mode 100644 index 0000000..8e8159f --- /dev/null +++ b/lib/dns/rdatasetiter.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include + +#include +#include + +void +dns_rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + /* + * Destroy '*iteratorp'. + */ + + REQUIRE(iteratorp != NULL); + REQUIRE(DNS_RDATASETITER_VALID(*iteratorp)); + + (*iteratorp)->methods->destroy(iteratorp); + + ENSURE(*iteratorp == NULL); +} + +isc_result_t +dns_rdatasetiter_first(dns_rdatasetiter_t *iterator) { + /* + * Move the rdataset cursor to the first rdataset at the node (if any). + */ + + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + + return (iterator->methods->first(iterator)); +} + +isc_result_t +dns_rdatasetiter_next(dns_rdatasetiter_t *iterator) { + /* + * Move the rdataset cursor to the next rdataset at the node (if any). + */ + + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + + return (iterator->methods->next(iterator)); +} + +void +dns_rdatasetiter_current(dns_rdatasetiter_t *iterator, + dns_rdataset_t *rdataset) { + /* + * Return the current rdataset. + */ + + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + iterator->methods->current(iterator, rdataset); +} diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c new file mode 100644 index 0000000..24fdaa8 --- /dev/null +++ b/lib/dns/rdataslab.c @@ -0,0 +1,1005 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include + +/* + * The rdataslab structure allows iteration to occur in both load order + * and DNSSEC order. The structure is as follows: + * + * header (reservelen bytes) + * record count (2 bytes) + * offset table (4 x record count bytes in load order) + * data records + * data length (2 bytes) + * order (2 bytes) + * meta data (1 byte for RRSIG's) + * data (data length bytes) + * + * If DNS_RDATASET_FIXED is defined to be zero (0) the format of a + * rdataslab is as follows: + * + * header (reservelen bytes) + * record count (2 bytes) + * data records + * data length (2 bytes) + * meta data (1 byte for RRSIG's) + * data (data length bytes) + * + * Offsets are from the end of the header. + * + * Load order traversal is performed by walking the offset table to find + * the start of the record (DNS_RDATASET_FIXED = 1). + * + * DNSSEC order traversal is performed by walking the data records. + * + * The order is stored with record to allow for efficient reconstruction + * of the offset table following a merge or subtraction. + * + * The iterator methods in rbtdb support both load order and DNSSEC order + * iteration. + * + * WARNING: + * rbtdb.c directly interacts with the slab's raw structures. If the + * structure changes then rbtdb.c also needs to be updated to reflect + * the changes. See the areas tagged with "RDATASLAB". + */ + +struct xrdata { + dns_rdata_t rdata; + unsigned int order; +}; + +/*% Note: the "const void *" are just to make qsort happy. */ +static int +compare_rdata(const void *p1, const void *p2) { + const struct xrdata *x1 = p1; + const struct xrdata *x2 = p2; + return (dns_rdata_compare(&x1->rdata, &x2->rdata)); +} + +#if DNS_RDATASET_FIXED +static void +fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable, + unsigned length) { + unsigned int i, j; + unsigned char *raw; + + for (i = 0, j = 0; i < length; i++) { + if (offsettable[i] == 0) { + continue; + } + + /* + * Fill in offset table. + */ + raw = &offsetbase[j * 4 + 2]; + *raw++ = (offsettable[i] & 0xff000000) >> 24; + *raw++ = (offsettable[i] & 0xff0000) >> 16; + *raw++ = (offsettable[i] & 0xff00) >> 8; + *raw = offsettable[i] & 0xff; + + /* + * Fill in table index. + */ + raw = offsetbase + offsettable[i] + 2; + *raw++ = (j & 0xff00) >> 8; + *raw = j++ & 0xff; + } +} +#endif /* if DNS_RDATASET_FIXED */ + +isc_result_t +dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, + isc_region_t *region, unsigned int reservelen) { + /* + * Use &removed as a sentinel pointer for duplicate + * rdata as rdata.data == NULL is valid. + */ + static unsigned char removed; + struct xrdata *x; + unsigned char *rawbuf; +#if DNS_RDATASET_FIXED + unsigned char *offsetbase; +#endif /* if DNS_RDATASET_FIXED */ + unsigned int buflen; + isc_result_t result; + unsigned int nitems; + unsigned int nalloc; + unsigned int i; +#if DNS_RDATASET_FIXED + unsigned int *offsettable; +#endif /* if DNS_RDATASET_FIXED */ + unsigned int length; + + buflen = reservelen + 2; + + nitems = dns_rdataset_count(rdataset); + + /* + * If there are no rdata then we can just need to allocate a header + * with zero a record count. + */ + if (nitems == 0) { + if (rdataset->type != 0) { + return (ISC_R_FAILURE); + } + rawbuf = isc_mem_get(mctx, buflen); + region->base = rawbuf; + region->length = buflen; + rawbuf += reservelen; + *rawbuf++ = 0; + *rawbuf = 0; + return (ISC_R_SUCCESS); + } + + if (nitems > 0xffff) { + return (ISC_R_NOSPACE); + } + + /* + * Remember the original number of items. + */ + nalloc = nitems; + x = isc_mem_get(mctx, nalloc * sizeof(struct xrdata)); + + /* + * Save all of the rdata members into an array. + */ + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) { + goto free_rdatas; + } + for (i = 0; i < nalloc && result == ISC_R_SUCCESS; i++) { + INSIST(result == ISC_R_SUCCESS); + dns_rdata_init(&x[i].rdata); + dns_rdataset_current(rdataset, &x[i].rdata); + INSIST(x[i].rdata.data != &removed); +#if DNS_RDATASET_FIXED + x[i].order = i; +#endif /* if DNS_RDATASET_FIXED */ + result = dns_rdataset_next(rdataset); + } + if (i != nalloc || result != ISC_R_NOMORE) { + /* + * Somehow we iterated over fewer rdatas than + * dns_rdataset_count() said there were or there + * were more items than dns_rdataset_count said + * there were. + */ + result = ISC_R_FAILURE; + goto free_rdatas; + } + + /* + * Put into DNSSEC order. + */ + if (nalloc > 1U) { + qsort(x, nalloc, sizeof(struct xrdata), compare_rdata); + } + + /* + * Remove duplicates and compute the total storage required. + * + * If an rdata is not a duplicate, accumulate the storage size + * required for the rdata. We do not store the class, type, etc, + * just the rdata, so our overhead is 2 bytes for the number of + * records, and 8 for each rdata, (length(2), offset(4) and order(2)) + * and then the rdata itself. + */ + for (i = 1; i < nalloc; i++) { + if (compare_rdata(&x[i - 1].rdata, &x[i].rdata) == 0) { + x[i - 1].rdata.data = &removed; +#if DNS_RDATASET_FIXED + /* + * Preserve the least order so A, B, A -> A, B + * after duplicate removal. + */ + if (x[i - 1].order < x[i].order) { + x[i].order = x[i - 1].order; + } +#endif /* if DNS_RDATASET_FIXED */ + nitems--; + } else { +#if DNS_RDATASET_FIXED + buflen += (8 + x[i - 1].rdata.length); +#else /* if DNS_RDATASET_FIXED */ + buflen += (2 + x[i - 1].rdata.length); +#endif /* if DNS_RDATASET_FIXED */ + /* + * Provide space to store the per RR meta data. + */ + if (rdataset->type == dns_rdatatype_rrsig) { + buflen++; + } + } + } + + /* + * Don't forget the last item! + */ +#if DNS_RDATASET_FIXED + buflen += (8 + x[i - 1].rdata.length); +#else /* if DNS_RDATASET_FIXED */ + buflen += (2 + x[i - 1].rdata.length); +#endif /* if DNS_RDATASET_FIXED */ + /* + * Provide space to store the per RR meta data. + */ + if (rdataset->type == dns_rdatatype_rrsig) { + buflen++; + } + + /* + * Ensure that singleton types are actually singletons. + */ + if (nitems > 1 && dns_rdatatype_issingleton(rdataset->type)) { + /* + * We have a singleton type, but there's more than one + * RR in the rdataset. + */ + result = DNS_R_SINGLETON; + goto free_rdatas; + } + + /* + * Allocate the memory, set up a buffer, start copying in + * data. + */ + rawbuf = isc_mem_get(mctx, buflen); + +#if DNS_RDATASET_FIXED + /* Allocate temporary offset table. */ + offsettable = isc_mem_get(mctx, nalloc * sizeof(unsigned int)); + memset(offsettable, 0, nalloc * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + region->base = rawbuf; + region->length = buflen; + + memset(rawbuf, 0, buflen); + rawbuf += reservelen; + +#if DNS_RDATASET_FIXED + offsetbase = rawbuf; +#endif /* if DNS_RDATASET_FIXED */ + + *rawbuf++ = (nitems & 0xff00) >> 8; + *rawbuf++ = (nitems & 0x00ff); + +#if DNS_RDATASET_FIXED + /* Skip load order table. Filled in later. */ + rawbuf += nitems * 4; +#endif /* if DNS_RDATASET_FIXED */ + + for (i = 0; i < nalloc; i++) { + if (x[i].rdata.data == &removed) { + continue; + } +#if DNS_RDATASET_FIXED + offsettable[x[i].order] = rawbuf - offsetbase; +#endif /* if DNS_RDATASET_FIXED */ + length = x[i].rdata.length; + if (rdataset->type == dns_rdatatype_rrsig) { + length++; + } + INSIST(length <= 0xffff); + *rawbuf++ = (length & 0xff00) >> 8; + *rawbuf++ = (length & 0x00ff); +#if DNS_RDATASET_FIXED + rawbuf += 2; /* filled in later */ +#endif /* if DNS_RDATASET_FIXED */ + /* + * Store the per RR meta data. + */ + if (rdataset->type == dns_rdatatype_rrsig) { + *rawbuf++ = (x[i].rdata.flags & DNS_RDATA_OFFLINE) + ? DNS_RDATASLAB_OFFLINE + : 0; + } + memmove(rawbuf, x[i].rdata.data, x[i].rdata.length); + rawbuf += x[i].rdata.length; + } + +#if DNS_RDATASET_FIXED + fillin_offsets(offsetbase, offsettable, nalloc); + isc_mem_put(mctx, offsettable, nalloc * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + result = ISC_R_SUCCESS; + +free_rdatas: + isc_mem_put(mctx, x, nalloc * sizeof(struct xrdata)); + return (result); +} + +unsigned int +dns_rdataslab_size(unsigned char *slab, unsigned int reservelen) { + unsigned int count, length; + unsigned char *current; + + REQUIRE(slab != NULL); + + current = slab + reservelen; + count = *current++ * 256; + count += *current++; +#if DNS_RDATASET_FIXED + current += (4 * count); +#endif /* if DNS_RDATASET_FIXED */ + while (count > 0) { + count--; + length = *current++ * 256; + length += *current++; +#if DNS_RDATASET_FIXED + current += length + 2; +#else /* if DNS_RDATASET_FIXED */ + current += length; +#endif /* if DNS_RDATASET_FIXED */ + } + + return ((unsigned int)(current - slab)); +} + +unsigned int +dns_rdataslab_rdatasize(unsigned char *slab, unsigned int reservelen) { + unsigned int count, length, rdatalen = 0; + unsigned char *current; + + REQUIRE(slab != NULL); + + current = slab + reservelen; + count = *current++ * 256; + count += *current++; +#if DNS_RDATASET_FIXED + current += (4 * count); +#endif /* if DNS_RDATASET_FIXED */ + while (count > 0) { + count--; + length = *current++ * 256; + length += *current++; + rdatalen += length; +#if DNS_RDATASET_FIXED + current += length + 2; +#else /* if DNS_RDATASET_FIXED */ + current += length; +#endif /* if DNS_RDATASET_FIXED */ + } + + return (rdatalen); +} + +unsigned int +dns_rdataslab_count(unsigned char *slab, unsigned int reservelen) { + unsigned int count; + unsigned char *current; + + REQUIRE(slab != NULL); + + current = slab + reservelen; + count = *current++ * 256; + count += *current++; + return (count); +} + +/* + * Make the dns_rdata_t 'rdata' refer to the slab item + * beginning at '*current', which is part of a slab of type + * 'type' and class 'rdclass', and advance '*current' to + * point to the next item in the slab. + */ +static void +rdata_from_slab(unsigned char **current, dns_rdataclass_t rdclass, + dns_rdatatype_t type, dns_rdata_t *rdata) { + unsigned char *tcurrent = *current; + isc_region_t region; + unsigned int length; + bool offline = false; + + length = *tcurrent++ * 256; + length += *tcurrent++; + + if (type == dns_rdatatype_rrsig) { + if ((*tcurrent & DNS_RDATASLAB_OFFLINE) != 0) { + offline = true; + } + length--; + tcurrent++; + } + region.length = length; +#if DNS_RDATASET_FIXED + tcurrent += 2; +#endif /* if DNS_RDATASET_FIXED */ + region.base = tcurrent; + tcurrent += region.length; + dns_rdata_fromregion(rdata, rdclass, type, ®ion); + if (offline) { + rdata->flags |= DNS_RDATA_OFFLINE; + } + *current = tcurrent; +} + +/* + * Return true iff 'slab' (slab data of type 'type' and class 'rdclass') + * contains an rdata identical to 'rdata'. This does case insensitive + * comparisons per DNSSEC. + */ +static bool +rdata_in_slab(unsigned char *slab, unsigned int reservelen, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + dns_rdata_t *rdata) { + unsigned int count, i; + unsigned char *current; + dns_rdata_t trdata = DNS_RDATA_INIT; + int n; + + current = slab + reservelen; + count = *current++ * 256; + count += *current++; + +#if DNS_RDATASET_FIXED + current += (4 * count); +#endif /* if DNS_RDATASET_FIXED */ + + for (i = 0; i < count; i++) { + rdata_from_slab(¤t, rdclass, type, &trdata); + + n = dns_rdata_compare(&trdata, rdata); + if (n == 0) { + return (true); + } + if (n > 0) { /* In DNSSEC order. */ + break; + } + dns_rdata_reset(&trdata); + } + return (false); +} + +isc_result_t +dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, + unsigned int reservelen, isc_mem_t *mctx, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int flags, unsigned char **tslabp) { + unsigned char *ocurrent, *ostart, *ncurrent, *tstart, *tcurrent, *data; + unsigned int ocount, ncount, count, olength, tlength, tcount, length; + dns_rdata_t ordata = DNS_RDATA_INIT; + dns_rdata_t nrdata = DNS_RDATA_INIT; + bool added_something = false; + unsigned int oadded = 0; + unsigned int nadded = 0; + unsigned int nncount = 0; +#if DNS_RDATASET_FIXED + unsigned int oncount; + unsigned int norder = 0; + unsigned int oorder = 0; + unsigned char *offsetbase; + unsigned int *offsettable; +#endif /* if DNS_RDATASET_FIXED */ + + /* + * XXX Need parameter to allow "delete rdatasets in nslab" merge, + * or perhaps another merge routine for this purpose. + */ + + REQUIRE(tslabp != NULL && *tslabp == NULL); + REQUIRE(oslab != NULL && nslab != NULL); + + ocurrent = oslab + reservelen; + ocount = *ocurrent++ * 256; + ocount += *ocurrent++; +#if DNS_RDATASET_FIXED + ocurrent += (4 * ocount); +#endif /* if DNS_RDATASET_FIXED */ + ostart = ocurrent; + ncurrent = nslab + reservelen; + ncount = *ncurrent++ * 256; + ncount += *ncurrent++; +#if DNS_RDATASET_FIXED + ncurrent += (4 * ncount); +#endif /* if DNS_RDATASET_FIXED */ + INSIST(ocount > 0 && ncount > 0); + +#if DNS_RDATASET_FIXED + oncount = ncount; +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Yes, this is inefficient! + */ + + /* + * Figure out the length of the old slab's data. + */ + olength = 0; + for (count = 0; count < ocount; count++) { + length = *ocurrent++ * 256; + length += *ocurrent++; +#if DNS_RDATASET_FIXED + olength += length + 8; + ocurrent += length + 2; +#else /* if DNS_RDATASET_FIXED */ + olength += length + 2; + ocurrent += length; +#endif /* if DNS_RDATASET_FIXED */ + } + + /* + * Start figuring out the target length and count. + */ + tlength = reservelen + 2 + olength; + tcount = ocount; + + /* + * Add in the length of rdata in the new slab that aren't in + * the old slab. + */ + do { + dns_rdata_init(&nrdata); + rdata_from_slab(&ncurrent, rdclass, type, &nrdata); + if (!rdata_in_slab(oslab, reservelen, rdclass, type, &nrdata)) { + /* + * This rdata isn't in the old slab. + */ +#if DNS_RDATASET_FIXED + tlength += nrdata.length + 8; +#else /* if DNS_RDATASET_FIXED */ + tlength += nrdata.length + 2; +#endif /* if DNS_RDATASET_FIXED */ + if (type == dns_rdatatype_rrsig) { + tlength++; + } + tcount++; + nncount++; + added_something = true; + } + ncount--; + } while (ncount > 0); + ncount = nncount; + + if (((flags & DNS_RDATASLAB_EXACT) != 0) && (tcount != ncount + ocount)) + { + return (DNS_R_NOTEXACT); + } + + if (!added_something && (flags & DNS_RDATASLAB_FORCE) == 0) { + return (DNS_R_UNCHANGED); + } + + /* + * Ensure that singleton types are actually singletons. + */ + if (tcount > 1 && dns_rdatatype_issingleton(type)) { + /* + * We have a singleton type, but there's more than one + * RR in the rdataset. + */ + return (DNS_R_SINGLETON); + } + + if (tcount > 0xffff) { + return (ISC_R_NOSPACE); + } + + /* + * Copy the reserved area from the new slab. + */ + tstart = isc_mem_get(mctx, tlength); + memmove(tstart, nslab, reservelen); + tcurrent = tstart + reservelen; +#if DNS_RDATASET_FIXED + offsetbase = tcurrent; +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Write the new count. + */ + *tcurrent++ = (tcount & 0xff00) >> 8; + *tcurrent++ = (tcount & 0x00ff); + +#if DNS_RDATASET_FIXED + /* + * Skip offset table. + */ + tcurrent += (tcount * 4); + + offsettable = isc_mem_get(mctx, + (ocount + oncount) * sizeof(unsigned int)); + memset(offsettable, 0, (ocount + oncount) * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Merge the two slabs. + */ + ocurrent = ostart; + INSIST(ocount != 0); +#if DNS_RDATASET_FIXED + oorder = ocurrent[2] * 256 + ocurrent[3]; + INSIST(oorder < ocount); +#endif /* if DNS_RDATASET_FIXED */ + rdata_from_slab(&ocurrent, rdclass, type, &ordata); + + ncurrent = nslab + reservelen + 2; +#if DNS_RDATASET_FIXED + ncurrent += (4 * oncount); +#endif /* if DNS_RDATASET_FIXED */ + + if (ncount > 0) { + do { + dns_rdata_reset(&nrdata); +#if DNS_RDATASET_FIXED + norder = ncurrent[2] * 256 + ncurrent[3]; + + INSIST(norder < oncount); +#endif /* if DNS_RDATASET_FIXED */ + rdata_from_slab(&ncurrent, rdclass, type, &nrdata); + } while (rdata_in_slab(oslab, reservelen, rdclass, type, + &nrdata)); + } + + while (oadded < ocount || nadded < ncount) { + bool fromold; + if (oadded == ocount) { + fromold = false; + } else if (nadded == ncount) { + fromold = true; + } else { + fromold = (dns_rdata_compare(&ordata, &nrdata) < 0); + } + if (fromold) { +#if DNS_RDATASET_FIXED + offsettable[oorder] = tcurrent - offsetbase; +#endif /* if DNS_RDATASET_FIXED */ + length = ordata.length; + data = ordata.data; + if (type == dns_rdatatype_rrsig) { + length++; + data--; + } + *tcurrent++ = (length & 0xff00) >> 8; + *tcurrent++ = (length & 0x00ff); +#if DNS_RDATASET_FIXED + tcurrent += 2; /* fill in later */ +#endif /* if DNS_RDATASET_FIXED */ + memmove(tcurrent, data, length); + tcurrent += length; + oadded++; + if (oadded < ocount) { + dns_rdata_reset(&ordata); +#if DNS_RDATASET_FIXED + oorder = ocurrent[2] * 256 + ocurrent[3]; + INSIST(oorder < ocount); +#endif /* if DNS_RDATASET_FIXED */ + rdata_from_slab(&ocurrent, rdclass, type, + &ordata); + } + } else { +#if DNS_RDATASET_FIXED + offsettable[ocount + norder] = tcurrent - offsetbase; +#endif /* if DNS_RDATASET_FIXED */ + length = nrdata.length; + data = nrdata.data; + if (type == dns_rdatatype_rrsig) { + length++; + data--; + } + *tcurrent++ = (length & 0xff00) >> 8; + *tcurrent++ = (length & 0x00ff); +#if DNS_RDATASET_FIXED + tcurrent += 2; /* fill in later */ +#endif /* if DNS_RDATASET_FIXED */ + memmove(tcurrent, data, length); + tcurrent += length; + nadded++; + if (nadded < ncount) { + do { + dns_rdata_reset(&nrdata); +#if DNS_RDATASET_FIXED + norder = ncurrent[2] * 256 + + ncurrent[3]; + INSIST(norder < oncount); +#endif /* if DNS_RDATASET_FIXED */ + rdata_from_slab(&ncurrent, rdclass, + type, &nrdata); + } while (rdata_in_slab(oslab, reservelen, + rdclass, type, &nrdata)); + } + } + } + +#if DNS_RDATASET_FIXED + fillin_offsets(offsetbase, offsettable, ocount + oncount); + + isc_mem_put(mctx, offsettable, + (ocount + oncount) * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + INSIST(tcurrent == tstart + tlength); + + *tslabp = tstart; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_rdataslab_subtract(unsigned char *mslab, unsigned char *sslab, + unsigned int reservelen, isc_mem_t *mctx, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int flags, unsigned char **tslabp) { + unsigned char *mcurrent, *sstart, *scurrent, *tstart, *tcurrent; + unsigned int mcount, scount, rcount, count, tlength, tcount, i; + dns_rdata_t srdata = DNS_RDATA_INIT; + dns_rdata_t mrdata = DNS_RDATA_INIT; +#if DNS_RDATASET_FIXED + unsigned char *offsetbase; + unsigned int *offsettable; + unsigned int order; +#endif /* if DNS_RDATASET_FIXED */ + + REQUIRE(tslabp != NULL && *tslabp == NULL); + REQUIRE(mslab != NULL && sslab != NULL); + + mcurrent = mslab + reservelen; + mcount = *mcurrent++ * 256; + mcount += *mcurrent++; + scurrent = sslab + reservelen; + scount = *scurrent++ * 256; + scount += *scurrent++; + INSIST(mcount > 0 && scount > 0); + + /* + * Yes, this is inefficient! + */ + + /* + * Start figuring out the target length and count. + */ + tlength = reservelen + 2; + tcount = 0; + rcount = 0; + +#if DNS_RDATASET_FIXED + mcurrent += 4 * mcount; + scurrent += 4 * scount; +#endif /* if DNS_RDATASET_FIXED */ + sstart = scurrent; + + /* + * Add in the length of rdata in the mslab that aren't in + * the sslab. + */ + for (i = 0; i < mcount; i++) { + unsigned char *mrdatabegin = mcurrent; + rdata_from_slab(&mcurrent, rdclass, type, &mrdata); + scurrent = sstart; + for (count = 0; count < scount; count++) { + dns_rdata_reset(&srdata); + rdata_from_slab(&scurrent, rdclass, type, &srdata); + if (dns_rdata_compare(&mrdata, &srdata) == 0) { + break; + } + } + if (count == scount) { + /* + * This rdata isn't in the sslab, and thus isn't + * being subtracted. + */ + tlength += (unsigned int)(mcurrent - mrdatabegin); + tcount++; + } else { + rcount++; + } + dns_rdata_reset(&mrdata); + } + +#if DNS_RDATASET_FIXED + tlength += (4 * tcount); +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Check that all the records originally existed. The numeric + * check only works as rdataslabs do not contain duplicates. + */ + if (((flags & DNS_RDATASLAB_EXACT) != 0) && (rcount != scount)) { + return (DNS_R_NOTEXACT); + } + + /* + * Don't continue if the new rdataslab would be empty. + */ + if (tcount == 0) { + return (DNS_R_NXRRSET); + } + + /* + * If nothing is going to change, we can stop. + */ + if (rcount == 0) { + return (DNS_R_UNCHANGED); + } + + /* + * Copy the reserved area from the mslab. + */ + tstart = isc_mem_get(mctx, tlength); + memmove(tstart, mslab, reservelen); + tcurrent = tstart + reservelen; +#if DNS_RDATASET_FIXED + offsetbase = tcurrent; + + offsettable = isc_mem_get(mctx, mcount * sizeof(unsigned int)); + memset(offsettable, 0, mcount * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Write the new count. + */ + *tcurrent++ = (tcount & 0xff00) >> 8; + *tcurrent++ = (tcount & 0x00ff); + +#if DNS_RDATASET_FIXED + tcurrent += (4 * tcount); +#endif /* if DNS_RDATASET_FIXED */ + + /* + * Copy the parts of mslab not in sslab. + */ + mcurrent = mslab + reservelen; + mcount = *mcurrent++ * 256; + mcount += *mcurrent++; +#if DNS_RDATASET_FIXED + mcurrent += (4 * mcount); +#endif /* if DNS_RDATASET_FIXED */ + for (i = 0; i < mcount; i++) { + unsigned char *mrdatabegin = mcurrent; +#if DNS_RDATASET_FIXED + order = mcurrent[2] * 256 + mcurrent[3]; + INSIST(order < mcount); +#endif /* if DNS_RDATASET_FIXED */ + rdata_from_slab(&mcurrent, rdclass, type, &mrdata); + scurrent = sstart; + for (count = 0; count < scount; count++) { + dns_rdata_reset(&srdata); + rdata_from_slab(&scurrent, rdclass, type, &srdata); + if (dns_rdata_compare(&mrdata, &srdata) == 0) { + break; + } + } + if (count == scount) { + /* + * This rdata isn't in the sslab, and thus should be + * copied to the tslab. + */ + unsigned int length; + length = (unsigned int)(mcurrent - mrdatabegin); +#if DNS_RDATASET_FIXED + offsettable[order] = tcurrent - offsetbase; +#endif /* if DNS_RDATASET_FIXED */ + memmove(tcurrent, mrdatabegin, length); + tcurrent += length; + } + dns_rdata_reset(&mrdata); + } + +#if DNS_RDATASET_FIXED + fillin_offsets(offsetbase, offsettable, mcount); + + isc_mem_put(mctx, offsettable, mcount * sizeof(unsigned int)); +#endif /* if DNS_RDATASET_FIXED */ + + INSIST(tcurrent == tstart + tlength); + + *tslabp = tstart; + + return (ISC_R_SUCCESS); +} + +bool +dns_rdataslab_equal(unsigned char *slab1, unsigned char *slab2, + unsigned int reservelen) { + unsigned char *current1, *current2; + unsigned int count1, count2; + unsigned int length1, length2; + + current1 = slab1 + reservelen; + count1 = *current1++ * 256; + count1 += *current1++; + + current2 = slab2 + reservelen; + count2 = *current2++ * 256; + count2 += *current2++; + + if (count1 != count2) { + return (false); + } + +#if DNS_RDATASET_FIXED + current1 += (4 * count1); + current2 += (4 * count2); +#endif /* if DNS_RDATASET_FIXED */ + + while (count1 > 0) { + length1 = *current1++ * 256; + length1 += *current1++; + + length2 = *current2++ * 256; + length2 += *current2++; + +#if DNS_RDATASET_FIXED + current1 += 2; + current2 += 2; +#endif /* if DNS_RDATASET_FIXED */ + + if (length1 != length2 || + memcmp(current1, current2, length1) != 0) + { + return (false); + } + + current1 += length1; + current2 += length1; + + count1--; + } + return (true); +} + +bool +dns_rdataslab_equalx(unsigned char *slab1, unsigned char *slab2, + unsigned int reservelen, dns_rdataclass_t rdclass, + dns_rdatatype_t type) { + unsigned char *current1, *current2; + unsigned int count1, count2; + dns_rdata_t rdata1 = DNS_RDATA_INIT; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + + current1 = slab1 + reservelen; + count1 = *current1++ * 256; + count1 += *current1++; + + current2 = slab2 + reservelen; + count2 = *current2++ * 256; + count2 += *current2++; + + if (count1 != count2) { + return (false); + } + +#if DNS_RDATASET_FIXED + current1 += (4 * count1); + current2 += (4 * count2); +#endif /* if DNS_RDATASET_FIXED */ + + while (count1-- > 0) { + rdata_from_slab(¤t1, rdclass, type, &rdata1); + rdata_from_slab(¤t2, rdclass, type, &rdata2); + if (dns_rdata_compare(&rdata1, &rdata2) != 0) { + return (false); + } + dns_rdata_reset(&rdata1); + dns_rdata_reset(&rdata2); + } + return (true); +} diff --git a/lib/dns/request.c b/lib/dns/request.c new file mode 100644 index 0000000..b2b7b11 --- /dev/null +++ b/lib/dns/request.c @@ -0,0 +1,1216 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REQUESTMGR_MAGIC ISC_MAGIC('R', 'q', 'u', 'M') +#define VALID_REQUESTMGR(mgr) ISC_MAGIC_VALID(mgr, REQUESTMGR_MAGIC) + +#define REQUEST_MAGIC ISC_MAGIC('R', 'q', 'u', '!') +#define VALID_REQUEST(request) ISC_MAGIC_VALID(request, REQUEST_MAGIC) + +typedef ISC_LIST(dns_request_t) dns_requestlist_t; + +#define DNS_REQUEST_NLOCKS 7 + +struct dns_requestmgr { + unsigned int magic; + isc_refcount_t references; + + isc_mutex_t lock; + isc_mem_t *mctx; + + /* locked */ + isc_taskmgr_t *taskmgr; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatchv4; + dns_dispatch_t *dispatchv6; + atomic_bool exiting; + isc_eventlist_t whenshutdown; + unsigned int hash; + isc_mutex_t locks[DNS_REQUEST_NLOCKS]; + dns_requestlist_t requests; +}; + +struct dns_request { + unsigned int magic; + isc_refcount_t references; + + unsigned int hash; + isc_mem_t *mctx; + int32_t flags; + ISC_LINK(dns_request_t) link; + isc_buffer_t *query; + isc_buffer_t *answer; + dns_requestevent_t *event; + dns_dispatch_t *dispatch; + dns_dispentry_t *dispentry; + dns_requestmgr_t *requestmgr; + isc_buffer_t *tsig; + dns_tsigkey_t *tsigkey; + isc_sockaddr_t destaddr; + unsigned int timeout; + unsigned int udpcount; +}; + +#define DNS_REQUEST_F_CONNECTING 0x0001 +#define DNS_REQUEST_F_SENDING 0x0002 +#define DNS_REQUEST_F_CANCELED 0x0004 +#define DNS_REQUEST_F_TCP 0x0010 + +#define DNS_REQUEST_CANCELED(r) (((r)->flags & DNS_REQUEST_F_CANCELED) != 0) +#define DNS_REQUEST_CONNECTING(r) (((r)->flags & DNS_REQUEST_F_CONNECTING) != 0) +#define DNS_REQUEST_SENDING(r) (((r)->flags & DNS_REQUEST_F_SENDING) != 0) + +/*** + *** Forward + ***/ + +static void +mgr_destroy(dns_requestmgr_t *requestmgr); +static unsigned int +mgr_gethash(dns_requestmgr_t *requestmgr); +static void +send_shutdown_events(dns_requestmgr_t *requestmgr); + +static isc_result_t +req_render(dns_message_t *message, isc_buffer_t **buffer, unsigned int options, + isc_mem_t *mctx); +static void +req_response(isc_result_t result, isc_region_t *region, void *arg); +static void +req_senddone(isc_result_t eresult, isc_region_t *region, void *arg); +static void +req_sendevent(dns_request_t *request, isc_result_t result); +static void +req_connected(isc_result_t eresult, isc_region_t *region, void *arg); +static void +req_attach(dns_request_t *source, dns_request_t **targetp); +static void +req_detach(dns_request_t **requestp); +static void +req_destroy(dns_request_t *request); +static void +req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); +void +request_cancel(dns_request_t *request); + +/*** + *** Public + ***/ + +isc_result_t +dns_requestmgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, + dns_requestmgr_t **requestmgrp) { + dns_requestmgr_t *requestmgr; + int i; + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create"); + + REQUIRE(requestmgrp != NULL && *requestmgrp == NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(dispatchmgr != NULL); + + requestmgr = isc_mem_get(mctx, sizeof(*requestmgr)); + *requestmgr = (dns_requestmgr_t){ 0 }; + + isc_taskmgr_attach(taskmgr, &requestmgr->taskmgr); + dns_dispatchmgr_attach(dispatchmgr, &requestmgr->dispatchmgr); + isc_mutex_init(&requestmgr->lock); + + for (i = 0; i < DNS_REQUEST_NLOCKS; i++) { + isc_mutex_init(&requestmgr->locks[i]); + } + if (dispatchv4 != NULL) { + dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4); + } + if (dispatchv6 != NULL) { + dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6); + } + isc_mem_attach(mctx, &requestmgr->mctx); + + isc_refcount_init(&requestmgr->references, 1); + + ISC_LIST_INIT(requestmgr->whenshutdown); + ISC_LIST_INIT(requestmgr->requests); + + atomic_init(&requestmgr->exiting, false); + + requestmgr->magic = REQUESTMGR_MAGIC; + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create: %p", requestmgr); + + *requestmgrp = requestmgr; + return (ISC_R_SUCCESS); +} + +void +dns_requestmgr_whenshutdown(dns_requestmgr_t *requestmgr, isc_task_t *task, + isc_event_t **eventp) { + isc_task_t *tclone; + isc_event_t *event; + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_whenshutdown"); + + REQUIRE(VALID_REQUESTMGR(requestmgr)); + REQUIRE(eventp != NULL); + + event = *eventp; + *eventp = NULL; + + LOCK(&requestmgr->lock); + + if (atomic_load_acquire(&requestmgr->exiting)) { + /* + * We're already shutdown. Send the event. + */ + event->ev_sender = requestmgr; + isc_task_send(task, &event); + } else { + tclone = NULL; + isc_task_attach(task, &tclone); + event->ev_sender = tclone; + ISC_LIST_APPEND(requestmgr->whenshutdown, event, ev_link); + } + UNLOCK(&requestmgr->lock); +} + +void +dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) { + dns_request_t *request; + + REQUIRE(VALID_REQUESTMGR(requestmgr)); + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_shutdown: %p", requestmgr); + + if (!atomic_compare_exchange_strong(&requestmgr->exiting, + &(bool){ false }, true)) + { + return; + } + + LOCK(&requestmgr->lock); + for (request = ISC_LIST_HEAD(requestmgr->requests); request != NULL; + request = ISC_LIST_NEXT(request, link)) + { + dns_request_cancel(request); + } + + if (ISC_LIST_EMPTY(requestmgr->requests)) { + send_shutdown_events(requestmgr); + } + + UNLOCK(&requestmgr->lock); +} + +void +dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) { + uint_fast32_t ref; + + REQUIRE(VALID_REQUESTMGR(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + REQUIRE(!atomic_load_acquire(&source->exiting)); + + ref = isc_refcount_increment(&source->references); + + req_log(ISC_LOG_DEBUG(3), + "dns_requestmgr_attach: %p: references = %" PRIuFAST32, source, + ref + 1); + + *targetp = source; +} + +void +dns_requestmgr_detach(dns_requestmgr_t **requestmgrp) { + dns_requestmgr_t *requestmgr = NULL; + uint_fast32_t ref; + + REQUIRE(requestmgrp != NULL && VALID_REQUESTMGR(*requestmgrp)); + + requestmgr = *requestmgrp; + *requestmgrp = NULL; + + ref = isc_refcount_decrement(&requestmgr->references); + + req_log(ISC_LOG_DEBUG(3), + "dns_requestmgr_detach: %p: references = %" PRIuFAST32, + requestmgr, ref - 1); + + if (ref == 1) { + INSIST(ISC_LIST_EMPTY(requestmgr->requests)); + mgr_destroy(requestmgr); + } +} + +/* FIXME */ +static void +send_shutdown_events(dns_requestmgr_t *requestmgr) { + isc_event_t *event, *next_event; + isc_task_t *etask; + + req_log(ISC_LOG_DEBUG(3), "send_shutdown_events: %p", requestmgr); + + /* + * Caller must be holding the manager lock. + */ + for (event = ISC_LIST_HEAD(requestmgr->whenshutdown); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(requestmgr->whenshutdown, event, ev_link); + etask = event->ev_sender; + event->ev_sender = requestmgr; + isc_task_sendanddetach(&etask, &event); + } +} + +static void +mgr_destroy(dns_requestmgr_t *requestmgr) { + int i; + + req_log(ISC_LOG_DEBUG(3), "mgr_destroy"); + + isc_refcount_destroy(&requestmgr->references); + + isc_mutex_destroy(&requestmgr->lock); + for (i = 0; i < DNS_REQUEST_NLOCKS; i++) { + isc_mutex_destroy(&requestmgr->locks[i]); + } + if (requestmgr->dispatchv4 != NULL) { + dns_dispatch_detach(&requestmgr->dispatchv4); + } + if (requestmgr->dispatchv6 != NULL) { + dns_dispatch_detach(&requestmgr->dispatchv6); + } + if (requestmgr->dispatchmgr != NULL) { + dns_dispatchmgr_detach(&requestmgr->dispatchmgr); + } + if (requestmgr->taskmgr != NULL) { + isc_taskmgr_detach(&requestmgr->taskmgr); + } + requestmgr->magic = 0; + isc_mem_putanddetach(&requestmgr->mctx, requestmgr, + sizeof(*requestmgr)); +} + +static unsigned int +mgr_gethash(dns_requestmgr_t *requestmgr) { + req_log(ISC_LOG_DEBUG(3), "mgr_gethash"); + /* + * Locked by caller. + */ + requestmgr->hash++; + return (requestmgr->hash % DNS_REQUEST_NLOCKS); +} + +static void +req_send(dns_request_t *request) { + isc_region_t r; + + req_log(ISC_LOG_DEBUG(3), "req_send: request %p", request); + + REQUIRE(VALID_REQUEST(request)); + + isc_buffer_usedregion(request->query, &r); + + request->flags |= DNS_REQUEST_F_SENDING; + + /* detached in req_senddone() */ + req_attach(request, &(dns_request_t *){ NULL }); + dns_dispatch_send(request->dispentry, &r); +} + +static isc_result_t +new_request(isc_mem_t *mctx, dns_request_t **requestp) { + dns_request_t *request = NULL; + + request = isc_mem_get(mctx, sizeof(*request)); + *request = (dns_request_t){ 0 }; + ISC_LINK_INIT(request, link); + + isc_refcount_init(&request->references, 1); + isc_mem_attach(mctx, &request->mctx); + + request->magic = REQUEST_MAGIC; + *requestp = request; + return (ISC_R_SUCCESS); +} + +static bool +isblackholed(dns_dispatchmgr_t *dispatchmgr, const isc_sockaddr_t *destaddr) { + dns_acl_t *blackhole; + isc_netaddr_t netaddr; + char netaddrstr[ISC_NETADDR_FORMATSIZE]; + int match; + isc_result_t result; + + blackhole = dns_dispatchmgr_getblackhole(dispatchmgr); + if (blackhole == NULL) { + return (false); + } + + isc_netaddr_fromsockaddr(&netaddr, destaddr); + result = dns_acl_match(&netaddr, NULL, blackhole, NULL, &match, NULL); + if (result != ISC_R_SUCCESS || match <= 0) { + return (false); + } + + isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr)); + req_log(ISC_LOG_DEBUG(10), "blackholed address %s", netaddrstr); + + return (true); +} + +static isc_result_t +tcp_dispatch(bool newtcp, dns_requestmgr_t *requestmgr, + const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr, + dns_dispatch_t **dispatchp) { + isc_result_t result; + + if (!newtcp) { + result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr, + srcaddr, dispatchp); + if (result == ISC_R_SUCCESS) { + char peer[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(destaddr, peer, sizeof(peer)); + req_log(ISC_LOG_DEBUG(1), + "attached to TCP connection to %s", peer); + return (result); + } + } + + result = dns_dispatch_createtcp(requestmgr->dispatchmgr, srcaddr, + destaddr, dispatchp); + return (result); +} + +static isc_result_t +udp_dispatch(dns_requestmgr_t *requestmgr, const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, dns_dispatch_t **dispatchp) { + dns_dispatch_t *disp = NULL; + + if (srcaddr == NULL) { + switch (isc_sockaddr_pf(destaddr)) { + case PF_INET: + disp = requestmgr->dispatchv4; + break; + + case PF_INET6: + disp = requestmgr->dispatchv6; + break; + + default: + return (ISC_R_NOTIMPLEMENTED); + } + if (disp == NULL) { + return (ISC_R_FAMILYNOSUPPORT); + } + dns_dispatch_attach(disp, dispatchp); + return (ISC_R_SUCCESS); + } + + return (dns_dispatch_createudp(requestmgr->dispatchmgr, srcaddr, + dispatchp)); +} + +static isc_result_t +get_dispatch(bool tcp, bool newtcp, dns_requestmgr_t *requestmgr, + const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr, + dns_dispatch_t **dispatchp) { + isc_result_t result; + + if (tcp) { + result = tcp_dispatch(newtcp, requestmgr, srcaddr, destaddr, + dispatchp); + } else { + result = udp_dispatch(requestmgr, srcaddr, destaddr, dispatchp); + } + return (result); +} + +isc_result_t +dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, unsigned int options, + unsigned int timeout, unsigned int udptimeout, + unsigned int udpretries, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp) { + dns_request_t *request = NULL; + isc_result_t result; + isc_mem_t *mctx = NULL; + dns_messageid_t id; + bool tcp = false; + bool newtcp = false; + isc_region_t r; + unsigned int dispopt = 0; + + REQUIRE(VALID_REQUESTMGR(requestmgr)); + REQUIRE(msgbuf != NULL); + REQUIRE(destaddr != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + REQUIRE(requestp != NULL && *requestp == NULL); + REQUIRE(timeout > 0); + REQUIRE(udpretries != UINT_MAX); + + if (srcaddr != NULL) { + REQUIRE(isc_sockaddr_pf(srcaddr) == isc_sockaddr_pf(destaddr)); + } + + mctx = requestmgr->mctx; + + req_log(ISC_LOG_DEBUG(3), "dns_request_createraw"); + + if (atomic_load_acquire(&requestmgr->exiting)) { + return (ISC_R_SHUTTINGDOWN); + } + + if (isblackholed(requestmgr->dispatchmgr, destaddr)) { + return (DNS_R_BLACKHOLED); + } + + /* detached in dns_request_destroy() */ + result = new_request(mctx, &request); + if (result != ISC_R_SUCCESS) { + return (result); + } + + request->udpcount = udpretries + 1; + + request->event = (dns_requestevent_t *)isc_event_allocate( + mctx, task, DNS_EVENT_REQUESTDONE, action, arg, + sizeof(dns_requestevent_t)); + isc_task_attach(task, &(isc_task_t *){ NULL }); + request->event->ev_sender = task; + request->event->request = request; + request->event->result = ISC_R_FAILURE; + + isc_buffer_usedregion(msgbuf, &r); + if (r.length < DNS_MESSAGE_HEADERLEN || r.length > 65535) { + result = DNS_R_FORMERR; + goto cleanup; + } + + if ((options & DNS_REQUESTOPT_TCP) != 0 || r.length > 512) { + tcp = true; + request->timeout = timeout * 1000; + } else { + if (udptimeout == 0) { + udptimeout = timeout / request->udpcount; + } + if (udptimeout == 0) { + udptimeout = 1; + } + request->timeout = udptimeout * 1000; + } + + isc_buffer_allocate(mctx, &request->query, r.length + (tcp ? 2 : 0)); + result = isc_buffer_copyregion(request->query, &r); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* detached in req_connected() */ + req_attach(request, &(dns_request_t *){ NULL }); + +again: + + result = get_dispatch(tcp, newtcp, requestmgr, srcaddr, destaddr, + &request->dispatch); + if (result != ISC_R_SUCCESS) { + goto detach; + } + + if ((options & DNS_REQUESTOPT_FIXEDID) != 0) { + id = (r.base[0] << 8) | r.base[1]; + dispopt |= DNS_DISPATCHOPT_FIXEDID; + } + + result = dns_dispatch_add(request->dispatch, dispopt, request->timeout, + destaddr, req_connected, req_senddone, + req_response, request, &id, + &request->dispentry); + if (result != ISC_R_SUCCESS) { + if ((options & DNS_REQUESTOPT_FIXEDID) != 0 && !newtcp) { + newtcp = true; + dns_dispatch_detach(&request->dispatch); + goto again; + } + + goto detach; + } + + /* Add message ID. */ + isc_buffer_usedregion(request->query, &r); + r.base[0] = (id >> 8) & 0xff; + r.base[1] = id & 0xff; + + LOCK(&requestmgr->lock); + dns_requestmgr_attach(requestmgr, &request->requestmgr); + request->hash = mgr_gethash(requestmgr); + ISC_LIST_APPEND(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + + request->destaddr = *destaddr; + + request->flags |= DNS_REQUEST_F_CONNECTING; + if (tcp) { + request->flags |= DNS_REQUEST_F_TCP; + } + + result = dns_dispatch_connect(request->dispentry); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + + req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: request %p", request); + *requestp = request; + return (ISC_R_SUCCESS); + +unlink: + LOCK(&requestmgr->lock); + ISC_LIST_UNLINK(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + +detach: + /* connect failed, detach here */ + req_detach(&(dns_request_t *){ request }); + +cleanup: + isc_task_detach(&(isc_task_t *){ task }); + /* final detach to shut down request */ + req_detach(&request); + req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: failed %s", + isc_result_totext(result)); + return (result); +} + +isc_result_t +dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, unsigned int options, + dns_tsigkey_t *key, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp) { + dns_request_t *request = NULL; + isc_result_t result; + isc_mem_t *mctx = NULL; + dns_messageid_t id; + bool tcp = false; + bool connected = false; + + REQUIRE(VALID_REQUESTMGR(requestmgr)); + REQUIRE(message != NULL); + REQUIRE(destaddr != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + REQUIRE(requestp != NULL && *requestp == NULL); + REQUIRE(timeout > 0); + REQUIRE(udpretries != UINT_MAX); + + mctx = requestmgr->mctx; + + req_log(ISC_LOG_DEBUG(3), "dns_request_create"); + + if (atomic_load_acquire(&requestmgr->exiting)) { + return (ISC_R_SHUTTINGDOWN); + } + + if (srcaddr != NULL && + isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr)) + { + return (ISC_R_FAMILYMISMATCH); + } + + if (isblackholed(requestmgr->dispatchmgr, destaddr)) { + return (DNS_R_BLACKHOLED); + } + + /* detached in dns_request_destroy() */ + result = new_request(mctx, &request); + if (result != ISC_R_SUCCESS) { + return (result); + } + + request->udpcount = udpretries + 1; + + request->event = (dns_requestevent_t *)isc_event_allocate( + mctx, task, DNS_EVENT_REQUESTDONE, action, arg, + sizeof(dns_requestevent_t)); + isc_task_attach(task, &(isc_task_t *){ NULL }); + request->event->ev_sender = task; + request->event->request = request; + request->event->result = ISC_R_FAILURE; + + if (key != NULL) { + dns_tsigkey_attach(key, &request->tsigkey); + } + + result = dns_message_settsigkey(message, request->tsigkey); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if ((options & DNS_REQUESTOPT_TCP) != 0) { + tcp = true; + request->timeout = timeout * 1000; + } else { + if (udptimeout == 0) { + udptimeout = timeout / request->udpcount; + } + if (udptimeout == 0) { + udptimeout = 1; + } + request->timeout = udptimeout * 1000; + } + + /* detached in req_connected() */ + req_attach(request, &(dns_request_t *){ NULL }); + +again: + result = get_dispatch(tcp, false, requestmgr, srcaddr, destaddr, + &request->dispatch); + if (result != ISC_R_SUCCESS) { + goto detach; + } + + result = dns_dispatch_add( + request->dispatch, 0, request->timeout, destaddr, req_connected, + req_senddone, req_response, request, &id, &request->dispentry); + if (result != ISC_R_SUCCESS) { + goto detach; + } + + message->id = id; + result = req_render(message, &request->query, options, mctx); + if (result == DNS_R_USETCP && !tcp) { + /* + * Try again using TCP. + */ + dns_message_renderreset(message); + dns_dispatch_done(&request->dispentry); + dns_dispatch_detach(&request->dispatch); + options |= DNS_REQUESTOPT_TCP; + tcp = true; + goto again; + } + if (result != ISC_R_SUCCESS) { + goto detach; + } + + result = dns_message_getquerytsig(message, mctx, &request->tsig); + if (result != ISC_R_SUCCESS) { + goto detach; + } + + LOCK(&requestmgr->lock); + dns_requestmgr_attach(requestmgr, &request->requestmgr); + request->hash = mgr_gethash(requestmgr); + ISC_LIST_APPEND(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + + request->destaddr = *destaddr; + if (tcp && connected) { + req_send(request); + + /* no need to call req_connected(), detach here */ + req_detach(&(dns_request_t *){ request }); + } else { + request->flags |= DNS_REQUEST_F_CONNECTING; + if (tcp) { + request->flags |= DNS_REQUEST_F_TCP; + } + + result = dns_dispatch_connect(request->dispentry); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + } + + req_log(ISC_LOG_DEBUG(3), "dns_request_create: request %p", request); + *requestp = request; + return (ISC_R_SUCCESS); + +unlink: + LOCK(&requestmgr->lock); + ISC_LIST_UNLINK(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + +detach: + /* connect failed, detach here */ + req_detach(&(dns_request_t *){ request }); + +cleanup: + isc_task_detach(&(isc_task_t *){ task }); + /* final detach to shut down request */ + req_detach(&request); + req_log(ISC_LOG_DEBUG(3), "dns_request_create: failed %s", + isc_result_totext(result)); + return (result); +} + +static isc_result_t +req_render(dns_message_t *message, isc_buffer_t **bufferp, unsigned int options, + isc_mem_t *mctx) { + isc_buffer_t *buf1 = NULL; + isc_buffer_t *buf2 = NULL; + isc_result_t result; + isc_region_t r; + dns_compress_t cctx; + bool cleanup_cctx = false; + + REQUIRE(bufferp != NULL && *bufferp == NULL); + + req_log(ISC_LOG_DEBUG(3), "request_render"); + + /* + * Create buffer able to hold largest possible message. + */ + isc_buffer_allocate(mctx, &buf1, 65535); + + result = dns_compress_init(&cctx, -1, mctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + cleanup_cctx = true; + + if ((options & DNS_REQUESTOPT_CASE) != 0) { + dns_compress_setsensitive(&cctx, true); + } + + /* + * Render message. + */ + result = dns_message_renderbegin(message, &cctx, buf1); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_rendersection(message, DNS_SECTION_ANSWER, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_rendersection(message, DNS_SECTION_AUTHORITY, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_rendersection(message, DNS_SECTION_ADDITIONAL, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_message_renderend(message); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_compress_invalidate(&cctx); + cleanup_cctx = false; + + /* + * Copy rendered message to exact sized buffer. + */ + isc_buffer_usedregion(buf1, &r); + if ((options & DNS_REQUESTOPT_TCP) == 0 && r.length > 512) { + result = DNS_R_USETCP; + goto cleanup; + } + isc_buffer_allocate(mctx, &buf2, r.length); + result = isc_buffer_copyregion(buf2, &r); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Cleanup and return. + */ + isc_buffer_free(&buf1); + *bufferp = buf2; + return (ISC_R_SUCCESS); + +cleanup: + dns_message_renderreset(message); + if (buf1 != NULL) { + isc_buffer_free(&buf1); + } + if (buf2 != NULL) { + isc_buffer_free(&buf2); + } + if (cleanup_cctx) { + dns_compress_invalidate(&cctx); + } + return (result); +} + +void +request_cancel(dns_request_t *request) { + if (!DNS_REQUEST_CANCELED(request)) { + req_log(ISC_LOG_DEBUG(3), "request_cancel: request %p", + request); + + request->flags |= DNS_REQUEST_F_CANCELED; + request->flags &= ~DNS_REQUEST_F_CONNECTING; + + if (request->dispentry != NULL) { + dns_dispatch_done(&request->dispentry); + } + + dns_dispatch_detach(&request->dispatch); + } +} + +void +dns_request_cancel(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "dns_request_cancel: request %p", request); + LOCK(&request->requestmgr->locks[request->hash]); + request_cancel(request); + req_sendevent(request, ISC_R_CANCELED); + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +isc_result_t +dns_request_getresponse(dns_request_t *request, dns_message_t *message, + unsigned int options) { + isc_result_t result; + + REQUIRE(VALID_REQUEST(request)); + REQUIRE(request->answer != NULL); + + req_log(ISC_LOG_DEBUG(3), "dns_request_getresponse: request %p", + request); + + result = dns_message_setquerytsig(message, request->tsig); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_message_settsigkey(message, request->tsigkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_message_parse(message, request->answer, options); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (request->tsigkey != NULL) { + result = dns_tsig_verify(request->answer, message, NULL, NULL); + } + return (result); +} + +isc_buffer_t * +dns_request_getanswer(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + return (request->answer); +} + +bool +dns_request_usedtcp(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + return ((request->flags & DNS_REQUEST_F_TCP) != 0); +} + +void +dns_request_destroy(dns_request_t **requestp) { + dns_request_t *request; + + REQUIRE(requestp != NULL && VALID_REQUEST(*requestp)); + + request = *requestp; + *requestp = NULL; + + req_log(ISC_LOG_DEBUG(3), "dns_request_destroy: request %p", request); + + LOCK(&request->requestmgr->lock); + LOCK(&request->requestmgr->locks[request->hash]); + ISC_LIST_UNLINK(request->requestmgr->requests, request, link); + UNLOCK(&request->requestmgr->locks[request->hash]); + UNLOCK(&request->requestmgr->lock); + + /* + * These should have been cleaned up before the completion + * event was sent. + */ + INSIST(request->dispentry == NULL); + INSIST(request->dispatch == NULL); + + /* final detach to shut down request */ + req_detach(&request); +} + +static void +req_connected(isc_result_t eresult, isc_region_t *region, void *arg) { + dns_request_t *request = (dns_request_t *)arg; + + UNUSED(region); + + req_log(ISC_LOG_DEBUG(3), "req_connected: request %p: %s", request, + isc_result_totext(eresult)); + + REQUIRE(VALID_REQUEST(request)); + REQUIRE(DNS_REQUEST_CONNECTING(request) || + DNS_REQUEST_CANCELED(request)); + + LOCK(&request->requestmgr->locks[request->hash]); + request->flags &= ~DNS_REQUEST_F_CONNECTING; + + if (eresult == ISC_R_TIMEDOUT) { + dns_dispatch_done(&request->dispentry); + dns_dispatch_detach(&request->dispatch); + req_sendevent(request, eresult); + } else if (DNS_REQUEST_CANCELED(request)) { + req_sendevent(request, ISC_R_CANCELED); + } else if (eresult == ISC_R_SUCCESS) { + req_send(request); + } else { + request_cancel(request); + req_sendevent(request, ISC_R_CANCELED); + } + UNLOCK(&request->requestmgr->locks[request->hash]); + + /* attached in dns_request_create/_createraw() */ + req_detach(&(dns_request_t *){ request }); +} + +static void +req_senddone(isc_result_t eresult, isc_region_t *region, void *arg) { + dns_request_t *request = (dns_request_t *)arg; + + REQUIRE(VALID_REQUEST(request)); + REQUIRE(DNS_REQUEST_SENDING(request)); + + UNUSED(region); + + req_log(ISC_LOG_DEBUG(3), "req_senddone: request %p", request); + + LOCK(&request->requestmgr->locks[request->hash]); + request->flags &= ~DNS_REQUEST_F_SENDING; + + if (DNS_REQUEST_CANCELED(request)) { + if (eresult == ISC_R_TIMEDOUT) { + req_sendevent(request, eresult); + } else { + req_sendevent(request, ISC_R_CANCELED); + } + } else if (eresult != ISC_R_SUCCESS) { + request_cancel(request); + req_sendevent(request, ISC_R_CANCELED); + } + + UNLOCK(&request->requestmgr->locks[request->hash]); + + /* attached in req_send() */ + req_detach(&request); +} + +static void +req_response(isc_result_t result, isc_region_t *region, void *arg) { + dns_request_t *request = (dns_request_t *)arg; + + if (result == ISC_R_CANCELED) { + return; + } + + req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request, + isc_result_totext(result)); + + REQUIRE(VALID_REQUEST(request)); + + if (result == ISC_R_TIMEDOUT) { + LOCK(&request->requestmgr->locks[request->hash]); + if (request->udpcount > 1 && + (request->flags & DNS_REQUEST_F_TCP) == 0) + { + request->udpcount -= 1; + dns_dispatch_resume(request->dispentry, + request->timeout); + if (!DNS_REQUEST_SENDING(request)) { + req_send(request); + } + UNLOCK(&request->requestmgr->locks[request->hash]); + return; + } + + /* The lock is unlocked below */ + goto done; + } + + LOCK(&request->requestmgr->locks[request->hash]); + + if (result != ISC_R_SUCCESS) { + goto done; + } + + /* + * Copy region to request. + */ + isc_buffer_allocate(request->mctx, &request->answer, region->length); + result = isc_buffer_copyregion(request->answer, region); + if (result != ISC_R_SUCCESS) { + isc_buffer_free(&request->answer); + } + +done: + /* + * Cleanup. + */ + if (request->dispentry != NULL) { + dns_dispatch_done(&request->dispentry); + } + request_cancel(request); + + /* + * Send completion event. + */ + req_sendevent(request, result); + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +static void +req_sendevent(dns_request_t *request, isc_result_t result) { + isc_task_t *task = NULL; + + REQUIRE(VALID_REQUEST(request)); + + if (request->event == NULL) { + return; + } + + req_log(ISC_LOG_DEBUG(3), "req_sendevent: request %p", request); + + /* + * Lock held by caller. + */ + task = request->event->ev_sender; + request->event->ev_sender = request; + request->event->result = result; + + isc_task_sendanddetach(&task, (isc_event_t **)&request->event); +} + +static void +req_attach(dns_request_t *source, dns_request_t **targetp) { + REQUIRE(VALID_REQUEST(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +req_detach(dns_request_t **requestp) { + dns_request_t *request = NULL; + uint_fast32_t ref; + + REQUIRE(requestp != NULL && VALID_REQUEST(*requestp)); + + request = *requestp; + *requestp = NULL; + + ref = isc_refcount_decrement(&request->references); + + if (request->requestmgr != NULL && + atomic_load_acquire(&request->requestmgr->exiting)) + { + /* We are shutting down and this was last request */ + LOCK(&request->requestmgr->lock); + if (ISC_LIST_EMPTY(request->requestmgr->requests)) { + send_shutdown_events(request->requestmgr); + } + UNLOCK(&request->requestmgr->lock); + } + + if (ref == 1) { + req_destroy(request); + } +} + +static void +req_destroy(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "req_destroy: request %p", request); + + isc_refcount_destroy(&request->references); + + request->magic = 0; + if (request->query != NULL) { + isc_buffer_free(&request->query); + } + if (request->answer != NULL) { + isc_buffer_free(&request->answer); + } + if (request->event != NULL) { + isc_event_free((isc_event_t **)&request->event); + } + if (request->dispentry != NULL) { + dns_dispatch_done(&request->dispentry); + } + if (request->dispatch != NULL) { + dns_dispatch_detach(&request->dispatch); + } + if (request->tsig != NULL) { + isc_buffer_free(&request->tsig); + } + if (request->tsigkey != NULL) { + dns_tsigkey_detach(&request->tsigkey); + } + if (request->requestmgr != NULL) { + dns_requestmgr_detach(&request->requestmgr); + } + isc_mem_putanddetach(&request->mctx, request, sizeof(*request)); +} + +static void +req_log(int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST, + level, fmt, ap); + va_end(ap); +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c new file mode 100644 index 0000000..66bb1ac --- /dev/null +++ b/lib/dns/resolver.c @@ -0,0 +1,11753 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Detailed logging of fctx attach/detach */ +#ifndef FCTX_TRACE +#undef FCTX_TRACE +#endif + +#ifdef WANT_QUERYTRACE +#define RTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \ + res, (m)) +#define RRTRACE(r, m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "res %p: %s", \ + (r), (m)) +#define FCTXTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s", fctx, fctx->info, (m)) +#define FCTXTRACE2(m1, m2) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s %s", fctx, fctx->info, (m1), (m2)) +#define FCTXTRACE3(m, res) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): [result: %s] %s", fctx, fctx->info, \ + isc_result_totext(res), (m)) +#define FCTXTRACE4(m1, m2, res) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): [result: %s] %s %s", fctx, fctx->info, \ + isc_result_totext(res), (m1), (m2)) +#define FCTXTRACE5(m1, m2, v) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fctx %p(%s): %s %s%u", fctx, fctx->info, (m1), (m2), \ + (v)) +#define FTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "fetch %p (fctx %p(%s)): %s", fetch, fetch->private, \ + fetch->private->info, (m)) +#define QTRACE(m) \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, \ + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), \ + "resquery %p (fctx %p(%s)): %s", query, query->fctx, \ + query->fctx->info, (m)) +#else /* ifdef WANT_QUERYTRACE */ +#define RTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#define RRTRACE(r, m) \ + do { \ + UNUSED(r); \ + UNUSED(m); \ + } while (0) +#define FCTXTRACE(m) \ + do { \ + UNUSED(fctx); \ + UNUSED(m); \ + } while (0) +#define FCTXTRACE2(m1, m2) \ + do { \ + UNUSED(fctx); \ + UNUSED(m1); \ + UNUSED(m2); \ + } while (0) +#define FCTXTRACE3(m1, res) \ + do { \ + UNUSED(fctx); \ + UNUSED(m1); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE4(m1, m2, res) \ + do { \ + UNUSED(fctx); \ + UNUSED(m1); \ + UNUSED(m2); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE5(m1, m2, v) \ + do { \ + UNUSED(fctx); \ + UNUSED(m1); \ + UNUSED(m2); \ + UNUSED(v); \ + } while (0) +#define FTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#define QTRACE(m) \ + do { \ + UNUSED(m); \ + } while (0) +#endif /* WANT_QUERYTRACE */ + +/* + * Add or remove an extra fctx reference without setting or clearing + * the pointer. + */ +#define fctx_addref(f) fctx_attach((f), &(fetchctx_t *){ NULL }) +#define fctx_unref(f) fctx_detach(&(fetchctx_t *){ (f) }) + +/* + * The maximum time we will wait for a single query. + */ +#define MAX_SINGLE_QUERY_TIMEOUT 9000U +#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT * US_PER_MS) + +/* + * We need to allow a individual query time to complete / timeout. + */ +#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U) + +/* The default time in seconds for the whole query to live. */ +#ifndef DEFAULT_QUERY_TIMEOUT +#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT +#endif /* ifndef DEFAULT_QUERY_TIMEOUT */ + +/* The maximum time in seconds for the whole query to live. */ +#ifndef MAXIMUM_QUERY_TIMEOUT +#define MAXIMUM_QUERY_TIMEOUT 30000 +#endif /* ifndef MAXIMUM_QUERY_TIMEOUT */ + +/* The default maximum number of recursions to follow before giving up. */ +#ifndef DEFAULT_RECURSION_DEPTH +#define DEFAULT_RECURSION_DEPTH 7 +#endif /* ifndef DEFAULT_RECURSION_DEPTH */ + +/* The default maximum number of iterative queries to allow before giving up. */ +#ifndef DEFAULT_MAX_QUERIES +#define DEFAULT_MAX_QUERIES 100 +#endif /* ifndef DEFAULT_MAX_QUERIES */ + +/* + * After NS_FAIL_LIMIT attempts to fetch a name server address, + * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT, + * stop trying to fetch, in order to avoid wasting resources. + */ +#define NS_FAIL_LIMIT 4 +#define NS_RR_LIMIT 5 +/* + * IP address lookups are performed for at most NS_PROCESSING_LIMIT NS RRs in + * any NS RRset encountered, to avoid excessive resource use while processing + * large delegations. + */ +#define NS_PROCESSING_LIMIT 20 + +STATIC_ASSERT(NS_PROCESSING_LIMIT > NS_RR_LIMIT, + "The maximum number of NS RRs processed for each delegation " + "(NS_PROCESSING_LIMIT) must be larger than the large delegation " + "threshold (NS_RR_LIMIT)."); + +/* Hash table for zone counters */ +#ifndef RES_DOMAIN_HASH_BITS +#define RES_DOMAIN_HASH_BITS 12 +#endif /* ifndef RES_DOMAIN_HASH_BITS */ +#define RES_NOBUCKET 0xffffffff + +#define GOLDEN_RATIO_32 0x61C88647 + +#define HASHSIZE(bits) (UINT64_C(1) << (bits)) + +#define RES_DOMAIN_MAX_BITS 32 +#define RES_DOMAIN_OVERCOMMIT 3 + +#define RES_DOMAIN_NEXTTABLE(hindex) ((hindex == 0) ? 1 : 0) + +static uint32_t +hash_32(uint32_t val, unsigned int bits) { + REQUIRE(bits <= RES_DOMAIN_MAX_BITS); + /* High bits are more random. */ + return (val * GOLDEN_RATIO_32 >> (32 - bits)); +} + +/*% + * Maximum EDNS0 input packet size. + */ +#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */ + +/*% + * Default EDNS0 buffer size + */ +#define DEFAULT_EDNS_BUFSIZE 1232 + +/*% + * This defines the maximum number of timeouts we will permit before we + * disable EDNS0 on the query. + */ +#define MAX_EDNS0_TIMEOUTS 3 + +#define DNS_RESOLVER_BADCACHESIZE 1021 +#define DNS_RESOLVER_BADCACHETTL(fctx) \ + (((fctx)->res->lame_ttl > 30) ? (fctx)->res->lame_ttl : 30) + +typedef struct fetchctx fetchctx_t; + +typedef struct query { + /* Locked by task event serialization. */ + unsigned int magic; + isc_refcount_t references; + fetchctx_t *fctx; + dns_message_t *rmessage; + isc_mem_t *mctx; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatch; + dns_adbaddrinfo_t *addrinfo; + isc_time_t start; + dns_messageid_t id; + dns_dispentry_t *dispentry; + ISC_LINK(struct query) link; + isc_buffer_t buffer; + isc_buffer_t *tsig; + dns_tsigkey_t *tsigkey; + int ednsversion; + unsigned int options; + unsigned int attributes; + unsigned int udpsize; + unsigned char data[512]; +} resquery_t; + +struct tried { + isc_sockaddr_t addr; + unsigned int count; + ISC_LINK(struct tried) link; +}; + +#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!') +#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC) + +#define RESQUERY_ATTR_CANCELED 0x02 + +#define RESQUERY_CONNECTING(q) ((q)->connects > 0) +#define RESQUERY_CANCELED(q) (((q)->attributes & RESQUERY_ATTR_CANCELED) != 0) +#define RESQUERY_SENDING(q) ((q)->sends > 0) + +typedef enum { + fetchstate_init = 0, /*%< Start event has not run yet. */ + fetchstate_active, + fetchstate_done /*%< FETCHDONE events posted. */ +} fetchstate_t; + +typedef enum { + badns_unreachable = 0, + badns_response, + badns_validation, + badns_forwarder, +} badnstype_t; + +struct fetchctx { + /*% Not locked. */ + unsigned int magic; + dns_resolver_t *res; + dns_fixedname_t fname; + dns_name_t *name; + dns_rdatatype_t type; + unsigned int options; + unsigned int bucketnum; + unsigned int dbucketnum; + char *info; + isc_mem_t *mctx; + isc_stdtime_t now; + isc_task_t *task; + + /* Atomic */ + isc_refcount_t references; + + /*% Locked by appropriate bucket lock. */ + fetchstate_t state; + atomic_bool want_shutdown; + bool cloned; + bool spilled; + isc_event_t control_event; + ISC_LINK(struct fetchctx) link; + ISC_LIST(dns_fetchevent_t) events; + + /*% Locked by task event serialization. */ + dns_fixedname_t dfname; + dns_name_t *domain; + dns_rdataset_t nameservers; + atomic_uint_fast32_t attributes; + isc_timer_t *timer; + isc_time_t expires; + isc_time_t expires_try_stale; + isc_time_t next_timeout; + isc_time_t final; + isc_interval_t interval; + dns_message_t *qmessage; + ISC_LIST(resquery_t) queries; + dns_adbfindlist_t finds; + dns_adbfind_t *find; + /* + * altfinds are names and/or addresses of dual stack servers that + * should be used when iterative resolution to a server is not + * possible because the address family of that server is not usable. + */ + dns_adbfindlist_t altfinds; + dns_adbfind_t *altfind; + dns_adbaddrinfolist_t forwaddrs; + dns_adbaddrinfolist_t altaddrs; + dns_forwarderlist_t forwarders; + dns_fwdpolicy_t fwdpolicy; + isc_sockaddrlist_t bad; + ISC_LIST(struct tried) edns; + isc_sockaddrlist_t bad_edns; + dns_validator_t *validator; + ISC_LIST(dns_validator_t) validators; + dns_db_t *cache; + dns_adb_t *adb; + bool ns_ttl_ok; + uint32_t ns_ttl; + isc_counter_t *qc; + bool minimized; + unsigned int qmin_labels; + isc_result_t qmin_warning; + bool ip6arpaskip; + bool forwarding; + dns_fixedname_t qminfname; + dns_name_t *qminname; + dns_rdatatype_t qmintype; + dns_fetch_t *qminfetch; + dns_rdataset_t qminrrset; + dns_fixedname_t qmindcfname; + dns_name_t *qmindcname; + dns_fixedname_t fwdfname; + dns_name_t *fwdname; + + /*% + * The number of events we're waiting for. + */ + atomic_uint_fast32_t pending; /* Bucket lock. */ + + /*% + * The number of times we've "restarted" the current + * nameserver set. This acts as a failsafe to prevent + * us from pounding constantly on a particular set of + * servers that, for whatever reason, are not giving + * us useful responses, but are responding in such a + * way that they are not marked "bad". + */ + unsigned int restarts; + + /*% + * The number of timeouts that have occurred since we + * last successfully received a response packet. This + * is used for EDNS0 black hole detection. + */ + unsigned int timeouts; + + /*% + * Look aside state for DS lookups. + */ + dns_fixedname_t nsfname; + dns_name_t *nsname; + + dns_fetch_t *nsfetch; + dns_rdataset_t nsrrset; + + /*% + * Number of queries that reference this context. + */ + atomic_uint_fast32_t nqueries; /* Bucket lock. */ + + /*% + * Random numbers to use for mixing up server addresses. + */ + uint32_t rand_buf; + uint32_t rand_bits; + + /*% + * Fetch-local statistics for detailed logging. + */ + isc_result_t result; /*%< fetch result */ + isc_result_t vresult; /*%< validation result */ + int exitline; + isc_time_t start; + uint64_t duration; + bool logged; + unsigned int querysent; + unsigned int referrals; + unsigned int lamecount; + unsigned int quotacount; + unsigned int neterr; + unsigned int badresp; + unsigned int adberr; + unsigned int findfail; + unsigned int valfail; + bool timeout; + dns_adbaddrinfo_t *addrinfo; + unsigned int depth; + char clientstr[ISC_SOCKADDR_FORMATSIZE]; +}; + +#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!') +#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC) + +#define FCTX_ATTR_HAVEANSWER 0x0001 +#define FCTX_ATTR_GLUING 0x0002 +#define FCTX_ATTR_ADDRWAIT 0x0004 +#define FCTX_ATTR_SHUTTINGDOWN 0x0008 /* Bucket lock */ +#define FCTX_ATTR_WANTCACHE 0x0010 +#define FCTX_ATTR_WANTNCACHE 0x0020 +#define FCTX_ATTR_NEEDEDNS0 0x0040 +#define FCTX_ATTR_TRIEDFIND 0x0080 +#define FCTX_ATTR_TRIEDALT 0x0100 + +#define HAVE_ANSWER(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_HAVEANSWER) != 0) +#define GLUING(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_GLUING) != 0) +#define ADDRWAIT(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_ADDRWAIT) != 0) +#define SHUTTINGDOWN(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_SHUTTINGDOWN) != 0) +#define WANTCACHE(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTCACHE) != 0) +#define WANTNCACHE(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_WANTNCACHE) != 0) +#define NEEDEDNS0(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_NEEDEDNS0) != 0) +#define TRIEDFIND(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDFIND) != 0) +#define TRIEDALT(f) \ + ((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_TRIEDALT) != 0) + +#define FCTX_ATTR_SET(f, a) atomic_fetch_or_release(&(f)->attributes, (a)) +#define FCTX_ATTR_CLR(f, a) atomic_fetch_and_release(&(f)->attributes, ~(a)) + +typedef struct { + dns_adbaddrinfo_t *addrinfo; + fetchctx_t *fctx; + dns_message_t *message; +} dns_valarg_t; + +struct dns_fetch { + unsigned int magic; + isc_mem_t *mctx; + dns_resolver_t *res; + fetchctx_t *private; +}; + +#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h') +#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC) + +typedef struct fctxbucket { + isc_task_t *task; + isc_mutex_t lock; + ISC_LIST(fetchctx_t) fctxs; + atomic_bool exiting; +} fctxbucket_t; + +typedef struct fctxcount fctxcount_t; +struct fctxcount { + dns_fixedname_t dfname; + dns_name_t *domain; + uint32_t count; + uint32_t allowed; + uint32_t dropped; + isc_stdtime_t logged; + ISC_LINK(fctxcount_t) link; +}; + +typedef struct zonebucket { + isc_mutex_t lock; + ISC_LIST(fctxcount_t) list; +} zonebucket_t; + +typedef struct alternate { + bool isaddress; + union { + isc_sockaddr_t addr; + struct { + dns_name_t name; + in_port_t port; + } _n; + } _u; + ISC_LINK(struct alternate) link; +} alternate_t; + +struct dns_resolver { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_mutex_t primelock; + dns_rdataclass_t rdclass; + isc_nm_t *nm; + isc_timermgr_t *timermgr; + isc_taskmgr_t *taskmgr; + dns_view_t *view; + bool frozen; + unsigned int options; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatchset_t *dispatches4; + dns_dispatchset_t *dispatches6; + unsigned int nbuckets; + fctxbucket_t *buckets; + uint8_t dhashbits; + zonebucket_t *dbuckets; + uint32_t lame_ttl; + ISC_LIST(alternate_t) alternates; + uint16_t udpsize; + dns_rbt_t *algorithms; + dns_rbt_t *digests; + dns_rbt_t *mustbesecure; + unsigned int spillatmax; + unsigned int spillatmin; + isc_timer_t *spillattimer; + bool zero_no_soa_ttl; + unsigned int query_timeout; + unsigned int maxdepth; + unsigned int maxqueries; + isc_result_t quotaresp[2]; + + /* Additions for serve-stale feature. */ + unsigned int retryinterval; /* in milliseconds */ + unsigned int nonbackofftries; + + /* Atomic */ + isc_refcount_t references; + atomic_uint_fast32_t zspill; /* fetches-per-zone */ + atomic_bool exiting; + atomic_bool priming; + + /* Locked by lock. */ + isc_eventlist_t whenshutdown; + isc_refcount_t activebuckets; + unsigned int spillat; /* clients-per-query */ + + dns_badcache_t *badcache; /* Bad cache. */ + + /* Locked by primelock. */ + dns_fetch_t *primefetch; + + /* Atomic. */ + atomic_uint_fast32_t nfctx; +}; + +#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!') +#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC) + +/*% + * Private addrinfo flags. + */ +enum { + FCTX_ADDRINFO_MARK = 1 << 0, + FCTX_ADDRINFO_FORWARDER = 1 << 1, + FCTX_ADDRINFO_EDNSOK = 1 << 2, + FCTX_ADDRINFO_NOCOOKIE = 1 << 3, + FCTX_ADDRINFO_BADCOOKIE = 1 << 4, + FCTX_ADDRINFO_DUALSTACK = 1 << 5, + FCTX_ADDRINFO_NOEDNS0 = 1 << 6, +}; + +#define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) == 0) +#define ISFORWARDER(a) (((a)->flags & FCTX_ADDRINFO_FORWARDER) != 0) +#define NOCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_NOCOOKIE) != 0) +#define EDNSOK(a) (((a)->flags & FCTX_ADDRINFO_EDNSOK) != 0) +#define BADCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_BADCOOKIE) != 0) +#define ISDUALSTACK(a) (((a)->flags & FCTX_ADDRINFO_DUALSTACK) != 0) + +#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) +#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) + +#define NXDOMAIN_RESULT(r) \ + ((r) == DNS_R_NXDOMAIN || (r) == DNS_R_NCACHENXDOMAIN) +#define NXRRSET_RESULT(r) \ + ((r) == DNS_R_NCACHENXRRSET || (r) == DNS_R_NXRRSET || \ + (r) == DNS_R_HINTNXRRSET) + +#ifdef ENABLE_AFL +bool dns_fuzzing_resolver = false; +void +dns_resolver_setfuzzing(void) { + dns_fuzzing_resolver = true; +} +#endif /* ifdef ENABLE_AFL */ + +static unsigned char ip6_arpa_data[] = "\003IP6\004ARPA"; +static unsigned char ip6_arpa_offsets[] = { 0, 4, 9 }; +static const dns_name_t ip6_arpa = DNS_NAME_INITABSOLUTE(ip6_arpa_data, + ip6_arpa_offsets); + +static void +destroy(dns_resolver_t *res); +static isc_result_t +resquery_send(resquery_t *query); +static void +resquery_response(isc_result_t eresult, isc_region_t *region, void *arg); +static void +resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg); +static void +fctx_try(fetchctx_t *fctx, bool retrying, bool badcache); +static void +fctx_shutdown(fetchctx_t *fctx); +static void +fctx_minimize_qname(fetchctx_t *fctx); +static void +fctx_destroy(fetchctx_t *fctx, bool exiting); +static void +send_shutdown_events(dns_resolver_t *res); +static isc_result_t +ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *ardataset, isc_result_t *eresultp); +static void +validated(isc_task_t *task, isc_event_t *event); +static void +maybe_cancel_validators(fetchctx_t *fctx, bool locked); +static void +add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo, + isc_result_t reason, badnstype_t badtype); +static isc_result_t +findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name, + dns_rdatatype_t type, dns_name_t **noqname); + +#define fctx_attach(fctx, fctxp) \ + fctx__attach(fctx, fctxp, __FILE__, __LINE__, __func__) +#define fctx_detach(fctxp) fctx__detach(fctxp, __FILE__, __LINE__, __func__) +#define fctx_done_detach(fctxp, result) \ + fctx__done_detach(fctxp, result, __FILE__, __LINE__, __func__); + +static void +fctx__attach(fetchctx_t *fctx, fetchctx_t **fctxp, const char *file, + unsigned int line, const char *func); +static void +fctx__detach(fetchctx_t **fctxp, const char *file, unsigned int line, + const char *func); + +static void +fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file, + unsigned int line, const char *func); + +static void +resume_qmin(isc_task_t *task, isc_event_t *event); + +/*% + * The structure and functions defined below implement the resolver + * query (resquery) response handling logic. + * + * When a resolver query is sent and a response is received, the + * resquery_response() event handler is run, which calls the rctx_*() + * functions. The respctx_t structure maintains state from function + * to function. + * + * The call flow is described below: + * + * 1. resquery_response(): + * - Initialize a respctx_t structure (rctx_respinit()). + * - Check for dispatcher failure (rctx_dispfail()). + * - Parse the response (rctx_parse()). + * - Log the response (rctx_logpacket()). + * - Check the parsed response for an OPT record and handle + * EDNS (rctx_opt(), rctx_edns()). + * - Check for a bad or lame server (rctx_badserver(), rctx_lameserver()). + * - Handle delegation-only zones (rctx_delonly_zone()). + * - If RCODE and ANCOUNT suggest this is a positive answer, and + * if so, call rctx_answer(): go to step 2. + * - If RCODE and NSCOUNT suggest this is a negative answer or a + * referral, call rctx_answer_none(): go to step 4. + * - Check the additional section for data that should be cached + * (rctx_additional()). + * - Clean up and finish by calling rctx_done(): go to step 5. + * + * 2. rctx_answer(): + * - If the answer appears to be positive, call rctx_answer_positive(): + * go to step 3. + * - If the response is a malformed delegation (with glue or NS records + * in the answer section), call rctx_answer_none(): go to step 4. + * + * 3. rctx_answer_positive(): + * - Initialize the portions of respctx_t needed for processing an answer + * (rctx_answer_init()). + * - Scan the answer section to find records that are responsive to the + * query (rctx_answer_scan()). + * - For whichever type of response was found, call a separate routine + * to handle it: matching QNAME/QTYPE (rctx_answer_match()), + * CNAME (rctx_answer_cname()), covering DNAME (rctx_answer_dname()), + * or any records returned in response to a query of type ANY + * (rctx_answer_any()). + * - Scan the authority section for NS or other records that may be + * included with a positive answer (rctx_authority_scan()). + * + * 4. rctx_answer_none(): + * - Determine whether this is an NXDOMAIN, NXRRSET, or referral. + * - If referral, set up the resolver to follow the delegation + * (rctx_referral()). + * - If NXDOMAIN/NXRRSET, scan the authority section for NS and SOA + * records included with a negative response (rctx_authority_negative()), + * then for DNSSEC proof of nonexistence (rctx_authority_dnssec()). + * + * 5. rctx_done(): + * - Set up chasing of DS records if needed (rctx_chaseds()). + * - If the response wasn't intended for us, wait for another response + * from the dispatcher (rctx_next()). + * - If there is a problem with the responding server, set up another + * query to a different server (rctx_nextserver()). + * - If there is a problem that might be temporary or dependent on + * EDNS options, set up another query to the same server with changed + * options (rctx_resend()). + * - Shut down the fetch context. + */ + +typedef struct respctx { + resquery_t *query; + fetchctx_t *fctx; + isc_result_t result; + isc_buffer_t buffer; + unsigned int retryopts; /* updated options to pass to + * fctx_query() when resending */ + + dns_rdatatype_t type; /* type being sought (set to + * ANY if qtype was SIG or RRSIG) */ + bool aa; /* authoritative answer? */ + dns_trust_t trust; /* answer trust level */ + bool chaining; /* CNAME/DNAME processing? */ + bool next_server; /* give up, try the next server + * */ + + badnstype_t broken_type; /* type of name server problem + * */ + isc_result_t broken_server; + + bool get_nameservers; /* get a new NS rrset at + * zone cut? */ + bool resend; /* resend this query? */ + bool nextitem; /* invalid response; keep + * listening for the correct one */ + bool truncated; /* response was truncated */ + bool no_response; /* no response was received */ + bool glue_in_answer; /* glue may be in the answer + * section */ + bool ns_in_answer; /* NS may be in the answer + * section */ + bool negative; /* is this a negative response? */ + + isc_stdtime_t now; /* time info */ + isc_time_t tnow; + isc_time_t *finish; + + unsigned int dname_labels; + unsigned int domain_labels; /* range of permissible number + * of + * labels in a DNAME */ + + dns_name_t *aname; /* answer name */ + dns_rdataset_t *ardataset; /* answer rdataset */ + + dns_name_t *cname; /* CNAME name */ + dns_rdataset_t *crdataset; /* CNAME rdataset */ + + dns_name_t *dname; /* DNAME name */ + dns_rdataset_t *drdataset; /* DNAME rdataset */ + + dns_name_t *ns_name; /* NS name */ + dns_rdataset_t *ns_rdataset; /* NS rdataset */ + + dns_name_t *soa_name; /* SOA name in a negative answer */ + dns_name_t *ds_name; /* DS name in a negative answer */ + + dns_name_t *found_name; /* invalid name in negative + * response */ + dns_rdatatype_t found_type; /* invalid type in negative + * response */ + + dns_rdataset_t *opt; /* OPT rdataset */ +} respctx_t; + +static void +rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result, + isc_region_t *region, respctx_t *rctx); + +static void +rctx_answer_init(respctx_t *rctx); + +static void +rctx_answer_scan(respctx_t *rctx); + +static void +rctx_authority_positive(respctx_t *rctx); + +static isc_result_t +rctx_answer_any(respctx_t *rctx); + +static isc_result_t +rctx_answer_match(respctx_t *rctx); + +static isc_result_t +rctx_answer_cname(respctx_t *rctx); + +static isc_result_t +rctx_answer_dname(respctx_t *rctx); + +static isc_result_t +rctx_answer_positive(respctx_t *rctx); + +static isc_result_t +rctx_authority_negative(respctx_t *rctx); + +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx); + +static void +rctx_additional(respctx_t *rctx); + +static isc_result_t +rctx_referral(respctx_t *rctx); + +static isc_result_t +rctx_answer_none(respctx_t *rctx); + +static void +rctx_nextserver(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result); + +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo); + +static isc_result_t +rctx_next(respctx_t *rctx); + +static void +rctx_chaseds(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result); + +static void +rctx_done(respctx_t *rctx, isc_result_t result); + +static void +rctx_logpacket(respctx_t *rctx); + +static void +rctx_opt(respctx_t *rctx); + +static void +rctx_edns(respctx_t *rctx); + +static isc_result_t +rctx_parse(respctx_t *rctx); + +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result); + +static isc_result_t +rctx_answer(respctx_t *rctx); + +static isc_result_t +rctx_lameserver(respctx_t *rctx); + +static isc_result_t +rctx_dispfail(respctx_t *rctx); + +static isc_result_t +rctx_timedout(respctx_t *rctx); + +static void +rctx_delonly_zone(respctx_t *rctx); + +static void +rctx_ncache(respctx_t *rctx); + +/*% + * Increment resolver-related statistics counters. + */ +static void +inc_stats(dns_resolver_t *res, isc_statscounter_t counter) { + if (res->view->resstats != NULL) { + isc_stats_increment(res->view->resstats, counter); + } +} + +static void +dec_stats(dns_resolver_t *res, isc_statscounter_t counter) { + if (res->view->resstats != NULL) { + isc_stats_decrement(res->view->resstats, counter); + } +} + +static isc_result_t +valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, + dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, unsigned int valoptions, + isc_task_t *task) { + dns_validator_t *validator = NULL; + dns_valarg_t *valarg; + isc_result_t result; + + valarg = isc_mem_get(fctx->mctx, sizeof(*valarg)); + + *valarg = (dns_valarg_t){ + .addrinfo = addrinfo, + }; + + fctx_attach(fctx, &valarg->fctx); + dns_message_attach(message, &valarg->message); + + if (!ISC_LIST_EMPTY(fctx->validators)) { + valoptions |= DNS_VALIDATOR_DEFER; + } else { + valoptions &= ~DNS_VALIDATOR_DEFER; + } + + result = dns_validator_create(fctx->res->view, name, type, rdataset, + sigrdataset, message, valoptions, task, + validated, valarg, &validator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (result == ISC_R_SUCCESS) { + inc_stats(fctx->res, dns_resstatscounter_val); + if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { + INSIST(fctx->validator == NULL); + fctx->validator = validator; + } + ISC_LIST_APPEND(fctx->validators, validator, link); + } else { + dns_message_detach(&valarg->message); + fctx_detach(&valarg->fctx); + isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); + } + return (result); +} + +static bool +rrsig_fromchildzone(fetchctx_t *fctx, dns_rdataset_t *rdataset) { + dns_namereln_t namereln; + dns_rdata_rrsig_t rrsig; + dns_rdata_t rdata = DNS_RDATA_INIT; + int order; + isc_result_t result; + unsigned int labels; + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + namereln = dns_name_fullcompare(&rrsig.signer, fctx->domain, + &order, &labels); + if (namereln == dns_namereln_subdomain) { + return (true); + } + dns_rdata_reset(&rdata); + } + return (false); +} + +static bool +fix_mustbedelegationornxdomain(dns_message_t *message, fetchctx_t *fctx) { + dns_name_t *name; + dns_name_t *domain = fctx->domain; + dns_rdataset_t *rdataset; + dns_rdatatype_t type; + isc_result_t result; + bool keep_auth = false; + + if (message->rcode == dns_rcode_nxdomain) { + return (false); + } + + /* + * A DS RRset can appear anywhere in a zone, even for a delegation-only + * zone. So a response to an explicit query for this type should be + * excluded from delegation-only fixup. + * + * SOA, NS, and DNSKEY can only exist at a zone apex, so a positive + * response to a query for these types can never violate the + * delegation-only assumption: if the query name is below a + * zone cut, the response should normally be a referral, which should + * be accepted; if the query name is below a zone cut but the server + * happens to have authority for the zone of the query name, the + * response is a (non-referral) answer. But this does not violate + * delegation-only because the query name must be in a different zone + * due to the "apex-only" nature of these types. Note that if the + * remote server happens to have authority for a child zone of a + * delegation-only zone, we may still incorrectly "fix" the response + * with NXDOMAIN for queries for other types. Unfortunately it's + * generally impossible to differentiate this case from violation of + * the delegation-only assumption. Once the resolver learns the + * correct zone cut, possibly via a separate query for an "apex-only" + * type, queries for other types will be resolved correctly. + * + * A query for type ANY will be accepted if it hits an exceptional + * type above in the answer section as it should be from a child + * zone. + * + * Also accept answers with RRSIG records from the child zone. + * Direct queries for RRSIG records should not be answered from + * the parent zone. + */ + + if (message->counts[DNS_SECTION_ANSWER] != 0 && + (fctx->type == dns_rdatatype_ns || fctx->type == dns_rdatatype_ds || + fctx->type == dns_rdatatype_soa || + fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_dnskey)) + { + result = dns_message_firstname(message, DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_ANSWER, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!dns_name_equal(name, fctx->name)) { + continue; + } + type = rdataset->type; + /* + * RRsig from child? + */ + if (type == dns_rdatatype_rrsig && + rrsig_fromchildzone(fctx, rdataset)) + { + return (false); + } + /* + * Direct query for apex records or DS. + */ + if (fctx->type == type && + (type == dns_rdatatype_ds || + type == dns_rdatatype_ns || + type == dns_rdatatype_soa || + type == dns_rdatatype_dnskey)) + { + return (false); + } + /* + * Indirect query for apex records or DS. + */ + if (fctx->type == dns_rdatatype_any && + (type == dns_rdatatype_ns || + type == dns_rdatatype_ds || + type == dns_rdatatype_soa || + type == dns_rdatatype_dnskey)) + { + return (false); + } + } + result = dns_message_nextname(message, + DNS_SECTION_ANSWER); + } + } + + /* + * A NODATA response to a DS query? + */ + if (fctx->type == dns_rdatatype_ds && + message->counts[DNS_SECTION_ANSWER] == 0) + { + return (false); + } + + /* Look for referral or indication of answer from child zone? */ + if (message->counts[DNS_SECTION_AUTHORITY] == 0) { + goto munge; + } + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + type = rdataset->type; + if (type == dns_rdatatype_soa && + dns_name_equal(name, domain)) + { + keep_auth = true; + } + + if (type != dns_rdatatype_ns && + type != dns_rdatatype_soa && + type != dns_rdatatype_rrsig) + { + continue; + } + + if (type == dns_rdatatype_rrsig) { + if (rrsig_fromchildzone(fctx, rdataset)) { + return (false); + } else { + continue; + } + } + + /* NS or SOA records. */ + if (dns_name_equal(name, domain)) { + /* + * If a query for ANY causes a negative + * response, we can be sure that this is + * an empty node. For other type of queries + * we cannot differentiate an empty node + * from a node that just doesn't have that + * type of record. We only accept the former + * case. + */ + if (message->counts[DNS_SECTION_ANSWER] == 0 && + fctx->type == dns_rdatatype_any) + { + return (false); + } + } else if (dns_name_issubdomain(name, domain)) { + /* Referral or answer from child zone. */ + return (false); + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + +munge: + message->rcode = dns_rcode_nxdomain; + message->counts[DNS_SECTION_ANSWER] = 0; + if (!keep_auth) { + message->counts[DNS_SECTION_AUTHORITY] = 0; + } + message->counts[DNS_SECTION_ADDITIONAL] = 0; + return (true); +} + +static void +resquery_destroy(resquery_t *query) { + fetchctx_t *fctx = query->fctx; + dns_resolver_t *res = fctx->res; + unsigned int bucket = fctx->bucketnum; + + if (ISC_LINK_LINKED(query, link)) { + ISC_LIST_UNLINK(fctx->queries, query, link); + } + + if (query->tsig != NULL) { + isc_buffer_free(&query->tsig); + } + + if (query->tsigkey != NULL) { + dns_tsigkey_detach(&query->tsigkey); + } + + if (query->dispentry != NULL) { + dns_dispatch_done(&query->dispentry); + } + + if (query->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + + isc_refcount_destroy(&query->references); + + LOCK(&res->buckets[bucket].lock); + atomic_fetch_sub_release(&fctx->nqueries, 1); + UNLOCK(&res->buckets[bucket].lock); + fctx_detach(&query->fctx); + + if (query->rmessage != NULL) { + dns_message_detach(&query->rmessage); + } + + query->magic = 0; + isc_mem_put(query->mctx, query, sizeof(*query)); +} + +static void +resquery_attach(resquery_t *source, resquery_t **targetp) { + REQUIRE(VALID_QUERY(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +resquery_detach(resquery_t **queryp) { + uint_fast32_t ref; + resquery_t *query = NULL; + + REQUIRE(queryp != NULL && VALID_QUERY(*queryp)); + + query = *queryp; + *queryp = NULL; + + ref = isc_refcount_decrement(&query->references); + if (ref == 1) { + resquery_destroy(query); + } +} + +/*% + * Update EDNS statistics for a server after not getting a response to a UDP + * query sent to it. + */ +static void +update_edns_stats(resquery_t *query) { + fetchctx_t *fctx = query->fctx; + + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + return; + } + + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + dns_adb_ednsto(fctx->adb, query->addrinfo); + } else { + dns_adb_timeout(fctx->adb, query->addrinfo); + } +} + +/* + * Start the maximum lifetime timer for the fetch. This will + * trigger if, for example, some ADB or validator dependency + * loop occurs and causes a fetch to hang. + */ +static isc_result_t +fctx_starttimer(fetchctx_t *fctx) { + return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->final, + NULL, true)); +} + +static void +fctx_stoptimer(fetchctx_t *fctx) { + isc_result_t result; + + /* + * We don't return a result if resetting the timer to inactive fails + * since there's nothing to be done about it. Resetting to inactive + * should never fail anyway, since the code as currently written + * cannot fail in that case. + */ + result = isc_timer_reset(fctx->timer, isc_timertype_inactive, NULL, + NULL, true); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_timer_reset(): %s", + isc_result_totext(result)); + } +} + +static void +fctx_cancelquery(resquery_t **queryp, isc_time_t *finish, bool no_response, + bool age_untried) { + resquery_t *query = NULL; + fetchctx_t *fctx = NULL; + unsigned int rtt, rttms; + unsigned int factor; + dns_adbfind_t *find = NULL; + dns_adbaddrinfo_t *addrinfo; + isc_stdtime_t now; + + REQUIRE(queryp != NULL); + + query = *queryp; + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + return; + } + + FCTXTRACE("cancelquery"); + + query->attributes |= RESQUERY_ATTR_CANCELED; + + /* + * Should we update the RTT? + */ + if (finish != NULL || no_response) { + if (finish != NULL) { + /* + * We have both the start and finish times for this + * packet, so we can compute a real RTT. + */ + rtt = (unsigned int)isc_time_microdiff(finish, + &query->start); + factor = DNS_ADB_RTTADJDEFAULT; + + rttms = rtt / US_PER_MS; + if (rttms < DNS_RESOLVER_QRYRTTCLASS0) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt0); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt1); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt2); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt3); + } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt4); + } else { + inc_stats(fctx->res, + dns_resstatscounter_queryrtt5); + } + } else { + uint32_t value; + uint32_t mask; + + update_edns_stats(query); + + /* + * If "forward first;" is used and a forwarder timed + * out, do not attempt to query it again in this fetch + * context. + */ + if (fctx->fwdpolicy == dns_fwdpolicy_first && + ISFORWARDER(query->addrinfo)) + { + add_bad(fctx, query->rmessage, query->addrinfo, + ISC_R_TIMEDOUT, badns_forwarder); + } + + /* + * We don't have an RTT for this query. Maybe the + * packet was lost, or maybe this server is very + * slow. We don't know. Increase the RTT. + */ + INSIST(no_response); + value = isc_random32(); + if (query->addrinfo->srtt > 800000) { + mask = 0x3fff; + } else if (query->addrinfo->srtt > 400000) { + mask = 0x7fff; + } else if (query->addrinfo->srtt > 200000) { + mask = 0xffff; + } else if (query->addrinfo->srtt > 100000) { + mask = 0x1ffff; + } else if (query->addrinfo->srtt > 50000) { + mask = 0x3ffff; + } else if (query->addrinfo->srtt > 25000) { + mask = 0x7ffff; + } else { + mask = 0xfffff; + } + + /* + * Don't adjust timeout on EDNS queries unless we have + * seen a EDNS response. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 && + !EDNSOK(query->addrinfo)) + { + mask >>= 2; + } + + rtt = query->addrinfo->srtt + (value & mask); + if (rtt > MAX_SINGLE_QUERY_TIMEOUT_US) { + rtt = MAX_SINGLE_QUERY_TIMEOUT_US; + } + + /* + * Replace the current RTT with our value. + */ + factor = DNS_ADB_RTTADJREPLACE; + } + + dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor); + } + + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + /* Inform the ADB that we're ending a UDP fetch */ + dns_adb_endudpfetch(fctx->adb, query->addrinfo); + } + + /* + * Age RTTs of servers not tried. + */ + isc_stdtime_get(&now); + if (finish != NULL || age_untried) { + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, now); + } + } + } + + if ((finish != NULL || age_untried) && TRIEDFIND(fctx)) { + for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; + find = ISC_LIST_NEXT(find, publink)) + { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, + now); + } + } + } + } + + if ((finish != NULL || age_untried) && TRIEDALT(fctx)) { + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, now); + } + } + for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; + find = ISC_LIST_NEXT(find, publink)) + { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (UNMARKED(addrinfo)) { + dns_adb_agesrtt(fctx->adb, addrinfo, + now); + } + } + } + } + + /* + * Check for any outstanding dispatch responses and if they + * exist, cancel them. + */ + if (query->dispentry != NULL) { + dns_dispatch_done(&query->dispentry); + } + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + if (ISC_LINK_LINKED(query, link)) { + ISC_LIST_UNLINK(fctx->queries, query, link); + } + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + resquery_detach(queryp); +} + +static void +fctx_cleanup(fetchctx_t *fctx) { + dns_adbfind_t *find = NULL, *next_find = NULL; + dns_adbaddrinfo_t *addr = NULL, *next_addr = NULL; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + for (find = ISC_LIST_HEAD(fctx->finds); find != NULL; find = next_find) + { + next_find = ISC_LIST_NEXT(find, publink); + ISC_LIST_UNLINK(fctx->finds, find, publink); + dns_adb_destroyfind(&find); + fctx_unref(fctx); + } + fctx->find = NULL; + + for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL; + find = next_find) + { + next_find = ISC_LIST_NEXT(find, publink); + ISC_LIST_UNLINK(fctx->altfinds, find, publink); + dns_adb_destroyfind(&find); + fctx_unref(fctx); + } + fctx->altfind = NULL; + + for (addr = ISC_LIST_HEAD(fctx->forwaddrs); addr != NULL; + addr = next_addr) + { + next_addr = ISC_LIST_NEXT(addr, publink); + ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink); + dns_adb_freeaddrinfo(fctx->adb, &addr); + } + + for (addr = ISC_LIST_HEAD(fctx->altaddrs); addr != NULL; + addr = next_addr) + { + next_addr = ISC_LIST_NEXT(addr, publink); + ISC_LIST_UNLINK(fctx->altaddrs, addr, publink); + dns_adb_freeaddrinfo(fctx->adb, &addr); + } +} + +static void +fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) { + resquery_t *query = NULL, *next_query = NULL; + ISC_LIST(resquery_t) queries; + + FCTXTRACE("cancelqueries"); + + ISC_LIST_INIT(queries); + + /* + * Move the queries to a local list so we can cancel + * them without holding the lock. + */ + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + ISC_LIST_MOVE(queries, fctx->queries); + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + for (query = ISC_LIST_HEAD(queries); query != NULL; query = next_query) + { + next_query = ISC_LIST_NEXT(query, link); + + /* + * Note that we have to unlink the query here, + * because if it's still linked in fctx_cancelquery(), + * then it will try to unlink it from fctx->queries. + */ + ISC_LIST_UNLINK(queries, query, link); + fctx_cancelquery(&query, NULL, no_response, age_untried); + } +} + +static void +fcount_logspill(fetchctx_t *fctx, fctxcount_t *counter, bool final) { + char dbuf[DNS_NAME_FORMATSIZE]; + isc_stdtime_t now; + + if (!isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + return; + } + + /* Do not log a message if there were no dropped fetches. */ + if (counter->dropped == 0) { + return; + } + + /* Do not log the cumulative message if the previous log is recent. */ + isc_stdtime_get(&now); + if (!final && counter->logged > now - 60) { + return; + } + + dns_name_format(fctx->domain, dbuf, sizeof(dbuf)); + + if (!final) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "too many simultaneous fetches for %s " + "(allowed %d spilled %d)", + dbuf, counter->allowed, counter->dropped); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_SPILL, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "fetch counters for %s now being discarded " + "(allowed %d spilled %d; cumulative since " + "initial trigger event)", + dbuf, counter->allowed, counter->dropped); + } + + counter->logged = now; +} + +static isc_result_t +fcount_incr(fetchctx_t *fctx, bool force) { + isc_result_t result = ISC_R_SUCCESS; + zonebucket_t *dbucket = NULL; + fctxcount_t *counter = NULL; + uint32_t hashval; + uint32_t dbucketnum; + + REQUIRE(fctx != NULL); + REQUIRE(fctx->res != NULL); + + INSIST(fctx->dbucketnum == RES_NOBUCKET); + hashval = dns_name_fullhash(fctx->domain, false); + dbucketnum = hash_32(hashval, fctx->res->dhashbits); + + dbucket = &fctx->res->dbuckets[dbucketnum]; + + LOCK(&dbucket->lock); + for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL; + counter = ISC_LIST_NEXT(counter, link)) + { + if (dns_name_equal(counter->domain, fctx->domain)) { + break; + } + } + + if (counter == NULL) { + counter = isc_mem_get(fctx->res->mctx, sizeof(*counter)); + *counter = (fctxcount_t){ + .count = 1, + .allowed = 1, + }; + + counter->domain = dns_fixedname_initname(&counter->dfname); + ISC_LINK_INIT(counter, link); + dns_name_copy(fctx->domain, counter->domain); + ISC_LIST_APPEND(dbucket->list, counter, link); + } else { + uint_fast32_t spill = atomic_load_acquire(&fctx->res->zspill); + if (!force && spill != 0 && counter->count >= spill) { + counter->dropped++; + fcount_logspill(fctx, counter, false); + result = ISC_R_QUOTA; + } else { + counter->count++; + counter->allowed++; + } + } + UNLOCK(&dbucket->lock); + + if (result == ISC_R_SUCCESS) { + fctx->dbucketnum = dbucketnum; + } + + return (result); +} + +static void +fcount_decr(fetchctx_t *fctx) { + zonebucket_t *dbucket = NULL; + fctxcount_t *counter = NULL; + + REQUIRE(fctx != NULL); + + if (fctx->dbucketnum == RES_NOBUCKET) { + return; + } + + dbucket = &fctx->res->dbuckets[fctx->dbucketnum]; + + LOCK(&dbucket->lock); + for (counter = ISC_LIST_HEAD(dbucket->list); counter != NULL; + counter = ISC_LIST_NEXT(counter, link)) + { + if (dns_name_equal(counter->domain, fctx->domain)) { + break; + } + } + + if (counter != NULL) { + INSIST(counter->count != 0); + counter->count--; + fctx->dbucketnum = RES_NOBUCKET; + + if (counter->count == 0) { + fcount_logspill(fctx, counter, true); + ISC_LIST_UNLINK(dbucket->list, counter, link); + isc_mem_put(fctx->res->mctx, counter, sizeof(*counter)); + } + } + + UNLOCK(&dbucket->lock); +} + +static void +fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) { + dns_fetchevent_t *event, *next_event; + isc_task_t *task; + unsigned int count = 0; + isc_interval_t i; + bool logit = false; + isc_time_t now; + unsigned int old_spillat; + unsigned int new_spillat = 0; /* initialized to silence + * compiler warnings */ + + /* + * Caller must be holding the appropriate bucket lock. + */ + REQUIRE(fctx->state == fetchstate_done); + + FCTXTRACE("sendevents"); + + /* + * Keep some record of fetch result for logging later (if required). + */ + fctx->result = result; + fctx->exitline = line; + TIME_NOW(&now); + fctx->duration = isc_time_microdiff(&now, &fctx->start); + + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(fctx->events, event, ev_link); + + /* + * Only the regular fetch events should be counted for the + * clients-per-query limit, in case if there are multiple events + * registered for a single client. + */ + if (event->ev_type == DNS_EVENT_FETCHDONE) { + count++; + } + + if (event->ev_type == DNS_EVENT_TRYSTALE) { + /* + * Not applicable to TRY STALE events, this function is + * called when the fetch has either completed or timed + * out due to resolver-query-timeout being reached. + */ + isc_task_detach((isc_task_t **)&event->ev_sender); + isc_event_free((isc_event_t **)&event); + continue; + } + task = event->ev_sender; + event->ev_sender = fctx; + event->vresult = fctx->vresult; + if (!HAVE_ANSWER(fctx)) { + event->result = result; + } + + INSIST(event->result != ISC_R_SUCCESS || + dns_rdataset_isassociated(event->rdataset) || + fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig); + + /* + * Negative results must be indicated in event->result. + */ + if (dns_rdataset_isassociated(event->rdataset) && + NEGATIVE(event->rdataset)) + { + INSIST(event->result == DNS_R_NCACHENXDOMAIN || + event->result == DNS_R_NCACHENXRRSET); + } + + FCTXTRACE("event"); + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event)); + } + + if (HAVE_ANSWER(fctx) && fctx->spilled && + (count < fctx->res->spillatmax || fctx->res->spillatmax == 0)) + { + LOCK(&fctx->res->lock); + if (count == fctx->res->spillat && + !atomic_load_acquire(&fctx->res->exiting)) + { + old_spillat = fctx->res->spillat; + fctx->res->spillat += 5; + if (fctx->res->spillat > fctx->res->spillatmax && + fctx->res->spillatmax != 0) + { + fctx->res->spillat = fctx->res->spillatmax; + } + new_spillat = fctx->res->spillat; + if (new_spillat != old_spillat) { + logit = true; + } + isc_interval_set(&i, 20 * 60, 0); + result = isc_timer_reset(fctx->res->spillattimer, + isc_timertype_ticker, NULL, &i, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + UNLOCK(&fctx->res->lock); + if (logit) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "clients-per-query increased to %u", + new_spillat); + } + } +} + +static void +fctx__done_detach(fetchctx_t **fctxp, isc_result_t result, const char *file, + unsigned int line, const char *func) { + fetchctx_t *fctx = NULL; + dns_resolver_t *res = NULL; + bool no_response = false; + bool age_untried = false; + + REQUIRE(fctxp != NULL && VALID_FCTX(*fctxp)); + + fctx = *fctxp; + res = fctx->res; + + FCTXTRACE("done"); + +#ifdef FCTX_TRACE + fprintf(stderr, "%s:%s:%u:%s(%p, %p): %s\n", func, file, line, __func__, + fctx, fctxp, isc_result_totext(result)); +#else + UNUSED(file); + UNUSED(line); + UNUSED(func); +#endif + + LOCK(&res->buckets[fctx->bucketnum].lock); + INSIST(fctx->state != fetchstate_done); + fctx->state = fetchstate_done; + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + if (result == ISC_R_SUCCESS) { + if (fctx->qmin_warning != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "success resolving '%s' " + "after disabling qname minimization due " + "to '%s'", + fctx->info, + isc_result_totext(fctx->qmin_warning)); + } + + /* + * A success result indicates we got a response to a + * query. That query should be canceled already. If + * there still are any outstanding queries attached to the + * same fctx, then those have *not* gotten a response, + * so we set 'no_response' to true here: that way, when + * we run fctx_cancelqueries() below, the SRTTs will + * be adjusted. + */ + no_response = true; + } else if (result == ISC_R_TIMEDOUT) { + age_untried = true; + } + + fctx->qmin_warning = ISC_R_SUCCESS; + + fctx_cancelqueries(fctx, no_response, age_untried); + fctx_stoptimer(fctx); + + LOCK(&res->buckets[fctx->bucketnum].lock); + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx_sendevents(fctx, result, line); + fctx_shutdown(fctx); + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + fctx_detach(fctxp); +} + +static void +resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) { + resquery_t *query = (resquery_t *)arg; + resquery_t *copy = query; + fetchctx_t *fctx = NULL; + + QTRACE("senddone"); + + UNUSED(region); + + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + goto detach; + } + + /* + * See the note in resquery_connected() about reference + * counting on error conditions. + */ + switch (eresult) { + case ISC_R_SUCCESS: + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: + break; + + case ISC_R_HOSTUNREACH: + case ISC_R_NETUNREACH: + case ISC_R_NOPERM: + case ISC_R_ADDRNOTAVAIL: + case ISC_R_CONNREFUSED: + /* No route to remote. */ + FCTXTRACE3("query canceled in resquery_senddone(): " + "no route to host; no response", + eresult); + add_bad(fctx, query->rmessage, query->addrinfo, eresult, + badns_unreachable); + fctx_cancelquery(©, NULL, true, false); + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx_try(fctx, true, false); + break; + + default: + FCTXTRACE3("query canceled in resquery_senddone() " + "due to unexpected result; responding", + eresult); + fctx_cancelquery(©, NULL, false, false); + fctx_done_detach(&fctx, eresult); + break; + } + +detach: + resquery_detach(&query); +} + +static isc_result_t +fctx_addopt(dns_message_t *message, unsigned int version, uint16_t udpsize, + dns_ednsopt_t *ednsopts, size_t count) { + dns_rdataset_t *rdataset = NULL; + isc_result_t result; + + result = dns_message_buildopt(message, &rdataset, version, udpsize, + DNS_MESSAGEEXTFLAG_DO, ednsopts, count); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (dns_message_setopt(message, rdataset)); +} + +static void +fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) { + unsigned int seconds, us; + uint64_t limit; + isc_time_t now; + + /* + * Has this fetch already expired? + */ + isc_time_now(&now); + limit = isc_time_microdiff(&fctx->expires, &now); + if (limit < US_PER_MS) { + FCTXTRACE("fetch already expired"); + isc_interval_set(&fctx->interval, 0, 0); + return; + } + + us = fctx->res->retryinterval * US_PER_MS; + + /* + * Exponential backoff after the first few tries. + */ + if (fctx->restarts > fctx->res->nonbackofftries) { + int shift = fctx->restarts - fctx->res->nonbackofftries; + if (shift > 6) { + shift = 6; + } + us <<= shift; + } + + /* + * Add a fudge factor to the expected rtt based on the current + * estimate. + */ + if (rtt < 50000) { + rtt += 50000; + } else if (rtt < 100000) { + rtt += 100000; + } else { + rtt += 200000; + } + + /* + * Always wait for at least the expected rtt. + */ + if (us < rtt) { + us = rtt; + } + + /* + * But don't wait past the stale timeout (if any), the final + * expiration of the fetch, or for more than 10 seconds total. + */ + if ((fctx->options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0) { + uint64_t stale = isc_time_microdiff(&fctx->expires_try_stale, + &now); + if (stale >= US_PER_MS && us > stale) { + FCTXTRACE("setting stale timeout"); + us = stale; + } + } + if (us > limit) { + us = limit; + } + if (us > MAX_SINGLE_QUERY_TIMEOUT_US) { + us = MAX_SINGLE_QUERY_TIMEOUT_US; + } + + seconds = us / US_PER_SEC; + us -= seconds * US_PER_SEC; + isc_interval_set(&fctx->interval, seconds, us * NS_PER_US); + isc_time_nowplusinterval(&fctx->next_timeout, &fctx->interval); +} + +static isc_result_t +resquery_timeout(resquery_t *query) { + fetchctx_t *fctx = query->fctx; + dns_fetchevent_t *event = NULL, *next = NULL; + uint64_t timeleft; + isc_time_t now; + + FCTXTRACE("timeout"); + + /* + * If not configured for serve-stale, do nothing. + */ + if ((fctx->options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) == 0) { + return (ISC_R_SUCCESS); + } + + /* + * If we haven't reached the serve-stale timeout, do nothing. + * (Note that netmgr timeouts have millisecond accuracy, so + * anything less than 1000 microseconds is close enough to zero.) + */ + isc_time_now(&now); + timeleft = isc_time_microdiff(&fctx->expires_try_stale, &now); + if (timeleft >= US_PER_MS) { + return (ISC_R_SUCCESS); + } + + /* + * Send the TRYSTALE events. + */ + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; event = next) { + isc_task_t *sender = NULL; + + next = ISC_LIST_NEXT(event, ev_link); + if (event->ev_type != DNS_EVENT_TRYSTALE) { + continue; + } + + ISC_LIST_UNLINK(fctx->events, event, ev_link); + sender = event->ev_sender; + event->vresult = ISC_R_TIMEDOUT; + event->result = ISC_R_TIMEDOUT; + isc_task_sendanddetach(&sender, ISC_EVENT_PTR(&event)); + } + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + /* + * If the next timeout is more than 1ms in the future, + * resume waiting. + */ + timeleft = isc_time_microdiff(&fctx->next_timeout, &now); + if (timeleft >= US_PER_MS) { + dns_dispatch_resume(query->dispentry, (timeleft / US_PER_MS)); + return (ISC_R_COMPLETE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + unsigned int options) { + isc_result_t result; + dns_resolver_t *res = NULL; + resquery_t *query = NULL; + isc_sockaddr_t addr; + bool have_addr = false; + unsigned int srtt; + + FCTXTRACE("query"); + + res = fctx->res; + + srtt = addrinfo->srtt; + + /* + * Allow an additional second for the kernel to resend the SYN + * (or SYN without ECN in the case of stupid firewalls blocking + * ECN negotiation) over the current RTT estimate. + */ + if ((options & DNS_FETCHOPT_TCP) != 0) { + srtt += US_PER_SEC; + } + + /* + * A forwarder needs to make multiple queries. Give it at least + * a second to do these in. + */ + if (ISFORWARDER(addrinfo) && srtt < US_PER_SEC) { + srtt = US_PER_SEC; + } + + fctx_setretryinterval(fctx, srtt); + if (isc_interval_iszero(&fctx->interval)) { + FCTXTRACE("fetch expired"); + return (ISC_R_TIMEDOUT); + } + + INSIST(ISC_LIST_EMPTY(fctx->validators)); + + query = isc_mem_get(fctx->mctx, sizeof(*query)); + *query = (resquery_t){ .mctx = fctx->mctx, + .options = options, + .addrinfo = addrinfo, + .dispatchmgr = res->dispatchmgr }; + + isc_refcount_init(&query->references, 1); + + /* + * Note that the caller MUST guarantee that 'addrinfo' will + * remain valid until this query is canceled. + */ + + dns_message_create(fctx->mctx, DNS_MESSAGE_INTENTPARSE, + &query->rmessage); + TIME_NOW(&query->start); + + /* + * If this is a TCP query, then we need to make a socket and + * a dispatch for it here. Otherwise we use the resolver's + * shared dispatch. + */ + if (res->view->peers != NULL) { + dns_peer_t *peer = NULL; + isc_netaddr_t dstip; + bool usetcp = false; + isc_netaddr_fromsockaddr(&dstip, &addrinfo->sockaddr); + result = dns_peerlist_peerbyaddr(res->view->peers, &dstip, + &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getquerysource(peer, &addr); + if (result == ISC_R_SUCCESS) { + have_addr = true; + } + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + query->options |= DNS_FETCHOPT_TCP; + } + } + } + + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + int pf; + + pf = isc_sockaddr_pf(&addrinfo->sockaddr); + if (!have_addr) { + switch (pf) { + case PF_INET: + result = dns_dispatch_getlocaladdress( + res->dispatches4->dispatches[0], &addr); + break; + case PF_INET6: + result = dns_dispatch_getlocaladdress( + res->dispatches6->dispatches[0], &addr); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + break; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + } + isc_sockaddr_setport(&addr, 0); + + result = dns_dispatch_createtcp(res->dispatchmgr, &addr, + &addrinfo->sockaddr, + &query->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + + FCTXTRACE("connecting via TCP"); + } else { + if (have_addr) { + result = dns_dispatch_createudp(res->dispatchmgr, &addr, + &query->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + } else { + switch (isc_sockaddr_pf(&addrinfo->sockaddr)) { + case PF_INET: + dns_dispatch_attach( + dns_resolver_dispatchv4(res), + &query->dispatch); + break; + case PF_INET6: + dns_dispatch_attach( + dns_resolver_dispatchv6(res), + &query->dispatch); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_query; + } + } + + /* + * We should always have a valid dispatcher here. If we + * don't support a protocol family, then its dispatcher + * will be NULL, but we shouldn't be finding addresses + * for protocol types we don't support, so the + * dispatcher we found should never be NULL. + */ + INSIST(query->dispatch != NULL); + } + + fctx_attach(fctx, &query->fctx); + ISC_LINK_INIT(query, link); + query->magic = QUERY_MAGIC; + + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + if (dns_adbentry_overquota(addrinfo->entry)) { + result = ISC_R_QUOTA; + goto cleanup_dispatch; + } + + /* Inform the ADB that we're starting a UDP fetch */ + dns_adb_beginudpfetch(fctx->adb, addrinfo); + } + + LOCK(&res->buckets[fctx->bucketnum].lock); + ISC_LIST_APPEND(fctx->queries, query, link); + atomic_fetch_add_relaxed(&fctx->nqueries, 1); + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + /* Set up the dispatch and set the query ID */ + result = dns_dispatch_add( + query->dispatch, 0, isc_interval_ms(&fctx->interval), + &query->addrinfo->sockaddr, resquery_connected, + resquery_senddone, resquery_response, query, &query->id, + &query->dispentry); + if (result != ISC_R_SUCCESS) { + goto cleanup_udpfetch; + } + + /* Connect the socket */ + resquery_attach(query, &(resquery_t *){ NULL }); + result = dns_dispatch_connect(query->dispentry); + + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + return (result); + +cleanup_udpfetch: + if (!RESQUERY_CANCELED(query)) { + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + /* Inform the ADB that we're ending a UDP fetch */ + dns_adb_endudpfetch(fctx->adb, addrinfo); + } + } + +cleanup_dispatch: + fctx_detach(&query->fctx); + + if (query->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + +cleanup_query: + LOCK(&res->buckets[fctx->bucketnum].lock); + if (ISC_LINK_LINKED(query, link)) { + atomic_fetch_sub_release(&fctx->nqueries, 1); + ISC_LIST_UNLINK(fctx->queries, query, link); + } + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + query->magic = 0; + dns_message_detach(&query->rmessage); + isc_mem_put(fctx->mctx, query, sizeof(*query)); + + return (result); +} + +static bool +bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + + for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + if (isc_sockaddr_equal(sa, address)) { + return (true); + } + } + + return (false); +} + +static void +add_bad_edns(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return; + } +#endif /* ifdef ENABLE_AFL */ + if (bad_edns(fctx, address)) { + return; + } + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + + *sa = *address; + ISC_LIST_INITANDAPPEND(fctx->bad_edns, sa, link); +} + +static struct tried * +triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL; + tried = ISC_LIST_NEXT(tried, link)) + { + if (isc_sockaddr_equal(&tried->addr, address)) { + return (tried); + } + } + + return (NULL); +} + +static void +add_triededns(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + tried = triededns(fctx, address); + if (tried != NULL) { + tried->count++; + return; + } + + tried = isc_mem_get(fctx->mctx, sizeof(*tried)); + + tried->addr = *address; + tried->count = 1; + ISC_LIST_INITANDAPPEND(fctx->edns, tried, link); +} + +static size_t +addr2buf(void *buf, const size_t bufsize, const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + switch (netaddr.family) { + case AF_INET: + INSIST(bufsize >= 4); + memmove(buf, &netaddr.type.in, 4); + return (4); + case AF_INET6: + INSIST(bufsize >= 16); + memmove(buf, &netaddr.type.in6, 16); + return (16); + default: + UNREACHABLE(); + } + return (0); +} + +static size_t +add_serveraddr(uint8_t *buf, const size_t bufsize, const resquery_t *query) { + return (addr2buf(buf, bufsize, &query->addrinfo->sockaddr)); +} + +/* + * Client cookie is 8 octets. + * Server cookie is [8..32] octets. + */ +#define CLIENT_COOKIE_SIZE 8U +#define COOKIE_BUFFER_SIZE (8U + 32U) + +static void +compute_cc(const resquery_t *query, uint8_t *cookie, const size_t len) { + INSIST(len >= CLIENT_COOKIE_SIZE); + STATIC_ASSERT(sizeof(query->fctx->res->view->secret) >= + ISC_SIPHASH24_KEY_LENGTH, + "The view->secret size can't fit SipHash 2-4 key " + "length"); + + uint8_t buf[16] ISC_NONSTRING = { 0 }; + size_t buflen = add_serveraddr(buf, sizeof(buf), query); + + uint8_t digest[ISC_SIPHASH24_TAG_LENGTH] ISC_NONSTRING = { 0 }; + isc_siphash24(query->fctx->res->view->secret, buf, buflen, digest); + memmove(cookie, digest, CLIENT_COOKIE_SIZE); +} + +static isc_result_t +issecuredomain(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, bool checknta, bool *ntap, bool *issecure) { + dns_name_t suffix; + unsigned int labels; + + /* + * For DS variants we need to check fom the parent domain, + * since there may be a negative trust anchor for the name, + * while the enclosing domain where the DS record lives is + * under a secure entry point. + */ + labels = dns_name_countlabels(name); + if (dns_rdatatype_atparent(type) && labels > 1) { + dns_name_init(&suffix, NULL); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } + + return (dns_view_issecuredomain(view, name, now, checknta, ntap, + issecure)); +} + +static isc_result_t +resquery_send(resquery_t *query) { + isc_result_t result; + fetchctx_t *fctx = query->fctx; + dns_resolver_t *res = fctx->res; + isc_buffer_t buffer; + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_region_t r; + isc_netaddr_t ipaddr; + dns_tsigkey_t *tsigkey = NULL; + dns_peer_t *peer = NULL; + dns_compress_t cctx; + bool cleanup_cctx = false; + bool useedns; + bool secure_domain; + bool tcp = ((query->options & DNS_FETCHOPT_TCP) != 0); + dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; + unsigned ednsopt = 0; + uint16_t hint = 0, udpsize = 0; /* No EDNS */ +#ifdef HAVE_DNSTAP + isc_sockaddr_t localaddr, *la = NULL; + unsigned char zone[DNS_NAME_MAXWIRE]; + dns_dtmsgtype_t dtmsgtype; + isc_region_t zr; + isc_buffer_t zb; +#endif /* HAVE_DNSTAP */ + + QTRACE("send"); + + if (atomic_load_acquire(&res->exiting)) { + FCTXTRACE("resquery_send: resolver shutting down"); + return (ISC_R_SHUTTINGDOWN); + } + + result = dns_message_gettempname(fctx->qmessage, &qname); + if (result != ISC_R_SUCCESS) { + goto cleanup_temps; + } + result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup_temps; + } + + fctx->qmessage->opcode = dns_opcode_query; + + /* + * Set up question. + */ + dns_name_clone(fctx->name, qname); + dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION); + qname = NULL; + qrdataset = NULL; + + /* + * Set RD if the client has requested that we do a recursive + * query, or if we're sending to a forwarder. + */ + if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 || + ISFORWARDER(query->addrinfo)) + { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD; + } + + /* + * Set CD if the client says not to validate, or if the + * question is under a secure entry point and this is a + * recursive/forward query -- unless the client said not to. + */ + if ((query->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + /* Do nothing */ + } else if ((query->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; + } else if (res->view->enablevalidation && + ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0)) + { + bool checknta = ((query->options & DNS_FETCHOPT_NONTA) == 0); + bool ntacovered = false; + result = issecuredomain(res->view, fctx->name, fctx->type, + isc_time_seconds(&query->start), + checknta, &ntacovered, &secure_domain); + if (result != ISC_R_SUCCESS) { + secure_domain = false; + } + if (secure_domain || + (ISFORWARDER(query->addrinfo) && ntacovered)) + { + fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD; + } + } + + /* + * We don't have to set opcode because it defaults to query. + */ + fctx->qmessage->id = query->id; + + /* + * Convert the question to wire format. + */ + result = dns_compress_init(&cctx, -1, fctx->res->mctx); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + cleanup_cctx = true; + + isc_buffer_init(&buffer, query->data, sizeof(query->data)); + result = dns_message_renderbegin(fctx->qmessage, &cctx, &buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + result = dns_message_rendersection(fctx->qmessage, DNS_SECTION_QUESTION, + 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr); + (void)dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer); + + /* + * The ADB does not know about servers with "edns no". Check + * this, and then inform the ADB for future use. + */ + if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) == 0 && + peer != NULL && + dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS && + !useedns) + { + query->options |= DNS_FETCHOPT_NOEDNS0; + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOEDNS0, + FCTX_ADDRINFO_NOEDNS0); + } + + /* Sync NOEDNS0 flag in addrinfo->flags and options now. */ + if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) != 0) { + query->options |= DNS_FETCHOPT_NOEDNS0; + } + + if (fctx->timeout && (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + isc_sockaddr_t *sockaddr = &query->addrinfo->sockaddr; + struct tried *tried; + + /* + * If this is the first timeout for this server in this + * fetch context, try setting EDNS UDP buffer size to + * the largest UDP response size we have seen from this + * server so far. + * + * If this server has already timed out twice or more in + * this fetch context, force TCP. + */ + if ((tried = triededns(fctx, sockaddr)) != NULL) { + if (tried->count == 1U) { + hint = dns_adb_getudpsize(fctx->adb, + query->addrinfo); + } else if (tried->count >= 2U) { + if ((query->options & DNS_FETCHOPT_TCP) == 0) { + /* + * Inform the ADB that we're ending a + * UDP fetch, and turn the query into + * a TCP query. + */ + dns_adb_endudpfetch(fctx->adb, + query->addrinfo); + query->options |= DNS_FETCHOPT_TCP; + } + } + } + } + fctx->timeout = false; + + /* + * Use EDNS0, unless the caller doesn't want it, or we know that + * the remote server doesn't like it. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + if ((query->addrinfo->flags & FCTX_ADDRINFO_NOEDNS0) == 0) { + uint16_t peerudpsize = 0; + unsigned int version = DNS_EDNS_VERSION; + unsigned int flags = query->addrinfo->flags; + bool reqnsid = res->view->requestnsid; + bool sendcookie = res->view->sendcookie; + bool tcpkeepalive = false; + unsigned char cookie[COOKIE_BUFFER_SIZE]; + uint16_t padding = 0; + + /* + * Set the default UDP size to what was + * configured as 'edns-buffer-size' + */ + udpsize = res->udpsize; + + /* + * This server timed out for the first time in + * this fetch context and we received a response + * from it before (either in this fetch context + * or in a different one). Set 'udpsize' to the + * size of the largest UDP response we have + * received from this server so far. + */ + if (hint != 0U) { + udpsize = hint; + } + + /* + * If a fixed EDNS UDP buffer size is configured + * for this server, make sure we obey that. + */ + if (peer != NULL) { + (void)dns_peer_getudpsize(peer, &peerudpsize); + if (peerudpsize != 0) { + udpsize = peerudpsize; + } + } + + if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) { + version = flags & DNS_FETCHOPT_EDNSVERSIONMASK; + version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT; + } + + /* Request NSID/COOKIE/VERSION for current peer? + */ + if (peer != NULL) { + uint8_t ednsversion; + (void)dns_peer_getrequestnsid(peer, &reqnsid); + (void)dns_peer_getsendcookie(peer, &sendcookie); + result = dns_peer_getednsversion(peer, + &ednsversion); + if (result == ISC_R_SUCCESS && + ednsversion < version) + { + version = ednsversion; + } + } + if (NOCOOKIE(query->addrinfo)) { + sendcookie = false; + } + if (reqnsid) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_NSID; + ednsopts[ednsopt].length = 0; + ednsopts[ednsopt].value = NULL; + ednsopt++; + } + if (sendcookie) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_COOKIE; + ednsopts[ednsopt].length = + (uint16_t)dns_adb_getcookie( + fctx->adb, query->addrinfo, + cookie, sizeof(cookie)); + if (ednsopts[ednsopt].length != 0) { + ednsopts[ednsopt].value = cookie; + inc_stats( + fctx->res, + dns_resstatscounter_cookieout); + } else { + compute_cc(query, cookie, + CLIENT_COOKIE_SIZE); + ednsopts[ednsopt].value = cookie; + ednsopts[ednsopt].length = + CLIENT_COOKIE_SIZE; + inc_stats( + fctx->res, + dns_resstatscounter_cookienew); + } + ednsopt++; + } + + /* Add TCP keepalive option if appropriate */ + if ((peer != NULL) && tcp) { + (void)dns_peer_gettcpkeepalive(peer, + &tcpkeepalive); + } + if (tcpkeepalive) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_TCP_KEEPALIVE; + ednsopts[ednsopt].length = 0; + ednsopts[ednsopt].value = NULL; + ednsopt++; + } + + /* Add PAD for current peer? Require TCP for now + */ + if ((peer != NULL) && tcp) { + (void)dns_peer_getpadding(peer, &padding); + } + if (padding != 0) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_PAD; + ednsopts[ednsopt].length = 0; + ednsopt++; + dns_message_setpadding(fctx->qmessage, padding); + } + + query->ednsversion = version; + result = fctx_addopt(fctx->qmessage, version, udpsize, + ednsopts, ednsopt); + if (reqnsid && result == ISC_R_SUCCESS) { + query->options |= DNS_FETCHOPT_WANTNSID; + } else if (result != ISC_R_SUCCESS) { + /* + * We couldn't add the OPT, but we'll + * press on. We're not using EDNS0, so + * set the NOEDNS0 bit. + */ + query->options |= DNS_FETCHOPT_NOEDNS0; + query->ednsversion = -1; + udpsize = 0; + } + } else { + /* + * We know this server doesn't like EDNS0, so we + * won't use it. Set the NOEDNS0 bit since + * we're not using EDNS0. + */ + query->options |= DNS_FETCHOPT_NOEDNS0; + query->ednsversion = -1; + } + } else { + query->ednsversion = -1; + } + + /* + * Record the UDP EDNS size chosen. + */ + query->udpsize = udpsize; + + /* + * If we need EDNS0 to do this query and aren't using it, we + * lose. + */ + if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) { + result = DNS_R_SERVFAIL; + goto cleanup_message; + } + + add_triededns(fctx, &query->addrinfo->sockaddr); + + /* + * Clear CD if EDNS is not in use. + */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) != 0) { + fctx->qmessage->flags &= ~DNS_MESSAGEFLAG_CD; + } + + /* + * Add TSIG record tailored to the current recipient. + */ + result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto cleanup_message; + } + + if (tsigkey != NULL) { + result = dns_message_settsigkey(fctx->qmessage, tsigkey); + dns_tsigkey_detach(&tsigkey); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + } + + result = dns_message_rendersection(fctx->qmessage, + DNS_SECTION_ADDITIONAL, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + + result = dns_message_renderend(fctx->qmessage); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + +#ifdef HAVE_DNSTAP + memset(&zr, 0, sizeof(zr)); + isc_buffer_init(&zb, zone, sizeof(zone)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(fctx->domain, &cctx, &zb); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(&zb, &zr); + } +#endif /* HAVE_DNSTAP */ + + dns_compress_invalidate(&cctx); + cleanup_cctx = false; + + if (dns_message_gettsigkey(fctx->qmessage) != NULL) { + dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage), + &query->tsigkey); + result = dns_message_getquerytsig( + fctx->qmessage, fctx->res->mctx, &query->tsig); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + } + + /* + * Log the outgoing packet. + */ + dns_message_logfmtpacket( + fctx->qmessage, "sending packet to", &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_PACKETS, + &dns_master_style_comment, ISC_LOG_DEBUG(11), fctx->res->mctx); + + /* + * We're now done with the query message. + */ + dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); + + isc_buffer_usedregion(&buffer, &r); + + resquery_attach(query, &(resquery_t *){ NULL }); + dns_dispatch_send(query->dispentry, &r); + + QTRACE("sent"); + +#ifdef HAVE_DNSTAP + /* + * Log the outgoing query via dnstap. + */ + if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + dtmsgtype = DNS_DTTYPE_FQ; + } else { + dtmsgtype = DNS_DTTYPE_RQ; + } + + result = dns_dispentry_getlocaladdress(query->dispentry, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr, + tcp, &zr, &query->start, NULL, &buffer); +#endif /* HAVE_DNSTAP */ + + return (ISC_R_SUCCESS); + +cleanup_message: + if (cleanup_cctx) { + dns_compress_invalidate(&cctx); + } + + dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER); + + /* + * Stop the dispatcher from listening. + */ + dns_dispatch_done(&query->dispentry); + +cleanup_temps: + if (qname != NULL) { + dns_message_puttempname(fctx->qmessage, &qname); + } + if (qrdataset != NULL) { + dns_message_puttemprdataset(fctx->qmessage, &qrdataset); + } + + return (result); +} + +static void +resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) { + resquery_t *query = (resquery_t *)arg; + resquery_t *copy = query; + isc_result_t result; + fetchctx_t *fctx = NULL; + dns_resolver_t *res = NULL; + int pf; + + REQUIRE(VALID_QUERY(query)); + + QTRACE("connected"); + + UNUSED(region); + + fctx = query->fctx; + res = fctx->res; + + if (RESQUERY_CANCELED(query)) { + goto detach; + } + + if (atomic_load_acquire(&fctx->res->exiting)) { + eresult = ISC_R_SHUTTINGDOWN; + } + + /* + * The reference counting of resquery objects is complex: + * + * 1. attached in fctx_query() + * 2. attached prior to dns_dispatch_connect(), detached in + * resquery_connected() + * 3. attached prior to dns_dispatch_send(), detached in + * resquery_senddone() + * 4. finally detached in fctx_cancelquery() + * + * On error conditions, it's necessary to call fctx_cancelquery() + * from resquery_connected() or _senddone(), detaching twice + * within the same function. To make it clear that's what's + * happening, we cancel-and-detach 'copy' and detach 'query', + * which are both pointing to the same object. + */ + switch (eresult) { + case ISC_R_SUCCESS: + /* + * We are connected. Send the query. + */ + + result = resquery_send(query); + if (result != ISC_R_SUCCESS) { + FCTXTRACE("query canceled: resquery_send() failed; " + "responding"); + + fctx_cancelquery(©, NULL, false, false); + fctx_done_detach(&fctx, result); + break; + } + + fctx->querysent++; + + pf = isc_sockaddr_pf(&query->addrinfo->sockaddr); + if (pf == PF_INET) { + inc_stats(res, dns_resstatscounter_queryv4); + } else { + inc_stats(res, dns_resstatscounter_queryv6); + } + if (res->view->resquerystats != NULL) { + dns_rdatatypestats_increment(res->view->resquerystats, + fctx->type); + } + break; + + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: + FCTXTRACE3("shutdown in resquery_connected()", eresult); + fctx_cancelquery(©, NULL, true, false); + fctx_done_detach(&fctx, eresult); + break; + + case ISC_R_NETUNREACH: + case ISC_R_HOSTUNREACH: + case ISC_R_CONNREFUSED: + case ISC_R_NOPERM: + case ISC_R_ADDRNOTAVAIL: + case ISC_R_CONNECTIONRESET: + case ISC_R_TIMEDOUT: + /* + * Do not query this server again in this fetch context. + */ + FCTXTRACE3("query failed in resquery_connected(): " + "no response", + eresult); + add_bad(fctx, query->rmessage, query->addrinfo, eresult, + badns_unreachable); + fctx_cancelquery(©, NULL, true, false); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx_try(fctx, true, false); + break; + + default: + FCTXTRACE3("query canceled in resquery_connected() " + "due to unexpected result; responding", + eresult); + + fctx_cancelquery(©, NULL, false, false); + fctx_done_detach(&fctx, eresult); + break; + } + +detach: + resquery_detach(&query); +} + +static void +fctx_finddone(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + dns_adbfind_t *find = event->ev_sender; + dns_resolver_t *res; + bool want_try = false; + bool want_done = false; + unsigned int bucketnum; + uint_fast32_t pending; + + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + + FCTXTRACE("finddone"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + pending = atomic_fetch_sub_release(&fctx->pending, 1); + INSIST(pending > 0); + + if (ADDRWAIT(fctx)) { + /* + * The fetch is waiting for a name to be found. + */ + INSIST(!SHUTTINGDOWN(fctx)); + if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES) { + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + want_try = true; + } else { + fctx->findfail++; + if (atomic_load_acquire(&fctx->pending) == 0) { + /* + * We've got nothing else to wait for + * and don't know the answer. There's + * nothing to do but fail the fctx. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + want_done = true; + } + } + } + + isc_event_free(&event); + UNLOCK(&res->buckets[bucketnum].lock); + + dns_adb_destroyfind(&find); + + if (want_done) { + FCTXTRACE("fetch failed in finddone(); return " + "ISC_R_FAILURE"); + + /* Detach the extra reference from findname(). */ + fctx_unref(fctx); + fctx_done_detach(&fctx, ISC_R_FAILURE); + } else if (want_try) { + fctx_try(fctx, true, false); + fctx_detach(&fctx); + } else { + fctx_detach(&fctx); + } +} + +static bool +bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) { + isc_sockaddr_t *sa; + + for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; + sa = ISC_LIST_NEXT(sa, link)) + { + if (isc_sockaddr_equal(sa, address)) { + return (true); + } + } + + return (false); +} + +static bool +mark_bad(fetchctx_t *fctx) { + dns_adbfind_t *curr; + dns_adbaddrinfo_t *addrinfo; + bool all_bad = true; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return (false); + } +#endif /* ifdef ENABLE_AFL */ + + /* + * Mark all known bad servers, so we don't try to talk to them + * again. + */ + + /* + * Mark any bad nameservers. + */ + for (curr = ISC_LIST_HEAD(fctx->finds); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + } + + /* + * Mark any bad forwarders. + */ + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + + /* + * Mark any bad alternates. + */ + for (curr = ISC_LIST_HEAD(fctx->altfinds); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + for (addrinfo = ISC_LIST_HEAD(curr->list); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + } + + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (bad_server(fctx, &addrinfo->sockaddr)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + } else { + all_bad = false; + } + } + + return (all_bad); +} + +static void +add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo, + isc_result_t reason, badnstype_t badtype) { + char namebuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + char code[64]; + isc_buffer_t b; + isc_sockaddr_t *sa; + const char *spc = ""; + isc_sockaddr_t *address = &addrinfo->sockaddr; + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver) { + return; + } +#endif /* ifdef ENABLE_AFL */ + + if (reason == DNS_R_LAME) { + fctx->lamecount++; + } else { + switch (badtype) { + case badns_unreachable: + fctx->neterr++; + break; + case badns_response: + fctx->badresp++; + break; + case badns_validation: + break; /* counted as 'valfail' */ + case badns_forwarder: + /* + * We were called to prevent the given forwarder + * from being used again for this fetch context. + */ + break; + } + } + + if (bad_server(fctx, address)) { + /* + * We already know this server is bad. + */ + return; + } + + FCTXTRACE("add_bad"); + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + *sa = *address; + ISC_LIST_INITANDAPPEND(fctx->bad, sa, link); + + if (reason == DNS_R_LAME) { /* already logged */ + return; + } + + if (reason == DNS_R_UNEXPECTEDRCODE && + rmessage->rcode == dns_rcode_servfail && ISFORWARDER(addrinfo)) + { + return; + } + + if (reason == DNS_R_UNEXPECTEDRCODE) { + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(rmessage->rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + spc = " "; + } else if (reason == DNS_R_UNEXPECTEDOPCODE) { + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_opcode_totext((dns_opcode_t)rmessage->opcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + spc = " "; + } else { + code[0] = '\0'; + } + dns_name_format(fctx->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); + isc_sockaddr_format(address, addrbuf, sizeof(addrbuf)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, DNS_LOGMODULE_RESOLVER, + ISC_LOG_INFO, "%s%s%s resolving '%s/%s/%s': %s", code, spc, + isc_result_totext(reason), namebuf, typebuf, classbuf, addrbuf); +} + +/* + * Sort addrinfo list by RTT. + */ +static void +sort_adbfind(dns_adbfind_t *find, unsigned int bias) { + dns_adbaddrinfo_t *best, *curr; + dns_adbaddrinfolist_t sorted; + unsigned int best_srtt, curr_srtt; + + /* Lame N^2 bubble sort. */ + ISC_LIST_INIT(sorted); + while (!ISC_LIST_EMPTY(find->list)) { + best = ISC_LIST_HEAD(find->list); + best_srtt = best->srtt; + if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) { + best_srtt += bias; + } + curr = ISC_LIST_NEXT(best, publink); + while (curr != NULL) { + curr_srtt = curr->srtt; + if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) { + curr_srtt += bias; + } + if (curr_srtt < best_srtt) { + best = curr; + best_srtt = curr_srtt; + } + curr = ISC_LIST_NEXT(curr, publink); + } + ISC_LIST_UNLINK(find->list, best, publink); + ISC_LIST_APPEND(sorted, best, publink); + } + find->list = sorted; +} + +/* + * Sort a list of finds by server RTT. + */ +static void +sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { + dns_adbfind_t *best, *curr; + dns_adbfindlist_t sorted; + dns_adbaddrinfo_t *addrinfo, *bestaddrinfo; + unsigned int best_srtt, curr_srtt; + + /* Sort each find's addrinfo list by SRTT. */ + for (curr = ISC_LIST_HEAD(*findlist); curr != NULL; + curr = ISC_LIST_NEXT(curr, publink)) + { + sort_adbfind(curr, bias); + } + + /* Lame N^2 bubble sort. */ + ISC_LIST_INIT(sorted); + while (!ISC_LIST_EMPTY(*findlist)) { + best = ISC_LIST_HEAD(*findlist); + bestaddrinfo = ISC_LIST_HEAD(best->list); + INSIST(bestaddrinfo != NULL); + best_srtt = bestaddrinfo->srtt; + if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) { + best_srtt += bias; + } + curr = ISC_LIST_NEXT(best, publink); + while (curr != NULL) { + addrinfo = ISC_LIST_HEAD(curr->list); + INSIST(addrinfo != NULL); + curr_srtt = addrinfo->srtt; + if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) { + curr_srtt += bias; + } + if (curr_srtt < best_srtt) { + best = curr; + best_srtt = curr_srtt; + } + curr = ISC_LIST_NEXT(curr, publink); + } + ISC_LIST_UNLINK(*findlist, best, publink); + ISC_LIST_APPEND(sorted, best, publink); + } + *findlist = sorted; +} + +/* + * Return true iff the ADB find has a pending fetch for 'type'. This is + * used to find out whether we're in a loop, where a fetch is waiting for a + * find which is waiting for that same fetch. + * + * Note: This could be done with either an equivalence check (e.g., + * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If + * we checked for equivalence, that would mean we could only detect a loop + * when there is exactly one pending fetch, and we're it. If there were + * pending fetches for *both* address families, then a loop would be + * undetected. + * + * However, using a bit check means that in theory, an ADB find might be + * aborted that could have succeeded, if the other fetch had returned an + * answer. + * + * Since there's a good chance the server is broken and won't answer either + * query, and since an ADB find with two pending fetches is a very rare + * occurrance anyway, we regard this theoretical SERVFAIL as the lesser + * evil. + */ +static bool +waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { + switch (type) { + case dns_rdatatype_a: + return ((find->query_pending & DNS_ADBFIND_INET) != 0); + case dns_rdatatype_aaaa: + return ((find->query_pending & DNS_ADBFIND_INET6) != 0); + default: + return (false); + } +} + +static void +findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, + unsigned int options, unsigned int flags, isc_stdtime_t now, + bool *overquota, bool *need_alternate, unsigned int *no_addresses) { + dns_adbaddrinfo_t *ai = NULL; + dns_adbfind_t *find = NULL; + dns_resolver_t *res = fctx->res; + bool unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0); + isc_result_t result; + + FCTXTRACE("FINDNAME"); + + /* + * If this name is a subdomain of the query domain, tell + * the ADB to start looking using zone/hint data. This keeps us + * from getting stuck if the nameserver is beneath the zone cut + * and we don't know its address (e.g. because the A record has + * expired). + */ + if (dns_name_issubdomain(name, fctx->domain)) { + options |= DNS_ADBFIND_STARTATZONE; + } + options |= DNS_ADBFIND_GLUEOK; + options |= DNS_ADBFIND_HINTOK; + + /* + * See what we know about this address. + */ + fctx_addref(fctx); + result = dns_adb_createfind( + fctx->adb, res->buckets[fctx->bucketnum].task, fctx_finddone, + fctx, name, fctx->name, fctx->type, options, now, NULL, + res->view->dstport, fctx->depth + 1, fctx->qc, &find); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "fctx %p(%s): createfind for %s - %s", fctx, fctx->info, + fctx->clientstr, isc_result_totext(result)); + + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_ALIAS) { + char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * XXXRTH Follow the CNAME/DNAME chain? + */ + dns_adb_destroyfind(&find); + fctx->adberr++; + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_CNAME, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "skipping nameserver '%s' because it " + "is a CNAME, while resolving '%s'", + namebuf, fctx->info); + } + fctx_detach(&fctx); + return; + } + + if (!ISC_LIST_EMPTY(find->list)) { + /* + * We have at least some of the addresses for the + * name. + */ + INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0); + if (flags != 0 || port != 0) { + for (ai = ISC_LIST_HEAD(find->list); ai != NULL; + ai = ISC_LIST_NEXT(ai, publink)) + { + ai->flags |= flags; + if (port != 0) { + isc_sockaddr_setport(&ai->sockaddr, + port); + } + } + } + if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) { + ISC_LIST_APPEND(fctx->altfinds, find, publink); + } else { + ISC_LIST_APPEND(fctx->finds, find, publink); + } + return; + } + + /* + * We don't know any of the addresses for this name. + * + * The find may be waiting on a resolver fetch for a server + * address. We need to make sure it isn't waiting on *this* + * fetch, because if it is, we won't be answering it and it + * won't be answering us. + */ + if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) { + fctx->adberr++; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "loop detected resolving '%s'", fctx->info); + + if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { + atomic_fetch_add_relaxed(&fctx->pending, 1); + dns_adb_cancelfind(find); + } else { + dns_adb_destroyfind(&find); + fctx_detach(&fctx); + } + return; + } + + /* + * We may be waiting for another fetch to complete, and + * we'll get an event later when the find has what it needs. + */ + if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { + atomic_fetch_add_relaxed(&fctx->pending, 1); + + /* + * Bootstrap. + */ + if (need_alternate != NULL && !*need_alternate && unshared && + ((res->dispatches4 == NULL && + find->result_v6 != DNS_R_NXDOMAIN) || + (res->dispatches6 == NULL && + find->result_v4 != DNS_R_NXDOMAIN))) + { + *need_alternate = true; + } + if (no_addresses != NULL) { + (*no_addresses)++; + } + return; + } + + /* + * No addresses and no pending events: the find failed. + */ + if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) { + if (overquota != NULL) { + *overquota = true; + } + fctx->quotacount++; /* quota exceeded */ + } else if ((find->options & DNS_ADBFIND_LAMEPRUNED) != 0) { + fctx->lamecount++; /* cached lame server */ + } else { + fctx->adberr++; /* unreachable server, etc. */ + } + + /* + * If we know there are no addresses for the family we are using then + * try to add an alternative server. + */ + if (need_alternate != NULL && !*need_alternate && + ((res->dispatches4 == NULL && find->result_v6 == DNS_R_NXRRSET) || + (res->dispatches6 == NULL && find->result_v4 == DNS_R_NXRRSET))) + { + *need_alternate = true; + } + dns_adb_destroyfind(&find); + fctx_detach(&fctx); +} + +static bool +isstrictsubdomain(const dns_name_t *name1, const dns_name_t *name2) { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + + namereln = dns_name_fullcompare(name1, name2, &order, &nlabels); + return (namereln == dns_namereln_subdomain); +} + +static isc_result_t +fctx_getaddresses(fetchctx_t *fctx, bool badcache) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_resolver_t *res; + isc_stdtime_t now; + unsigned int stdoptions = 0; + dns_forwarder_t *fwd; + dns_adbaddrinfo_t *ai; + bool all_bad; + dns_rdata_ns_t ns; + bool need_alternate = false; + bool all_spilled = true; + unsigned int no_addresses = 0; + unsigned int ns_processed = 0; + + FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth); + + /* + * Don't pound on remote servers. (Failsafe!) + */ + fctx->restarts++; + if (fctx->restarts > 100) { + FCTXTRACE("too many restarts"); + return (DNS_R_SERVFAIL); + } + + res = fctx->res; + + if (fctx->depth > res->maxdepth) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "too much NS indirection resolving '%s' " + "(depth=%u, maxdepth=%u)", + fctx->info, fctx->depth, res->maxdepth); + return (DNS_R_SERVFAIL); + } + + /* + * Forwarders. + */ + + INSIST(ISC_LIST_EMPTY(fctx->forwaddrs)); + INSIST(ISC_LIST_EMPTY(fctx->altaddrs)); + + /* + * If we have DNS_FETCHOPT_NOFORWARD set and forwarding policy + * allows us to not forward - skip forwarders and go straight + * to NSes. This is currently used to make sure that priming + * query gets root servers' IP addresses in ADDITIONAL section. + */ + if ((fctx->options & DNS_FETCHOPT_NOFORWARD) != 0 && + (fctx->fwdpolicy != dns_fwdpolicy_only)) + { + goto normal_nses; + } + + /* + * If this fctx has forwarders, use them; otherwise use any + * selective forwarders specified in the view; otherwise use the + * resolver's forwarders (if any). + */ + fwd = ISC_LIST_HEAD(fctx->forwarders); + if (fwd == NULL) { + dns_forwarders_t *forwarders = NULL; + dns_name_t *name = fctx->name; + dns_name_t suffix; + unsigned int labels; + dns_fixedname_t fixed; + dns_name_t *domain; + + /* + * DS records are found in the parent server. + * Strip label to get the correct forwarder (if any). + */ + if (dns_rdatatype_atparent(fctx->type) && + dns_name_countlabels(name) > 1) + { + dns_name_init(&suffix, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } + + domain = dns_fixedname_initname(&fixed); + result = dns_fwdtable_find(res->view->fwdtable, name, domain, + &forwarders); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copy(domain, fctx->fwdname); + if (fctx->fwdpolicy == dns_fwdpolicy_only && + isstrictsubdomain(domain, fctx->domain)) + { + fcount_decr(fctx); + dns_name_copy(domain, fctx->domain); + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } + } + + while (fwd != NULL) { + if ((isc_sockaddr_pf(&fwd->addr) == AF_INET && + res->dispatches4 == NULL) || + (isc_sockaddr_pf(&fwd->addr) == AF_INET6 && + res->dispatches6 == NULL)) + { + fwd = ISC_LIST_NEXT(fwd, link); + continue; + } + ai = NULL; + result = dns_adb_findaddrinfo(fctx->adb, &fwd->addr, &ai, 0); + if (result == ISC_R_SUCCESS) { + dns_adbaddrinfo_t *cur; + ai->flags |= FCTX_ADDRINFO_FORWARDER; + cur = ISC_LIST_HEAD(fctx->forwaddrs); + while (cur != NULL && cur->srtt < ai->srtt) { + cur = ISC_LIST_NEXT(cur, publink); + } + if (cur != NULL) { + ISC_LIST_INSERTBEFORE(fctx->forwaddrs, cur, ai, + publink); + } else { + ISC_LIST_APPEND(fctx->forwaddrs, ai, publink); + } + } + fwd = ISC_LIST_NEXT(fwd, link); + } + + /* + * If the forwarding policy is "only", we don't need the + * addresses of the nameservers. + */ + if (fctx->fwdpolicy == dns_fwdpolicy_only) { + goto out; + } + + /* + * Normal nameservers. + */ +normal_nses: + stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT; + if (fctx->restarts == 1) { + /* + * To avoid sending out a flood of queries likely to + * result in NXRRSET, we suppress fetches for address + * families we don't have the first time through, + * provided that we have addresses in some family we + * can use. + * + * We don't want to set this option all the time, since + * if fctx->restarts > 1, we've clearly been having + * trouble with the addresses we had, so getting more + * could help. + */ + stdoptions |= DNS_ADBFIND_AVOIDFETCHES; + } + if (res->dispatches4 != NULL) { + stdoptions |= DNS_ADBFIND_INET; + } + if (res->dispatches6 != NULL) { + stdoptions |= DNS_ADBFIND_INET6; + } + + if ((stdoptions & DNS_ADBFIND_ADDRESSMASK) == 0) { + return (DNS_R_SERVFAIL); + } + + isc_stdtime_get(&now); + + INSIST(ISC_LIST_EMPTY(fctx->finds)); + INSIST(ISC_LIST_EMPTY(fctx->altfinds)); + + for (result = dns_rdataset_first(&fctx->nameservers); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&fctx->nameservers)) + { + bool overquota = false; + + dns_rdataset_current(&fctx->nameservers, &rdata); + /* + * Extract the name from the NS record. + */ + result = dns_rdata_tostruct(&rdata, &ns, NULL); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (no_addresses > NS_FAIL_LIMIT && + dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT) + { + stdoptions |= DNS_ADBFIND_NOFETCH; + } + findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota, + &need_alternate, &no_addresses); + + if (!overquota) { + all_spilled = false; + } + + dns_rdata_reset(&rdata); + dns_rdata_freestruct(&ns); + + if (++ns_processed >= NS_PROCESSING_LIMIT) { + result = ISC_R_NOMORE; + break; + } + } + if (result != ISC_R_NOMORE) { + return (result); + } + + /* + * Do we need to use 6 to 4? + */ + if (need_alternate) { + int family; + alternate_t *a; + family = (res->dispatches6 != NULL) ? AF_INET6 : AF_INET; + for (a = ISC_LIST_HEAD(res->alternates); a != NULL; + a = ISC_LIST_NEXT(a, link)) + { + if (!a->isaddress) { + findname(fctx, &a->_u._n.name, a->_u._n.port, + stdoptions, FCTX_ADDRINFO_DUALSTACK, + now, NULL, NULL, NULL); + continue; + } + if (isc_sockaddr_pf(&a->_u.addr) != family) { + continue; + } + ai = NULL; + result = dns_adb_findaddrinfo(fctx->adb, &a->_u.addr, + &ai, 0); + if (result == ISC_R_SUCCESS) { + dns_adbaddrinfo_t *cur; + ai->flags |= FCTX_ADDRINFO_FORWARDER; + ai->flags |= FCTX_ADDRINFO_DUALSTACK; + cur = ISC_LIST_HEAD(fctx->altaddrs); + while (cur != NULL && cur->srtt < ai->srtt) { + cur = ISC_LIST_NEXT(cur, publink); + } + if (cur != NULL) { + ISC_LIST_INSERTBEFORE(fctx->altaddrs, + cur, ai, publink); + } else { + ISC_LIST_APPEND(fctx->altaddrs, ai, + publink); + } + } + } + } + +out: + /* + * Mark all known bad servers. + */ + all_bad = mark_bad(fctx); + + /* + * How are we doing? + */ + if (all_bad) { + /* + * We've got no addresses. + */ + if (atomic_load_acquire(&fctx->pending) > 0) { + /* + * We're fetching the addresses, but don't have + * any yet. Tell the caller to wait for an + * answer. + */ + result = DNS_R_WAIT; + } else { + isc_time_t expire; + isc_interval_t i; + /* + * We've lost completely. We don't know any + * addresses, and the ADB has told us it can't + * get them. + */ + FCTXTRACE("no addresses"); + isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0); + result = isc_time_nowplusinterval(&expire, &i); + if (badcache && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_ds) && + result == ISC_R_SUCCESS) + { + dns_resolver_addbadcache(res, fctx->name, + fctx->type, &expire); + } + + result = ISC_R_FAILURE; + + /* + * If all of the addresses found were over the + * fetches-per-server quota, return the + * configured response. + */ + if (all_spilled) { + result = res->quotaresp[dns_quotatype_server]; + inc_stats(res, dns_resstatscounter_serverquota); + } + } + } else { + /* + * We've found some addresses. We might still be + * looking for more addresses. + */ + sort_finds(&fctx->finds, res->view->v6bias); + sort_finds(&fctx->altfinds, 0); + result = ISC_R_SUCCESS; + } + + return (result); +} + +static void +possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) { + isc_netaddr_t na; + char buf[ISC_NETADDR_FORMATSIZE]; + isc_sockaddr_t *sa; + bool aborted = false; + bool bogus; + dns_acl_t *blackhole; + isc_netaddr_t ipaddr; + dns_peer_t *peer = NULL; + dns_resolver_t *res; + const char *msg = NULL; + + sa = &addr->sockaddr; + + res = fctx->res; + isc_netaddr_fromsockaddr(&ipaddr, sa); + blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr); + (void)dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer); + + if (blackhole != NULL) { + int match; + + if ((dns_acl_match(&ipaddr, NULL, blackhole, res->view->aclenv, + &match, NULL) == ISC_R_SUCCESS) && + match > 0) + { + aborted = true; + } + } + + if (peer != NULL && dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS && + bogus) + { + aborted = true; + } + + if (aborted) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring blackholed / bogus server: "; + } else if (isc_sockaddr_isnetzero(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring net zero address: "; + } else if (isc_sockaddr_ismulticast(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring multicast address: "; + } else if (isc_sockaddr_isexperimental(sa)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring experimental address: "; + } else if (sa->type.sa.sa_family != AF_INET6) { + return; + } else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring IPv6 mapped IPV4 address: "; + } else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) { + addr->flags |= FCTX_ADDRINFO_MARK; + msg = "ignoring IPv6 compatibility IPV4 address: "; + } else { + return; + } + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + isc_netaddr_fromsockaddr(&na, sa); + isc_netaddr_format(&na, buf, sizeof(buf)); + FCTXTRACE2(msg, buf); + } +} + +static dns_adbaddrinfo_t * +fctx_nextaddress(fetchctx_t *fctx) { + dns_adbfind_t *find, *start; + dns_adbaddrinfo_t *addrinfo; + dns_adbaddrinfo_t *faddrinfo; + + /* + * Return the next untried address, if any. + */ + + /* + * Find the first unmarked forwarder (if any). + */ + for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + fctx->find = NULL; + fctx->forwarding = true; + + /* + * QNAME minimization is disabled when + * forwarding, and has to remain disabled if + * we switch back to normal recursion; otherwise + * forwarding could leave us in an inconsistent + * state. + */ + fctx->minimized = false; + return (addrinfo); + } + } + + /* + * No forwarders. Move to the next find. + */ + fctx->forwarding = false; + FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND); + + find = fctx->find; + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } else { + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } + } + + /* + * Find the first unmarked addrinfo. + */ + addrinfo = NULL; + if (find != NULL) { + start = find; + do { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + if (addrinfo != NULL) { + break; + } + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->finds); + } + } while (find != start); + } + + fctx->find = find; + if (addrinfo != NULL) { + return (addrinfo); + } + + /* + * No nameservers left. Try alternates. + */ + + FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDALT); + + find = fctx->altfind; + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } else { + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } + } + + /* + * Find the first unmarked addrinfo. + */ + addrinfo = NULL; + if (find != NULL) { + start = find; + do { + for (addrinfo = ISC_LIST_HEAD(find->list); + addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo)) { + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + if (addrinfo != NULL) { + break; + } + find = ISC_LIST_NEXT(find, publink); + if (find == NULL) { + find = ISC_LIST_HEAD(fctx->altfinds); + } + } while (find != start); + } + + faddrinfo = addrinfo; + + /* + * See if we have a better alternate server by address. + */ + + for (addrinfo = ISC_LIST_HEAD(fctx->altaddrs); addrinfo != NULL; + addrinfo = ISC_LIST_NEXT(addrinfo, publink)) + { + if (!UNMARKED(addrinfo)) { + continue; + } + possibly_mark(fctx, addrinfo); + if (UNMARKED(addrinfo) && + (faddrinfo == NULL || addrinfo->srtt < faddrinfo->srtt)) + { + if (faddrinfo != NULL) { + faddrinfo->flags &= ~FCTX_ADDRINFO_MARK; + } + addrinfo->flags |= FCTX_ADDRINFO_MARK; + break; + } + } + + if (addrinfo == NULL) { + addrinfo = faddrinfo; + fctx->altfind = find; + } + + return (addrinfo); +} + +static void +fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) { + isc_result_t result; + dns_adbaddrinfo_t *addrinfo = NULL; + dns_resolver_t *res; + isc_task_t *task; + unsigned int bucketnum; + + FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc)); + + REQUIRE(!ADDRWAIT(fctx)); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + /* We've already exceeded maximum query count */ + if (isc_counter_used(fctx->qc) > res->maxqueries) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded max queries resolving '%s' " + "(querycount=%u, maxqueries=%u)", + fctx->info, isc_counter_used(fctx->qc), + res->maxqueries); + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + return; + } + + addrinfo = fctx_nextaddress(fctx); + + /* Try to find an address that isn't over quota */ + while (addrinfo != NULL && dns_adbentry_overquota(addrinfo->entry)) { + addrinfo = fctx_nextaddress(fctx); + } + + if (addrinfo == NULL) { + /* We have no more addresses. Start over. */ + fctx_cancelqueries(fctx, true, false); + fctx_cleanup(fctx); + result = fctx_getaddresses(fctx, badcache); + if (result == DNS_R_WAIT) { + /* + * Sleep waiting for addresses. + */ + FCTXTRACE("addrwait"); + FCTX_ATTR_SET(fctx, FCTX_ATTR_ADDRWAIT); + return; + } else if (result != ISC_R_SUCCESS) { + /* + * Something bad happened. + */ + fctx_done_detach(&fctx, result); + return; + } + + addrinfo = fctx_nextaddress(fctx); + + while (addrinfo != NULL && + dns_adbentry_overquota(addrinfo->entry)) + { + addrinfo = fctx_nextaddress(fctx); + } + + /* + * While we may have addresses from the ADB, they + * might be bad ones. In this case, return SERVFAIL. + */ + if (addrinfo == NULL) { + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + return; + } + } + /* + * We're minimizing and we're not yet at the final NS - + * we need to launch a query for NS for 'upper' domain + */ + if (fctx->minimized && !fctx->forwarding) { + unsigned int options = fctx->options; + /* + * Also clear DNS_FETCHOPT_TRYSTALE_ONTIMEOUT here, + * otherwise every query minimization step will activate + * the try-stale timer again. + */ + options &= ~(DNS_FETCHOPT_QMINIMIZE | + DNS_FETCHOPT_TRYSTALE_ONTIMEOUT); + + /* + * Is another QNAME minimization fetch still running? + */ + if (fctx->qminfetch != NULL) { + bool validfctx = (DNS_FETCH_VALID(fctx->qminfetch) && + VALID_FCTX(fctx->qminfetch->private)); + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(fctx->qminname, namebuf, + sizeof(namebuf)); + dns_rdatatype_format(fctx->qmintype, typebuf, + sizeof(typebuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, + "fctx %p(%s): attempting QNAME " + "minimization fetch for %s/%s but " + "fetch %p(%s) still running", + fctx, fctx->info, namebuf, typebuf, + fctx->qminfetch, + validfctx ? fctx->qminfetch->private->info + : ""); + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + return; + } + + /* + * Turn on NOFOLLOW in relaxed mode so that QNAME minimisation + * doesn't cause additional queries to resolve the target of the + * QNAME minimisation request when a referral is returned. This + * will also reduce the impact of mis-matched NS RRsets where + * the child's NS RRset is garbage. If a delegation is + * discovered DNS_R_DELEGATION will be returned to resume_qmin. + */ + if ((options & DNS_FETCHOPT_QMIN_STRICT) == 0) { + options |= DNS_FETCHOPT_NOFOLLOW; + } + fctx_addref(fctx); + task = res->buckets[bucketnum].task; + result = dns_resolver_createfetch( + fctx->res, fctx->qminname, fctx->qmintype, fctx->domain, + &fctx->nameservers, NULL, NULL, 0, options, 0, fctx->qc, + task, resume_qmin, fctx, &fctx->qminrrset, NULL, + &fctx->qminfetch); + if (result != ISC_R_SUCCESS) { + fctx_unref(fctx); + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + } + return; + } + + result = isc_counter_increment(fctx->qc); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + "exceeded max queries resolving '%s'", + fctx->info); + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + return; + } + + result = fctx_query(fctx, addrinfo, fctx->options); + if (result != ISC_R_SUCCESS) { + fctx_done_detach(&fctx, result); + } else if (retrying) { + inc_stats(res, dns_resstatscounter_retry); + } +} + +static void +resume_qmin(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *fevent = NULL; + dns_resolver_t *res = NULL; + fetchctx_t *fctx = NULL; + isc_result_t result; + unsigned int bucketnum; + unsigned int findoptions = 0; + dns_name_t *fname = NULL, *dcname = NULL; + dns_fixedname_t ffixed, dcfixed; + + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + FCTXTRACE("resume_qmin"); + + fname = dns_fixedname_initname(&ffixed); + dcname = dns_fixedname_initname(&dcfixed); + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + + bucketnum = fctx->bucketnum; + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + + result = fevent->result; + isc_event_free(&event); + + dns_resolver_destroyfetch(&fctx->qminfetch); + + LOCK(&res->buckets[bucketnum].lock); + if (SHUTTINGDOWN(fctx)) { + maybe_cancel_validators(fctx, true); + UNLOCK(&res->buckets[bucketnum].lock); + fctx_detach(&fctx); + return; + } + UNLOCK(&res->buckets[bucketnum].lock); + + switch (result) { + case ISC_R_SHUTTINGDOWN: + case ISC_R_CANCELED: + goto cleanup; + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_FORMERR: + case DNS_R_REMOTEFORMERR: + case ISC_R_FAILURE: + if ((fctx->options & DNS_FETCHOPT_QMIN_STRICT) == 0) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; + /* + * We store the result. If we succeed in the end + * we'll issue a warning that the server is + * broken. + */ + fctx->qmin_warning = result; + } else { + goto cleanup; + } + break; + default: + /* + * When DNS_FETCHOPT_NOFOLLOW is set and a delegation + * was discovered, DNS_R_DELEGATION is returned and is + * processed here. + */ + break; + } + + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + result = dns_view_findzonecut(res->view, fctx->name, fname, dcname, + fctx->now, findoptions, true, true, + &fctx->nameservers, NULL); + + /* + * DNS_R_NXDOMAIN here means we have not loaded the root zone + * mirror yet - but DNS_R_NXDOMAIN is not a valid return value + * when doing recursion, we need to patch it. + */ + if (result == DNS_R_NXDOMAIN) { + result = DNS_R_SERVFAIL; + } + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + fcount_decr(fctx); + + dns_name_copy(fname, fctx->domain); + + result = fcount_incr(fctx, false); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_name_copy(dcname, fctx->qmindcname); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + + fctx_minimize_qname(fctx); + + if (!fctx->minimized) { + /* + * We have finished minimizing, but fctx->finds was + * filled at the beginning of the run - now we need to + * clear it before sending the final query to use proper + * nameservers. + */ + fctx_cancelqueries(fctx, false, false); + fctx_cleanup(fctx); + } + + fctx_try(fctx, true, false); + fctx_detach(&fctx); + return; + +cleanup: + /* Detach the extra reference from fctx_try() */ + fctx_unref(fctx); + fctx_done_detach(&fctx, result); +} + +static void +fctx_destroy(fetchctx_t *fctx, bool exiting) { + dns_resolver_t *res = NULL; + isc_sockaddr_t *sa = NULL, *next_sa = NULL; + struct tried *tried = NULL; + unsigned int bucketnum; + bool bucket_empty = false; + uint_fast32_t nfctx; + + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(ISC_LIST_EMPTY(fctx->events)); + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + REQUIRE(ISC_LIST_EMPTY(fctx->finds)); + REQUIRE(ISC_LIST_EMPTY(fctx->altfinds)); + REQUIRE(atomic_load_acquire(&fctx->pending) == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + + FCTXTRACE("destroy"); + + fctx->magic = 0; + + res = fctx->res; + bucketnum = fctx->bucketnum; + + LOCK(&res->buckets[bucketnum].lock); + REQUIRE(fctx->state != fetchstate_active); + + ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link); + + nfctx = atomic_fetch_sub_release(&res->nfctx, 1); + INSIST(nfctx > 0); + + dec_stats(res, dns_resstatscounter_nfetch); + + if (atomic_load_acquire(&res->buckets[bucketnum].exiting) && + ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs)) + { + bucket_empty = true; + } + UNLOCK(&res->buckets[bucketnum].lock); + + if (bucket_empty && exiting && + isc_refcount_decrement(&res->activebuckets) == 1) + { + send_shutdown_events(res); + } + + isc_refcount_destroy(&fctx->references); + + /* + * Free bad. + */ + for (sa = ISC_LIST_HEAD(fctx->bad); sa != NULL; sa = next_sa) { + next_sa = ISC_LIST_NEXT(sa, link); + ISC_LIST_UNLINK(fctx->bad, sa, link); + isc_mem_put(fctx->mctx, sa, sizeof(*sa)); + } + + for (tried = ISC_LIST_HEAD(fctx->edns); tried != NULL; + tried = ISC_LIST_HEAD(fctx->edns)) + { + ISC_LIST_UNLINK(fctx->edns, tried, link); + isc_mem_put(fctx->mctx, tried, sizeof(*tried)); + } + + for (sa = ISC_LIST_HEAD(fctx->bad_edns); sa != NULL; sa = next_sa) { + next_sa = ISC_LIST_NEXT(sa, link); + ISC_LIST_UNLINK(fctx->bad_edns, sa, link); + isc_mem_put(fctx->mctx, sa, sizeof(*sa)); + } + + isc_counter_detach(&fctx->qc); + fcount_decr(fctx); + dns_message_detach(&fctx->qmessage); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_db_detach(&fctx->cache); + dns_adb_detach(&fctx->adb); + + isc_timer_destroy(&fctx->timer); + + dns_resolver_detach(&fctx->res); + + isc_mem_free(fctx->mctx, fctx->info); + isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx)); +} + +static void +fctx_shutdown(fetchctx_t *fctx) { + isc_event_t *cevent = NULL; + + FCTXTRACE("shutdown"); + + /* + * Start the shutdown process for fctx, if it isn't already + * under way. + */ + if (!atomic_compare_exchange_strong_acq_rel(&fctx->want_shutdown, + &(bool){ false }, true)) + { + FCTXTRACE("already shut down"); + return; + } + + /* + * Unless we're still initializing (in which case the + * control event is still outstanding), we need to post + * the control event to tell the fetch we want it to + * exit. + */ + if (fctx->state != fetchstate_init) { + FCTXTRACE("posting control event"); + cevent = &fctx->control_event; + isc_task_sendto(fctx->res->buckets[fctx->bucketnum].task, + &cevent, fctx->bucketnum); + } +} + +static void +fctx_doshutdown(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + dns_resolver_t *res = NULL; + unsigned int bucketnum; + dns_validator_t *validator = NULL; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + FCTXTRACE("doshutdown"); + + /* + * An fctx that is shutting down is no longer in ADDRWAIT mode. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + + /* + * Cancel all pending validators. Note that this must be done + * without the bucket lock held, since that could cause + * deadlock. + */ + validator = ISC_LIST_HEAD(fctx->validators); + while (validator != NULL) { + dns_validator_cancel(validator); + validator = ISC_LIST_NEXT(validator, link); + } + + if (fctx->nsfetch != NULL) { + dns_resolver_cancelfetch(fctx->nsfetch); + } + + if (fctx->qminfetch != NULL) { + dns_resolver_cancelfetch(fctx->qminfetch); + } + + /* + * Shut down anything still running on behalf of this + * fetch, and clean up finds and addresses. To avoid deadlock + * with the ADB, we must do this before we lock the bucket lock. + * Increment the fctx references to avoid a race. + */ + fctx_cancelqueries(fctx, false, false); + fctx_cleanup(fctx); + + LOCK(&res->buckets[bucketnum].lock); + + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + + INSIST(fctx->state != fetchstate_init); + INSIST(atomic_load_acquire(&fctx->want_shutdown)); + + if (fctx->state == fetchstate_active) { + fctx->state = fetchstate_done; + + fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); + + /* Detach the extra ref from dns_resolver_createfetch(). */ + fctx_unref(fctx); + } + + UNLOCK(&res->buckets[bucketnum].lock); + + fctx_detach(&fctx); +} + +static void +fctx_start(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + dns_resolver_t *res = NULL; + unsigned int bucketnum; + isc_result_t result; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + FCTXTRACE("start"); + + LOCK(&res->buckets[bucketnum].lock); + + INSIST(fctx->state == fetchstate_init); + if (atomic_load_acquire(&fctx->want_shutdown)) { + /* + * We haven't started this fctx yet, but we've been + * requested to shut it down. Since we haven't started, + * we INSIST that we have no pending ADB finds or + * validations. + */ + INSIST(atomic_load_acquire(&fctx->pending) == 0); + INSIST(atomic_load_acquire(&fctx->nqueries) == 0); + INSIST(ISC_LIST_EMPTY(fctx->validators)); + UNLOCK(&res->buckets[bucketnum].lock); + + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + + /* Detach the extra ref from dns_resolver_createfetch(). */ + fctx_unref(fctx); + fctx_done_detach(&fctx, ISC_R_SHUTTINGDOWN); + return; + } + + /* + * Normal fctx startup. + */ + fctx->state = fetchstate_active; + + /* + * Reset the control event for later use in shutting + * down the fctx. + */ + ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, DNS_EVENT_FETCHCONTROL, + fctx_doshutdown, fctx, NULL, NULL, NULL); + + UNLOCK(&res->buckets[bucketnum].lock); + + result = fctx_starttimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done_detach(&fctx, result); + } else { + fctx_try(fctx, false, false); + } +} + +/* + * Fetch Creation, Joining, and Cancellation. + */ + +static void +fctx_add_event(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client, + dns_messageid_t id, isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t *fetch, isc_eventtype_t event_type) { + dns_fetchevent_t *event = NULL; + + FCTXTRACE("addevent"); + + /* + * We store the task we're going to send this event to in the + * sender field. We'll make the fetch the sender when we + * actually send the event. + */ + isc_task_attach(task, &(isc_task_t *){ NULL }); + event = (dns_fetchevent_t *)isc_event_allocate( + fctx->res->mctx, task, event_type, action, arg, sizeof(*event)); + event->result = DNS_R_SERVFAIL; + event->qtype = fctx->type; + event->db = NULL; + event->node = NULL; + event->rdataset = rdataset; + event->sigrdataset = sigrdataset; + event->fetch = fetch; + event->client = client; + event->id = id; + event->foundname = dns_fixedname_initname(&event->fname); + + /* + * Store the sigrdataset in the first event in case it is needed + * by any of the events. + */ + if (event->sigrdataset != NULL) { + ISC_LIST_PREPEND(fctx->events, event, ev_link); + } else { + ISC_LIST_APPEND(fctx->events, event, ev_link); + } +} + +static isc_result_t +fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client, + dns_messageid_t id, isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t *fetch) { + FCTXTRACE("join"); + + fctx_add_event(fctx, task, client, id, action, arg, rdataset, + sigrdataset, fetch, DNS_EVENT_FETCHDONE); + + fetch->magic = DNS_FETCH_MAGIC; + fctx_attach(fctx, &fetch->private); + + return (ISC_R_SUCCESS); +} + +static void +log_ns_ttl(fetchctx_t *fctx, const char *where) { + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), + "log_ns_ttl: fctx %p: %s: %s (in '%s'?): %u %u", fctx, + where, namebuf, domainbuf, fctx->ns_ttl_ok, fctx->ns_ttl); +} + +static void +fctx_expired(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "shut down hung fetch while resolving '%s'", fctx->info); + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + fctx_shutdown(fctx); + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + isc_event_free(&event); +} + +static isc_result_t +fctx_create(dns_resolver_t *res, isc_task_t *task, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain, + dns_rdataset_t *nameservers, const isc_sockaddr_t *client, + unsigned int options, unsigned int bucketnum, unsigned int depth, + isc_counter_t *qc, fetchctx_t **fctxp) { + fetchctx_t *fctx = NULL; + isc_result_t result; + isc_result_t iresult; + isc_interval_t interval; + unsigned int findoptions = 0; + char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1]; + uint_fast32_t nfctx; + size_t p; + + /* + * Caller must be holding the lock for bucket number + * 'bucketnum'. + */ + REQUIRE(fctxp != NULL && *fctxp == NULL); + + fctx = isc_mem_get(res->mctx, sizeof(*fctx)); + *fctx = (fetchctx_t){ + .type = type, + .qmintype = type, + .options = options, + .task = task, + .bucketnum = bucketnum, + .dbucketnum = RES_NOBUCKET, + .state = fetchstate_init, + .depth = depth, + .qmin_labels = 1, + .fwdpolicy = dns_fwdpolicy_none, + .result = ISC_R_FAILURE, + .exitline = -1, /* sentinel */ + }; + + dns_resolver_attach(res, &fctx->res); + + if (qc != NULL) { + isc_counter_attach(qc, &fctx->qc); + } else { + result = isc_counter_create(res->mctx, res->maxqueries, + &fctx->qc); + if (result != ISC_R_SUCCESS) { + goto cleanup_fetch; + } + } + + /* + * Make fctx->info point to a copy of a formatted string + * "name/type". FCTXTRACE won't work until this is done. + */ + dns_name_format(name, buf, sizeof(buf)); + p = strlcat(buf, "/", sizeof(buf)); + INSIST(p + DNS_RDATATYPE_FORMATSIZE < sizeof(buf)); + dns_rdatatype_format(type, buf + p, sizeof(buf) - p); + fctx->info = isc_mem_strdup(res->mctx, buf); + + FCTXTRACE("create"); + + isc_refcount_init(&fctx->references, 1); + + ISC_LIST_INIT(fctx->queries); + ISC_LIST_INIT(fctx->finds); + ISC_LIST_INIT(fctx->altfinds); + ISC_LIST_INIT(fctx->forwaddrs); + ISC_LIST_INIT(fctx->altaddrs); + ISC_LIST_INIT(fctx->forwarders); + ISC_LIST_INIT(fctx->bad); + ISC_LIST_INIT(fctx->edns); + ISC_LIST_INIT(fctx->bad_edns); + ISC_LIST_INIT(fctx->validators); + + atomic_init(&fctx->attributes, 0); + + fctx->name = dns_fixedname_initname(&fctx->fname); + fctx->nsname = dns_fixedname_initname(&fctx->nsfname); + fctx->domain = dns_fixedname_initname(&fctx->dfname); + fctx->qminname = dns_fixedname_initname(&fctx->qminfname); + fctx->qmindcname = dns_fixedname_initname(&fctx->qmindcfname); + fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname); + + dns_name_copy(name, fctx->name); + dns_name_copy(name, fctx->qminname); + + dns_rdataset_init(&fctx->nameservers); + dns_rdataset_init(&fctx->qminrrset); + dns_rdataset_init(&fctx->nsrrset); + + TIME_NOW(&fctx->start); + fctx->now = (isc_stdtime_t)fctx->start.seconds; + + if (client != NULL) { + isc_sockaddr_format(client, fctx->clientstr, + sizeof(fctx->clientstr)); + } else { + strlcpy(fctx->clientstr, "", sizeof(fctx->clientstr)); + } + + if (domain == NULL) { + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed; + dns_name_t *fname = dns_fixedname_initname(&fixed); + unsigned int labels; + const dns_name_t *fwdname = name; + dns_name_t suffix; + + /* + * DS records are found in the parent server. Strip one + * leading label from the name (to be used in finding + * the forwarder). + */ + if (dns_rdatatype_atparent(fctx->type) && + dns_name_countlabels(name) > 1) + { + dns_name_init(&suffix, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + fwdname = &suffix; + } + + /* Find the forwarder for this name. */ + result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname, + fname, &forwarders); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copy(fname, fctx->fwdname); + } + + if (fctx->fwdpolicy != dns_fwdpolicy_only) { + dns_fixedname_t dcfixed; + dns_name_t *dcname = dns_fixedname_initname(&dcfixed); + + /* + * The caller didn't supply a query domain and + * nameservers, and we're not in forward-only + * mode, so find the best nameservers to use. + */ + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + result = dns_view_findzonecut(res->view, name, fname, + dcname, fctx->now, + findoptions, true, true, + &fctx->nameservers, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup_nameservers; + } + + dns_name_copy(fname, fctx->domain); + dns_name_copy(dcname, fctx->qmindcname); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + } else { + /* + * We're in forward-only mode. Set the query + * domain. + */ + dns_name_copy(fname, fctx->domain); + dns_name_copy(fname, fctx->qmindcname); + /* + * Disable query minimization + */ + options &= ~DNS_FETCHOPT_QMINIMIZE; + } + } else { + dns_name_copy(domain, fctx->domain); + dns_name_copy(domain, fctx->qmindcname); + dns_rdataset_clone(nameservers, &fctx->nameservers); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + } + + /* + * Are there too many simultaneous queries for this domain? + */ + result = fcount_incr(fctx, false); + if (result != ISC_R_SUCCESS) { + result = fctx->res->quotaresp[dns_quotatype_zone]; + inc_stats(res, dns_resstatscounter_zonequota); + goto cleanup_nameservers; + } + + log_ns_ttl(fctx, "fctx_create"); + + if (!dns_name_issubdomain(fctx->name, fctx->domain)) { + dns_name_format(fctx->domain, buf, sizeof(buf)); + UNEXPECTED_ERROR("'%s' is not subdomain of '%s'", fctx->info, + buf); + result = ISC_R_UNEXPECTED; + goto cleanup_fcount; + } + + dns_message_create(res->mctx, DNS_MESSAGE_INTENTRENDER, + &fctx->qmessage); + + /* + * Compute an expiration time for the entire fetch. + */ + isc_interval_set(&interval, res->query_timeout / 1000, + res->query_timeout % 1000 * 1000000); + iresult = isc_time_nowplusinterval(&fctx->expires, &interval); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_time_nowplusinterval: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + /* + * As a backstop, we also set a timer to stop the fetch + * if in-band netmgr timeouts don't work. It will fire two + * seconds after the fetch should have finished. (This + * should be enough of a gap to avoid the timer firing + * while a response is being processed normally.) + */ + isc_interval_set(&interval, 2, 0); + iresult = isc_time_add(&fctx->expires, &interval, &fctx->final); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_time_add: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + /* + * Create an inactive timer to enforce maximum query + * lifetime. It will be made active when the fetch is + * started. + */ + iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL, + NULL, res->buckets[bucketnum].task, + fctx_expired, fctx, &fctx->timer); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_timer_create: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + /* + * Default retry interval initialization. We set the interval + * now mostly so it won't be uninitialized. It will be set to + * the correct value before a query is issued. + */ + isc_interval_set(&fctx->interval, 2, 0); + + /* + * If stale answers are enabled, compute an expiration time + * after which stale data will be served, if the target RRset is + * available in cache. + */ + if ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0) { + INSIST(res->view->staleanswerclienttimeout <= + (res->query_timeout - 1000)); + isc_interval_set( + &interval, res->view->staleanswerclienttimeout / 1000, + res->view->staleanswerclienttimeout % 1000 * 1000000); + iresult = isc_time_nowplusinterval(&fctx->expires_try_stale, + &interval); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_time_nowplusinterval: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_timer; + } + } + + /* + * Attach to the view's cache and adb. + */ + dns_db_attach(res->view->cachedb, &fctx->cache); + dns_adb_attach(res->view->adb, &fctx->adb); + isc_mem_attach(res->mctx, &fctx->mctx); + + ISC_LIST_INIT(fctx->events); + ISC_LINK_INIT(fctx, link); + fctx->magic = FCTX_MAGIC; + + /* + * If qname minimization is enabled we need to trim + * the name in fctx to proper length. + */ + if ((options & DNS_FETCHOPT_QMINIMIZE) != 0) { + fctx->ip6arpaskip = (options & DNS_FETCHOPT_QMIN_SKIP_IP6A) != + 0 && + dns_name_issubdomain(fctx->name, &ip6_arpa); + fctx_minimize_qname(fctx); + } + + ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link); + + nfctx = atomic_fetch_add_relaxed(&res->nfctx, 1); + INSIST(nfctx < UINT32_MAX); + + inc_stats(res, dns_resstatscounter_nfetch); + + *fctxp = fctx; + + return (ISC_R_SUCCESS); + +cleanup_timer: + isc_timer_destroy(&fctx->timer); + +cleanup_qmessage: + dns_message_detach(&fctx->qmessage); + +cleanup_fcount: + fcount_decr(fctx); + +cleanup_nameservers: + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + isc_mem_free(res->mctx, fctx->info); + isc_counter_detach(&fctx->qc); + +cleanup_fetch: + dns_resolver_detach(&fctx->res); + isc_mem_put(res->mctx, fctx, sizeof(*fctx)); + + return (result); +} + +/* + * Handle Responses + */ +static bool +is_lame(fetchctx_t *fctx, dns_message_t *message) { + dns_name_t *name; + dns_rdataset_t *rdataset; + isc_result_t result; + + if (message->rcode != dns_rcode_noerror && + message->rcode != dns_rcode_yxdomain && + message->rcode != dns_rcode_nxdomain) + { + return (false); + } + + if (message->counts[DNS_SECTION_ANSWER] != 0) { + return (false); + } + + if (message->counts[DNS_SECTION_AUTHORITY] == 0) { + return (false); + } + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_namereln_t namereln; + int order; + unsigned int labels; + if (rdataset->type != dns_rdatatype_ns) { + continue; + } + namereln = dns_name_fullcompare(name, fctx->domain, + &order, &labels); + if (namereln == dns_namereln_equal && + (message->flags & DNS_MESSAGEFLAG_AA) != 0) + { + return (false); + } + if (namereln == dns_namereln_subdomain) { + return (false); + } + return (true); + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + + return (false); +} + +static void +log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) { + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + dns_name_format(fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf)); + isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "lame server resolving '%s' (in '%s'?): %s", namebuf, + domainbuf, addrbuf); +} + +static void +log_formerr(fetchctx_t *fctx, const char *format, ...) { + char nsbuf[ISC_SOCKADDR_FORMATSIZE]; + char msgbuf[2048]; + va_list args; + + va_start(args, format); + vsnprintf(msgbuf, sizeof(msgbuf), format, args); + va_end(args); + + isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "DNS format error from %s resolving %s for %s: %s", nsbuf, + fctx->info, fctx->clientstr, msgbuf); +} + +static isc_result_t +same_question(fetchctx_t *fctx, dns_message_t *message) { + isc_result_t result; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + + /* + * Caller must be holding the fctx lock. + */ + + /* + * XXXRTH Currently we support only one question. + */ + if (message->counts[DNS_SECTION_QUESTION] == 0) { + if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) { + /* + * If TC=1 and the question section is empty, we + * accept the reply message as a truncated + * answer, to be retried over TCP. + * + * It is really a FORMERR condition, but this is + * a workaround to accept replies from some + * implementations. + * + * Because the question section matching is not + * performed, the worst that could happen is + * that an attacker who gets past the ID and + * source port checks can force the use of + * TCP. This is considered an acceptable risk. + */ + log_formerr(fctx, "empty question section, " + "accepting it anyway as TC=1"); + return (ISC_R_SUCCESS); + } else { + log_formerr(fctx, "empty question section"); + return (DNS_R_FORMERR); + } + } else if (message->counts[DNS_SECTION_QUESTION] > 1) { + log_formerr(fctx, "too many questions"); + return (DNS_R_FORMERR); + } + + result = dns_message_firstname(message, DNS_SECTION_QUESTION); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_message_currentname(message, DNS_SECTION_QUESTION, &name); + rdataset = ISC_LIST_HEAD(name->list); + INSIST(rdataset != NULL); + INSIST(ISC_LIST_NEXT(rdataset, link) == NULL); + + if (fctx->type != rdataset->type || + fctx->res->rdclass != rdataset->rdclass || + !dns_name_equal(fctx->name, name)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + char classbuf[DNS_RDATACLASS_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + log_formerr(fctx, "question section mismatch: got %s/%s/%s", + namebuf, classbuf, typebuf); + return (DNS_R_FORMERR); + } + + return (ISC_R_SUCCESS); +} + +static void +clone_results(fetchctx_t *fctx) { + dns_fetchevent_t *event = NULL, *hevent = NULL; + + FCTXTRACE("clone_results"); + + /* + * Set up any other events to have the same data as the first + * event. + * + * Caller must be holding the appropriate lock. + */ + + fctx->cloned = true; + + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = ISC_LIST_NEXT(event, ev_link)) + { + /* This is the the head event; keep a pointer and move + * on */ + if (hevent == NULL) { + hevent = ISC_LIST_HEAD(fctx->events); + continue; + } + + if (event->ev_type == DNS_EVENT_TRYSTALE) { + /* + * We don't need to clone resulting data to this + * type of event, as its associated callback is + * only called when stale-answer-client-timeout + * triggers, and the logic in there doesn't + * expect any result as input, as it will itself + * lookup for stale data in cache to use as + * result, if any is available. + * + * Also, if we reached this point, then the + * whole fetch context is done, it will cancel + * timers, process associated callbacks of type + * DNS_EVENT_FETCHDONE, and silently remove/free + * events of type DNS_EVENT_TRYSTALE. + */ + continue; + } + + event->result = hevent->result; + dns_name_copy(hevent->foundname, event->foundname); + dns_db_attach(hevent->db, &event->db); + dns_db_attachnode(hevent->db, hevent->node, &event->node); + + INSIST(hevent->rdataset != NULL); + INSIST(event->rdataset != NULL); + if (dns_rdataset_isassociated(hevent->rdataset)) { + dns_rdataset_clone(hevent->rdataset, event->rdataset); + } + + INSIST(!(hevent->sigrdataset == NULL && + event->sigrdataset != NULL)); + if (hevent->sigrdataset != NULL && + dns_rdataset_isassociated(hevent->sigrdataset) && + event->sigrdataset != NULL) + { + dns_rdataset_clone(hevent->sigrdataset, + event->sigrdataset); + } + } +} + +#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0) +#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0) +#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0) +#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0) +#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0) +#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0) +#define CHECKNAMES(r) (((r)->attributes & DNS_RDATASETATTR_CHECKNAMES) != 0) + +/* + * Cancel validators associated with '*fctx' if it is ready to be + * destroyed (i.e., no queries waiting for it and no pending ADB finds). + * + * Requires: + * '*fctx' is shutting down. + */ +static void +maybe_cancel_validators(fetchctx_t *fctx, bool locked) { + unsigned int bucketnum; + dns_resolver_t *res = fctx->res; + dns_validator_t *validator, *next_validator; + + bucketnum = fctx->bucketnum; + if (!locked) { + LOCK(&res->buckets[bucketnum].lock); + } + + REQUIRE(SHUTTINGDOWN(fctx)); + + if (atomic_load_acquire(&fctx->pending) != 0 || + atomic_load_acquire(&fctx->nqueries) != 0) + { + goto unlock; + } + + for (validator = ISC_LIST_HEAD(fctx->validators); validator != NULL; + validator = next_validator) + { + next_validator = ISC_LIST_NEXT(validator, link); + dns_validator_cancel(validator); + } +unlock: + if (!locked) { + UNLOCK(&res->buckets[bucketnum].lock); + } +} + +/* + * typemap with just RRSIG(46) and NSEC(47) bits set. + * + * Bitmap calculation from dns_nsec_setbit: + * + * 46 47 + * shift = 7 - (type % 8); 0 1 + * mask = 1 << shift; 0x02 0x01 + * array[type / 8] |= mask; + * + * Window (0), bitmap length (6), and bitmap. + */ +static const unsigned char minimal_typemap[] = { 0, 6, 0, 0, 0, 0, 0, 0x03 }; + +static bool +is_minimal_nsec(dns_rdataset_t *nsecset) { + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsecset, &rdataset); + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec_t nsec; + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec.len == sizeof(minimal_typemap) && + memcmp(nsec.typebits, minimal_typemap, nsec.len) == 0) + { + dns_rdataset_disassociate(&rdataset); + return (true); + } + } + dns_rdataset_disassociate(&rdataset); + return (false); +} + +/* + * If there is a SOA record in the type map then there must be a DNSKEY. + */ +static bool +check_soa_and_dnskey(dns_rdataset_t *nsecset) { + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsecset, &rdataset); + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + if (dns_nsec_typepresent(&rdata, dns_rdatatype_soa) && + (!dns_nsec_typepresent(&rdata, dns_rdatatype_dnskey) || + !dns_nsec_typepresent(&rdata, dns_rdatatype_ns))) + { + dns_rdataset_disassociate(&rdataset); + return (false); + } + } + dns_rdataset_disassociate(&rdataset); + return (true); +} + +/* + * Look for NSEC next name that starts with the label '\000'. + */ +static bool +has_000_label(dns_rdataset_t *nsecset) { + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsecset, &rdataset); + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + if (rdata.length > 1 && rdata.data[0] == 1 && + rdata.data[1] == 0) + { + dns_rdataset_disassociate(&rdataset); + return (true); + } + } + dns_rdataset_disassociate(&rdataset); + return (false); +} + +/* + * The validator has finished. + */ +static void +validated(isc_task_t *task, isc_event_t *event) { + dns_adbaddrinfo_t *addrinfo = NULL; + dns_dbnode_t *node = NULL; + dns_dbnode_t *nsnode = NULL; + dns_fetchevent_t *hevent = NULL; + dns_name_t *name = NULL; + dns_rdataset_t *ardataset = NULL; + dns_rdataset_t *asigrdataset = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdataset_t *sigrdataset = NULL; + dns_resolver_t *res = NULL; + dns_valarg_t *valarg = NULL; + dns_validatorevent_t *vevent = NULL; + fetchctx_t *fctx = NULL; + bool chaining; + bool negative; + bool sentresponse; + isc_result_t eresult = ISC_R_SUCCESS; + isc_result_t result = ISC_R_SUCCESS; + isc_stdtime_t now; + uint32_t ttl; + unsigned options; + uint32_t bucketnum; + dns_fixedname_t fwild; + dns_name_t *wild = NULL; + dns_message_t *message = NULL; + + UNUSED(task); /* for now */ + + REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE); + valarg = event->ev_arg; + + REQUIRE(VALID_FCTX(valarg->fctx)); + REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators)); + + fctx = valarg->fctx; + valarg->fctx = NULL; + + FCTXTRACE("received validation completion event"); + + res = fctx->res; + addrinfo = valarg->addrinfo; + + message = valarg->message; + valarg->message = NULL; + + vevent = (dns_validatorevent_t *)event; + fctx->vresult = vevent->result; + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + ISC_LIST_UNLINK(fctx->validators, vevent->validator, link); + fctx->validator = NULL; + UNLOCK(&res->buckets[bucketnum].lock); + + /* + * Destroy the validator early so that we can + * destroy the fctx if necessary. Save the wildcard name. + */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + wild = dns_fixedname_initname(&fwild); + dns_name_copy(dns_fixedname_name(&vevent->validator->wild), + wild); + } + dns_validator_destroy(&vevent->validator); + isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); + + negative = (vevent->rdataset == NULL); + + LOCK(&res->buckets[bucketnum].lock); + sentresponse = ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0); + + /* + * If shutting down, ignore the results. Check to see if we're + * done waiting for validator completions and ADB pending + * events; if so, destroy the fctx. + */ + if (SHUTTINGDOWN(fctx) && !sentresponse) { + UNLOCK(&res->buckets[bucketnum].lock); + fctx_detach(&fctx); + goto cleanup_event; + } + + isc_stdtime_get(&now); + + /* + * If chaining, we need to make sure that the right result code + * is returned, and that the rdatasets are bound. + */ + if (vevent->result == ISC_R_SUCCESS && !negative && + vevent->rdataset != NULL && CHAINING(vevent->rdataset)) + { + if (vevent->rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(vevent->rdataset->type == dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + chaining = true; + } else { + chaining = false; + } + + /* + * Either we're not shutting down, or we are shutting down but + * want to cache the result anyway (if this was a validation + * started by a query with cd set) + */ + + hevent = ISC_LIST_HEAD(fctx->events); + if (hevent != NULL) { + if (!negative && !chaining && + (fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig)) + { + /* + * Don't bind rdatasets; the caller + * will iterate the node. + */ + } else { + ardataset = hevent->rdataset; + asigrdataset = hevent->sigrdataset; + } + } + + if (vevent->result != ISC_R_SUCCESS) { + FCTXTRACE("validation failed"); + inc_stats(res, dns_resstatscounter_valfail); + fctx->valfail++; + fctx->vresult = vevent->result; + if (fctx->vresult != DNS_R_BROKENCHAIN) { + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) { + result = dns_db_findnode( + fctx->cache, vevent->name, true, &node); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, vevent->type, + 0); + } + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + { + (void)dns_db_deleterdataset( + fctx->cache, node, NULL, + dns_rdatatype_rrsig, vevent->type); + } + if (result == ISC_R_SUCCESS) { + dns_db_detachnode(fctx->cache, &node); + } + } + if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) { + /* + * Cache the data as pending for later + * validation. + */ + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) { + result = dns_db_findnode( + fctx->cache, vevent->name, true, &node); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset( + fctx->cache, node, NULL, now, + vevent->rdataset, 0, NULL); + } + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + { + (void)dns_db_addrdataset( + fctx->cache, node, NULL, now, + vevent->sigrdataset, 0, NULL); + } + if (result == ISC_R_SUCCESS) { + dns_db_detachnode(fctx->cache, &node); + } + } + result = fctx->vresult; + add_bad(fctx, message, addrinfo, result, badns_validation); + dns_message_detach(&message); + isc_event_free(&event); + + UNLOCK(&res->buckets[bucketnum].lock); + INSIST(fctx->validator == NULL); + + fctx->validator = ISC_LIST_HEAD(fctx->validators); + if (fctx->validator != NULL) { + dns_validator_send(fctx->validator); + fctx_detach(&fctx); + } else if (sentresponse) { + /* Detach the extra ref that was set in valcreate() */ + fctx_unref(fctx); + fctx_done_detach(&fctx, result); /* Locks bucket */ + } else if (result == DNS_R_BROKENCHAIN) { + isc_result_t tresult; + isc_time_t expire; + isc_interval_t i; + + isc_interval_set(&i, DNS_RESOLVER_BADCACHETTL(fctx), 0); + tresult = isc_time_nowplusinterval(&expire, &i); + if (negative && + (fctx->type == dns_rdatatype_dnskey || + fctx->type == dns_rdatatype_ds) && + tresult == ISC_R_SUCCESS) + { + dns_resolver_addbadcache(res, fctx->name, + fctx->type, &expire); + } + + /* Detach the extra ref that was set in valcreate() */ + fctx_unref(fctx); + fctx_done_detach(&fctx, result); /* Locks bucket */ + } else { + fctx_try(fctx, true, true); /* Locks bucket */ + fctx_detach(&fctx); + } + return; + } + + if (negative) { + dns_rdatatype_t covers; + FCTXTRACE("nonexistence validation OK"); + + inc_stats(res, dns_resstatscounter_valnegsuccess); + + /* + * Cache DS NXDOMAIN separately to other types. + */ + if (message->rcode == dns_rcode_nxdomain && + fctx->type != dns_rdatatype_ds) + { + covers = dns_rdatatype_any; + } else { + covers = fctx->type; + } + + result = dns_db_findnode(fctx->cache, vevent->name, true, + &node); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + + /* + * If we are asking for a SOA record set the cache time + * to zero to facilitate locating the containing zone of + * a arbitrary zone. + */ + ttl = res->view->maxncachettl; + if (fctx->type == dns_rdatatype_soa && + covers == dns_rdatatype_any && res->zero_no_soa_ttl) + { + ttl = 0; + } + + result = ncache_adderesult(message, fctx->cache, node, covers, + now, fctx->res->view->minncachettl, + ttl, vevent->optout, vevent->secure, + ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + goto answer_response; + } else { + inc_stats(res, dns_resstatscounter_valsuccess); + } + + FCTXTRACE("validation OK"); + + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + result = dns_rdataset_addnoqname( + vevent->rdataset, + vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + INSIST(vevent->sigrdataset != NULL); + vevent->sigrdataset->ttl = vevent->rdataset->ttl; + if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) { + result = dns_rdataset_addclosest( + vevent->rdataset, + vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + } else if (vevent->rdataset->trust == dns_trust_answer && + vevent->rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname(fctx, message, vevent->name, + vevent->rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && noqname != NULL) { + tresult = dns_rdataset_addnoqname(vevent->rdataset, + noqname); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + } + } + + /* + * The data was already cached as pending data. + * Re-cache it as secure and bind the cached + * rdatasets to the first event on the fetch + * event list. + */ + result = dns_db_findnode(fctx->cache, vevent->name, true, &node); + if (result != ISC_R_SUCCESS) { + goto noanswer_response; + } + + options = 0; + if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) { + options = DNS_DBADD_PREFETCH; + } + result = dns_db_addrdataset(fctx->cache, node, NULL, now, + vevent->rdataset, options, ardataset); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + goto noanswer_response; + } + if (ardataset != NULL && NEGATIVE(ardataset)) { + if (NXDOMAIN(ardataset)) { + eresult = DNS_R_NCACHENXDOMAIN; + } else { + eresult = DNS_R_NCACHENXRRSET; + } + } else if (vevent->sigrdataset != NULL) { + result = dns_db_addrdataset(fctx->cache, node, NULL, now, + vevent->sigrdataset, options, + asigrdataset); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + goto noanswer_response; + } + } + + if (sentresponse) { + /* + * If we only deferred the destroy because we wanted to + * cache the data, destroy now. + */ + dns_db_detachnode(fctx->cache, &node); + if (SHUTTINGDOWN(fctx)) { + maybe_cancel_validators(fctx, true); + } + UNLOCK(&res->buckets[bucketnum].lock); + fctx_detach(&fctx); + goto cleanup_event; + } + + if (!ISC_LIST_EMPTY(fctx->validators)) { + INSIST(!negative); + INSIST(fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig); + /* + * Don't send a response yet - we have + * more rdatasets that still need to + * be validated. + */ + dns_db_detachnode(fctx->cache, &node); + UNLOCK(&res->buckets[bucketnum].lock); + dns_validator_send(ISC_LIST_HEAD(fctx->validators)); + fctx_detach(&fctx); + goto cleanup_event; + } + +answer_response: + + /* + * Cache any SOA/NS/NSEC records that happened to be validated. + */ + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if ((rdataset->type != dns_rdatatype_ns && + rdataset->type != dns_rdatatype_soa && + rdataset->type != dns_rdatatype_nsec) || + rdataset->trust != dns_trust_secure) + { + continue; + } + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != rdataset->type) + { + continue; + } + break; + } + if (sigrdataset == NULL || + sigrdataset->trust != dns_trust_secure) + { + continue; + } + + /* + * Don't cache NSEC if missing NSEC or RRSIG types. + */ + if (rdataset->type == dns_rdatatype_nsec && + !dns_nsec_requiredtypespresent(rdataset)) + { + continue; + } + + /* + * Don't cache "white lies" but do cache + * "black lies". + */ + if (rdataset->type == dns_rdatatype_nsec && + !dns_name_equal(fctx->name, name) && + is_minimal_nsec(rdataset)) + { + continue; + } + + /* + * Check SOA and DNSKEY consistency. + */ + if (rdataset->type == dns_rdatatype_nsec && + !check_soa_and_dnskey(rdataset)) + { + continue; + } + + /* + * Look for \000 label in next name. + */ + if (rdataset->type == dns_rdatatype_nsec && + has_000_label(rdataset)) + { + continue; + } + + result = dns_db_findnode(fctx->cache, name, true, + &nsnode); + if (result != ISC_R_SUCCESS) { + continue; + } + + result = dns_db_addrdataset(fctx->cache, nsnode, NULL, + now, rdataset, 0, NULL); + if (result == ISC_R_SUCCESS) { + result = dns_db_addrdataset( + fctx->cache, nsnode, NULL, now, + sigrdataset, 0, NULL); + } + dns_db_detachnode(fctx->cache, &nsnode); + if (result != ISC_R_SUCCESS) { + continue; + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + + /* + * Add the wild card entry. + */ + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL && + vevent->rdataset != NULL && + dns_rdataset_isassociated(vevent->rdataset) && + vevent->rdataset->trust == dns_trust_secure && + vevent->sigrdataset != NULL && + dns_rdataset_isassociated(vevent->sigrdataset) && + vevent->sigrdataset->trust == dns_trust_secure && wild != NULL) + { + dns_dbnode_t *wnode = NULL; + + result = dns_db_findnode(fctx->cache, wild, true, &wnode); + if (result == ISC_R_SUCCESS) { + result = dns_db_addrdataset(fctx->cache, wnode, NULL, + now, vevent->rdataset, 0, + NULL); + } + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset(fctx->cache, wnode, NULL, now, + vevent->sigrdataset, 0, NULL); + } + if (wnode != NULL) { + dns_db_detachnode(fctx->cache, &wnode); + } + } + + result = ISC_R_SUCCESS; + + /* + * Respond with an answer, positive or negative, + * as opposed to an error. 'node' must be non-NULL. + */ + + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + + if (hevent != NULL) { + /* + * Negative results must be indicated in event->result. + */ + INSIST(hevent->rdataset != NULL); + if (dns_rdataset_isassociated(hevent->rdataset) && + NEGATIVE(hevent->rdataset)) + { + INSIST(eresult == DNS_R_NCACHENXDOMAIN || + eresult == DNS_R_NCACHENXRRSET); + } + + hevent->result = eresult; + dns_name_copy(vevent->name, hevent->foundname); + dns_db_attach(fctx->cache, &hevent->db); + dns_db_transfernode(fctx->cache, &node, &hevent->node); + clone_results(fctx); + } + +noanswer_response: + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + UNLOCK(&res->buckets[bucketnum].lock); + /* Detach the extra reference that was set in valcreate() */ + fctx_unref(fctx); + fctx_done_detach(&fctx, result); /* Locks bucket. */ + +cleanup_event: + INSIST(node == NULL); + dns_message_detach(&message); + isc_event_free(&event); +} + +static void +fctx_log(void *arg, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list args; + fetchctx_t *fctx = arg; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); + va_end(args); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, "fctx %p(%s): %s", fctx, + fctx->info, msgbuf); +} + +static isc_result_t +findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name, + dns_rdatatype_t type, dns_name_t **noqnamep) { + dns_rdataset_t *nrdataset, *next, *sigrdataset; + dns_rdata_rrsig_t rrsig; + isc_result_t result; + unsigned int labels; + dns_section_t section; + dns_name_t *zonename; + dns_fixedname_t fzonename; + dns_name_t *closest; + dns_fixedname_t fclosest; + dns_name_t *nearest; + dns_fixedname_t fnearest; + dns_rdatatype_t found = dns_rdatatype_none; + dns_name_t *noqname = NULL; + + FCTXTRACE("findnoqname"); + + REQUIRE(noqnamep != NULL && *noqnamep == NULL); + + /* + * Find the SIG for this rdataset, if we have it. + */ + for (sigrdataset = ISC_LIST_HEAD(name->list); sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == type) + { + break; + } + } + + if (sigrdataset == NULL) { + return (ISC_R_NOTFOUND); + } + + labels = dns_name_countlabels(name); + + for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigrdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(sigrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* Wildcard has rrsig.labels < labels - 1. */ + if (rrsig.labels + 1U >= labels) { + continue; + } + break; + } + + if (result == ISC_R_NOMORE) { + return (ISC_R_NOTFOUND); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + zonename = dns_fixedname_initname(&fzonename); + closest = dns_fixedname_initname(&fclosest); + nearest = dns_fixedname_initname(&fnearest); + +#define NXND(x) ((x) == ISC_R_SUCCESS) + + section = DNS_SECTION_AUTHORITY; + for (result = dns_message_firstname(message, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, section)) + { + dns_name_t *nsec = NULL; + dns_message_currentname(message, section, &nsec); + for (nrdataset = ISC_LIST_HEAD(nsec->list); nrdataset != NULL; + nrdataset = next) + { + bool data = false, exists = false; + bool optout = false, unknown = false; + bool setclosest = false; + bool setnearest = false; + + next = ISC_LIST_NEXT(nrdataset, link); + if (nrdataset->type != dns_rdatatype_nsec && + nrdataset->type != dns_rdatatype_nsec3) + { + continue; + } + + if (nrdataset->type == dns_rdatatype_nsec && + NXND(dns_nsec_noexistnodata( + type, name, nsec, nrdataset, &exists, &data, + NULL, fctx_log, fctx))) + { + if (!exists) { + noqname = nsec; + found = dns_rdatatype_nsec; + } + } + + if (nrdataset->type == dns_rdatatype_nsec3 && + NXND(dns_nsec3_noexistnodata( + type, name, nsec, nrdataset, zonename, + &exists, &data, &optout, &unknown, + &setclosest, &setnearest, closest, nearest, + fctx_log, fctx))) + { + if (!exists && setnearest) { + noqname = nsec; + found = dns_rdatatype_nsec3; + } + } + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + if (noqname != NULL) { + for (sigrdataset = ISC_LIST_HEAD(noqname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == found) + { + break; + } + } + if (sigrdataset != NULL) { + *noqnamep = noqname; + } + } + return (result); +} + +static isc_result_t +cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t *addedrdataset = NULL; + dns_rdataset_t *ardataset = NULL, *asigrdataset = NULL; + dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL; + dns_dbnode_t *node = NULL, **anodep = NULL; + dns_db_t **adbp = NULL; + dns_resolver_t *res = fctx->res; + bool need_validation = false; + bool secure_domain = false; + bool have_answer = false; + isc_result_t result, eresult = ISC_R_SUCCESS; + dns_fetchevent_t *event = NULL; + unsigned int options; + isc_task_t *task; + bool fail; + unsigned int valoptions = 0; + bool checknta = true; + + FCTXTRACE("cache_name"); + + /* + * The appropriate bucket lock must be held. + */ + task = res->buckets[fctx->bucketnum].task; + + /* + * Is DNSSEC validation required for this name? + */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = false; + } + + if (res->view->enablevalidation) { + result = issecuredomain(res->view, name, fctx->type, now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + valoptions |= DNS_VALIDATOR_NOCDFLAG; + } + + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + need_validation = false; + } else { + need_validation = secure_domain; + } + + if (((name->attributes & DNS_NAMEATTR_ANSWER) != 0) && + (!need_validation)) + { + have_answer = true; + event = ISC_LIST_HEAD(fctx->events); + + if (event != NULL) { + adbp = &event->db; + dns_name_copy(name, event->foundname); + anodep = &event->node; + + /* + * If this is an ANY, SIG or RRSIG query, we're + * not going to return any rdatasets, unless we + * encountered a CNAME or DNAME as "the answer". + * In this case, we're going to return + * DNS_R_CNAME or DNS_R_DNAME and we must set up + * the rdatasets. + */ + if ((fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_rrsig && + fctx->type != dns_rdatatype_sig) || + (name->attributes & DNS_NAMEATTR_CHAINING) != 0) + { + ardataset = event->rdataset; + asigrdataset = event->sigrdataset; + } + } + } + + /* + * Find or create the cache node. + */ + node = NULL; + result = dns_db_findnode(fctx->cache, name, true, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Cache or validate each cacheable rdataset. + */ + fail = ((fctx->res->options & DNS_RESOLVER_CHECKNAMESFAIL) != 0); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!CACHE(rdataset)) { + continue; + } + if (CHECKNAMES(rdataset)) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char classbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "check-names %s %s/%s/%s", + fail ? "failure" : "warning", namebuf, + typebuf, classbuf); + if (fail) { + if (ANSWER(rdataset)) { + dns_db_detachnode(fctx->cache, &node); + return (DNS_R_BADNAME); + } + continue; + } + } + + /* + * Enforce the configure maximum cache TTL. + */ + if (rdataset->ttl > res->view->maxcachettl) { + rdataset->ttl = res->view->maxcachettl; + } + + /* + * Enforce configured minimum cache TTL. + */ + if (rdataset->ttl < res->view->mincachettl) { + rdataset->ttl = res->view->mincachettl; + } + + /* + * Mark the rdataset as being prefetch eligible. + */ + if (rdataset->ttl >= fctx->res->view->prefetch_eligible) { + rdataset->attributes |= DNS_RDATASETATTR_PREFETCH; + } + + /* + * Find the SIG for this rdataset, if we have it. + */ + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == rdataset->type) + { + break; + } + } + + /* + * If this RRset is in a secure domain, is in bailiwick, + * and is not glue, attempt DNSSEC validation. (We do + * not attempt to validate glue or out-of-bailiwick + * data--even though there might be some performance + * benefit to doing so--because it makes it simpler and + * safer to ensure that records from a secure domain are + * only cached if validated within the context of a + * query to the domain that owns them.) + */ + if (secure_domain && rdataset->trust != dns_trust_glue && + !EXTERNAL(rdataset)) + { + dns_trust_t trust; + + /* + * RRSIGs are validated as part of validating + * the type they cover. + */ + if (rdataset->type == dns_rdatatype_rrsig) { + continue; + } + + if (sigrdataset == NULL && need_validation && + !ANSWER(rdataset)) + { + /* + * Ignore unrelated non-answer + * rdatasets that are missing + * signatures. + */ + continue; + } + + /* + * Normalize the rdataset and sigrdataset TTLs. + */ + if (sigrdataset != NULL) { + rdataset->ttl = ISC_MIN(rdataset->ttl, + sigrdataset->ttl); + sigrdataset->ttl = rdataset->ttl; + } + + /* + * Mark the rdataset as being prefetch eligible. + */ + if (rdataset->ttl >= fctx->res->view->prefetch_eligible) + { + rdataset->attributes |= + DNS_RDATASETATTR_PREFETCH; + } + + /* + * Cache this rdataset/sigrdataset pair as + * pending data. Track whether it was + * additional or not. If this was a priming + * query, additional should be cached as glue. + */ + if (rdataset->trust == dns_trust_additional) { + trust = dns_trust_pending_additional; + } else { + trust = dns_trust_pending_answer; + } + + rdataset->trust = trust; + if (sigrdataset != NULL) { + sigrdataset->trust = trust; + } + if (!need_validation || !ANSWER(rdataset)) { + options = 0; + if (ANSWER(rdataset) && + rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname( + fctx, message, name, + rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && + noqname != NULL) + { + (void)dns_rdataset_addnoqname( + rdataset, noqname); + } + } + if ((fctx->options & DNS_FETCHOPT_PREFETCH) != + 0) + { + options = DNS_DBADD_PREFETCH; + } + if ((fctx->options & DNS_FETCHOPT_NOCACHED) != + 0) + { + options |= DNS_DBADD_FORCE; + } + addedrdataset = ardataset; + result = dns_db_addrdataset( + fctx->cache, node, NULL, now, rdataset, + options, addedrdataset); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + if (!need_validation && + ardataset != NULL && + NEGATIVE(ardataset)) + { + /* + * The answer in the + * cache is better than + * the answer we found, + * and is a negative + * cache entry, so we + * must set eresult + * appropriately. + */ + if (NXDOMAIN(ardataset)) { + eresult = + DNS_R_NCACHENXDOMAIN; + } else { + eresult = + DNS_R_NCACHENXRRSET; + } + /* + * We have a negative + * response from the + * cache so don't + * attempt to add the + * RRSIG rrset. + */ + continue; + } + } + if (result != ISC_R_SUCCESS) { + break; + } + if (sigrdataset != NULL) { + addedrdataset = asigrdataset; + result = dns_db_addrdataset( + fctx->cache, node, NULL, now, + sigrdataset, options, + addedrdataset); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS) { + break; + } + } else if (!ANSWER(rdataset)) { + continue; + } + } + + if (ANSWER(rdataset) && need_validation) { + if (fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_rrsig && + fctx->type != dns_rdatatype_sig) + { + /* + * This is The Answer. We will + * validate it, but first we + * cache the rest of the + * response - it may contain + * useful keys. + */ + INSIST(valrdataset == NULL && + valsigrdataset == NULL); + valrdataset = rdataset; + valsigrdataset = sigrdataset; + } else { + /* + * This is one of (potentially) + * multiple answers to an ANY + * or SIG query. To keep things + * simple, we just start the + * validator right away rather + * than caching first and + * having to remember which + * rdatasets needed validation. + */ + result = valcreate( + fctx, message, addrinfo, name, + rdataset->type, rdataset, + sigrdataset, valoptions, task); + } + } else if (CHAINING(rdataset)) { + if (rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(rdataset->type == + dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + } + } else if (!EXTERNAL(rdataset)) { + /* + * It's OK to cache this rdataset now. + */ + if (ANSWER(rdataset)) { + addedrdataset = ardataset; + } else if (ANSWERSIG(rdataset)) { + addedrdataset = asigrdataset; + } else { + addedrdataset = NULL; + } + if (CHAINING(rdataset)) { + if (rdataset->type == dns_rdatatype_cname) { + eresult = DNS_R_CNAME; + } else { + INSIST(rdataset->type == + dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + } + if (rdataset->trust == dns_trust_glue && + (rdataset->type == dns_rdatatype_ns || + (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == dns_rdatatype_ns))) + { + /* + * If the trust level is + * 'dns_trust_glue' then we are adding + * data from a referral we got while + * executing the search algorithm. New + * referral data always takes precedence + * over the existing cache contents. + */ + options = DNS_DBADD_FORCE; + } else if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) + { + options = DNS_DBADD_PREFETCH; + } else { + options = 0; + } + + if (ANSWER(rdataset) && + rdataset->type != dns_rdatatype_rrsig) + { + isc_result_t tresult; + dns_name_t *noqname = NULL; + tresult = findnoqname(fctx, message, name, + rdataset->type, &noqname); + if (tresult == ISC_R_SUCCESS && noqname != NULL) + { + (void)dns_rdataset_addnoqname(rdataset, + noqname); + } + } + + /* + * Now we can add the rdataset. + */ + result = dns_db_addrdataset(fctx->cache, node, NULL, + now, rdataset, options, + addedrdataset); + + if (result == DNS_R_UNCHANGED) { + if (ANSWER(rdataset) && ardataset != NULL && + NEGATIVE(ardataset)) + { + /* + * The answer in the cache is + * better than the answer we + * found, and is a negative + * cache entry, so we must set + * eresult appropriately. + */ + if (NXDOMAIN(ardataset)) { + eresult = DNS_R_NCACHENXDOMAIN; + } else { + eresult = DNS_R_NCACHENXRRSET; + } + } + result = ISC_R_SUCCESS; + } else if (result != ISC_R_SUCCESS) { + break; + } + } + } + + if (valrdataset != NULL) { + dns_rdatatype_t vtype = fctx->type; + if (CHAINING(valrdataset)) { + if (valrdataset->type == dns_rdatatype_cname) { + vtype = dns_rdatatype_cname; + } else { + vtype = dns_rdatatype_dname; + } + } + + result = valcreate(fctx, message, addrinfo, name, vtype, + valrdataset, valsigrdataset, valoptions, + task); + } + + if (result == ISC_R_SUCCESS && have_answer) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + if (event != NULL) { + /* + * Negative results must be indicated in + * event->result. + */ + if (dns_rdataset_isassociated(event->rdataset) && + NEGATIVE(event->rdataset)) + { + INSIST(eresult == DNS_R_NCACHENXDOMAIN || + eresult == DNS_R_NCACHENXRRSET); + } + event->result = eresult; + if (adbp != NULL && *adbp != NULL) { + if (anodep != NULL && *anodep != NULL) { + dns_db_detachnode(*adbp, anodep); + } + dns_db_detach(adbp); + } + dns_db_attach(fctx->cache, adbp); + dns_db_transfernode(fctx->cache, &node, anodep); + clone_results(fctx); + } + } + + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + return (result); +} + +static isc_result_t +cache_message(fetchctx_t *fctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) { + isc_result_t result; + dns_section_t section; + dns_name_t *name; + + FCTXTRACE("cache_message"); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE); + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL; + section++) + { + result = dns_message_firstname(message, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, section, &name); + if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) { + result = cache_name(fctx, name, message, + addrinfo, now); + if (result != ISC_R_SUCCESS) { + break; + } + } + result = dns_message_nextname(message, section); + } + if (result != ISC_R_NOMORE) { + break; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + return (result); +} + +/* + * Do what dns_ncache_addoptout() does, and then compute an appropriate + * eresult. + */ +static isc_result_t +ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, + dns_ttl_t maxttl, bool optout, bool secure, + dns_rdataset_t *ardataset, isc_result_t *eresultp) { + isc_result_t result; + dns_rdataset_t rdataset; + + if (ardataset == NULL) { + dns_rdataset_init(&rdataset); + ardataset = &rdataset; + } + if (secure) { + result = dns_ncache_addoptout(message, cache, node, covers, now, + minttl, maxttl, optout, + ardataset); + } else { + result = dns_ncache_add(message, cache, node, covers, now, + minttl, maxttl, ardataset); + } + if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) { + /* + * If the cache now contains a negative entry and we + * care about whether it is DNS_R_NCACHENXDOMAIN or + * DNS_R_NCACHENXRRSET then extract it. + */ + if (NEGATIVE(ardataset)) { + /* + * The cache data is a negative cache entry. + */ + if (NXDOMAIN(ardataset)) { + *eresultp = DNS_R_NCACHENXDOMAIN; + } else { + *eresultp = DNS_R_NCACHENXRRSET; + } + } else { + /* + * Either we don't care about the nature of the + * cache rdataset (because no fetch is + * interested in the outcome), or the cache + * rdataset is not a negative cache entry. + * Whichever case it is, we can return success. + * + * XXXRTH There's a CNAME/DNAME problem here. + */ + *eresultp = ISC_R_SUCCESS; + } + result = ISC_R_SUCCESS; + } + if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) { + dns_rdataset_disassociate(ardataset); + } + + return (result); +} + +static isc_result_t +ncache_message(fetchctx_t *fctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, dns_rdatatype_t covers, + isc_stdtime_t now) { + isc_result_t result, eresult = ISC_R_SUCCESS; + dns_name_t *name = fctx->name; + dns_resolver_t *res = fctx->res; + dns_db_t **adbp = NULL; + dns_dbnode_t *node = NULL, **anodep = NULL; + dns_rdataset_t *ardataset = NULL; + bool need_validation = false, secure_domain = false; + dns_fetchevent_t *event = NULL; + uint32_t ttl; + unsigned int valoptions = 0; + bool checknta = true; + + FCTXTRACE("ncache_message"); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE); + + POST(need_validation); + + /* + * XXXMPA remove when we follow cnames and adjust the setting + * of FCTX_ATTR_WANTNCACHE in rctx_answer_none(). + */ + INSIST(message->counts[DNS_SECTION_ANSWER] == 0); + + /* + * Is DNSSEC validation required for this name? + */ + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + valoptions |= DNS_VALIDATOR_NONTA; + checknta = false; + } + + if (fctx->res->view->enablevalidation) { + result = issecuredomain(res->view, name, fctx->type, now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if ((fctx->options & DNS_FETCHOPT_NOCDFLAG) != 0) { + valoptions |= DNS_VALIDATOR_NOCDFLAG; + } + + if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0) { + need_validation = false; + } else { + need_validation = secure_domain; + } + + if (secure_domain) { + /* + * Mark all rdatasets as pending. + */ + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + dns_rdataset_t *trdataset = NULL; + dns_name_t *tname = NULL; + + dns_message_currentname(message, DNS_SECTION_AUTHORITY, + &tname); + for (trdataset = ISC_LIST_HEAD(tname->list); + trdataset != NULL; + trdataset = ISC_LIST_NEXT(trdataset, link)) + { + trdataset->trust = dns_trust_pending_answer; + } + result = dns_message_nextname(message, + DNS_SECTION_AUTHORITY); + } + if (result != ISC_R_NOMORE) { + return (result); + } + } + + if (need_validation) { + /* + * Do negative response validation. + */ + result = valcreate(fctx, message, addrinfo, name, fctx->type, + NULL, NULL, valoptions, + res->buckets[fctx->bucketnum].task); + /* + * If validation is necessary, return now. Otherwise + * continue to process the message, letting the + * validation complete in its own good time. + */ + return (result); + } + + LOCK(&res->buckets[fctx->bucketnum].lock); + + if (!HAVE_ANSWER(fctx)) { + event = ISC_LIST_HEAD(fctx->events); + if (event != NULL) { + adbp = &event->db; + dns_name_copy(name, event->foundname); + anodep = &event->node; + ardataset = event->rdataset; + } + } + + result = dns_db_findnode(fctx->cache, name, true, &node); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + /* + * If we are asking for a SOA record set the cache time + * to zero to facilitate locating the containing zone of + * a arbitrary zone. + */ + ttl = fctx->res->view->maxncachettl; + if (fctx->type == dns_rdatatype_soa && covers == dns_rdatatype_any && + fctx->res->zero_no_soa_ttl) + { + ttl = 0; + } + + result = ncache_adderesult(message, fctx->cache, node, covers, now, + fctx->res->view->minncachettl, ttl, false, + false, ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + if (!HAVE_ANSWER(fctx)) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_HAVEANSWER); + if (event != NULL) { + event->result = eresult; + if (adbp != NULL && *adbp != NULL) { + if (anodep != NULL && *anodep != NULL) { + dns_db_detachnode(*adbp, anodep); + } + dns_db_detach(adbp); + } + dns_db_attach(fctx->cache, adbp); + dns_db_transfernode(fctx->cache, &node, anodep); + clone_results(fctx); + } + } + +unlock: + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + if (node != NULL) { + dns_db_detachnode(fctx->cache, &node); + } + + return (result); +} + +static void +mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external, + bool gluing) { + name->attributes |= DNS_NAMEATTR_CACHE; + if (gluing) { + rdataset->trust = dns_trust_glue; + /* + * Glue with 0 TTL causes problems. We force the TTL to + * 1 second to prevent this. + */ + if (rdataset->ttl == 0) { + rdataset->ttl = 1; + } + } else { + rdataset->trust = dns_trust_additional; + } + /* + * Avoid infinite loops by only marking new rdatasets. + */ + if (!CACHE(rdataset)) { + name->attributes |= DNS_NAMEATTR_CHASE; + rdataset->attributes |= DNS_RDATASETATTR_CHASE; + } + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + if (external) { + rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL; + } +} + +/* + * Returns true if 'name' is external to the namespace for which + * the server being queried can answer, either because it's not a + * subdomain or because it's below a forward declaration or a + * locally served zone. + */ +static inline bool +name_external(const dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) { + isc_result_t result; + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed, zfixed; + dns_name_t *fname = dns_fixedname_initname(&fixed); + dns_name_t *zfname = dns_fixedname_initname(&zfixed); + dns_name_t *apex = NULL; + dns_name_t suffix; + dns_zone_t *zone = NULL; + unsigned int labels; + dns_namereln_t rel; + + apex = (ISDUALSTACK(fctx->addrinfo) || !ISFORWARDER(fctx->addrinfo)) + ? fctx->domain + : fctx->fwdname; + + /* + * The name is outside the queried namespace. + */ + rel = dns_name_fullcompare(name, apex, &(int){ 0 }, + &(unsigned int){ 0U }); + if (rel != dns_namereln_subdomain && rel != dns_namereln_equal) { + return (true); + } + + /* + * If the record lives in the parent zone, adjust the name so we + * look for the correct zone or forward clause. + */ + labels = dns_name_countlabels(name); + if (dns_rdatatype_atparent(type) && labels > 1U) { + dns_name_init(&suffix, NULL); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + name = &suffix; + } else if (rel == dns_namereln_equal) { + /* If 'name' is 'apex', no further checking is needed. */ + return (false); + } + + /* + * If there is a locally served zone between 'apex' and 'name' + * then don't cache. + */ + LOCK(&fctx->res->view->lock); + if (fctx->res->view->zonetable != NULL) { + unsigned int options = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_MIRROR; + result = dns_zt_find(fctx->res->view->zonetable, name, options, + zfname, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + if (dns_name_fullcompare(zfname, apex, &(int){ 0 }, + &(unsigned int){ 0U }) == + dns_namereln_subdomain) + { + UNLOCK(&fctx->res->view->lock); + return (true); + } + } + } + UNLOCK(&fctx->res->view->lock); + + /* + * Look for a forward declaration below 'name'. + */ + result = dns_fwdtable_find(fctx->res->view->fwdtable, name, fname, + &forwarders); + + if (ISFORWARDER(fctx->addrinfo)) { + /* + * See if the forwarder declaration is better. + */ + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + return (!dns_name_equal(fname, fctx->fwdname)); + } + + /* + * If the lookup failed, the configuration must have + * changed: play it safe and don't cache. + */ + return (true); + } else if ((result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) && + forwarders->fwdpolicy == dns_fwdpolicy_only && + !ISC_LIST_EMPTY(forwarders->fwdrs)) + { + /* + * If 'name' is covered by a 'forward only' clause then we + * can't cache this repsonse. + */ + return (true); + } + + return (false); +} + +static isc_result_t +check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type, + dns_rdataset_t *found, dns_section_t section) { + respctx_t *rctx = arg; + fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + bool external; + dns_rdatatype_t rtype; + bool gluing; + + REQUIRE(VALID_FCTX(fctx)); + +#if CHECK_FOR_GLUE_IN_ANSWER + if (section == DNS_SECTION_ANSWER && type != dns_rdatatype_a) { + return (ISC_R_SUCCESS); + } +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + + gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns && + dns_name_equal(fctx->name, dns_rootname))); + + result = dns_message_findname(rctx->query->rmessage, section, addname, + dns_rdatatype_any, 0, &name, NULL); + if (result == ISC_R_SUCCESS) { + external = name_external(name, type, fctx); + if (type == dns_rdatatype_a) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_rrsig) { + rtype = rdataset->covers; + } else { + rtype = rdataset->type; + } + if (rtype == dns_rdatatype_a || + rtype == dns_rdatatype_aaaa) + { + mark_related(name, rdataset, external, + gluing); + } + } + } else { + result = dns_message_findtype(name, type, 0, &rdataset); + if (result == ISC_R_SUCCESS) { + mark_related(name, rdataset, external, gluing); + if (found != NULL) { + dns_rdataset_clone(rdataset, found); + } + /* + * Do we have its SIG too? + */ + rdataset = NULL; + result = dns_message_findtype( + name, dns_rdatatype_rrsig, type, + &rdataset); + if (result == ISC_R_SUCCESS) { + mark_related(name, rdataset, external, + gluing); + } + } + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +check_related(void *arg, const dns_name_t *addname, dns_rdatatype_t type, + dns_rdataset_t *found) { + return (check_section(arg, addname, type, found, + DNS_SECTION_ADDITIONAL)); +} + +#ifndef CHECK_FOR_GLUE_IN_ANSWER +#define CHECK_FOR_GLUE_IN_ANSWER 0 +#endif /* ifndef CHECK_FOR_GLUE_IN_ANSWER */ + +#if CHECK_FOR_GLUE_IN_ANSWER +static isc_result_t +check_answer(void *arg, const dns_name_t *addname, dns_rdatatype_t type, + dns_rdataset_t *found) { + return (check_section(arg, addname, type, found, DNS_SECTION_ANSWER)); +} +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + +static bool +is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, + dns_rdataset_t *rdataset) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct in_addr ina; + struct in6_addr in6a; + isc_netaddr_t netaddr; + char addrbuf[ISC_NETADDR_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + int match; + + /* By default, we allow any addresses. */ + if (view->denyansweracl == NULL) { + return (true); + } + + /* + * If the owner name matches one in the exclusion list, either + * exactly or partially, allow it. + */ + if (view->answeracl_exclude != NULL) { + dns_rbtnode_t *node = NULL; + + result = dns_rbt_findnode(view->answeracl_exclude, name, NULL, + &node, NULL, 0, NULL, NULL); + + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + return (true); + } + } + + /* + * Otherwise, search the filter list for a match for each + * address record. If a match is found, the address should be + * filtered, so should the entire answer. + */ + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + if (rdataset->type == dns_rdatatype_a) { + INSIST(rdata.length == sizeof(ina.s_addr)); + memmove(&ina.s_addr, rdata.data, sizeof(ina.s_addr)); + isc_netaddr_fromin(&netaddr, &ina); + } else { + INSIST(rdata.length == sizeof(in6a.s6_addr)); + memmove(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr)); + isc_netaddr_fromin6(&netaddr, &in6a); + } + + result = dns_acl_match(&netaddr, NULL, view->denyansweracl, + view->aclenv, &match, NULL); + if (result == ISC_R_SUCCESS && match > 0) { + isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf)); + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + dns_rdataclass_format(rdataset->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "answer address %s denied for %s/%s/%s", + addrbuf, namebuf, typebuf, classbuf); + return (false); + } + } + + return (true); +} + +static bool +is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname, + dns_rdataset_t *rdataset, bool *chainingp) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + char qnamebuf[DNS_NAME_FORMATSIZE]; + char tnamebuf[DNS_NAME_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + dns_name_t *tname = NULL; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + dns_view_t *view = fctx->res->view; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + dns_fixedname_t fixed; + dns_name_t prefix; + int order; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname); + + /* + * By default, we allow any target name. + * If newqname != NULL we also need to extract the newqname. + */ + if (chainingp == NULL && view->denyanswernames == NULL) { + return (true); + } + + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + switch (rdataset->type) { + case dns_rdatatype_cname: + result = dns_rdata_tostruct(&rdata, &cname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tname = &cname.cname; + break; + case dns_rdatatype_dname: + if (dns_name_fullcompare(qname, rname, &order, &nlabels) != + dns_namereln_subdomain) + { + return (true); + } + result = dns_rdata_tostruct(&rdata, &dname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_init(&prefix, NULL); + tname = dns_fixedname_initname(&fixed); + nlabels = dns_name_countlabels(rname); + dns_name_split(qname, nlabels, &prefix, NULL); + result = dns_name_concatenate(&prefix, &dname.dname, tname, + NULL); + if (result == DNS_R_NAMETOOLONG) { + if (chainingp != NULL) { + *chainingp = true; + } + return (true); + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + break; + default: + UNREACHABLE(); + } + + if (chainingp != NULL) { + *chainingp = true; + } + + if (view->denyanswernames == NULL) { + return (true); + } + + /* + * If the owner name matches one in the exclusion list, either + * exactly or partially, allow it. + */ + if (view->answernames_exclude != NULL) { + result = dns_rbt_findnode(view->answernames_exclude, qname, + NULL, &node, NULL, 0, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + return (true); + } + } + + /* + * If the target name is a subdomain of the search domain, allow + * it. + * + * Note that if BIND is configured as a forwarding DNS server, + * the search domain will always match the root domain ("."), so + * we must also check whether forwarding is enabled so that + * filters can be applied; see GL #1574. + */ + if (!fctx->forwarding && dns_name_issubdomain(tname, fctx->domain)) { + return (true); + } + + /* + * Otherwise, apply filters. + */ + result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node, + NULL, 0, NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + dns_name_format(qname, qnamebuf, sizeof(qnamebuf)); + dns_name_format(tname, tnamebuf, sizeof(tnamebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(view->rdclass, classbuf, + sizeof(classbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "%s target %s denied for %s/%s", typebuf, + tnamebuf, qnamebuf, classbuf); + return (false); + } + + return (true); +} + +static void +trim_ns_ttl(fetchctx_t *fctx, dns_name_t *name, dns_rdataset_t *rdataset) { + char ns_namebuf[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + if (fctx->ns_ttl_ok && rdataset->ttl > fctx->ns_ttl) { + dns_name_format(name, ns_namebuf, sizeof(ns_namebuf)); + dns_name_format(fctx->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(fctx->type, tbuf, sizeof(tbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(10), + "fctx %p: trimming ttl of %s/NS for %s/%s: " + "%u -> %u", + fctx, ns_namebuf, namebuf, tbuf, rdataset->ttl, + fctx->ns_ttl); + rdataset->ttl = fctx->ns_ttl; + } +} + +static bool +validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) { + if (rdataset->type == dns_rdatatype_nsec3) { + /* + * NSEC3 records are not allowed to + * appear in the answer section. + */ + log_formerr(fctx, "NSEC3 in answer"); + return (false); + } + if (rdataset->type == dns_rdatatype_tkey) { + /* + * TKEY is not a valid record in a + * response to any query we can make. + */ + log_formerr(fctx, "TKEY in answer"); + return (false); + } + if (rdataset->rdclass != fctx->res->rdclass) { + log_formerr(fctx, "Mismatched class in answer"); + return (false); + } + return (true); +} + +static void +fctx__attach(fetchctx_t *fctx, fetchctx_t **fctxp, const char *file, + unsigned int line, const char *func) { + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(fctxp != NULL && *fctxp == NULL); + uint_fast32_t refs = isc_refcount_increment(&fctx->references); + +#ifdef FCTX_TRACE + fprintf(stderr, "%s:%s:%u:%s(%p, %p) -> %" PRIuFAST32 "\n", func, file, + line, __func__, fctx, fctxp, refs + 1); +#else + UNUSED(refs); + UNUSED(file); + UNUSED(line); + UNUSED(func); +#endif + + *fctxp = fctx; +} + +static void +fctx__detach(fetchctx_t **fctxp, const char *file, unsigned int line, + const char *func) { + fetchctx_t *fctx = NULL; + uint_fast32_t refs; + + REQUIRE(fctxp != NULL && VALID_FCTX(*fctxp)); + + fctx = *fctxp; + *fctxp = NULL; + + refs = isc_refcount_decrement(&fctx->references); + +#ifdef FCTX_TRACE + fprintf(stderr, "%s:%s:%u:%s(%p, %p) -> %" PRIuFAST32 "\n", func, file, + line, __func__, fctx, fctxp, refs - 1); +#else + UNUSED(refs); + UNUSED(file); + UNUSED(line); + UNUSED(func); +#endif + + if (refs == 1) { + fctx_destroy(fctx, true); + } +} + +static void +resume_dslookup(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_fetchevent_t *fevent = (dns_fetchevent_t *)event; + fetchctx_t *fctx = event->ev_arg; + dns_resolver_t *res = NULL; + dns_rdataset_t *frdataset = NULL, *nsrdataset = NULL; + dns_rdataset_t nameservers; + dns_fixedname_t fixed; + dns_name_t *domain = NULL; + unsigned int n; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + FCTXTRACE("resume_dslookup"); + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + + /* Preserve data from fevent before freeing it. */ + frdataset = fevent->rdataset; + result = fevent->result; + isc_event_free(&event); + + LOCK(&res->buckets[fctx->bucketnum].lock); + if (SHUTTINGDOWN(fctx)) { + maybe_cancel_validators(fctx, true); + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + if (dns_rdataset_isassociated(frdataset)) { + dns_rdataset_disassociate(frdataset); + } + + dns_resolver_destroyfetch(&fctx->nsfetch); + fctx_detach(&fctx); + return; + } + UNLOCK(&res->buckets[fctx->bucketnum].lock); + + /* + * Detach the extra reference that was set in rctx_chaseds() + * or a prior iteration of this function. + */ + fctx_unref(fctx); + + switch (result) { + case ISC_R_CANCELED: + dns_resolver_destroyfetch(&fctx->nsfetch); + if (dns_rdataset_isassociated(frdataset)) { + dns_rdataset_disassociate(frdataset); + } + fctx_done_detach(&fctx, ISC_R_CANCELED); + break; + + case ISC_R_SUCCESS: + FCTXTRACE("resuming DS lookup"); + + dns_resolver_destroyfetch(&fctx->nsfetch); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + dns_rdataset_clone(frdataset, &fctx->nameservers); + if (dns_rdataset_isassociated(frdataset)) { + dns_rdataset_disassociate(frdataset); + } + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + log_ns_ttl(fctx, "resume_dslookup"); + + fcount_decr(fctx); + dns_name_copy(fctx->nsname, fctx->domain); + result = fcount_incr(fctx, true); + if (result == ISC_R_SUCCESS) { + /* + * Try again. + */ + fctx_try(fctx, true, false); + } else { + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + } + break; + + default: + if (dns_rdataset_isassociated(frdataset)) { + dns_rdataset_disassociate(frdataset); + } + + /* + * Get domain from fctx->nsfetch before we destroy it. + */ + domain = dns_fixedname_initname(&fixed); + dns_name_copy(fctx->nsfetch->private->domain, domain); + + /* + * If the chain of resume_dslookup() invocations managed to + * chop off enough labels from the original DS owner name to + * reach the top of the namespace, no further progress can be + * made. Interrupt the DS chasing process, returning SERVFAIL. + */ + if (dns_name_equal(fctx->nsname, domain)) { + dns_resolver_destroyfetch(&fctx->nsfetch); + fctx_done_detach(&fctx, DNS_R_SERVFAIL); + return; + } + + /* + * Get nameservers from fctx->nsfetch before we destroy it. + */ + dns_rdataset_init(&nameservers); + if (dns_rdataset_isassociated( + &fctx->nsfetch->private->nameservers)) + { + dns_rdataset_clone(&fctx->nsfetch->private->nameservers, + &nameservers); + nsrdataset = &nameservers; + } else { + domain = NULL; + } + + dns_resolver_destroyfetch(&fctx->nsfetch); + + n = dns_name_countlabels(fctx->nsname); + dns_name_getlabelsequence(fctx->nsname, 1, n - 1, fctx->nsname); + + FCTXTRACE("continuing to look for parent's NS records"); + + /* Starting a new fetch, so restore the extra reference */ + fctx_addref(fctx); + result = dns_resolver_createfetch( + res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset, + NULL, NULL, 0, fctx->options, 0, NULL, task, + resume_dslookup, fctx, &fctx->nsrrset, NULL, + &fctx->nsfetch); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_unref(fctx); + fctx_done_detach(&fctx, result); + } + + if (dns_rdataset_isassociated(&nameservers)) { + dns_rdataset_disassociate(&nameservers); + } + } +} + +static void +checknamessection(dns_message_t *message, dns_section_t section) { + isc_result_t result; + dns_name_t *name; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t *rdataset; + + for (result = dns_message_firstname(message, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, section)) + { + name = NULL; + dns_message_currentname(message, section, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + if (!dns_rdata_checkowner(name, rdata.rdclass, + rdata.type, false) || + !dns_rdata_checknames(&rdata, name, NULL)) + { + rdataset->attributes |= + DNS_RDATASETATTR_CHECKNAMES; + } + dns_rdata_reset(&rdata); + } + } + } +} + +static void +checknames(dns_message_t *message) { + checknamessection(message, DNS_SECTION_ANSWER); + checknamessection(message, DNS_SECTION_AUTHORITY); + checknamessection(message, DNS_SECTION_ADDITIONAL); +} + +/* + * Log server NSID at log level 'level' + */ +static void +log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level, + isc_mem_t *mctx) { + static const char hex[17] = "0123456789abcdef"; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + size_t buflen; + unsigned char *p, *nsid; + unsigned char *buf = NULL, *pbuf = NULL; + + REQUIRE(nsid_len <= UINT16_MAX); + + /* Allocate buffer for storing hex version of the NSID */ + buflen = nsid_len * 2 + 1; + buf = isc_mem_get(mctx, buflen); + pbuf = isc_mem_get(mctx, nsid_len + 1); + + /* Convert to hex */ + p = buf; + nsid = isc_buffer_current(opt); + for (size_t i = 0; i < nsid_len; i++) { + *p++ = hex[(nsid[i] >> 4) & 0xf]; + *p++ = hex[nsid[i] & 0xf]; + } + *p = '\0'; + + /* Make printable version */ + p = pbuf; + for (size_t i = 0; i < nsid_len; i++) { + *p++ = isprint(nsid[i]) ? nsid[i] : '.'; + } + *p = '\0'; + + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_NSID, DNS_LOGMODULE_RESOLVER, + level, "received NSID %s (\"%s\") from %s", buf, pbuf, + addrbuf); + + isc_mem_put(mctx, pbuf, nsid_len + 1); + isc_mem_put(mctx, buf, buflen); +} + +static bool +iscname(dns_message_t *message, dns_name_t *name) { + isc_result_t result; + + result = dns_message_findname(message, DNS_SECTION_ANSWER, name, + dns_rdatatype_cname, 0, NULL, NULL); + return (result == ISC_R_SUCCESS ? true : false); +} + +static bool +betterreferral(respctx_t *rctx) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + + for (result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY)) + { + name = NULL; + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + if (!isstrictsubdomain(name, rctx->fctx->domain)) { + continue; + } + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_ns) { + return (true); + } + } + } + return (false); +} + +/* + * resquery_response(): + * Handles responses received in response to iterative queries sent by + * resquery_send(). Sets up a response context (respctx_t). + */ +static void +resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) { + isc_result_t result; + resquery_t *query = (resquery_t *)arg; + fetchctx_t *fctx = NULL; + respctx_t rctx; + + if (eresult == ISC_R_CANCELED) { + return; + } + + REQUIRE(VALID_QUERY(query)); + fctx = query->fctx; + REQUIRE(VALID_FCTX(fctx)); + + QTRACE("response"); + + if (eresult == ISC_R_TIMEDOUT) { + result = resquery_timeout(query); + if (result == ISC_R_COMPLETE) { + return; + } + } + + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) { + inc_stats(fctx->res, dns_resstatscounter_responsev4); + } else { + inc_stats(fctx->res, dns_resstatscounter_responsev6); + } + + rctx_respinit(query, fctx, eresult, region, &rctx); + + if (eresult == ISC_R_SHUTTINGDOWN || + atomic_load_acquire(&fctx->res->exiting)) + { + result = ISC_R_SHUTTINGDOWN; + FCTXTRACE("resolver shutting down"); + rctx.finish = NULL; + rctx_done(&rctx, result); + return; + } + + result = rctx_timedout(&rctx); + if (result == ISC_R_COMPLETE) { + FCTXTRACE("timed out"); + return; + } + + fctx->addrinfo = query->addrinfo; + fctx->timeout = false; + fctx->timeouts = 0; + + /* + * Check whether the dispatcher has failed; if so we're done + */ + result = rctx_dispfail(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + if (query->tsig != NULL) { + result = dns_message_setquerytsig(query->rmessage, query->tsig); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set query tsig", result); + rctx_done(&rctx, result); + return; + } + } + + if (query->tsigkey != NULL) { + result = dns_message_settsigkey(query->rmessage, + query->tsigkey); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set tsig key", result); + rctx_done(&rctx, result); + return; + } + } + + dns_message_setclass(query->rmessage, fctx->res->rdclass); + + if ((rctx.retryopts & DNS_FETCHOPT_TCP) == 0) { + if ((rctx.retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + dns_adb_setudpsize(fctx->adb, query->addrinfo, + isc_buffer_usedlength(&rctx.buffer)); + } else { + dns_adb_plainresponse(fctx->adb, query->addrinfo); + } + } + + /* + * Parse response message. + */ + result = rctx_parse(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Log the incoming packet. + */ + rctx_logpacket(&rctx); + + if (query->rmessage->rdclass != fctx->res->rdclass) { + rctx.resend = true; + FCTXTRACE("bad class"); + rctx_done(&rctx, result); + return; + } + + /* + * Process receive opt record. + */ + rctx.opt = dns_message_getopt(query->rmessage); + if (rctx.opt != NULL) { + rctx_opt(&rctx); + } + + if (query->rmessage->cc_bad && (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * If the COOKIE is bad, assume it is an attack and + * keep listening for a good answer. + */ + rctx.nextitem = true; + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "bad cookie from %s", addrbuf); + } + rctx_done(&rctx, result); + return; + } + + /* + * Is the question the same as the one we asked? + * NOERROR/NXDOMAIN/YXDOMAIN/REFUSED/SERVFAIL/BADCOOKIE must + * have the same question. FORMERR/NOTIMP if they have a + * question section then it must match. + */ + switch (query->rmessage->rcode) { + case dns_rcode_notimp: + case dns_rcode_formerr: + if (query->rmessage->counts[DNS_SECTION_QUESTION] == 0) { + break; + } + FALLTHROUGH; + case dns_rcode_nxrrset: /* Not expected. */ + case dns_rcode_badcookie: + case dns_rcode_noerror: + case dns_rcode_nxdomain: + case dns_rcode_yxdomain: + case dns_rcode_refused: + case dns_rcode_servfail: + default: + result = same_question(fctx, query->rmessage); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("question section invalid", result); + rctx.nextitem = true; + rctx_done(&rctx, result); + return; + } + break; + } + + /* + * If the message is signed, check the signature. If not, this + * returns success anyway. + */ + result = dns_message_checksig(query->rmessage, fctx->res->view); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("signature check failed", result); + if (result == DNS_R_UNEXPECTEDTSIG || + result == DNS_R_EXPECTEDTSIG) + { + rctx.nextitem = true; + } + rctx_done(&rctx, result); + return; + } + + /* + * The dispatcher should ensure we only get responses with QR + * set. + */ + INSIST((query->rmessage->flags & DNS_MESSAGEFLAG_QR) != 0); + + /* + * If we have had a server cookie and don't get one retry over + * TCP. This may be a misconfigured anycast server or an attempt + * to send a spoofed response. Skip if we have a valid tsig. + */ + if (dns_message_gettsig(query->rmessage, NULL) == NULL && + !query->rmessage->cc_ok && !query->rmessage->cc_bad && + (rctx.retryopts & DNS_FETCHOPT_TCP) == 0) + { + unsigned char cookie[COOKIE_BUFFER_SIZE]; + if (dns_adb_getcookie(fctx->adb, query->addrinfo, cookie, + sizeof(cookie)) > CLIENT_COOKIE_SIZE) + { + if (isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&query->addrinfo->sockaddr, + addrbuf, sizeof(addrbuf)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "missing expected cookie " + "from %s", + addrbuf); + } + rctx.retryopts |= DNS_FETCHOPT_TCP; + rctx.resend = true; + rctx_done(&rctx, result); + return; + } + } + + rctx_edns(&rctx); + + /* + * Deal with truncated responses by retrying using TCP. + */ + if ((query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0) { + rctx.truncated = true; + } + + if (rctx.truncated) { + inc_stats(fctx->res, dns_resstatscounter_truncated); + if ((rctx.retryopts & DNS_FETCHOPT_TCP) != 0) { + rctx.broken_server = DNS_R_TRUNCATEDTCP; + rctx.next_server = true; + } else { + rctx.retryopts |= DNS_FETCHOPT_TCP; + rctx.resend = true; + } + FCTXTRACE3("message truncated", result); + rctx_done(&rctx, result); + return; + } + + /* + * Is it a query response? + */ + if (query->rmessage->opcode != dns_opcode_query) { + rctx.broken_server = DNS_R_UNEXPECTEDOPCODE; + rctx.next_server = true; + FCTXTRACE("invalid message opcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Update statistics about erroneous responses. + */ + switch (query->rmessage->rcode) { + case dns_rcode_noerror: + /* no error */ + break; + case dns_rcode_nxdomain: + inc_stats(fctx->res, dns_resstatscounter_nxdomain); + break; + case dns_rcode_servfail: + inc_stats(fctx->res, dns_resstatscounter_servfail); + break; + case dns_rcode_formerr: + inc_stats(fctx->res, dns_resstatscounter_formerr); + break; + case dns_rcode_refused: + inc_stats(fctx->res, dns_resstatscounter_refused); + break; + case dns_rcode_badvers: + inc_stats(fctx->res, dns_resstatscounter_badvers); + break; + case dns_rcode_badcookie: + inc_stats(fctx->res, dns_resstatscounter_badcookie); + break; + default: + inc_stats(fctx->res, dns_resstatscounter_othererror); + break; + } + + /* + * Bad server? + */ + result = rctx_badserver(&rctx, result); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Lame server? + */ + result = rctx_lameserver(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + + /* + * Handle delegation-only zones like NET or COM. + */ + rctx_delonly_zone(&rctx); + + /* + * Optionally call dns_rdata_checkowner() and + * dns_rdata_checknames() to validate the names in the response + * message. + */ + if ((fctx->res->options & DNS_RESOLVER_CHECKNAMES) != 0) { + checknames(query->rmessage); + } + + /* + * Clear cache bits. + */ + FCTX_ATTR_CLR(fctx, (FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE)); + + /* + * Did we get any answers? + */ + if (query->rmessage->counts[DNS_SECTION_ANSWER] > 0 && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_yxdomain || + query->rmessage->rcode == dns_rcode_nxdomain)) + { + result = rctx_answer(&rctx); + if (result == ISC_R_COMPLETE) { + return; + } + } else if (query->rmessage->counts[DNS_SECTION_AUTHORITY] > 0 || + query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain) + { + /* + * This might be an NXDOMAIN, NXRRSET, or referral. + * Call rctx_answer_none() to determine which it is. + */ + result = rctx_answer_none(&rctx); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_CHASEDSSERVERS: + break; + case DNS_R_DELEGATION: + /* + * With NOFOLLOW we want to pass return + * DNS_R_DELEGATION to resume_qmin. + */ + if ((fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + result = ISC_R_SUCCESS; + } + break; + default: + /* + * Something has gone wrong. + */ + if (result == DNS_R_FORMERR) { + rctx.next_server = true; + } + FCTXTRACE3("rctx_answer_none", result); + rctx_done(&rctx, result); + return; + } + } else { + /* + * The server is insane. + */ + /* XXXRTH Log */ + rctx.broken_server = DNS_R_UNEXPECTEDRCODE; + rctx.next_server = true; + FCTXTRACE("broken server: unexpected rcode"); + rctx_done(&rctx, result); + return; + } + + /* + * Follow additional section data chains. + */ + rctx_additional(&rctx); + + /* + * Cache the cacheable parts of the message. This may also + * cause work to be queued to the DNSSEC validator. + */ + if (WANTCACHE(fctx)) { + isc_result_t tresult; + tresult = cache_message(fctx, query->rmessage, query->addrinfo, + rctx.now); + if (tresult != ISC_R_SUCCESS) { + FCTXTRACE3("cache_message complete", tresult); + rctx_done(&rctx, tresult); + return; + } + } + + /* + * Negative caching + */ + rctx_ncache(&rctx); + + FCTXTRACE("resquery_response done"); + rctx_done(&rctx, result); +} + +/* + * rctx_respinit(): + * Initialize the response context structure 'rctx' to all zeroes, then + * set the task, event, query and fctx information from + * resquery_response(). + */ +static void +rctx_respinit(resquery_t *query, fetchctx_t *fctx, isc_result_t result, + isc_region_t *region, respctx_t *rctx) { + *rctx = (respctx_t){ .result = result, + .query = query, + .fctx = fctx, + .broken_type = badns_response, + .retryopts = query->options }; + if (result == ISC_R_SUCCESS) { + REQUIRE(region != NULL); + isc_buffer_init(&rctx->buffer, region->base, region->length); + isc_buffer_add(&rctx->buffer, region->length); + } else { + isc_buffer_initnull(&rctx->buffer); + } + TIME_NOW(&rctx->tnow); + rctx->finish = &rctx->tnow; + rctx->now = (isc_stdtime_t)isc_time_seconds(&rctx->tnow); +} + +/* + * rctx_answer_init(): + * Clear and reinitialize those portions of 'rctx' that will be needed + * when scanning the answer section of the response message. This can be + * called more than once if scanning needs to be restarted (though + * currently there are no cases in which this occurs). + */ +static void +rctx_answer_init(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + + rctx->aa = ((rctx->query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0); + if (rctx->aa) { + rctx->trust = dns_trust_authanswer; + } else { + rctx->trust = dns_trust_answer; + } + + /* + * There can be multiple RRSIG and SIG records at a name so + * we treat these types as a subset of ANY. + */ + rctx->type = fctx->type; + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_sig) + { + rctx->type = dns_rdatatype_any; + } + + /* + * Bigger than any valid DNAME label count. + */ + rctx->dname_labels = dns_name_countlabels(fctx->name); + rctx->domain_labels = dns_name_countlabels(fctx->domain); + + rctx->found_type = dns_rdatatype_none; + + rctx->aname = NULL; + rctx->ardataset = NULL; + + rctx->cname = NULL; + rctx->crdataset = NULL; + + rctx->dname = NULL; + rctx->drdataset = NULL; + + rctx->ns_name = NULL; + rctx->ns_rdataset = NULL; + + rctx->soa_name = NULL; + rctx->ds_name = NULL; + rctx->found_name = NULL; +} + +/* + * rctx_dispfail(): + * Handle the case where the dispatcher failed + */ +static isc_result_t +rctx_dispfail(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + + if (rctx->result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + /* + * There's no hope for this response. + */ + rctx->next_server = true; + + /* + * If this is a network failure, the operation is cancelled, + * or the network manager is being shut down, we mark the server + * as bad so that we won't try it for this fetch again. Also + * adjust finish and no_response so that we penalize this + * address in SRTT adjustments later. + */ + switch (rctx->result) { + case ISC_R_EOF: + case ISC_R_HOSTUNREACH: + case ISC_R_NETUNREACH: + case ISC_R_CONNREFUSED: + case ISC_R_CONNECTIONRESET: + case ISC_R_INVALIDPROTO: + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: + rctx->broken_server = rctx->result; + rctx->broken_type = badns_unreachable; + rctx->finish = NULL; + rctx->no_response = true; + break; + default: + break; + } + + FCTXTRACE3("dispatcher failure", rctx->result); + rctx_done(rctx, ISC_R_SUCCESS); + return (ISC_R_COMPLETE); +} + +/* + * rctx_timedout(): + * Handle the case where a dispatch read timed out. + */ +static isc_result_t +rctx_timedout(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + + if (rctx->result == ISC_R_TIMEDOUT) { + isc_time_t now; + + inc_stats(fctx->res, dns_resstatscounter_querytimeout); + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx->timeout = true; + fctx->timeouts++; + + isc_time_now(&now); + /* netmgr timeouts are accurate to the millisecond */ + if (isc_time_microdiff(&fctx->expires, &now) < US_PER_MS) { + FCTXTRACE("query timed out; stopped trying to make " + "fetch happen"); + } else { + FCTXTRACE("query timed out; trying next server"); + /* try next server */ + rctx->no_response = true; + rctx->finish = NULL; + rctx->next_server = true; + } + + rctx_done(rctx, rctx->result); + return (ISC_R_COMPLETE); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_parse(): + * Parse the response message. + */ +static isc_result_t +rctx_parse(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + result = dns_message_parse(query->rmessage, &rctx->buffer, 0); + if (result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + FCTXTRACE3("message failed to parse", result); + + switch (result) { + case ISC_R_UNEXPECTEDEND: + if (query->rmessage->question_ok && + (query->rmessage->flags & DNS_MESSAGEFLAG_TC) != 0 && + (rctx->retryopts & DNS_FETCHOPT_TCP) == 0) + { + /* + * We defer retrying via TCP for a bit so we can + * check out this message further. + */ + rctx->truncated = true; + return (ISC_R_SUCCESS); + } + + /* + * Either the message ended prematurely, + * and/or wasn't marked as being truncated, + * and/or this is a response to a query we + * sent over TCP. In all of these cases, + * something is wrong with the remote + * server and we don't want to retry using + * TCP. + */ + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = result; + rctx->next_server = true; + } + + rctx_done(rctx, result); + break; + case DNS_R_FORMERR: + if ((rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = true; + } + + rctx_done(rctx, result); + break; + default: + /* + * Something bad has happened. + */ + rctx_done(rctx, result); + break; + } + + return (ISC_R_COMPLETE); +} + +/* + * rctx_opt(): + * Process the OPT record in the response. + */ +static void +rctx_opt(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_rdata_t rdata; + isc_buffer_t optbuf; + isc_result_t result; + uint16_t optcode; + uint16_t optlen; + unsigned char *optvalue; + dns_adbaddrinfo_t *addrinfo; + unsigned char cookie[CLIENT_COOKIE_SIZE]; + bool seen_cookie = false; + bool seen_nsid = false; + + result = dns_rdataset_first(rctx->opt); + if (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(rctx->opt, &rdata); + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) >= 4) { + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + INSIST(optlen <= isc_buffer_remaininglength(&optbuf)); + switch (optcode) { + case DNS_OPT_NSID: + if (!seen_nsid && (query->options & + DNS_FETCHOPT_WANTNSID) != 0) + { + log_nsid(&optbuf, optlen, query, + ISC_LOG_INFO, fctx->res->mctx); + } + isc_buffer_forward(&optbuf, optlen); + seen_nsid = true; + break; + case DNS_OPT_COOKIE: + /* + * Only process the first cookie option. + */ + if (seen_cookie) { + isc_buffer_forward(&optbuf, optlen); + break; + } + optvalue = isc_buffer_current(&optbuf); + compute_cc(query, cookie, sizeof(cookie)); + INSIST(query->rmessage->cc_bad == 0 && + query->rmessage->cc_ok == 0); + if (optlen >= CLIENT_COOKIE_SIZE && + memcmp(cookie, optvalue, + CLIENT_COOKIE_SIZE) == 0) + { + if (optlen == CLIENT_COOKIE_SIZE) { + query->rmessage->cc_echoed = 1; + } else { + query->rmessage->cc_ok = 1; + inc_stats( + fctx->res, + dns_resstatscounter_cookieok); + addrinfo = query->addrinfo; + dns_adb_setcookie( + fctx->adb, addrinfo, + optvalue, optlen); + } + } else { + query->rmessage->cc_bad = 1; + } + isc_buffer_forward(&optbuf, optlen); + inc_stats(fctx->res, + dns_resstatscounter_cookiein); + seen_cookie = true; + break; + default: + isc_buffer_forward(&optbuf, optlen); + break; + } + } + INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + } +} + +/* + * rctx_edns(): + * Determine whether the remote server is using EDNS correctly or + * incorrectly and record that information if needed. + */ +static void +rctx_edns(respctx_t *rctx) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + + /* + * We have an affirmative response to the query and we have + * previously got a response from this server which indicated + * EDNS may not be supported so we can now cache the lack of + * EDNS support. + */ + if (rctx->opt == NULL && !EDNSOK(query->addrinfo) && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain || + query->rmessage->rcode == dns_rcode_refused || + query->rmessage->rcode == dns_rcode_yxdomain) && + bad_edns(fctx, &query->addrinfo->sockaddr)) + { + dns_message_logpacket( + query->rmessage, "received packet (bad edns) from", + &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOEDNS0, + FCTX_ADDRINFO_NOEDNS0); + } else if (rctx->opt == NULL && + (query->rmessage->flags & DNS_MESSAGEFLAG_TC) == 0 && + !EDNSOK(query->addrinfo) && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * We didn't get a OPT record in response to a EDNS + * query. + * + * Old versions of named incorrectly drop the OPT record + * when there is a signed, truncated response so we + * check that TC is not set. + * + * Record that the server is not talking EDNS. While + * this should be safe to do for any rcode we limit it + * to NOERROR and NXDOMAIN. + */ + dns_message_logpacket( + query->rmessage, "received packet (no opt) from", + &query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), + fctx->res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOEDNS0, + FCTX_ADDRINFO_NOEDNS0); + } + + /* + * If we get a non error EDNS response record the fact so we + * won't fallback to plain DNS in the future for this server. + */ + if (rctx->opt != NULL && !EDNSOK(query->addrinfo) && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0 && + (query->rmessage->rcode == dns_rcode_noerror || + query->rmessage->rcode == dns_rcode_nxdomain || + query->rmessage->rcode == dns_rcode_refused || + query->rmessage->rcode == dns_rcode_yxdomain)) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, FCTX_ADDRINFO_EDNSOK); + } +} + +/* + * rctx_answer(): + * We might have answers, or we might have a malformed delegation with + * records in the answer section. Call rctx_answer_positive() or + * rctx_answer_none() as appropriate. + */ +static isc_result_t +rctx_answer(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if ((query->rmessage->flags & DNS_MESSAGEFLAG_AA) != 0 || + ISFORWARDER(query->addrinfo)) + { + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (AA/fwd)", result); + } + } else if (iscname(query->rmessage, fctx->name) && + fctx->type != dns_rdatatype_any && + fctx->type != dns_rdatatype_cname) + { + /* + * A BIND8 server could return a non-authoritative + * answer when a CNAME is followed. We should treat + * it as a valid answer. + */ + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)", + result); + } + } else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) { + result = rctx_answer_positive(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_positive (!NS)", result); + } + } else { + /* + * This may be a delegation. First let's check for + */ + + if (fctx->type == dns_rdatatype_ns) { + /* + * A BIND 8 server could incorrectly return a + * non-authoritative answer to an NS query + * instead of a referral. Since this answer + * lacks the SIGs necessary to do DNSSEC + * validation, we must invoke the following + * special kludge to treat it as a referral. + */ + rctx->ns_in_answer = true; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none (NS)", result); + } + } else { + /* + * Some other servers may still somehow include + * an answer when it should return a referral + * with an empty answer. Check to see if we can + * treat this as a referral by ignoring the + * answer. Further more, there may be an + * implementation that moves A/AAAA glue records + * to the answer section for that type of + * delegation when the query is for that glue + * record. glue_in_answer will handle + * such a corner case. + */ + rctx->glue_in_answer = true; + result = rctx_answer_none(rctx); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("rctx_answer_none", result); + } + } + + if (result == DNS_R_DELEGATION) { + /* + * With NOFOLLOW we want to return DNS_R_DELEGATION to + * resume_qmin. + */ + if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) != 0) + { + return (result); + } + result = ISC_R_SUCCESS; + } else { + /* + * At this point, AA is not set, the response + * is not a referral, and the server is not a + * forwarder. It is technically lame and it's + * easier to treat it as such than to figure out + * some more elaborate course of action. + */ + rctx->broken_server = DNS_R_LAME; + rctx->next_server = true; + FCTXTRACE3("rctx_answer lame", result); + rctx_done(rctx, result); + return (ISC_R_COMPLETE); + } + } + + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_FORMERR) { + rctx->next_server = true; + } + FCTXTRACE3("rctx_answer failed", result); + rctx_done(rctx, result); + return (ISC_R_COMPLETE); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_positive(): + * Handles positive responses. Depending which type of answer this is + * (matching QNAME/QTYPE, CNAME, DNAME, ANY) calls the proper routine + * to handle it (rctx_answer_match(), rctx_answer_cname(), + * rctx_answer_dname(), rctx_answer_any()). + */ +static isc_result_t +rctx_answer_positive(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + FCTXTRACE("rctx_answer_positive"); + + rctx_answer_init(rctx); + rctx_answer_scan(rctx); + + /* + * Determine which type of positive answer this is: + * type ANY, CNAME, DNAME, or an answer matching QNAME/QTYPE. + * Call the appropriate routine to handle the answer type. + */ + if (rctx->aname != NULL && rctx->type == dns_rdatatype_any) { + result = rctx_answer_any(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->aname != NULL) { + result = rctx_answer_match(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->cname != NULL) { + result = rctx_answer_cname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else if (rctx->dname != NULL) { + result = rctx_answer_dname(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } else { + log_formerr(fctx, "reply has no answer"); + return (DNS_R_FORMERR); + } + + /* + * This response is now potentially cacheable. + */ + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE); + + /* + * Did chaining end before we got the final answer? + */ + if (rctx->chaining) { + return (ISC_R_SUCCESS); + } + + /* + * We didn't end with an incomplete chain, so the rcode should + * be "no error". + */ + if (rctx->query->rmessage->rcode != dns_rcode_noerror) { + log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE " + "indicates error"); + return (DNS_R_FORMERR); + } + + /* + * Cache records in the authority section, if + * there are any suitable for caching. + */ + rctx_authority_positive(rctx); + + log_ns_ttl(fctx, "rctx_answer"); + + if (rctx->ns_rdataset != NULL && + dns_name_equal(fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_scan(): + * Perform a single pass over the answer section of a response, looking + * for an answer that matches QNAME/QTYPE, or a CNAME matching QNAME, or + * a covering DNAME. If more than one rdataset is found matching these + * criteria, then only one is kept. Order of preference is 1) the + * shortest DNAME, 2) the first matching answer, or 3) the first CNAME. + */ +static void +rctx_answer_scan(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_rdataset_t *rdataset = NULL; + + for (result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_ANSWER)) + { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_ANSWER, &name); + namereln = dns_name_fullcompare(fctx->name, name, &order, + &nlabels); + switch (namereln) { + case dns_namereln_equal: + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == rctx->type || + rctx->type == dns_rdatatype_any) + { + rctx->aname = name; + if (rctx->type != dns_rdatatype_any) { + rctx->ardataset = rdataset; + } + break; + } + if (rdataset->type == dns_rdatatype_cname) { + rctx->cname = name; + rctx->crdataset = rdataset; + break; + } + } + break; + + case dns_namereln_subdomain: + /* + * Don't accept DNAME from parent namespace. + */ + if (name_external(name, dns_rdatatype_dname, fctx)) { + continue; + } + + /* + * In-scope DNAME records must have at least + * as many labels as the domain being queried. + * They also must be less that qname's labels + * and any previously found dname. + */ + if (nlabels >= rctx->dname_labels || + nlabels < rctx->domain_labels) + { + continue; + } + + /* + * We are looking for the shortest DNAME if + * there are multiple ones (which there + * shouldn't be). + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_dname) { + continue; + } + rctx->dname = name; + rctx->drdataset = rdataset; + rctx->dname_labels = nlabels; + break; + } + break; + default: + break; + } + } + + /* + * If a DNAME was found, then any CNAME or other answer matching + * QNAME that may also have been found must be ignored. + * Similarly, if a matching answer was found along with a CNAME, + * the CNAME must be ignored. + */ + if (rctx->dname != NULL) { + rctx->aname = NULL; + rctx->ardataset = NULL; + rctx->cname = NULL; + rctx->crdataset = NULL; + } else if (rctx->aname != NULL) { + rctx->cname = NULL; + rctx->crdataset = NULL; + } +} + +/* + * rctx_answer_any(): + * Handle responses to queries of type ANY. Scan the answer section, + * and as long as each RRset is of a type that is valid in the answer + * section, and the rdata isn't filtered, cache it. + */ +static isc_result_t +rctx_answer_any(respctx_t *rctx) { + dns_rdataset_t *rdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + for (rdataset = ISC_LIST_HEAD(rctx->aname->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!validinanswer(rdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if ((fctx->type == dns_rdatatype_sig || + fctx->type == dns_rdatatype_rrsig) && + rdataset->type != fctx->type) + { + continue; + } + + if ((rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, rctx->aname, + rdataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + if ((rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, fctx->name, rctx->aname, + rdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = rctx->trust; + + (void)dns_rdataset_additionaldata(rdataset, rctx->aname, + check_related, rctx); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_match(): + * Handle responses that match the QNAME/QTYPE of the resolver query. + * If QTYPE is valid in the answer section and the rdata isn't filtered, + * the answer can be cached. If there is additional section data related + * to the answer, it can be cached as well. + */ +static isc_result_t +rctx_answer_match(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->ardataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if ((rctx->ardataset->type == dns_rdatatype_a || + rctx->ardataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(fctx->res->view, rctx->aname, + rctx->ardataset)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + if ((rctx->ardataset->type == dns_rdatatype_cname || + rctx->ardataset->type == dns_rdatatype_dname) && + rctx->type != rctx->ardataset->type && + rctx->type != dns_rdatatype_any && + !is_answertarget_allowed(fctx, fctx->name, rctx->aname, + rctx->ardataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->aname->attributes |= DNS_NAMEATTR_CACHE; + rctx->aname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->ardataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->ardataset->trust = rctx->trust; + (void)dns_rdataset_additionaldata(rctx->ardataset, rctx->aname, + check_related, rctx); + + for (sigrdataset = ISC_LIST_HEAD(rctx->aname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != rctx->type) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_cname(): + * Handle answers containing a CNAME. Cache the CNAME, and flag that + * there may be additional chain answers to find. + */ +static isc_result_t +rctx_answer_cname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->crdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (rctx->type == dns_rdatatype_rrsig || + rctx->type == dns_rdatatype_key || rctx->type == dns_rdatatype_nsec) + { + char buf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(rctx->type, buf, sizeof(buf)); + log_formerr(fctx, "CNAME response for %s RR", buf); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (!is_answertarget_allowed(fctx, fctx->name, rctx->cname, + rctx->crdataset, NULL)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->cname->attributes |= DNS_NAMEATTR_CACHE; + rctx->cname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->cname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->crdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->crdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->crdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->cname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_cname) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + rctx->chaining = true; + return (ISC_R_SUCCESS); +} + +/* + * rctx_answer_dname(): + * Handle responses with covering DNAME records. + */ +static isc_result_t +rctx_answer_dname(respctx_t *rctx) { + dns_rdataset_t *sigrdataset = NULL; + fetchctx_t *fctx = rctx->fctx; + + if (!validinanswer(rctx->drdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (!is_answertarget_allowed(fctx, fctx->name, rctx->dname, + rctx->drdataset, &rctx->chaining)) + { + rctx->result = DNS_R_SERVFAIL; + return (ISC_R_COMPLETE); + } + + rctx->dname->attributes |= DNS_NAMEATTR_CACHE; + rctx->dname->attributes |= DNS_NAMEATTR_ANSWER; + rctx->dname->attributes |= DNS_NAMEATTR_CHAINING; + rctx->drdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CACHE; + rctx->drdataset->attributes |= DNS_RDATASETATTR_CHAINING; + rctx->drdataset->trust = rctx->trust; + + for (sigrdataset = ISC_LIST_HEAD(rctx->dname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_dname) + { + continue; + } + + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = rctx->trust; + break; + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_authority_positive(): + * Examine the records in the authority section (if there are any) for a + * positive answer. We expect the names for all rdatasets in this + * section to be subdomains of the domain being queried; any that are + * not are skipped. We expect to find only *one* owner name; any names + * after the first one processed are ignored. We expect to find only + * rdatasets of type NS, RRSIG, or SIG; all others are ignored. Whatever + * remains can be cached at trust level authauthority or additional + * (depending on whether the AA bit was set on the answer). + */ +static void +rctx_authority_positive(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + bool done = false; + isc_result_t result; + + result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + while (!done && result == ISC_R_SUCCESS) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + + if (!name_external(name, dns_rdatatype_ns, fctx)) { + dns_rdataset_t *rdataset = NULL; + + /* + * We expect to find NS or SIG NS rdatasets, and + * nothing else. + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_ns || + (rdataset->type == dns_rdatatype_rrsig && + rdataset->covers == dns_rdatatype_ns)) + { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else { + rdataset->trust = + dns_trust_additional; + } + + if (rdataset->type == dns_rdatatype_ns) + { + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; + } + /* + * Mark any additional data + * related to this rdataset. + */ + (void)dns_rdataset_additionaldata( + rdataset, name, check_related, + rctx); + done = true; + } + } + } + + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + } +} + +/* + * rctx_answer_none(): + * Handles a response without an answer: this is either a negative + * response (NXDOMAIN or NXRRSET) or a referral. Determine which it is, + * then either scan the authority section for negative caching and + * DNSSEC proof of nonexistence, or else call rctx_referral(). + */ +static isc_result_t +rctx_answer_none(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + FCTXTRACE("rctx_answer_none"); + + rctx_answer_init(rctx); + + /* + * Sometimes we can tell if its a negative response by looking + * at the message header. + */ + if (rctx->query->rmessage->rcode == dns_rcode_nxdomain || + (rctx->query->rmessage->counts[DNS_SECTION_ANSWER] == 0 && + rctx->query->rmessage->counts[DNS_SECTION_AUTHORITY] == 0)) + { + rctx->negative = true; + } + + /* + * Process the authority section + */ + result = rctx_authority_negative(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + + log_ns_ttl(fctx, "rctx_answer_none"); + + if (rctx->ns_rdataset != NULL && + dns_name_equal(fctx->domain, rctx->ns_name) && + !dns_name_equal(rctx->ns_name, dns_rootname)) + { + trim_ns_ttl(fctx, rctx->ns_name, rctx->ns_rdataset); + } + + /* + * A negative response has a SOA record (Type 2) + * and a optional NS RRset (Type 1) or it has neither + * a SOA or a NS RRset (Type 3, handled above) or + * rcode is NXDOMAIN (handled above) in which case + * the NS RRset is allowed (Type 4). + */ + if (rctx->soa_name != NULL) { + rctx->negative = true; + } + + if (!rctx->ns_in_answer && !rctx->glue_in_answer) { + /* + * Process DNSSEC records in the authority section. + */ + result = rctx_authority_dnssec(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + } + + /* + * Trigger lookups for DNS nameservers. + */ + if (rctx->negative && + rctx->query->rmessage->rcode == dns_rcode_noerror && + fctx->type == dns_rdatatype_ds && rctx->soa_name != NULL && + dns_name_equal(rctx->soa_name, fctx->name) && + !dns_name_equal(fctx->name, dns_rootname)) + { + return (DNS_R_CHASEDSSERVERS); + } + + /* + * Did we find anything? + */ + if (!rctx->negative && rctx->ns_name == NULL) { + /* + * The responder is insane. + */ + if (rctx->found_name == NULL) { + log_formerr(fctx, "invalid response"); + return (DNS_R_FORMERR); + } + if (!dns_name_issubdomain(rctx->found_name, fctx->domain)) { + char nbuf[DNS_NAME_FORMATSIZE]; + char dbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_rdatatype_format(rctx->found_type, tbuf, + sizeof(tbuf)); + dns_name_format(rctx->found_name, nbuf, sizeof(nbuf)); + dns_name_format(fctx->domain, dbuf, sizeof(dbuf)); + + log_formerr(fctx, + "Name %s (%s) not subdomain" + " of zone %s -- invalid response", + nbuf, tbuf, dbuf); + } else { + log_formerr(fctx, "invalid response"); + } + return (DNS_R_FORMERR); + } + + /* + * If we found both NS and SOA, they should be the same name. + */ + if (rctx->ns_name != NULL && rctx->soa_name != NULL && + rctx->ns_name != rctx->soa_name) + { + log_formerr(fctx, "NS/SOA mismatch"); + return (DNS_R_FORMERR); + } + + /* + * Handle a referral. + */ + result = rctx_referral(rctx); + if (result == ISC_R_COMPLETE) { + return (rctx->result); + } + + /* + * Since we're not doing a referral, we don't want to cache any + * NS RRs we may have found. + */ + if (rctx->ns_name != NULL) { + rctx->ns_name->attributes &= ~DNS_NAMEATTR_CACHE; + } + + if (rctx->negative) { + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTNCACHE); + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_authority_negative(): + * Scan the authority section of a negative answer, handling + * NS and SOA records. (Note that this function does *not* handle + * DNSSEC records; those are addressed separately in + * rctx_authority_dnssec() below.) + */ +static isc_result_t +rctx_authority_negative(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_section_t section; + dns_rdataset_t *rdataset = NULL; + bool finished = false; + + if (rctx->ns_in_answer) { + INSIST(fctx->type == dns_rdatatype_ns); + section = DNS_SECTION_ANSWER; + } else { + section = DNS_SECTION_AUTHORITY; + } + + result = dns_message_firstname(rctx->query->rmessage, section); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, section, &name); + result = dns_message_nextname(rctx->query->rmessage, section); + if (result != ISC_R_SUCCESS) { + finished = true; + } + + if (!dns_name_issubdomain(name, fctx->domain)) { + continue; + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_rdatatype_t type = rdataset->type; + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + if (((type == dns_rdatatype_ns || + type == dns_rdatatype_soa) && + !dns_name_issubdomain(fctx->name, name))) + { + char qbuf[DNS_NAME_FORMATSIZE]; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(type, tbuf, sizeof(tbuf)); + dns_name_format(name, nbuf, sizeof(nbuf)); + dns_name_format(fctx->name, qbuf, sizeof(qbuf)); + log_formerr(fctx, + "unrelated %s %s in " + "%s authority section", + tbuf, nbuf, qbuf); + break; + } + + switch (type) { + case dns_rdatatype_ns: + /* + * NS or RRSIG NS. + * + * Only one set of NS RRs is allowed. + */ + if (rdataset->type == dns_rdatatype_ns) { + if (rctx->ns_name != NULL && + name != rctx->ns_name) + { + log_formerr(fctx, "multiple NS " + "RRsets " + "in " + "authority " + "section"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ns_name = name; + rctx->ns_rdataset = rdataset; + } + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = dns_trust_glue; + break; + case dns_rdatatype_soa: + /* + * SOA, or RRSIG SOA. + * + * Only one SOA is allowed. + */ + if (rdataset->type == dns_rdatatype_soa) { + if (rctx->soa_name != NULL && + name != rctx->soa_name) + { + log_formerr(fctx, "multiple " + "SOA RRs " + "in " + "authority " + "section"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->soa_name = name; + } + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= DNS_RDATASETATTR_NCACHE; + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; + } + } + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_ncache(): + * Cache the negatively cacheable parts of the message. This may + * also cause work to be queued to the DNSSEC validator. + */ +static void +rctx_ncache(respctx_t *rctx) { + isc_result_t result; + dns_rdatatype_t covers; + fetchctx_t *fctx = rctx->fctx; + + if (!WANTNCACHE(fctx)) { + return; + } + + /* + * Cache DS NXDOMAIN separately to other types. + */ + if (rctx->query->rmessage->rcode == dns_rcode_nxdomain && + fctx->type != dns_rdatatype_ds) + { + covers = dns_rdatatype_any; + } else { + covers = fctx->type; + } + + /* + * Cache any negative cache entries in the message. + */ + result = ncache_message(fctx, rctx->query->rmessage, + rctx->query->addrinfo, covers, rctx->now); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("ncache_message complete", result); + } +} + +/* + * rctx_authority_dnssec(): + * + * Scan the authority section of a negative answer or referral, + * handling DNSSEC records (i.e. NSEC, NSEC3, DS). + */ +static isc_result_t +rctx_authority_dnssec(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + dns_rdataset_t *rdataset = NULL; + bool finished = false; + + REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer); + + result = dns_message_firstname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + while (!finished) { + dns_name_t *name = NULL; + + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY, &name); + result = dns_message_nextname(rctx->query->rmessage, + DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) { + finished = true; + } + + if (!dns_name_issubdomain(name, fctx->domain)) { + /* + * Invalid name found; preserve it for logging + * later. + */ + rctx->found_name = name; + rctx->found_type = ISC_LIST_HEAD(name->list)->type; + continue; + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + bool checknta = true; + bool secure_domain = false; + dns_rdatatype_t type = rdataset->type; + + if (type == dns_rdatatype_rrsig) { + type = rdataset->covers; + } + + switch (type) { + case dns_rdatatype_nsec: + case dns_rdatatype_nsec3: + if (rctx->negative) { + name->attributes |= DNS_NAMEATTR_NCACHE; + rdataset->attributes |= + DNS_RDATASETATTR_NCACHE; + } else if (type == dns_rdatatype_nsec) { + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + } + + if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + /* + * No additional data needs to be + * marked. + */ + break; + case dns_rdatatype_ds: + /* + * DS or SIG DS. + * + * These should only be here if this is + * a referral, and there should only be + * one DS RRset. + */ + if (rctx->ns_name == NULL) { + log_formerr(fctx, "DS with no " + "referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + if (rdataset->type == dns_rdatatype_ds) { + if (rctx->ds_name != NULL && + name != rctx->ds_name) + { + log_formerr(fctx, "DS doesn't " + "match " + "referral " + "(NS)"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + rctx->ds_name = name; + } + + name->attributes |= DNS_NAMEATTR_CACHE; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + + if ((fctx->options & DNS_FETCHOPT_NONTA) != 0) { + checknta = false; + } + if (fctx->res->view->enablevalidation) { + result = issecuredomain( + fctx->res->view, name, + dns_rdatatype_ds, fctx->now, + checknta, NULL, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (secure_domain) { + rdataset->trust = + dns_trust_pending_answer; + } else if (rctx->aa) { + rdataset->trust = + dns_trust_authauthority; + } else if (ISFORWARDER(fctx->addrinfo)) { + rdataset->trust = dns_trust_answer; + } else { + rdataset->trust = dns_trust_additional; + } + break; + default: + continue; + } + } + } + + return (ISC_R_SUCCESS); +} + +/* + * rctx_referral(): + * Handles referral responses. Check for sanity, find glue as needed, + * and update the fetch context to follow the delegation. + */ +static isc_result_t +rctx_referral(respctx_t *rctx) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + + if (rctx->negative || rctx->ns_name == NULL) { + return (ISC_R_SUCCESS); + } + + /* + * We already know ns_name is a subdomain of fctx->domain. + * If ns_name is equal to fctx->domain, we're not making + * progress. We return DNS_R_FORMERR so that we'll keep + * trying other servers. + */ + if (dns_name_equal(rctx->ns_name, fctx->domain)) { + log_formerr(fctx, "non-improving referral"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + /* + * If the referral name is not a parent of the query + * name, consider the responder insane. + */ + if (!dns_name_issubdomain(fctx->name, rctx->ns_name)) { + /* Logged twice */ + log_formerr(fctx, "referral to non-parent"); + FCTXTRACE("referral to non-parent"); + rctx->result = DNS_R_FORMERR; + return (ISC_R_COMPLETE); + } + + /* + * Mark any additional data related to this rdataset. + * It's important that we do this before we change the + * query domain. + */ + INSIST(rctx->ns_rdataset != NULL); + FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING); + (void)dns_rdataset_additionaldata(rctx->ns_rdataset, rctx->ns_name, + check_related, rctx); +#if CHECK_FOR_GLUE_IN_ANSWER + /* + * Look in the answer section for "glue" that is incorrectly + * returned as a answer. This is needed if the server also + * minimizes the response size by not adding records to the + * additional section that are in the answer section or if + * the record gets dropped due to message size constraints. + */ + if (rctx->glue_in_answer && + (fctx->type == dns_rdatatype_aaaa || fctx->type == dns_rdatatype_a)) + { + (void)dns_rdataset_additionaldata( + rctx->ns_rdataset, rctx->ns_name, check_answer, fctx); + } +#endif /* if CHECK_FOR_GLUE_IN_ANSWER */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING); + + /* + * NS rdatasets with 0 TTL cause problems. + * dns_view_findzonecut() will not find them when we + * try to follow the referral, and we'll SERVFAIL + * because the best nameservers are now above QDOMAIN. + * We force the TTL to 1 second to prevent this. + */ + if (rctx->ns_rdataset->ttl == 0) { + rctx->ns_rdataset->ttl = 1; + } + + /* + * Set the current query domain to the referral name. + * + * XXXRTH We should check if we're in forward-only mode, and + * if so we should bail out. + */ + INSIST(dns_name_countlabels(fctx->domain) > 0); + fcount_decr(fctx); + + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + dns_name_copy(rctx->ns_name, fctx->domain); + + if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) { + dns_name_copy(rctx->ns_name, fctx->qmindcname); + + fctx_minimize_qname(fctx); + } + + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); + } + + FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE); + fctx->ns_ttl_ok = false; + log_ns_ttl(fctx, "DELEGATION"); + rctx->result = DNS_R_DELEGATION; + + /* + * Reinitialize 'rctx' to prepare for following the delegation: + * set the get_nameservers and next_server flags appropriately + * and reset the fetch context counters. + * + */ + if ((rctx->fctx->options & DNS_FETCHOPT_NOFOLLOW) == 0) { + rctx->get_nameservers = true; + rctx->next_server = true; + rctx->fctx->restarts = 0; + rctx->fctx->referrals++; + rctx->fctx->querysent = 0; + rctx->fctx->lamecount = 0; + rctx->fctx->quotacount = 0; + rctx->fctx->neterr = 0; + rctx->fctx->badresp = 0; + rctx->fctx->adberr = 0; + } + + return (ISC_R_COMPLETE); +} + +/* + * rctx_additional(): + * Scan the additional section of a response to find records related + * to answers we were interested in. + */ +static void +rctx_additional(respctx_t *rctx) { + bool rescan; + dns_section_t section = DNS_SECTION_ADDITIONAL; + isc_result_t result; + +again: + rescan = false; + + for (result = dns_message_firstname(rctx->query->rmessage, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(rctx->query->rmessage, section)) + { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + dns_message_currentname(rctx->query->rmessage, + DNS_SECTION_ADDITIONAL, &name); + if ((name->attributes & DNS_NAMEATTR_CHASE) == 0) { + continue; + } + name->attributes &= ~DNS_NAMEATTR_CHASE; + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (CHASE(rdataset)) { + rdataset->attributes &= ~DNS_RDATASETATTR_CHASE; + (void)dns_rdataset_additionaldata( + rdataset, name, check_related, rctx); + rescan = true; + } + } + } + if (rescan) { + goto again; + } +} + +/* + * rctx_nextserver(): + * We found something wrong with the remote server, but it may be + * useful to try another one. + */ +static void +rctx_nextserver(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + bool retrying = true; + + if (result == DNS_R_FORMERR) { + rctx->broken_server = DNS_R_FORMERR; + } + if (rctx->broken_server != ISC_R_SUCCESS) { + /* + * Add this server to the list of bad servers for + * this fctx. + */ + add_bad(fctx, message, addrinfo, rctx->broken_server, + rctx->broken_type); + } + + if (rctx->get_nameservers) { + dns_fixedname_t foundname, founddc; + dns_name_t *name, *fname, *dcname; + unsigned int findoptions = 0; + + fname = dns_fixedname_initname(&foundname); + dcname = dns_fixedname_initname(&founddc); + + if (result != ISC_R_SUCCESS) { + fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL); + return; + } + if (dns_rdatatype_atparent(fctx->type)) { + findoptions |= DNS_DBFIND_NOEXACT; + } + if ((rctx->retryopts & DNS_FETCHOPT_UNSHARED) == 0) { + name = fctx->name; + } else { + name = fctx->domain; + } + result = dns_view_findzonecut( + fctx->res->view, name, fname, dcname, fctx->now, + findoptions, true, true, &fctx->nameservers, NULL); + if (result != ISC_R_SUCCESS) { + FCTXTRACE("couldn't find a zonecut"); + fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL); + return; + } + if (!dns_name_issubdomain(fname, fctx->domain)) { + /* + * The best nameservers are now above our + * QDOMAIN. + */ + FCTXTRACE("nameservers now above QDOMAIN"); + fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL); + return; + } + + fcount_decr(fctx); + + dns_name_copy(fname, fctx->domain); + dns_name_copy(dcname, fctx->qmindcname); + + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL); + return; + } + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + fctx_cancelqueries(fctx, true, false); + fctx_cleanup(fctx); + retrying = false; + } + + /* + * Try again. + */ + fctx_try(fctx, retrying, false); +} + +/* + * rctx_resend(): + * + * Resend the query, probably with the options changed. Calls + * fctx_query(), passing rctx->retryopts (which is based on + * query->options, but may have been updated since the last time + * fctx_query() was called). + */ +static void +rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) { + fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + + FCTXTRACE("resend"); + inc_stats(fctx->res, dns_resstatscounter_retry); + result = fctx_query(fctx, addrinfo, rctx->retryopts); + if (result != ISC_R_SUCCESS) { + fctx_done_detach(&rctx->fctx, result); + } +} + +/* + * rctx_next(): + * We got what appeared to be a response but it didn't match the + * question or the cookie; it may have been meant for someone else, or + * it may be a spoofing attack. Drop it and continue listening for the + * response we wanted. + */ +static isc_result_t +rctx_next(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + isc_result_t result; + + FCTXTRACE("nextitem"); + inc_stats(rctx->fctx->res, dns_resstatscounter_nextitem); + INSIST(rctx->query->dispentry != NULL); + dns_message_reset(rctx->query->rmessage, DNS_MESSAGE_INTENTPARSE); + result = dns_dispatch_getnext(rctx->query->dispentry); + return (result); +} + +/* + * rctx_chaseds(): + * Look up the parent zone's NS records so that DS records can be + * fetched. + */ +static void +rctx_chaseds(respctx_t *rctx, dns_message_t *message, + dns_adbaddrinfo_t *addrinfo, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + isc_task_t *task = NULL; + unsigned int n; + + add_bad(fctx, message, addrinfo, result, rctx->broken_type); + fctx_cancelqueries(fctx, true, false); + fctx_cleanup(fctx); + + n = dns_name_countlabels(fctx->name); + dns_name_getlabelsequence(fctx->name, 1, n - 1, fctx->nsname); + + FCTXTRACE("suspending DS lookup to find parent's NS records"); + + fctx_addref(fctx); + task = fctx->res->buckets[fctx->bucketnum].task; + result = dns_resolver_createfetch( + fctx->res, fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL, + NULL, 0, fctx->options, 0, NULL, task, resume_dslookup, fctx, + &fctx->nsrrset, NULL, &fctx->nsfetch); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_detach(&fctx); + fctx_done_detach(&rctx->fctx, result); + } +} + +/* + * rctx_done(): + * This resolver query response is finished, either because we + * encountered a problem or because we've gotten all the information + * from it that we can. We either wait for another response, resend the + * query to the same server, resend to a new server, or clean up and + * shut down the fetch. + */ +static void +rctx_done(respctx_t *rctx, isc_result_t result) { + resquery_t *query = rctx->query; + fetchctx_t *fctx = rctx->fctx; + dns_adbaddrinfo_t *addrinfo = query->addrinfo; + dns_message_t *message = NULL; + + /* + * Need to attach to the message until the scope + * of this function ends, since there are many places + * where the message is used and/or may be destroyed + * before this function ends. + */ + dns_message_attach(query->rmessage, &message); + + FCTXTRACE4("query canceled in rctx_done();", + rctx->no_response ? "no response" : "responding", result); + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver && + (rctx->next_server || rctx->resend || rctx->nextitem)) + { + fctx_cancelquery(&query, rctx->finish, rctx->no_response, + false); + fctx_done_detach(&rctx->fctx, DNS_R_SERVFAIL); + goto detach; + } +#endif /* ifdef ENABLE_AFL */ + + if (rctx->nextitem) { + REQUIRE(!rctx->next_server); + REQUIRE(!rctx->resend); + + result = rctx_next(rctx); + if (result == ISC_R_SUCCESS) { + goto detach; + } + } + + /* Cancel the query */ + fctx_cancelquery(&query, rctx->finish, rctx->no_response, false); + + /* + * If nobody's waiting for results, don't resend or try next server. + */ + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + if (ISC_LIST_EMPTY(fctx->events)) { + rctx->next_server = false; + rctx->resend = false; + } + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + if (rctx->next_server) { + rctx_nextserver(rctx, message, addrinfo, result); + } else if (rctx->resend) { + rctx_resend(rctx, addrinfo); + } else if (result == DNS_R_CHASEDSSERVERS) { + rctx_chaseds(rctx, message, addrinfo, result); + } else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) { + /* + * All has gone well so far, but we are waiting for the DNSSEC + * validator to validate the answer. + */ + FCTXTRACE("wait for validator"); + fctx_cancelqueries(fctx, true, false); + } else { + /* + * We're done. + */ + fctx_done_detach(&rctx->fctx, result); + } + +detach: + dns_message_detach(&message); +} + +/* + * rctx_logpacket(): + * Log the incoming packet; also log to DNSTAP if configured. + */ +static void +rctx_logpacket(respctx_t *rctx) { +#ifdef HAVE_DNSTAP + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + isc_sockaddr_t localaddr, *la = NULL; + unsigned char zone[DNS_NAME_MAXWIRE]; + dns_dtmsgtype_t dtmsgtype; + dns_compress_t cctx; + isc_region_t zr; + isc_buffer_t zb; +#endif /* HAVE_DNSTAP */ + + dns_message_logfmtpacket( + rctx->query->rmessage, "received packet from", + &rctx->query->addrinfo->sockaddr, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS, &dns_master_style_comment, + ISC_LOG_DEBUG(10), rctx->fctx->res->mctx); + +#ifdef HAVE_DNSTAP + /* + * Log the response via dnstap. + */ + memset(&zr, 0, sizeof(zr)); + result = dns_compress_init(&cctx, -1, fctx->res->mctx); + if (result == ISC_R_SUCCESS) { + isc_buffer_init(&zb, zone, sizeof(zone)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(fctx->domain, &cctx, &zb); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(&zb, &zr); + } + dns_compress_invalidate(&cctx); + } + + if ((fctx->qmessage->flags & DNS_MESSAGEFLAG_RD) != 0) { + dtmsgtype = DNS_DTTYPE_FR; + } else { + dtmsgtype = DNS_DTTYPE_RR; + } + + result = dns_dispentry_getlocaladdress(rctx->query->dispentry, + &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, + &rctx->query->addrinfo->sockaddr, + ((rctx->query->options & DNS_FETCHOPT_TCP) != 0), &zr, + &rctx->query->start, NULL, &rctx->buffer); +#endif /* HAVE_DNSTAP */ +} + +/* + * rctx_badserver(): + * Is the remote server broken, or does it dislike us? + */ +static isc_result_t +rctx_badserver(respctx_t *rctx, isc_result_t result) { + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + isc_buffer_t b; + char code[64]; + dns_rcode_t rcode = rctx->query->rmessage->rcode; + + if (rcode == dns_rcode_noerror || rcode == dns_rcode_yxdomain || + rcode == dns_rcode_nxdomain) + { + return (ISC_R_SUCCESS); + } + + if ((rcode == dns_rcode_formerr) && rctx->opt == NULL && + (rctx->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * It's very likely they don't like EDNS0. + */ + rctx->retryopts |= DNS_FETCHOPT_NOEDNS0; + rctx->resend = true; + /* + * Remember that they may not like EDNS0. + */ + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(fctx->res, dns_resstatscounter_edns0fail); + } else if (rcode == dns_rcode_formerr) { + if (query->rmessage->cc_echoed) { + /* + * Retry without DNS COOKIE. + */ + query->addrinfo->flags |= FCTX_ADDRINFO_NOCOOKIE; + rctx->resend = true; + log_formerr(fctx, "server sent FORMERR with echoed DNS " + "COOKIE"); + } else { + /* + * The server (or forwarder) doesn't understand us, + * but others might. + */ + rctx->next_server = true; + rctx->broken_server = DNS_R_REMOTEFORMERR; + log_formerr(fctx, "server sent FORMERR"); + } + } else if (rcode == dns_rcode_badvers) { + unsigned int version; +#if DNS_EDNS_VERSION > 0 + unsigned int flags, mask; +#endif /* if DNS_EDNS_VERSION > 0 */ + + INSIST(rctx->opt != NULL); + version = (rctx->opt->ttl >> 16) & 0xff; +#if DNS_EDNS_VERSION > 0 + flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) | + DNS_FETCHOPT_EDNSVERSIONSET; + mask = DNS_FETCHOPT_EDNSVERSIONMASK | + DNS_FETCHOPT_EDNSVERSIONSET; +#endif /* if DNS_EDNS_VERSION > 0 */ + + /* + * Record that we got a good EDNS response. + */ + if (query->ednsversion > (int)version && + !EDNSOK(query->addrinfo)) + { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, + FCTX_ADDRINFO_EDNSOK); + } + + /* + * RFC 2671 was not clear that unknown options should + * be ignored. RFC 6891 is clear that that they + * should be ignored. If we are supporting the + * experimental EDNS > 0 then perform strict + * version checking of badvers responses. We won't + * be sending COOKIE etc. in that case. + */ +#if DNS_EDNS_VERSION > 0 + if ((int)version < query->ednsversion) { + dns_adb_changeflags(fctx->adb, query->addrinfo, flags, + mask); + rctx->resend = true; + } else { + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = true; + } +#else /* if DNS_EDNS_VERSION > 0 */ + rctx->broken_server = DNS_R_BADVERS; + rctx->next_server = true; +#endif /* if DNS_EDNS_VERSION > 0 */ + } else if (rcode == dns_rcode_badcookie && rctx->query->rmessage->cc_ok) + { + /* + * We have recorded the new cookie. + */ + if (BADCOOKIE(query->addrinfo)) { + rctx->retryopts |= DNS_FETCHOPT_TCP; + } + query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE; + rctx->resend = true; + } else { + rctx->broken_server = DNS_R_UNEXPECTEDRCODE; + rctx->next_server = true; + } + + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + FCTXTRACE2("remote server broken: returned ", code); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_lameserver(): + * Is the server lame? + */ +static isc_result_t +rctx_lameserver(respctx_t *rctx) { + isc_result_t result = ISC_R_SUCCESS; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (ISFORWARDER(query->addrinfo) || !is_lame(fctx, query->rmessage)) { + return (ISC_R_SUCCESS); + } + + inc_stats(fctx->res, dns_resstatscounter_lame); + log_lame(fctx, query->addrinfo); + if (fctx->res->lame_ttl != 0) { + result = dns_adb_marklame(fctx->adb, query->addrinfo, + fctx->name, fctx->type, + rctx->now + fctx->res->lame_ttl); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_ERROR, + "could not mark server as lame: %s", + isc_result_totext(result)); + } + } + rctx->broken_server = DNS_R_LAME; + rctx->next_server = true; + FCTXTRACE("lame server"); + rctx_done(rctx, result); + + return (ISC_R_COMPLETE); +} + +/* + * rctx_delonly_zone(): + * Handle delegation-only zones like NET and COM. + */ +static void +rctx_delonly_zone(respctx_t *rctx) { + fetchctx_t *fctx = rctx->fctx; + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + + if (ISFORWARDER(rctx->query->addrinfo) || + !dns_view_isdelegationonly(fctx->res->view, fctx->domain) || + dns_name_equal(fctx->domain, fctx->name) || + !fix_mustbedelegationornxdomain(rctx->query->rmessage, fctx)) + { + return; + } + + dns_name_format(fctx->name, namebuf, sizeof(namebuf)); + dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf)); + dns_rdatatype_format(fctx->type, typebuf, sizeof(typebuf)); + dns_rdataclass_format(fctx->res->rdclass, classbuf, sizeof(classbuf)); + isc_sockaddr_format(&rctx->query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DELEGATION_ONLY, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "enforced delegation-only for '%s' (%s/%s/%s) from %s", + domainbuf, namebuf, typebuf, classbuf, addrbuf); +} + +/*** + *** Resolver Methods + ***/ +static void +destroy(dns_resolver_t *res) { + unsigned int i; + alternate_t *a; + + isc_refcount_destroy(&res->references); + REQUIRE(!atomic_load_acquire(&res->priming)); + REQUIRE(res->primefetch == NULL); + + RTRACE("destroy"); + + REQUIRE(atomic_load_acquire(&res->nfctx) == 0); + + isc_mutex_destroy(&res->primelock); + isc_mutex_destroy(&res->lock); + for (i = 0; i < res->nbuckets; i++) { + INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs)); + isc_task_shutdown(res->buckets[i].task); + isc_task_detach(&res->buckets[i].task); + isc_mutex_destroy(&res->buckets[i].lock); + } + isc_mem_put(res->mctx, res->buckets, + res->nbuckets * sizeof(fctxbucket_t)); + for (i = 0; i < HASHSIZE(res->dhashbits); i++) { + INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list)); + isc_mutex_destroy(&res->dbuckets[i].lock); + } + isc_mem_put(res->mctx, res->dbuckets, + HASHSIZE(res->dhashbits) * sizeof(zonebucket_t)); + if (res->dispatches4 != NULL) { + dns_dispatchset_destroy(&res->dispatches4); + } + if (res->dispatches6 != NULL) { + dns_dispatchset_destroy(&res->dispatches6); + } + while ((a = ISC_LIST_HEAD(res->alternates)) != NULL) { + ISC_LIST_UNLINK(res->alternates, a, link); + if (!a->isaddress) { + dns_name_free(&a->_u._n.name, res->mctx); + } + isc_mem_put(res->mctx, a, sizeof(*a)); + } + dns_resolver_reset_algorithms(res); + dns_resolver_reset_ds_digests(res); + dns_badcache_destroy(&res->badcache); + dns_resolver_resetmustbesecure(res); + isc_timer_destroy(&res->spillattimer); + res->magic = 0; + isc_mem_putanddetach(&res->mctx, res, sizeof(*res)); +} + +static void +send_shutdown_events(dns_resolver_t *res) { + isc_event_t *event, *next_event; + isc_task_t *etask; + + LOCK(&res->lock); + for (event = ISC_LIST_HEAD(res->whenshutdown); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(res->whenshutdown, event, ev_link); + etask = event->ev_sender; + event->ev_sender = res; + isc_task_sendanddetach(&etask, &event); + } + UNLOCK(&res->lock); +} + +static void +spillattimer_countdown(isc_task_t *task, isc_event_t *event) { + dns_resolver_t *res = event->ev_arg; + isc_result_t result; + unsigned int count; + bool logit = false; + + REQUIRE(VALID_RESOLVER(res)); + + UNUSED(task); + + LOCK(&res->lock); + if (res->spillat > res->spillatmin) { + res->spillat--; + logit = true; + } + if (res->spillat <= res->spillatmin) { + result = isc_timer_reset(res->spillattimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + count = res->spillat; + UNLOCK(&res->lock); + if (logit) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "clients-per-query decreased to %u", count); + } + + isc_event_free(&event); +} + +isc_result_t +dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm, + isc_timermgr_t *timermgr, unsigned int options, + dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4, + dns_dispatch_t *dispatchv6, dns_resolver_t **resp) { + isc_result_t result = ISC_R_SUCCESS; + char name[sizeof("res4294967295")]; + dns_resolver_t *res = NULL; + isc_task_t *task = NULL; + + /* + * Create a resolver. + */ + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ntasks > 0); + REQUIRE(ndisp > 0); + REQUIRE(resp != NULL && *resp == NULL); + REQUIRE(dispatchmgr != NULL); + REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL); + + RTRACE("create"); + res = isc_mem_get(view->mctx, sizeof(*res)); + *res = (dns_resolver_t){ .rdclass = view->rdclass, + .nm = nm, + .timermgr = timermgr, + .taskmgr = taskmgr, + .dispatchmgr = dispatchmgr, + .view = view, + .options = options, + .udpsize = DEFAULT_EDNS_BUFSIZE, + .spillatmin = 10, + .spillat = 10, + .spillatmax = 100, + .retryinterval = 10000, + .nonbackofftries = 3, + .query_timeout = DEFAULT_QUERY_TIMEOUT, + .maxdepth = DEFAULT_RECURSION_DEPTH, + .maxqueries = DEFAULT_MAX_QUERIES, + .nbuckets = ntasks, + .dhashbits = RES_DOMAIN_HASH_BITS }; + + atomic_init(&res->activebuckets, res->nbuckets); + + isc_mem_attach(view->mctx, &res->mctx); + + res->quotaresp[dns_quotatype_zone] = DNS_R_DROP; + res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL; + isc_refcount_init(&res->references, 1); + atomic_init(&res->exiting, false); + atomic_init(&res->priming, false); + atomic_init(&res->zspill, 0); + atomic_init(&res->nfctx, 0); + ISC_LIST_INIT(res->whenshutdown); + ISC_LIST_INIT(res->alternates); + + result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE, + &res->badcache); + if (result != ISC_R_SUCCESS) { + goto cleanup_res; + } + + if (view->resstats != NULL) { + isc_stats_set(view->resstats, res->nbuckets, + dns_resstatscounter_buckets); + } + + res->buckets = isc_mem_get(view->mctx, + res->nbuckets * sizeof(res->buckets[0])); + for (uint32_t i = 0; i < ntasks; i++) { + res->buckets[i] = (fctxbucket_t){ 0 }; + + isc_mutex_init(&res->buckets[i].lock); + + /* + * Since we have a pool of tasks we bind them to task + * queues to spread the load evenly + */ + result = isc_task_create_bound(taskmgr, 0, + &res->buckets[i].task, i); + if (result != ISC_R_SUCCESS) { + ntasks = i; + isc_mutex_destroy(&res->buckets[i].lock); + goto cleanup_buckets; + } + + snprintf(name, sizeof(name), "res%" PRIu32, i); + isc_task_setname(res->buckets[i].task, name, res); + + ISC_LIST_INIT(res->buckets[i].fctxs); + atomic_init(&res->buckets[i].exiting, false); + } + + res->dbuckets = isc_mem_get(view->mctx, + HASHSIZE(res->dhashbits) * + sizeof(res->dbuckets[0])); + for (size_t i = 0; i < HASHSIZE(res->dhashbits); i++) { + res->dbuckets[i] = (zonebucket_t){ .list = { 0 } }; + ISC_LIST_INIT(res->dbuckets[i].list); + isc_mutex_init(&res->dbuckets[i].lock); + } + + if (dispatchv4 != NULL) { + dns_dispatchset_create(view->mctx, dispatchv4, + &res->dispatches4, ndisp); + } + + if (dispatchv6 != NULL) { + dns_dispatchset_create(view->mctx, dispatchv6, + &res->dispatches6, ndisp); + } + + isc_mutex_init(&res->lock); + isc_mutex_init(&res->primelock); + + result = isc_task_create(taskmgr, 0, &task); + if (result != ISC_R_SUCCESS) { + goto cleanup_primelock; + } + isc_task_setname(task, "resolver_task", NULL); + + result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, + task, spillattimer_countdown, res, + &res->spillattimer); + isc_task_detach(&task); + if (result != ISC_R_SUCCESS) { + goto cleanup_primelock; + } + + res->magic = RES_MAGIC; + + *resp = res; + + return (ISC_R_SUCCESS); + +cleanup_primelock: + isc_mutex_destroy(&res->primelock); + isc_mutex_destroy(&res->lock); + + if (res->dispatches6 != NULL) { + dns_dispatchset_destroy(&res->dispatches6); + } + if (res->dispatches4 != NULL) { + dns_dispatchset_destroy(&res->dispatches4); + } + + for (size_t i = 0; i < HASHSIZE(res->dhashbits); i++) { + isc_mutex_destroy(&res->dbuckets[i].lock); + } + isc_mem_put(view->mctx, res->dbuckets, + HASHSIZE(res->dhashbits) * sizeof(zonebucket_t)); + +cleanup_buckets: + for (size_t i = 0; i < ntasks; i++) { + isc_mutex_destroy(&res->buckets[i].lock); + isc_task_shutdown(res->buckets[i].task); + isc_task_detach(&res->buckets[i].task); + } + isc_mem_put(view->mctx, res->buckets, + res->nbuckets * sizeof(fctxbucket_t)); + + dns_badcache_destroy(&res->badcache); + +cleanup_res: + isc_mem_put(view->mctx, res, sizeof(*res)); + + return (result); +} + +static void +prime_done(isc_task_t *task, isc_event_t *event) { + dns_resolver_t *res; + dns_fetchevent_t *fevent; + dns_fetch_t *fetch; + dns_db_t *db = NULL; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + res = event->ev_arg; + REQUIRE(VALID_RESOLVER(res)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "resolver priming query complete: %s", + isc_result_totext(fevent->result)); + + UNUSED(task); + + LOCK(&res->primelock); + fetch = res->primefetch; + res->primefetch = NULL; + UNLOCK(&res->primelock); + + atomic_compare_exchange_enforced(&res->priming, &(bool){ true }, false); + + if (fevent->result == ISC_R_SUCCESS && res->view->cache != NULL && + res->view->hints != NULL) + { + dns_cache_attachdb(res->view->cache, &db); + dns_root_checkhints(res->view, res->view->hints, db); + dns_db_detach(&db); + } + + if (fevent->node != NULL) { + dns_db_detachnode(fevent->db, &fevent->node); + } + if (fevent->db != NULL) { + dns_db_detach(&fevent->db); + } + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + INSIST(fevent->sigrdataset == NULL); + + isc_mem_put(res->mctx, fevent->rdataset, sizeof(*fevent->rdataset)); + + isc_event_free(&event); + dns_resolver_destroyfetch(&fetch); +} + +void +dns_resolver_prime(dns_resolver_t *res) { + bool want_priming = false; + dns_rdataset_t *rdataset; + isc_result_t result; + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(res->frozen); + + RTRACE("dns_resolver_prime"); + + if (!atomic_load_acquire(&res->exiting)) { + want_priming = atomic_compare_exchange_strong_acq_rel( + &res->priming, &(bool){ false }, true); + } + + if (want_priming) { + /* + * To avoid any possible recursive locking problems, we + * start the priming fetch like any other fetch, and + * holding no resolver locks. No one else will try to + * start it because we're the ones who set res->priming + * to true. Any other callers of dns_resolver_prime() + * while we're running will see that res->priming is + * already true and do nothing. + */ + RTRACE("priming"); + rdataset = isc_mem_get(res->mctx, sizeof(*rdataset)); + dns_rdataset_init(rdataset); + + LOCK(&res->primelock); + INSIST(res->primefetch == NULL); + result = dns_resolver_createfetch( + res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL, + NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, + res->buckets[0].task, prime_done, res, rdataset, NULL, + &res->primefetch); + UNLOCK(&res->primelock); + + if (result != ISC_R_SUCCESS) { + isc_mem_put(res->mctx, rdataset, sizeof(*rdataset)); + atomic_compare_exchange_enforced( + &res->priming, &(bool){ true }, false); + } + inc_stats(res, dns_resstatscounter_priming); + } +} + +void +dns_resolver_freeze(dns_resolver_t *res) { + /* + * Freeze resolver. + */ + + REQUIRE(VALID_RESOLVER(res)); + + res->frozen = true; +} + +void +dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) { + REQUIRE(VALID_RESOLVER(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + RRTRACE(source, "attach"); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task, + isc_event_t **eventp) { + isc_event_t *event = NULL; + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(eventp != NULL); + + event = *eventp; + *eventp = NULL; + + LOCK(&res->lock); + + if (atomic_load_acquire(&res->exiting) && + atomic_load_acquire(&res->activebuckets) == 0) + { + /* + * We're already shutdown. Send the event. + */ + event->ev_sender = res; + isc_task_send(task, &event); + } else { + isc_task_attach(task, &(isc_task_t *){ NULL }); + event->ev_sender = task; + ISC_LIST_APPEND(res->whenshutdown, event, ev_link); + } + + UNLOCK(&res->lock); +} + +void +dns_resolver_shutdown(dns_resolver_t *res) { + unsigned int i; + fetchctx_t *fctx; + isc_result_t result; + bool is_false = false; + bool is_done = false; + + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("shutdown"); + + if (atomic_compare_exchange_strong(&res->exiting, &is_false, true)) { + RTRACE("exiting"); + + for (i = 0; i < res->nbuckets; i++) { + LOCK(&res->buckets[i].lock); + for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs); + fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) + { + fctx_shutdown(fctx); + } + atomic_store(&res->buckets[i].exiting, true); + if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) { + if (isc_refcount_decrement( + &res->activebuckets) == 1) + { + is_done = true; + } + } + UNLOCK(&res->buckets[i].lock); + } + if (is_done) { + send_shutdown_events(res); + } + result = isc_timer_reset(res->spillattimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +} + +void +dns_resolver_detach(dns_resolver_t **resp) { + dns_resolver_t *res; + + REQUIRE(resp != NULL); + res = *resp; + *resp = NULL; + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("detach"); + + if (isc_refcount_decrement(&res->references) == 1) { + isc_refcount_destroy(&res->activebuckets); + INSIST(atomic_load_acquire(&res->exiting)); + destroy(res); + } +} + +static bool +fctx_match(fetchctx_t *fctx, const dns_name_t *name, dns_rdatatype_t type, + unsigned int options) { + /* + * Don't match fetch contexts that are shutting down. + */ + if (fctx->cloned || fctx->state == fetchstate_done || + ISC_LIST_EMPTY(fctx->events)) + { + return (false); + } + + if (fctx->type != type || fctx->options != options) { + return (false); + } + return (dns_name_equal(fctx->name, name)); +} + +static void +log_fetch(const dns_name_t *name, dns_rdatatype_t type) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + int level = ISC_LOG_DEBUG(1); + + /* + * If there's no chance of logging it, don't render (format) the + * name and RDATA type (further below), and return early. + */ + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, "fetch: %s/%s", namebuf, + typebuf); +} + +static void +fctx_minimize_qname(fetchctx_t *fctx) { + isc_result_t result; + unsigned int dlabels, nlabels; + dns_name_t name; + + REQUIRE(VALID_FCTX(fctx)); + + dns_name_init(&name, NULL); + + dlabels = dns_name_countlabels(fctx->qmindcname); + nlabels = dns_name_countlabels(fctx->name); + + if (dlabels > fctx->qmin_labels) { + fctx->qmin_labels = dlabels + 1; + } else { + fctx->qmin_labels++; + } + + if (fctx->ip6arpaskip) { + /* + * For ip6.arpa we want to skip some of the labels, with + * boundaries at /16, /32, /48, /56, /64 and /128 + * In 'label count' terms that's equal to + * 7 11 15 17 19 35 + * We fix fctx->qmin_labels to point to the nearest + * boundary + */ + if (fctx->qmin_labels < 7) { + fctx->qmin_labels = 7; + } else if (fctx->qmin_labels < 11) { + fctx->qmin_labels = 11; + } else if (fctx->qmin_labels < 15) { + fctx->qmin_labels = 15; + } else if (fctx->qmin_labels < 17) { + fctx->qmin_labels = 17; + } else if (fctx->qmin_labels < 19) { + fctx->qmin_labels = 19; + } else if (fctx->qmin_labels < 35) { + fctx->qmin_labels = 35; + } else { + fctx->qmin_labels = nlabels; + } + } else if (fctx->qmin_labels > DNS_QMIN_MAXLABELS) { + fctx->qmin_labels = DNS_MAX_LABELS + 1; + } + + if (fctx->qmin_labels < nlabels) { + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_name_t *fname = dns_fixedname_initname(&fixed); + dns_rdataset_init(&rdataset); + do { + /* + * We want to query for qmin_labels from fctx->name. + */ + dns_name_split(fctx->name, fctx->qmin_labels, NULL, + &name); + /* + * Look to see if we have anything cached about NS + * RRsets at this name and if so skip this name and + * try with an additional label prepended. + */ + result = dns_db_find(fctx->cache, &name, NULL, + dns_rdatatype_ns, 0, 0, NULL, + fname, &rdataset, NULL); + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_CNAME: + case DNS_R_DNAME: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + fctx->qmin_labels++; + continue; + default: + break; + } + break; + } while (fctx->qmin_labels < nlabels); + } + + if (fctx->qmin_labels < nlabels) { + dns_name_copy(&name, fctx->qminname); + fctx->qmintype = dns_rdatatype_ns; + fctx->minimized = true; + } else { + /* Minimization is done, we'll ask for whole qname */ + dns_name_copy(fctx->name, fctx->qminname); + fctx->qmintype = fctx->type; + fctx->minimized = false; + } + + char domainbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(fctx->qminname, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(5), + "QNAME minimization - %s minimized, qmintype %d " + "qminname %s", + fctx->minimized ? "" : "not", fctx->qmintype, domainbuf); +} + +isc_result_t +dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain, + dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + const isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, unsigned int depth, + isc_counter_t *qc, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp) { + dns_fetch_t *fetch; + fetchctx_t *fctx = NULL; + isc_result_t result = ISC_R_SUCCESS; + unsigned int bucketnum; + bool new_fctx = false; + isc_event_t *event; + unsigned int count = 0; + unsigned int spillat; + unsigned int spillatmin; + bool dodestroy = false; + + UNUSED(forwarders); + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(res->frozen); + /* XXXRTH Check for meta type */ + if (domain != NULL) { + REQUIRE(DNS_RDATASET_VALID(nameservers)); + REQUIRE(nameservers->type == dns_rdatatype_ns); + } else { + REQUIRE(nameservers == NULL); + } + REQUIRE(forwarders == NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + REQUIRE(sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)); + REQUIRE(fetchp != NULL && *fetchp == NULL); + + if (atomic_load_acquire(&res->exiting)) { + return (ISC_R_SHUTTINGDOWN); + } + + log_fetch(name, type); + + fetch = isc_mem_get(res->mctx, sizeof(*fetch)); + *fetch = (dns_fetch_t){ 0 }; + + dns_resolver_attach(res, &fetch->res); + isc_mem_attach(res->mctx, &fetch->mctx); + + bucketnum = dns_name_fullhash(name, false) % res->nbuckets; + + LOCK(&res->lock); + spillat = res->spillat; + spillatmin = res->spillatmin; + UNLOCK(&res->lock); + LOCK(&res->buckets[bucketnum].lock); + + if (atomic_load(&res->buckets[bucketnum].exiting)) { + result = ISC_R_SHUTTINGDOWN; + goto unlock; + } + + if ((options & DNS_FETCHOPT_UNSHARED) == 0) { + for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs); + fctx != NULL; fctx = ISC_LIST_NEXT(fctx, link)) + { + if (fctx_match(fctx, name, type, options)) { + break; + } + } + } + + /* + * Is this a duplicate? + */ + if (fctx != NULL && client != NULL) { + dns_fetchevent_t *fevent; + for (fevent = ISC_LIST_HEAD(fctx->events); fevent != NULL; + fevent = ISC_LIST_NEXT(fevent, ev_link)) + { + if (fevent->client != NULL && fevent->id == id && + isc_sockaddr_equal(fevent->client, client)) + { + result = DNS_R_DUPLICATE; + goto unlock; + } + + /* + * Only the regular fetch events should be + * counted for the clients-per-query limit, in + * case if there are multiple events registered + * for a single client. + */ + if (fevent->ev_type == DNS_EVENT_FETCHDONE) { + count++; + } + } + } + if (count >= spillatmin && spillatmin != 0) { + INSIST(fctx != NULL); + if (count >= spillat) { + fctx->spilled = true; + } + if (fctx->spilled) { + inc_stats(res, dns_resstatscounter_clientquota); + result = DNS_R_DROP; + goto unlock; + } + } + + if (fctx == NULL) { + result = fctx_create(res, task, name, type, domain, nameservers, + client, options, bucketnum, depth, qc, + &fctx); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + new_fctx = true; + } else if (fctx->depth > depth) { + fctx->depth = depth; + } + + result = fctx_join(fctx, task, client, id, action, arg, rdataset, + sigrdataset, fetch); + + if (result == ISC_R_SUCCESS && + ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0)) + { + fctx_add_event(fctx, task, client, id, action, arg, NULL, NULL, + fetch, DNS_EVENT_TRYSTALE); + } + + if (new_fctx) { + if (result == ISC_R_SUCCESS) { + /* + * Launch this fctx. + */ + event = &fctx->control_event; + fctx_addref(fctx); + ISC_EVENT_INIT(event, sizeof(*event), 0, NULL, + DNS_EVENT_FETCHCONTROL, fctx_start, fctx, + NULL, NULL, NULL); + isc_task_send(res->buckets[bucketnum].task, &event); + } else { + dodestroy = true; + } + } + +unlock: + UNLOCK(&res->buckets[bucketnum].lock); + + if (dodestroy) { + fctx_destroy(fctx, false); + } + + if (result == ISC_R_SUCCESS) { + FTRACE("created"); + *fetchp = fetch; + } else { + dns_resolver_detach(&fetch->res); + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + } + + return (result); +} + +void +dns_resolver_cancelfetch(dns_fetch_t *fetch) { + fetchctx_t *fctx = NULL; + dns_resolver_t *res = NULL; + dns_fetchevent_t *event = NULL; + dns_fetchevent_t *event_trystale = NULL; + dns_fetchevent_t *event_fetchdone = NULL; + + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + FTRACE("cancelfetch"); + + LOCK(&res->buckets[fctx->bucketnum].lock); + + /* + * Find the events for this fetch (as opposed + * to those for other fetches that have joined the same + * fctx) and send them with result = ISC_R_CANCELED. + */ + if (fctx->state != fetchstate_done) { + dns_fetchevent_t *next_event = NULL; + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + if (event->fetch == fetch) { + ISC_LIST_UNLINK(fctx->events, event, ev_link); + switch (event->ev_type) { + case DNS_EVENT_TRYSTALE: + INSIST(event_trystale == NULL); + event_trystale = event; + break; + case DNS_EVENT_FETCHDONE: + INSIST(event_fetchdone == NULL); + event_fetchdone = event; + break; + default: + UNREACHABLE(); + } + if (event_trystale != NULL && + event_fetchdone != NULL) + { + break; + } + } + } + } + /* + * The "trystale" event must be sent before the "fetchdone" event, + * because the latter clears the "recursing" query attribute, which is + * required by both events (handled by the same callback function). + */ + if (event_trystale != NULL) { + isc_task_t *etask = event_trystale->ev_sender; + event_trystale->ev_sender = fctx; + event_trystale->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale)); + } + if (event_fetchdone != NULL) { + isc_task_t *etask = event_fetchdone->ev_sender; + event_fetchdone->ev_sender = fctx; + event_fetchdone->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone)); + } + + /* + * The fctx continues running even if no fetches remain; + * the answer is still cached. + */ + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +void +dns_resolver_destroyfetch(dns_fetch_t **fetchp) { + dns_fetch_t *fetch = NULL; + dns_resolver_t *res = NULL; + fetchctx_t *fctx = NULL; + unsigned int bucketnum; + + REQUIRE(fetchp != NULL); + fetch = *fetchp; + *fetchp = NULL; + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fetch->res; + + FTRACE("destroyfetch"); + + fetch->magic = 0; + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + /* + * Sanity check: the caller should have gotten its event before + * trying to destroy the fetch. + */ + if (fctx->state != fetchstate_done) { + dns_fetchevent_t *event = NULL, *next_event = NULL; + for (event = ISC_LIST_HEAD(fctx->events); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + RUNTIME_CHECK(event->fetch != fetch); + } + } + UNLOCK(&res->buckets[bucketnum].lock); + + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + + fctx_detach(&fctx); + dns_resolver_detach(&res); +} + +void +dns_resolver_logfetch(dns_fetch_t *fetch, isc_log_t *lctx, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, bool duplicateok) { + fetchctx_t *fctx; + dns_resolver_t *res; + char domainbuf[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + LOCK(&res->buckets[fctx->bucketnum].lock); + + INSIST(fctx->exitline >= 0); + if (!fctx->logged || duplicateok) { + dns_name_format(fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(lctx, category, module, level, + "fetch completed at %s:%d for %s in " + "%" PRIu64 "." + "%06" PRIu64 ": %s/%s " + "[domain:%s,referral:%u,restart:%u,qrysent:%u," + "timeout:%u,lame:%u,quota:%u,neterr:%u," + "badresp:%u,adberr:%u,findfail:%u,valfail:%u]", + __FILE__, fctx->exitline, fctx->info, + fctx->duration / US_PER_SEC, + fctx->duration % US_PER_SEC, + isc_result_totext(fctx->result), + isc_result_totext(fctx->vresult), domainbuf, + fctx->referrals, fctx->restarts, fctx->querysent, + fctx->timeouts, fctx->lamecount, fctx->quotacount, + fctx->neterr, fctx->badresp, fctx->adberr, + fctx->findfail, fctx->valfail); + fctx->logged = true; + } + + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +dns_dispatchmgr_t * +dns_resolver_dispatchmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->dispatchmgr); +} + +dns_dispatch_t * +dns_resolver_dispatchv4(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (dns_dispatchset_get(resolver->dispatches4)); +} + +dns_dispatch_t * +dns_resolver_dispatchv6(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (dns_dispatchset_get(resolver->dispatches6)); +} + +isc_taskmgr_t * +dns_resolver_taskmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->taskmgr); +} + +uint32_t +dns_resolver_getlamettl(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->lame_ttl); +} + +void +dns_resolver_setlamettl(dns_resolver_t *resolver, uint32_t lame_ttl) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->lame_ttl = lame_ttl; +} + +void +dns_resolver_addalternate(dns_resolver_t *resolver, const isc_sockaddr_t *alt, + const dns_name_t *name, in_port_t port) { + alternate_t *a; + + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(!resolver->frozen); + REQUIRE((alt == NULL) ^ (name == NULL)); + + a = isc_mem_get(resolver->mctx, sizeof(*a)); + if (alt != NULL) { + a->isaddress = true; + a->_u.addr = *alt; + } else { + a->isaddress = false; + a->_u._n.port = port; + dns_name_init(&a->_u._n.name, NULL); + dns_name_dup(name, resolver->mctx, &a->_u._n.name); + } + ISC_LINK_INIT(a, link); + ISC_LIST_APPEND(resolver->alternates, a, link); +} + +void +dns_resolver_setudpsize(dns_resolver_t *resolver, uint16_t udpsize) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->udpsize = udpsize; +} + +uint16_t +dns_resolver_getudpsize(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->udpsize); +} + +void +dns_resolver_flushbadcache(dns_resolver_t *resolver, const dns_name_t *name) { + if (name != NULL) { + dns_badcache_flushname(resolver->badcache, name); + } else { + dns_badcache_flush(resolver->badcache); + } +} + +void +dns_resolver_flushbadnames(dns_resolver_t *resolver, const dns_name_t *name) { + dns_badcache_flushtree(resolver->badcache, name); +} + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *expire) { +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) +#endif /* ifdef ENABLE_AFL */ + { + dns_badcache_add(resolver->badcache, name, type, false, 0, + expire); + } +} + +bool +dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name, + dns_rdatatype_t type, isc_time_t *now) { + return (dns_badcache_find(resolver->badcache, name, type, NULL, now)); +} + +void +dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp) { + (void)dns_badcache_print(resolver->badcache, "Bad cache", fp); +} + +static void +free_algorithm(void *node, void *arg) { + unsigned char *algorithms = node; + isc_mem_t *mctx = arg; + + isc_mem_put(mctx, algorithms, *algorithms); +} + +void +dns_resolver_reset_algorithms(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->algorithms != NULL) { + dns_rbt_destroy(&resolver->algorithms); + } +} + +isc_result_t +dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int alg) { + unsigned int len, mask; + unsigned char *tmp; + unsigned char *algorithms; + isc_result_t result; + dns_rbtnode_t *node = NULL; + + /* + * Whether an algorithm is disabled (or not) is stored in a + * per-name bitfield that is stored as the node data of an + * RBT. + */ + + REQUIRE(VALID_RESOLVER(resolver)); + if (alg > 255) { + return (ISC_R_RANGE); + } + + if (resolver->algorithms == NULL) { + result = dns_rbt_create(resolver->mctx, free_algorithm, + resolver->mctx, &resolver->algorithms); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + len = alg / 8 + 2; + mask = 1 << (alg % 8); + + result = dns_rbt_addnode(resolver->algorithms, name, &node); + + if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) { + algorithms = node->data; + /* + * If algorithms is set, algorithms[0] contains its + * length. + */ + if (algorithms == NULL || len > *algorithms) { + /* + * If no bitfield exists in the node data, or if + * it is not long enough, allocate a new + * bitfield and copy the old (smaller) bitfield + * into it if one exists. + */ + tmp = isc_mem_get(resolver->mctx, len); + memset(tmp, 0, len); + if (algorithms != NULL) { + memmove(tmp, algorithms, *algorithms); + } + tmp[len - 1] |= mask; + /* tmp[0] should contain the length of 'tmp'. */ + *tmp = len; + node->data = tmp; + /* Free the older bitfield. */ + if (algorithms != NULL) { + isc_mem_put(resolver->mctx, algorithms, + *algorithms); + } + } else { + algorithms[len - 1] |= mask; + } + } + result = ISC_R_SUCCESS; +cleanup: + return (result); +} + +bool +dns_resolver_algorithm_supported(dns_resolver_t *resolver, + const dns_name_t *name, unsigned int alg) { + unsigned int len, mask; + unsigned char *algorithms; + void *data = NULL; + isc_result_t result; + bool found = false; + + REQUIRE(VALID_RESOLVER(resolver)); + + /* + * DH is unsupported for DNSKEYs, see RFC 4034 sec. A.1. + */ + if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) { + return (false); + } + + if (resolver->algorithms == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->algorithms, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + len = alg / 8 + 2; + mask = 1 << (alg % 8); + algorithms = data; + if (len <= *algorithms && (algorithms[len - 1] & mask) != 0) { + found = true; + } + } +unlock: + if (found) { + return (false); + } + + return (dst_algorithm_supported(alg)); +} + +static void +free_digest(void *node, void *arg) { + unsigned char *digests = node; + isc_mem_t *mctx = arg; + + isc_mem_put(mctx, digests, *digests); +} + +void +dns_resolver_reset_ds_digests(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->digests != NULL) { + dns_rbt_destroy(&resolver->digests); + } +} + +isc_result_t +dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name, + unsigned int digest_type) { + unsigned int len, mask; + unsigned char *tmp; + unsigned char *digests; + isc_result_t result; + dns_rbtnode_t *node = NULL; + + /* + * Whether a digest is disabled (or not) is stored in a per-name + * bitfield that is stored as the node data of an RBT. + */ + + REQUIRE(VALID_RESOLVER(resolver)); + if (digest_type > 255) { + return (ISC_R_RANGE); + } + + if (resolver->digests == NULL) { + result = dns_rbt_create(resolver->mctx, free_digest, + resolver->mctx, &resolver->digests); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + len = digest_type / 8 + 2; + mask = 1 << (digest_type % 8); + + result = dns_rbt_addnode(resolver->digests, name, &node); + + if (result == ISC_R_SUCCESS || result == ISC_R_EXISTS) { + digests = node->data; + /* If digests is set, digests[0] contains its length. */ + if (digests == NULL || len > *digests) { + /* + * If no bitfield exists in the node data, or if + * it is not long enough, allocate a new + * bitfield and copy the old (smaller) bitfield + * into it if one exists. + */ + tmp = isc_mem_get(resolver->mctx, len); + memset(tmp, 0, len); + if (digests != NULL) { + memmove(tmp, digests, *digests); + } + tmp[len - 1] |= mask; + /* tmp[0] should contain the length of 'tmp'. */ + *tmp = len; + node->data = tmp; + /* Free the older bitfield. */ + if (digests != NULL) { + isc_mem_put(resolver->mctx, digests, *digests); + } + } else { + digests[len - 1] |= mask; + } + } + result = ISC_R_SUCCESS; +cleanup: + return (result); +} + +bool +dns_resolver_ds_digest_supported(dns_resolver_t *resolver, + const dns_name_t *name, + unsigned int digest_type) { + unsigned int len, mask; + unsigned char *digests; + void *data = NULL; + isc_result_t result; + bool found = false; + + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->digests == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->digests, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + len = digest_type / 8 + 2; + mask = 1 << (digest_type % 8); + digests = data; + if (len <= *digests && (digests[len - 1] & mask) != 0) { + found = true; + } + } +unlock: + if (found) { + return (false); + } + return (dst_ds_digest_supported(digest_type)); +} + +void +dns_resolver_resetmustbesecure(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->mustbesecure != NULL) { + dns_rbt_destroy(&resolver->mustbesecure); + } +} + +static bool yes = true, no = false; + +isc_result_t +dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name, + bool value) { + isc_result_t result; + + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->mustbesecure == NULL) { + result = dns_rbt_create(resolver->mctx, NULL, NULL, + &resolver->mustbesecure); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = dns_rbt_addname(resolver->mustbesecure, name, + value ? &yes : &no); +cleanup: + return (result); +} + +bool +dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) { + void *data = NULL; + bool value = false; + isc_result_t result; + + REQUIRE(VALID_RESOLVER(resolver)); + + if (resolver->mustbesecure == NULL) { + goto unlock; + } + result = dns_rbt_findname(resolver->mustbesecure, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + value = *(bool *)data; + } +unlock: + return (value); +} + +void +dns_resolver_getclientsperquery(dns_resolver_t *resolver, uint32_t *cur, + uint32_t *min, uint32_t *max) { + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + if (cur != NULL) { + *cur = resolver->spillat; + } + if (min != NULL) { + *min = resolver->spillatmin; + } + if (max != NULL) { + *max = resolver->spillatmax; + } + UNLOCK(&resolver->lock); +} + +void +dns_resolver_setclientsperquery(dns_resolver_t *resolver, uint32_t min, + uint32_t max) { + REQUIRE(VALID_RESOLVER(resolver)); + + LOCK(&resolver->lock); + resolver->spillatmin = resolver->spillat = min; + resolver->spillatmax = max; + UNLOCK(&resolver->lock); +} + +void +dns_resolver_setfetchesperzone(dns_resolver_t *resolver, uint32_t clients) { + REQUIRE(VALID_RESOLVER(resolver)); + + atomic_store_release(&resolver->zspill, clients); +} + +bool +dns_resolver_getzeronosoattl(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->zero_no_soa_ttl); +} + +void +dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->zero_no_soa_ttl = state; +} + +unsigned int +dns_resolver_getoptions(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->options); +} + +unsigned int +dns_resolver_gettimeout(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->query_timeout); +} + +void +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (timeout <= 300) { + timeout *= 1000; + } + + if (timeout == 0) { + timeout = DEFAULT_QUERY_TIMEOUT; + } + if (timeout > MAXIMUM_QUERY_TIMEOUT) { + timeout = MAXIMUM_QUERY_TIMEOUT; + } + if (timeout < MINIMUM_QUERY_TIMEOUT) { + timeout = MINIMUM_QUERY_TIMEOUT; + } + + resolver->query_timeout = timeout; +} + +void +dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->maxdepth = maxdepth; +} + +unsigned int +dns_resolver_getmaxdepth(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->maxdepth); +} + +void +dns_resolver_setmaxqueries(dns_resolver_t *resolver, unsigned int queries) { + REQUIRE(VALID_RESOLVER(resolver)); + resolver->maxqueries = queries; +} + +unsigned int +dns_resolver_getmaxqueries(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->maxqueries); +} + +void +dns_resolver_dumpfetches(dns_resolver_t *resolver, isc_statsformat_t format, + FILE *fp) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(fp != NULL); + REQUIRE(format == isc_statsformat_file); + + for (size_t i = 0; i < HASHSIZE(resolver->dhashbits); i++) { + fctxcount_t *fc; + LOCK(&resolver->dbuckets[i].lock); + for (fc = ISC_LIST_HEAD(resolver->dbuckets[i].list); fc != NULL; + fc = ISC_LIST_NEXT(fc, link)) + { + dns_name_print(fc->domain, fp); + fprintf(fp, + ": %u active (%u spilled, %u " + "allowed)\n", + fc->count, fc->dropped, fc->allowed); + } + UNLOCK(&resolver->dbuckets[i].lock); + } +} + +void +dns_resolver_setquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which, + isc_result_t resp) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server); + REQUIRE(resp == DNS_R_DROP || resp == DNS_R_SERVFAIL); + + resolver->quotaresp[which] = resp; +} + +isc_result_t +dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(which == dns_quotatype_zone || which == dns_quotatype_server); + + return (resolver->quotaresp[which]); +} + +unsigned int +dns_resolver_getretryinterval(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->retryinterval); +} + +void +dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(interval > 0); + + resolver->retryinterval = ISC_MIN(interval, 2000); +} + +unsigned int +dns_resolver_getnonbackofftries(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->nonbackofftries); +} + +void +dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(tries > 0); + + resolver->nonbackofftries = tries; +} diff --git a/lib/dns/result.c b/lib/dns/result.c new file mode 100644 index 0000000..bb9afa9 --- /dev/null +++ b/lib/dns/result.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include + +dns_rcode_t +dns_result_torcode(isc_result_t result) { + /* Try to supply an appropriate rcode. */ + switch (result) { + case DNS_R_NOERROR: + case ISC_R_SUCCESS: + return (dns_rcode_noerror); + case DNS_R_FORMERR: + case ISC_R_BADBASE64: + case ISC_R_RANGE: + case ISC_R_UNEXPECTEDEND: + case DNS_R_BADAAAA: + /* case DNS_R_BADBITSTRING: deprecated */ + case DNS_R_BADCKSUM: + case DNS_R_BADCLASS: + case DNS_R_BADLABELTYPE: + case DNS_R_BADPOINTER: + case DNS_R_BADTTL: + case DNS_R_BADZONE: + /* case DNS_R_BITSTRINGTOOLONG: deprecated */ + case DNS_R_EXTRADATA: + case DNS_R_LABELTOOLONG: + case DNS_R_NOREDATA: + case DNS_R_SYNTAX: + case DNS_R_TEXTTOOLONG: + case DNS_R_TOOMANYHOPS: + case DNS_R_TSIGERRORSET: + case DNS_R_UNKNOWN: + case DNS_R_NAMETOOLONG: + case DNS_R_OPTERR: + return (dns_rcode_formerr); + case DNS_R_SERVFAIL: + return (dns_rcode_servfail); + case DNS_R_NXDOMAIN: + return (dns_rcode_nxdomain); + case DNS_R_NOTIMP: + return (dns_rcode_notimp); + case DNS_R_REFUSED: + case DNS_R_DISALLOWED: + return (dns_rcode_refused); + case DNS_R_YXDOMAIN: + return (dns_rcode_yxdomain); + case DNS_R_YXRRSET: + return (dns_rcode_yxrrset); + case DNS_R_NXRRSET: + return (dns_rcode_nxrrset); + case DNS_R_NOTAUTH: + case DNS_R_TSIGVERIFYFAILURE: + case DNS_R_CLOCKSKEW: + return (dns_rcode_notauth); + case DNS_R_NOTZONE: + return (dns_rcode_notzone); + case DNS_R_RCODE11: + case DNS_R_RCODE12: + case DNS_R_RCODE13: + case DNS_R_RCODE14: + case DNS_R_RCODE15: + return (result - DNS_R_NOERROR); + case DNS_R_BADVERS: + return (dns_rcode_badvers); + case DNS_R_BADCOOKIE: + return (dns_rcode_badcookie); + default: + return (dns_rcode_servfail); + } +} + +isc_result_t +dns_result_fromrcode(dns_rcode_t rcode) { + switch (rcode) { + case dns_rcode_noerror: + return (DNS_R_NOERROR); + case dns_rcode_formerr: + return (DNS_R_FORMERR); + case dns_rcode_servfail: + return (DNS_R_SERVFAIL); + case dns_rcode_nxdomain: + return (DNS_R_NXDOMAIN); + case dns_rcode_notimp: + return (DNS_R_NOTIMP); + case dns_rcode_refused: + return (DNS_R_REFUSED); + case dns_rcode_yxdomain: + return (DNS_R_YXDOMAIN); + case dns_rcode_yxrrset: + return (DNS_R_YXRRSET); + case dns_rcode_nxrrset: + return (DNS_R_NXRRSET); + case dns_rcode_notauth: + return (DNS_R_NOTAUTH); + case dns_rcode_notzone: + return (DNS_R_NOTZONE); + case dns_rcode_badvers: + return (DNS_R_BADVERS); + case dns_rcode_badcookie: + return (DNS_R_BADCOOKIE); + default: + return (DNS_R_SERVFAIL); + } +} diff --git a/lib/dns/rootns.c b/lib/dns/rootns.c new file mode 100644 index 0000000..9a61587 --- /dev/null +++ b/lib/dns/rootns.c @@ -0,0 +1,566 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char root_ns[] = + ";\n" + "; Internet Root Nameservers\n" + ";\n" + "$TTL 518400\n" + ". 518400 IN NS A.ROOT-SERVERS.NET.\n" + ". 518400 IN NS B.ROOT-SERVERS.NET.\n" + ". 518400 IN NS C.ROOT-SERVERS.NET.\n" + ". 518400 IN NS D.ROOT-SERVERS.NET.\n" + ". 518400 IN NS E.ROOT-SERVERS.NET.\n" + ". 518400 IN NS F.ROOT-SERVERS.NET.\n" + ". 518400 IN NS G.ROOT-SERVERS.NET.\n" + ". 518400 IN NS H.ROOT-SERVERS.NET.\n" + ". 518400 IN NS I.ROOT-SERVERS.NET.\n" + ". 518400 IN NS J.ROOT-SERVERS.NET.\n" + ". 518400 IN NS K.ROOT-SERVERS.NET.\n" + ". 518400 IN NS L.ROOT-SERVERS.NET.\n" + ". 518400 IN NS M.ROOT-SERVERS.NET.\n" + "A.ROOT-SERVERS.NET. 3600000 IN A 198.41.0.4\n" + "A.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:BA3E::2:30\n" + "B.ROOT-SERVERS.NET. 3600000 IN A 199.9.14.201\n" + "B.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:200::b\n" + "C.ROOT-SERVERS.NET. 3600000 IN A 192.33.4.12\n" + "C.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2::c\n" + "D.ROOT-SERVERS.NET. 3600000 IN A 199.7.91.13\n" + "D.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2d::d\n" + "E.ROOT-SERVERS.NET. 3600000 IN A 192.203.230.10\n" + "E.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:a8::e\n" + "F.ROOT-SERVERS.NET. 3600000 IN A 192.5.5.241\n" + "F.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:2F::F\n" + "G.ROOT-SERVERS.NET. 3600000 IN A 192.112.36.4\n" + "G.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:12::d0d\n" + "H.ROOT-SERVERS.NET. 3600000 IN A 198.97.190.53\n" + "H.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:1::53\n" + "I.ROOT-SERVERS.NET. 3600000 IN A 192.36.148.17\n" + "I.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7fe::53\n" + "J.ROOT-SERVERS.NET. 3600000 IN A 192.58.128.30\n" + "J.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:503:C27::2:30\n" + "K.ROOT-SERVERS.NET. 3600000 IN A 193.0.14.129\n" + "K.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:7FD::1\n" + "L.ROOT-SERVERS.NET. 3600000 IN A 199.7.83.42\n" + "L.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:500:9f::42\n" + "M.ROOT-SERVERS.NET. 3600000 IN A 202.12.27.33\n" + "M.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:DC3::35\n"; + +static isc_result_t +in_rootns(dns_rdataset_t *rootns, dns_name_t *name) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_ns_t ns; + + if (!dns_rdataset_isassociated(rootns)) { + return (ISC_R_NOTFOUND); + } + + result = dns_rdataset_first(rootns); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rootns, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (dns_name_compare(name, &ns.name) == 0) { + return (ISC_R_SUCCESS); + } + result = dns_rdataset_next(rootns); + dns_rdata_reset(&rdata); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_NOTFOUND; + } + return (result); +} + +static isc_result_t +check_node(dns_rdataset_t *rootns, dns_name_t *name, + dns_rdatasetiter_t *rdsiter) { + isc_result_t result; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + switch (rdataset.type) { + case dns_rdatatype_a: + case dns_rdatatype_aaaa: + result = in_rootns(rootns, name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + break; + case dns_rdatatype_ns: + if (dns_name_compare(name, dns_rootname) == 0) { + break; + } + FALLTHROUGH; + default: + result = ISC_R_FAILURE; + goto cleanup; + } + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + return (result); +} + +static isc_result_t +check_hints(dns_db_t *db) { + isc_result_t result; + dns_rdataset_t rootns; + dns_dbiterator_t *dbiter = NULL; + dns_dbnode_t *node = NULL; + isc_stdtime_t now; + dns_fixedname_t fixname; + dns_name_t *name; + dns_rdatasetiter_t *rdsiter = NULL; + + isc_stdtime_get(&now); + + name = dns_fixedname_initname(&fixname); + + dns_rdataset_init(&rootns); + (void)dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now, + NULL, name, &rootns, NULL); + result = dns_db_createiterator(db, 0, &dbiter); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_dbiterator_first(dbiter); + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &node, name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dns_db_allrdatasets(db, node, NULL, 0, now, &rdsiter); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = check_node(&rootns, name, rdsiter); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(dbiter); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + if (dns_rdataset_isassociated(&rootns)) { + dns_rdataset_disassociate(&rootns); + } + if (rdsiter != NULL) { + dns_rdatasetiter_destroy(&rdsiter); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (dbiter != NULL) { + dns_dbiterator_destroy(&dbiter); + } + return (result); +} + +isc_result_t +dns_rootns_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + const char *filename, dns_db_t **target) { + isc_result_t result, eresult; + isc_buffer_t source; + unsigned int len; + dns_rdatacallbacks_t callbacks; + dns_db_t *db = NULL; + + REQUIRE(target != NULL && *target == NULL); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + rdclass, 0, NULL, &db); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + len = strlen(root_ns); + isc_buffer_init(&source, root_ns, len); + isc_buffer_add(&source, len); + + dns_rdatacallbacks_init(&callbacks); + result = dns_db_beginload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + goto failure; + } + if (filename != NULL) { + /* + * Load the hints from the specified filename. + */ + result = dns_master_loadfile(filename, &db->origin, &db->origin, + db->rdclass, DNS_MASTER_HINT, 0, + &callbacks, NULL, NULL, db->mctx, + dns_masterformat_text, 0); + } else if (rdclass == dns_rdataclass_in) { + /* + * Default to using the Internet root servers. + */ + result = dns_master_loadbuffer( + &source, &db->origin, &db->origin, db->rdclass, + DNS_MASTER_HINT, &callbacks, db->mctx); + } else { + result = ISC_R_NOTFOUND; + } + eresult = dns_db_endload(db, &callbacks); + if (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE) { + result = eresult; + } + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + goto failure; + } + if (check_hints(db) != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "extra data in root hints '%s'", + (filename != NULL) ? filename : ""); + } + *target = db; + return (ISC_R_SUCCESS); + +failure: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_HINTS, + ISC_LOG_ERROR, + "could not configure root hints from " + "'%s': %s", + (filename != NULL) ? filename : "", + isc_result_totext(result)); + + if (db != NULL) { + dns_db_detach(&db); + } + + return (result); +} + +static void +report(dns_view_t *view, dns_name_t *name, bool missing, dns_rdata_t *rdata) { + const char *viewname = "", *sep = ""; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char databuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123")]; + isc_buffer_t buffer; + isc_result_t result; + + if (strcmp(view->name, "_bind") != 0 && + strcmp(view->name, "_default") != 0) + { + viewname = view->name; + sep = ": view "; + } + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); + isc_buffer_init(&buffer, databuf, sizeof(databuf) - 1); + result = dns_rdata_totext(rdata, NULL, &buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + databuf[isc_buffer_usedlength(&buffer)] = '\0'; + + if (missing) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: %s/%s (%s) missing from hints", + sep, viewname, namebuf, typebuf, databuf); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: %s/%s (%s) extra record " + "in hints", + sep, viewname, namebuf, typebuf, databuf); + } +} + +static bool +inrrset(dns_rdataset_t *rrset, dns_rdata_t *rdata) { + isc_result_t result; + dns_rdata_t current = DNS_RDATA_INIT; + + result = dns_rdataset_first(rrset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(rrset, ¤t); + if (dns_rdata_compare(rdata, ¤t) == 0) { + return (true); + } + dns_rdata_reset(¤t); + result = dns_rdataset_next(rrset); + } + return (false); +} + +/* + * Check that the address RRsets match. + * + * Note we don't complain about missing glue records. + */ + +static void +check_address_records(dns_view_t *view, dns_db_t *hints, dns_db_t *db, + dns_name_t *name, isc_stdtime_t now) { + isc_result_t hresult, rresult, result; + dns_rdataset_t hintrrset, rootrrset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_name_t *foundname; + dns_fixedname_t fixed; + + dns_rdataset_init(&hintrrset); + dns_rdataset_init(&rootrrset); + foundname = dns_fixedname_initname(&fixed); + + hresult = dns_db_find(hints, name, NULL, dns_rdatatype_a, 0, now, NULL, + foundname, &hintrrset, NULL); + rresult = dns_db_find(db, name, NULL, dns_rdatatype_a, + DNS_DBFIND_GLUEOK, now, NULL, foundname, + &rootrrset, NULL); + if (hresult == ISC_R_SUCCESS && + (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) + { + result = dns_rdataset_first(&rootrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rootrrset, &rdata); + if (!inrrset(&hintrrset, &rdata)) { + report(view, name, true, &rdata); + } + result = dns_rdataset_next(&rootrrset); + } + result = dns_rdataset_first(&hintrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&hintrrset, &rdata); + if (!inrrset(&rootrrset, &rdata)) { + report(view, name, false, &rdata); + } + result = dns_rdataset_next(&hintrrset); + } + } + if (hresult == ISC_R_NOTFOUND && + (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) + { + result = dns_rdataset_first(&rootrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rootrrset, &rdata); + report(view, name, true, &rdata); + result = dns_rdataset_next(&rootrrset); + } + } + if (dns_rdataset_isassociated(&rootrrset)) { + dns_rdataset_disassociate(&rootrrset); + } + if (dns_rdataset_isassociated(&hintrrset)) { + dns_rdataset_disassociate(&hintrrset); + } + + /* + * Check AAAA records. + */ + hresult = dns_db_find(hints, name, NULL, dns_rdatatype_aaaa, 0, now, + NULL, foundname, &hintrrset, NULL); + rresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, + DNS_DBFIND_GLUEOK, now, NULL, foundname, + &rootrrset, NULL); + if (hresult == ISC_R_SUCCESS && + (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) + { + result = dns_rdataset_first(&rootrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rootrrset, &rdata); + if (!inrrset(&hintrrset, &rdata)) { + report(view, name, true, &rdata); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rootrrset); + } + result = dns_rdataset_first(&hintrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&hintrrset, &rdata); + if (!inrrset(&rootrrset, &rdata)) { + report(view, name, false, &rdata); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&hintrrset); + } + } + if (hresult == ISC_R_NOTFOUND && + (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) + { + result = dns_rdataset_first(&rootrrset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rootrrset, &rdata); + report(view, name, true, &rdata); + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rootrrset); + } + } + if (dns_rdataset_isassociated(&rootrrset)) { + dns_rdataset_disassociate(&rootrrset); + } + if (dns_rdataset_isassociated(&hintrrset)) { + dns_rdataset_disassociate(&hintrrset); + } +} + +void +dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_ns_t ns; + dns_rdataset_t hintns, rootns; + const char *viewname = "", *sep = ""; + isc_stdtime_t now; + dns_name_t *name; + dns_fixedname_t fixed; + + REQUIRE(hints != NULL); + REQUIRE(db != NULL); + REQUIRE(view != NULL); + + isc_stdtime_get(&now); + + if (strcmp(view->name, "_bind") != 0 && + strcmp(view->name, "_default") != 0) + { + viewname = view->name; + sep = ": view "; + } + + dns_rdataset_init(&hintns); + dns_rdataset_init(&rootns); + name = dns_fixedname_initname(&fixed); + + result = dns_db_find(hints, dns_rootname, NULL, dns_rdatatype_ns, 0, + now, NULL, name, &hintns, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: unable to get root NS rrset " + "from hints: %s", + sep, viewname, isc_result_totext(result)); + goto cleanup; + } + + result = dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now, + NULL, name, &rootns, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: unable to get root NS rrset " + "from cache: %s", + sep, viewname, isc_result_totext(result)); + goto cleanup; + } + + /* + * Look for missing root NS names. + */ + result = dns_rdataset_first(&rootns); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rootns, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = in_rootns(&hintns, &ns.name); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + /* missing from hints */ + dns_name_format(&ns.name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: unable to find root " + "NS '%s' in hints", + sep, viewname, namebuf); + } else { + check_address_records(view, hints, db, &ns.name, now); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rootns); + } + if (result != ISC_R_NOMORE) { + goto cleanup; + } + + /* + * Look for extra root NS names. + */ + result = dns_rdataset_first(&hintns); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&hintns, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = in_rootns(&rootns, &ns.name); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + /* extra entry in hints */ + dns_name_format(&ns.name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, + "checkhints%s%s: extra NS '%s' in hints", + sep, viewname, namebuf); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&hintns); + } + if (result != ISC_R_NOMORE) { + goto cleanup; + } + +cleanup: + if (dns_rdataset_isassociated(&rootns)) { + dns_rdataset_disassociate(&rootns); + } + if (dns_rdataset_isassociated(&hintns)) { + dns_rdataset_disassociate(&hintns); + } +} diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c new file mode 100644 index 0000000..62d13d3 --- /dev/null +++ b/lib/dns/rpz.c @@ -0,0 +1,2734 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DNS_RPZ_ZONE_MAGIC ISC_MAGIC('r', 'p', 'z', ' ') +#define DNS_RPZ_ZONES_MAGIC ISC_MAGIC('r', 'p', 'z', 's') + +#define DNS_RPZ_ZONE_VALID(rpz) ISC_MAGIC_VALID(rpz, DNS_RPZ_ZONE_MAGIC) +#define DNS_RPZ_ZONES_VALID(rpzs) ISC_MAGIC_VALID(rpzs, DNS_RPZ_ZONES_MAGIC) + +/* + * Parallel radix trees for databases of response policy IP addresses + * + * The radix or patricia trees are somewhat specialized to handle response + * policy addresses by representing the two sets of IP addresses and name + * server IP addresses in a single tree. One set of IP addresses is + * for rpz-ip policies or policies triggered by addresses in A or + * AAAA records in responses. + * The second set is for rpz-nsip policies or policies triggered by addresses + * in A or AAAA records for NS records that are authorities for responses. + * + * Each leaf indicates that an IP address is listed in the IP address or the + * name server IP address policy sub-zone (or both) of the corresponding + * response policy zone. The policy data such as a CNAME or an A record + * is kept in the policy zone. After an IP address has been found in a radix + * tree, the node in the policy zone's database is found by converting + * the IP address to a domain name in a canonical form. + * + * + * The response policy zone canonical form of an IPv6 address is one of: + * prefix.W.W.W.W.W.W.W.W + * prefix.WORDS.zz + * prefix.WORDS.zz.WORDS + * prefix.zz.WORDS + * where + * prefix is the prefix length of the IPv6 address between 1 and 128 + * W is a number between 0 and 65535 + * WORDS is one or more numbers W separated with "." + * zz corresponds to :: in the standard IPv6 text representation + * + * The canonical form of IPv4 addresses is: + * prefix.B.B.B.B + * where + * prefix is the prefix length of the address between 1 and 32 + * B is a number between 0 and 255 + * + * Names for IPv4 addresses are distinguished from IPv6 addresses by having + * 5 labels all of which are numbers, and a prefix between 1 and 32. + */ + +/* + * Nodes hashtable calculation parameters + */ +#define DNS_RPZ_HTSIZE_MAX 24 +#define DNS_RPZ_HTSIZE_DIV 3 + +static isc_result_t +dns__rpz_shuttingdown(dns_rpz_zones_t *rpzs); +static void +dns__rpz_timer_cb(isc_task_t *task, isc_event_t *event); + +/* + * Use a private definition of IPv6 addresses because s6_addr32 is not + * always defined and our IPv6 addresses are in non-standard byte order + */ +typedef uint32_t dns_rpz_cidr_word_t; +#define DNS_RPZ_CIDR_WORD_BITS ((int)sizeof(dns_rpz_cidr_word_t) * 8) +#define DNS_RPZ_CIDR_KEY_BITS ((int)sizeof(dns_rpz_cidr_key_t) * 8) +#define DNS_RPZ_CIDR_WORDS (128 / DNS_RPZ_CIDR_WORD_BITS) +typedef struct { + dns_rpz_cidr_word_t w[DNS_RPZ_CIDR_WORDS]; +} dns_rpz_cidr_key_t; + +#define ADDR_V4MAPPED 0xffff +#define KEY_IS_IPV4(prefix, ip) \ + ((prefix) >= 96 && (ip)->w[0] == 0 && (ip)->w[1] == 0 && \ + (ip)->w[2] == ADDR_V4MAPPED) + +#define DNS_RPZ_WORD_MASK(b) \ + ((b) == 0 ? (dns_rpz_cidr_word_t)(-1) \ + : ((dns_rpz_cidr_word_t)(-1) \ + << (DNS_RPZ_CIDR_WORD_BITS - (b)))) + +/* + * Get bit #n from the array of words of an IP address. + */ +#define DNS_RPZ_IP_BIT(ip, n) \ + (1 & ((ip)->w[(n) / DNS_RPZ_CIDR_WORD_BITS] >> \ + (DNS_RPZ_CIDR_WORD_BITS - 1 - ((n) % DNS_RPZ_CIDR_WORD_BITS)))) + +/* + * A triplet of arrays of bits flagging the existence of + * client-IP, IP, and NSIP policy triggers. + */ +typedef struct dns_rpz_addr_zbits dns_rpz_addr_zbits_t; +struct dns_rpz_addr_zbits { + dns_rpz_zbits_t client_ip; + dns_rpz_zbits_t ip; + dns_rpz_zbits_t nsip; +}; + +/* + * A CIDR or radix tree node. + */ +struct dns_rpz_cidr_node { + dns_rpz_cidr_node_t *parent; + dns_rpz_cidr_node_t *child[2]; + dns_rpz_cidr_key_t ip; + dns_rpz_prefix_t prefix; + dns_rpz_addr_zbits_t set; + dns_rpz_addr_zbits_t sum; +}; + +/* + * A pair of arrays of bits flagging the existence of + * QNAME and NSDNAME policy triggers. + */ +typedef struct dns_rpz_nm_zbits dns_rpz_nm_zbits_t; +struct dns_rpz_nm_zbits { + dns_rpz_zbits_t qname; + dns_rpz_zbits_t ns; +}; + +/* + * The data in a RBT node has two pairs of bits for policy zones. + * One pair is for the corresponding name of the node such as example.com + * and the other pair is for a wildcard child such as *.example.com. + */ +typedef struct dns_rpz_nm_data dns_rpz_nm_data_t; +struct dns_rpz_nm_data { + dns_rpz_nm_zbits_t set; + dns_rpz_nm_zbits_t wild; +}; + +static isc_result_t +rpz_add(dns_rpz_zone_t *rpz, const dns_name_t *src_name); +static void +rpz_del(dns_rpz_zone_t *rpz, const dns_name_t *src_name); + +const char * +dns_rpz_type2str(dns_rpz_type_t type) { + switch (type) { + case DNS_RPZ_TYPE_CLIENT_IP: + return ("CLIENT-IP"); + case DNS_RPZ_TYPE_QNAME: + return ("QNAME"); + case DNS_RPZ_TYPE_IP: + return ("IP"); + case DNS_RPZ_TYPE_NSIP: + return ("NSIP"); + case DNS_RPZ_TYPE_NSDNAME: + return ("NSDNAME"); + case DNS_RPZ_TYPE_BAD: + break; + } + FATAL_ERROR("impossible rpz type %d", type); + return ("impossible"); +} + +dns_rpz_policy_t +dns_rpz_str2policy(const char *str) { + static struct { + const char *str; + dns_rpz_policy_t policy; + } tbl[] = { + { "given", DNS_RPZ_POLICY_GIVEN }, + { "disabled", DNS_RPZ_POLICY_DISABLED }, + { "passthru", DNS_RPZ_POLICY_PASSTHRU }, + { "drop", DNS_RPZ_POLICY_DROP }, + { "tcp-only", DNS_RPZ_POLICY_TCP_ONLY }, + { "nxdomain", DNS_RPZ_POLICY_NXDOMAIN }, + { "nodata", DNS_RPZ_POLICY_NODATA }, + { "cname", DNS_RPZ_POLICY_CNAME }, + { "no-op", DNS_RPZ_POLICY_PASSTHRU }, /* old passthru */ + }; + unsigned int n; + + if (str == NULL) { + return (DNS_RPZ_POLICY_ERROR); + } + for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n) { + if (!strcasecmp(tbl[n].str, str)) { + return (tbl[n].policy); + } + } + return (DNS_RPZ_POLICY_ERROR); +} + +const char * +dns_rpz_policy2str(dns_rpz_policy_t policy) { + const char *str = NULL; + + switch (policy) { + case DNS_RPZ_POLICY_PASSTHRU: + str = "PASSTHRU"; + break; + case DNS_RPZ_POLICY_DROP: + str = "DROP"; + break; + case DNS_RPZ_POLICY_TCP_ONLY: + str = "TCP-ONLY"; + break; + case DNS_RPZ_POLICY_NXDOMAIN: + str = "NXDOMAIN"; + break; + case DNS_RPZ_POLICY_NODATA: + str = "NODATA"; + break; + case DNS_RPZ_POLICY_RECORD: + str = "Local-Data"; + break; + case DNS_RPZ_POLICY_CNAME: + case DNS_RPZ_POLICY_WILDCNAME: + str = "CNAME"; + break; + case DNS_RPZ_POLICY_MISS: + str = "MISS"; + break; + case DNS_RPZ_POLICY_DNS64: + str = "DNS64"; + break; + case DNS_RPZ_POLICY_ERROR: + str = "ERROR"; + break; + default: + UNREACHABLE(); + } + return (str); +} + +/* + * Return the bit number of the highest set bit in 'zbit'. + * (for example, 0x01 returns 0, 0xFF returns 7, etc.) + */ +static int +zbit_to_num(dns_rpz_zbits_t zbit) { + dns_rpz_num_t rpz_num; + + REQUIRE(zbit != 0); + rpz_num = 0; + if ((zbit & 0xffffffff00000000ULL) != 0) { + zbit >>= 32; + rpz_num += 32; + } + if ((zbit & 0xffff0000) != 0) { + zbit >>= 16; + rpz_num += 16; + } + if ((zbit & 0xff00) != 0) { + zbit >>= 8; + rpz_num += 8; + } + if ((zbit & 0xf0) != 0) { + zbit >>= 4; + rpz_num += 4; + } + if ((zbit & 0xc) != 0) { + zbit >>= 2; + rpz_num += 2; + } + if ((zbit & 2) != 0) { + ++rpz_num; + } + return (rpz_num); +} + +/* + * Make a set of bit masks given one or more bits and their type. + */ +static void +make_addr_set(dns_rpz_addr_zbits_t *tgt_set, dns_rpz_zbits_t zbits, + dns_rpz_type_t type) { + switch (type) { + case DNS_RPZ_TYPE_CLIENT_IP: + tgt_set->client_ip = zbits; + tgt_set->ip = 0; + tgt_set->nsip = 0; + break; + case DNS_RPZ_TYPE_IP: + tgt_set->client_ip = 0; + tgt_set->ip = zbits; + tgt_set->nsip = 0; + break; + case DNS_RPZ_TYPE_NSIP: + tgt_set->client_ip = 0; + tgt_set->ip = 0; + tgt_set->nsip = zbits; + break; + default: + UNREACHABLE(); + } +} + +static void +make_nm_set(dns_rpz_nm_zbits_t *tgt_set, dns_rpz_num_t rpz_num, + dns_rpz_type_t type) { + switch (type) { + case DNS_RPZ_TYPE_QNAME: + tgt_set->qname = DNS_RPZ_ZBIT(rpz_num); + tgt_set->ns = 0; + break; + case DNS_RPZ_TYPE_NSDNAME: + tgt_set->qname = 0; + tgt_set->ns = DNS_RPZ_ZBIT(rpz_num); + break; + default: + UNREACHABLE(); + } +} + +/* + * Mark a node and all of its parents as having client-IP, IP, or NSIP data + */ +static void +set_sum_pair(dns_rpz_cidr_node_t *cnode) { + dns_rpz_addr_zbits_t sum; + + do { + dns_rpz_cidr_node_t *child = cnode->child[0]; + sum = cnode->set; + + if (child != NULL) { + sum.client_ip |= child->sum.client_ip; + sum.ip |= child->sum.ip; + sum.nsip |= child->sum.nsip; + } + + child = cnode->child[1]; + if (child != NULL) { + sum.client_ip |= child->sum.client_ip; + sum.ip |= child->sum.ip; + sum.nsip |= child->sum.nsip; + } + + if (cnode->sum.client_ip == sum.client_ip && + cnode->sum.ip == sum.ip && cnode->sum.nsip == sum.nsip) + { + break; + } + cnode->sum = sum; + cnode = cnode->parent; + } while (cnode != NULL); +} + +/* Caller must hold rpzs->maint_lock */ +static void +fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) { + dns_rpz_zbits_t mask; + + /* + * qname_wait_recurse and qname_skip_recurse are used to + * implement the "qname-wait-recurse" config option. + * + * When "qname-wait-recurse" is yes, no processing happens without + * recursion. In this case, qname_wait_recurse is true, and + * qname_skip_recurse (a bit field indicating which policy zones + * can be processed without recursion) is set to all 0's by + * fix_qname_skip_recurse(). + * + * When "qname-wait-recurse" is no, qname_skip_recurse may be + * set to a non-zero value by fix_qname_skip_recurse(). The mask + * has to have bits set for the policy zones for which + * processing may continue without recursion, and bits cleared + * for the rest. + * + * (1) The ARM says: + * + * The "qname-wait-recurse no" option overrides that default + * behavior when recursion cannot change a non-error + * response. The option does not affect QNAME or client-IP + * triggers in policy zones listed after other zones + * containing IP, NSIP and NSDNAME triggers, because those may + * depend on the A, AAAA, and NS records that would be found + * during recursive resolution. + * + * Let's consider the following: + * + * zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 | + * rpzs->have.nsdname | + * rpzs->have.nsipv4 | rpzs->have.nsipv6); + * + * zbits_req now contains bits set for zones which require + * recursion. + * + * But going by the description in the ARM, if the first policy + * zone requires recursion, then all zones after that (higher + * order bits) have to wait as well. If the Nth zone requires + * recursion, then (N+1)th zone onwards all need to wait. + * + * So mapping this, examples: + * + * zbits_req = 0b000 mask = 0xffffffff (no zones have to wait for + * recursion) + * zbits_req = 0b001 mask = 0x00000000 (all zones have to wait) + * zbits_req = 0b010 mask = 0x00000001 (the first zone doesn't have to + * wait, second zone onwards need + * to wait) + * zbits_req = 0b011 mask = 0x00000000 (all zones have to wait) + * zbits_req = 0b100 mask = 0x00000011 (the 1st and 2nd zones don't + * have to wait, third zone + * onwards need to wait) + * + * More generally, we have to count the number of trailing 0 + * bits in zbits_req and only these can be processed without + * recursion. All the rest need to wait. + * + * (2) The ARM says that "qname-wait-recurse no" option + * overrides the default behavior when recursion cannot change a + * non-error response. So, in the order of listing of policy + * zones, within the first policy zone where recursion may be + * required, we should first allow CLIENT-IP and QNAME policy + * records to be attempted without recursion. + */ + + /* + * Get a mask covering all policy zones that are not subordinate to + * other policy zones containing triggers that require that the + * qname be resolved before they can be checked. + */ + rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6; + rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6; + rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6; + + if (rpzs->p.qname_wait_recurse) { + mask = 0; + } else { + dns_rpz_zbits_t zbits_req; + dns_rpz_zbits_t zbits_notreq; + dns_rpz_zbits_t mask2; + dns_rpz_zbits_t req_mask; + + /* + * Get the masks of zones with policies that + * do/don't require recursion + */ + + zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 | + rpzs->have.nsdname | rpzs->have.nsipv4 | + rpzs->have.nsipv6); + zbits_notreq = (rpzs->have.client_ip | rpzs->have.qname); + + if (zbits_req == 0) { + mask = DNS_RPZ_ALL_ZBITS; + goto set; + } + + /* + * req_mask is a mask covering used bits in + * zbits_req. (For instance, 0b1 => 0b1, 0b101 => 0b111, + * 0b11010101 => 0b11111111). + */ + req_mask = zbits_req; + req_mask |= req_mask >> 1; + req_mask |= req_mask >> 2; + req_mask |= req_mask >> 4; + req_mask |= req_mask >> 8; + req_mask |= req_mask >> 16; + req_mask |= req_mask >> 32; + + /* + * There's no point in skipping recursion for a later + * zone if it is required in a previous zone. + */ + if ((zbits_notreq & req_mask) == 0) { + mask = 0; + goto set; + } + + /* + * This bit arithmetic creates a mask of zones in which + * it is okay to skip recursion. After the first zone + * that has to wait for recursion, all the others have + * to wait as well, so we want to create a mask in which + * all the trailing zeroes in zbits_req are are 1, and + * more significant bits are 0. (For instance, + * 0x0700 => 0x00ff, 0x0007 => 0x0000) + */ + mask = ~(zbits_req | ((~zbits_req) + 1)); + + /* + * As mentioned in (2) above, the zone corresponding to + * the least significant zero could have its CLIENT-IP + * and QNAME policies checked before recursion, if it + * has any of those policies. So if it does, we + * can set its 0 to 1. + * + * Locate the least significant 0 bit in the mask (for + * instance, 0xff => 0x100)... + */ + mask2 = (mask << 1) & ~mask; + + /* + * Also set the bit for zone 0, because if it's in + * zbits_notreq then it's definitely okay to attempt to + * skip recursion for zone 0... + */ + mask2 |= 1; + + /* Clear any bits *not* in zbits_notreq... */ + mask2 &= zbits_notreq; + + /* And merge the result into the skip-recursion mask */ + mask |= mask2; + } + +set: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, + DNS_RPZ_DEBUG_QUIET, + "computed RPZ qname_skip_recurse mask=0x%" PRIx64, + (uint64_t)mask); + rpzs->have.qname_skip_recurse = mask; +} + +static void +adj_trigger_cnt(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, + bool inc) { + dns_rpz_trigger_counter_t *cnt = NULL; + dns_rpz_zbits_t *have = NULL; + + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpz->rpzs->triggers[rpz->num].client_ipv4; + have = &rpz->rpzs->have.client_ipv4; + } else { + cnt = &rpz->rpzs->triggers[rpz->num].client_ipv6; + have = &rpz->rpzs->have.client_ipv6; + } + break; + case DNS_RPZ_TYPE_QNAME: + cnt = &rpz->rpzs->triggers[rpz->num].qname; + have = &rpz->rpzs->have.qname; + break; + case DNS_RPZ_TYPE_IP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpz->rpzs->triggers[rpz->num].ipv4; + have = &rpz->rpzs->have.ipv4; + } else { + cnt = &rpz->rpzs->triggers[rpz->num].ipv6; + have = &rpz->rpzs->have.ipv6; + } + break; + case DNS_RPZ_TYPE_NSDNAME: + cnt = &rpz->rpzs->triggers[rpz->num].nsdname; + have = &rpz->rpzs->have.nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpz->rpzs->triggers[rpz->num].nsipv4; + have = &rpz->rpzs->have.nsipv4; + } else { + cnt = &rpz->rpzs->triggers[rpz->num].nsipv6; + have = &rpz->rpzs->have.nsipv6; + } + break; + default: + UNREACHABLE(); + } + + if (inc) { + if (++*cnt == 1U) { + *have |= DNS_RPZ_ZBIT(rpz->num); + fix_qname_skip_recurse(rpz->rpzs); + } + } else { + REQUIRE(*cnt != 0U); + if (--*cnt == 0U) { + *have &= ~DNS_RPZ_ZBIT(rpz->num); + fix_qname_skip_recurse(rpz->rpzs); + } + } +} + +static dns_rpz_cidr_node_t * +new_node(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *ip, + dns_rpz_prefix_t prefix, const dns_rpz_cidr_node_t *child) { + dns_rpz_cidr_node_t *node = NULL; + int i, words, wlen; + + node = isc_mem_get(rpzs->mctx, sizeof(*node)); + *node = (dns_rpz_cidr_node_t){ + .prefix = prefix, + }; + + if (child != NULL) { + node->sum = child->sum; + } + + words = prefix / DNS_RPZ_CIDR_WORD_BITS; + wlen = prefix % DNS_RPZ_CIDR_WORD_BITS; + i = 0; + while (i < words) { + node->ip.w[i] = ip->w[i]; + ++i; + } + if (wlen != 0) { + node->ip.w[i] = ip->w[i] & DNS_RPZ_WORD_MASK(wlen); + ++i; + } + while (i < DNS_RPZ_CIDR_WORDS) { + node->ip.w[i++] = 0; + } + + return (node); +} + +static void +badname(int level, const dns_name_t *name, const char *str1, const char *str2) { + /* + * bin/tests/system/rpz/tests.sh looks for "invalid rpz". + */ + if (level < DNS_RPZ_DEBUG_QUIET && isc_log_wouldlog(dns_lctx, level)) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, level, + "invalid rpz IP address \"%s\"%s%s", namebuf, + str1, str2); + } +} + +/* + * Convert an IP address from radix tree binary (host byte order) to + * to its canonical response policy domain name without the origin of the + * policy zone. + * + * Generate a name for an IPv6 address that fits RFC 5952, except that our + * reversed format requires that when the length of the consecutive 16-bit + * 0 fields are equal (e.g., 1.0.0.1.0.0.db8.2001 corresponding to + * 2001:db8:0:0:1:0:0:1), we shorted the last instead of the first + * (e.g., 1.0.0.1.zz.db8.2001 corresponding to 2001:db8::1:0:0:1). + */ +static isc_result_t +ip2name(const dns_rpz_cidr_key_t *tgt_ip, dns_rpz_prefix_t tgt_prefix, + const dns_name_t *base_name, dns_name_t *ip_name) { +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif /* ifndef INET6_ADDRSTRLEN */ + char str[1 + 8 + 1 + INET6_ADDRSTRLEN + 1]; + isc_buffer_t buffer; + isc_result_t result; + int len; + + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + len = snprintf(str, sizeof(str), "%u.%u.%u.%u.%u", + tgt_prefix - 96U, tgt_ip->w[3] & 0xffU, + (tgt_ip->w[3] >> 8) & 0xffU, + (tgt_ip->w[3] >> 16) & 0xffU, + (tgt_ip->w[3] >> 24) & 0xffU); + if (len < 0 || (size_t)len >= sizeof(str)) { + return (ISC_R_FAILURE); + } + } else { + int w[DNS_RPZ_CIDR_WORDS * 2]; + int best_first, best_len, cur_first, cur_len; + + len = snprintf(str, sizeof(str), "%d", tgt_prefix); + if (len < 0 || (size_t)len >= sizeof(str)) { + return (ISC_R_FAILURE); + } + + for (int n = 0; n < DNS_RPZ_CIDR_WORDS; n++) { + w[n * 2 + 1] = + ((tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - n] >> 16) & + 0xffff); + w[n * 2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - n] & + 0xffff; + } + /* + * Find the start and length of the first longest sequence + * of zeros in the address. + */ + best_first = -1; + best_len = 0; + cur_first = -1; + cur_len = 0; + for (int n = 0; n <= 7; ++n) { + if (w[n] != 0) { + cur_len = 0; + cur_first = -1; + } else { + ++cur_len; + if (cur_first < 0) { + cur_first = n; + } else if (cur_len >= best_len) { + best_first = cur_first; + best_len = cur_len; + } + } + } + + for (int n = 0; n <= 7; ++n) { + int i; + + INSIST(len > 0 && (size_t)len < sizeof(str)); + if (n == best_first) { + i = snprintf(str + len, sizeof(str) - len, + ".zz"); + n += best_len - 1; + } else { + i = snprintf(str + len, sizeof(str) - len, + ".%x", w[n]); + } + if (i < 0 || (size_t)i >= (size_t)(sizeof(str) - len)) { + return (ISC_R_FAILURE); + } + len += i; + } + } + + isc_buffer_init(&buffer, str, sizeof(str)); + isc_buffer_add(&buffer, len); + result = dns_name_fromtext(ip_name, &buffer, base_name, 0, NULL); + return (result); +} + +/* + * Determine the type of a name in a response policy zone. + */ +static dns_rpz_type_t +type_from_name(const dns_rpz_zones_t *rpzs, dns_rpz_zone_t *rpz, + const dns_name_t *name) { + if (dns_name_issubdomain(name, &rpz->ip)) { + return (DNS_RPZ_TYPE_IP); + } + + if (dns_name_issubdomain(name, &rpz->client_ip)) { + return (DNS_RPZ_TYPE_CLIENT_IP); + } + + if ((rpzs->p.nsip_on & DNS_RPZ_ZBIT(rpz->num)) != 0 && + dns_name_issubdomain(name, &rpz->nsip)) + { + return (DNS_RPZ_TYPE_NSIP); + } + + if ((rpzs->p.nsdname_on & DNS_RPZ_ZBIT(rpz->num)) != 0 && + dns_name_issubdomain(name, &rpz->nsdname)) + { + return (DNS_RPZ_TYPE_NSDNAME); + } + + return (DNS_RPZ_TYPE_QNAME); +} + +/* + * Convert an IP address from canonical response policy domain name form + * to radix tree binary (host byte order) for adding or deleting IP or NSIP + * data. + */ +static isc_result_t +name2ipkey(int log_level, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name, dns_rpz_cidr_key_t *tgt_ip, + dns_rpz_prefix_t *tgt_prefix, dns_rpz_addr_zbits_t *new_set) { + char ip_str[DNS_NAME_FORMATSIZE]; + dns_offsets_t ip_name_offsets; + dns_fixedname_t ip_name2f; + dns_name_t ip_name; + const char *prefix_str = NULL, *cp = NULL, *end = NULL; + char *cp2; + int ip_labels; + dns_rpz_prefix_t prefix; + unsigned long prefix_num, l; + isc_result_t result; + int i; + + REQUIRE(rpz != NULL); + REQUIRE(rpz->rpzs != NULL && rpz->num < rpz->rpzs->p.num_zones); + + make_addr_set(new_set, DNS_RPZ_ZBIT(rpz->num), rpz_type); + + ip_labels = dns_name_countlabels(src_name); + if (rpz_type == DNS_RPZ_TYPE_QNAME) { + ip_labels -= dns_name_countlabels(&rpz->origin); + } else { + ip_labels -= dns_name_countlabels(&rpz->nsdname); + } + if (ip_labels < 2) { + badname(log_level, src_name, "; too short", ""); + return (ISC_R_FAILURE); + } + dns_name_init(&ip_name, ip_name_offsets); + dns_name_getlabelsequence(src_name, 0, ip_labels, &ip_name); + + /* + * Get text for the IP address + */ + dns_name_format(&ip_name, ip_str, sizeof(ip_str)); + end = &ip_str[strlen(ip_str) + 1]; + prefix_str = ip_str; + + prefix_num = strtoul(prefix_str, &cp2, 10); + if (*cp2 != '.') { + badname(log_level, src_name, "; invalid leading prefix length", + ""); + return (ISC_R_FAILURE); + } + /* + * Patch in trailing nul character to print just the length + * label (for various cases below). + */ + *cp2 = '\0'; + if (prefix_num < 1U || prefix_num > 128U) { + badname(log_level, src_name, "; invalid prefix length of ", + prefix_str); + return (ISC_R_FAILURE); + } + cp = cp2 + 1; + + if (--ip_labels == 4 && !strchr(cp, 'z')) { + /* + * Convert an IPv4 address + * from the form "prefix.z.y.x.w" + */ + if (prefix_num > 32U) { + badname(log_level, src_name, + "; invalid IPv4 prefix length of ", prefix_str); + return (ISC_R_FAILURE); + } + prefix_num += 96; + *tgt_prefix = (dns_rpz_prefix_t)prefix_num; + tgt_ip->w[0] = 0; + tgt_ip->w[1] = 0; + tgt_ip->w[2] = ADDR_V4MAPPED; + tgt_ip->w[3] = 0; + for (i = 0; i < 32; i += 8) { + l = strtoul(cp, &cp2, 10); + if (l > 255U || (*cp2 != '.' && *cp2 != '\0')) { + if (*cp2 == '.') { + *cp2 = '\0'; + } + badname(log_level, src_name, + "; invalid IPv4 octet ", cp); + return (ISC_R_FAILURE); + } + tgt_ip->w[3] |= l << i; + cp = cp2 + 1; + } + } else { + /* + * Convert a text IPv6 address. + */ + *tgt_prefix = (dns_rpz_prefix_t)prefix_num; + for (i = 0; ip_labels > 0 && i < DNS_RPZ_CIDR_WORDS * 2; + ip_labels--) + { + if (cp[0] == 'z' && cp[1] == 'z' && + (cp[2] == '.' || cp[2] == '\0') && i <= 6) + { + do { + if ((i & 1) == 0) { + tgt_ip->w[3 - i / 2] = 0; + } + ++i; + } while (ip_labels + i <= 8); + cp += 3; + } else { + l = strtoul(cp, &cp2, 16); + if (l > 0xffffu || + (*cp2 != '.' && *cp2 != '\0')) + { + if (*cp2 == '.') { + *cp2 = '\0'; + } + badname(log_level, src_name, + "; invalid IPv6 word ", cp); + return (ISC_R_FAILURE); + } + if ((i & 1) == 0) { + tgt_ip->w[3 - i / 2] = l; + } else { + tgt_ip->w[3 - i / 2] |= l << 16; + } + i++; + cp = cp2 + 1; + } + } + } + if (cp != end) { + badname(log_level, src_name, "", ""); + return (ISC_R_FAILURE); + } + + /* + * Check for 1s after the prefix length. + */ + prefix = (dns_rpz_prefix_t)prefix_num; + while (prefix < DNS_RPZ_CIDR_KEY_BITS) { + dns_rpz_cidr_word_t aword; + + i = prefix % DNS_RPZ_CIDR_WORD_BITS; + aword = tgt_ip->w[prefix / DNS_RPZ_CIDR_WORD_BITS]; + if ((aword & ~DNS_RPZ_WORD_MASK(i)) != 0) { + badname(log_level, src_name, + "; too small prefix length of ", prefix_str); + return (ISC_R_FAILURE); + } + prefix -= i; + prefix += DNS_RPZ_CIDR_WORD_BITS; + } + + /* + * Complain about bad names but be generous and accept them. + */ + if (log_level < DNS_RPZ_DEBUG_QUIET && + isc_log_wouldlog(dns_lctx, log_level)) + { + /* + * Convert the address back to a canonical domain name + * to ensure that the original name is in canonical form. + */ + dns_name_t *ip_name2 = dns_fixedname_initname(&ip_name2f); + result = ip2name(tgt_ip, (dns_rpz_prefix_t)prefix_num, NULL, + ip_name2); + if (result != ISC_R_SUCCESS || + !dns_name_equal(&ip_name, ip_name2)) + { + char ip2_str[DNS_NAME_FORMATSIZE]; + dns_name_format(ip_name2, ip2_str, sizeof(ip2_str)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, log_level, + "rpz IP address \"%s\"" + " is not the canonical \"%s\"", + ip_str, ip2_str); + } + } + + return (ISC_R_SUCCESS); +} + +/* + * Get trigger name and data bits for adding or deleting summary NSDNAME + * or QNAME data. + */ +static void +name2data(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name, dns_name_t *trig_name, + dns_rpz_nm_data_t *new_data) { + dns_offsets_t tmp_name_offsets; + dns_name_t tmp_name; + unsigned int prefix_len, n; + + REQUIRE(rpz != NULL); + REQUIRE(rpz->rpzs != NULL && rpz->num < rpz->rpzs->p.num_zones); + + /* + * Handle wildcards by putting only the parent into the + * summary RBT. The summary database only causes a check of the + * real policy zone where wildcards will be handled. + */ + if (dns_name_iswildcard(src_name)) { + prefix_len = 1; + memset(&new_data->set, 0, sizeof(new_data->set)); + make_nm_set(&new_data->wild, rpz->num, rpz_type); + } else { + prefix_len = 0; + make_nm_set(&new_data->set, rpz->num, rpz_type); + memset(&new_data->wild, 0, sizeof(new_data->wild)); + } + + dns_name_init(&tmp_name, tmp_name_offsets); + n = dns_name_countlabels(src_name); + n -= prefix_len; + if (rpz_type == DNS_RPZ_TYPE_QNAME) { + n -= dns_name_countlabels(&rpz->origin); + } else { + n -= dns_name_countlabels(&rpz->nsdname); + } + dns_name_getlabelsequence(src_name, prefix_len, n, &tmp_name); + (void)dns_name_concatenate(&tmp_name, dns_rootname, trig_name, NULL); +} + +#ifndef HAVE_BUILTIN_CLZ +/** + * \brief Count Leading Zeros: Find the location of the left-most set + * bit. + */ +static unsigned int +clz(dns_rpz_cidr_word_t w) { + unsigned int bit; + + bit = DNS_RPZ_CIDR_WORD_BITS - 1; + + if ((w & 0xffff0000) != 0) { + w >>= 16; + bit -= 16; + } + + if ((w & 0xff00) != 0) { + w >>= 8; + bit -= 8; + } + + if ((w & 0xf0) != 0) { + w >>= 4; + bit -= 4; + } + + if ((w & 0xc) != 0) { + w >>= 2; + bit -= 2; + } + + if ((w & 2) != 0) { + --bit; + } + + return (bit); +} +#endif /* ifndef HAVE_BUILTIN_CLZ */ + +/* + * Find the first differing bit in two keys (IP addresses). + */ +static int +diff_keys(const dns_rpz_cidr_key_t *key1, dns_rpz_prefix_t prefix1, + const dns_rpz_cidr_key_t *key2, dns_rpz_prefix_t prefix2) { + dns_rpz_cidr_word_t delta; + dns_rpz_prefix_t maxbit, bit; + int i; + + bit = 0; + maxbit = ISC_MIN(prefix1, prefix2); + + /* + * find the first differing words + */ + for (i = 0; bit < maxbit; i++, bit += DNS_RPZ_CIDR_WORD_BITS) { + delta = key1->w[i] ^ key2->w[i]; + if (delta != 0) { +#ifdef HAVE_BUILTIN_CLZ + bit += __builtin_clz(delta); +#else /* ifdef HAVE_BUILTIN_CLZ */ + bit += clz(delta); +#endif /* ifdef HAVE_BUILTIN_CLZ */ + break; + } + } + return (ISC_MIN(bit, maxbit)); +} + +/* + * Given a hit while searching the radix trees, + * clear all bits for higher numbered zones. + */ +static dns_rpz_zbits_t +trim_zbits(dns_rpz_zbits_t zbits, dns_rpz_zbits_t found) { + dns_rpz_zbits_t x; + + /* + * Isolate the first or smallest numbered hit bit. + * Make a mask of that bit and all smaller numbered bits. + */ + x = zbits & found; + x &= (~x + 1); + x = (x << 1) - 1; + zbits &= x; + return (zbits); +} + +/* + * Search a radix tree for an IP address for ordinary lookup + * or for a CIDR block adding or deleting an entry + * + * Return ISC_R_SUCCESS, DNS_R_PARTIALMATCH, ISC_R_NOTFOUND, + * and *found=longest match node + * or with create==true, ISC_R_EXISTS or ISC_R_NOMEMORY + */ +static isc_result_t +search(dns_rpz_zones_t *rpzs, const dns_rpz_cidr_key_t *tgt_ip, + dns_rpz_prefix_t tgt_prefix, const dns_rpz_addr_zbits_t *tgt_set, + bool create, dns_rpz_cidr_node_t **found) { + dns_rpz_cidr_node_t *cur = NULL, *parent = NULL, *child = NULL; + dns_rpz_cidr_node_t *new_parent = NULL, *sibling = NULL; + dns_rpz_addr_zbits_t set; + int cur_num, child_num; + isc_result_t find_result; + + set = *tgt_set; + find_result = ISC_R_NOTFOUND; + *found = NULL; + cur = rpzs->cidr; + parent = NULL; + cur_num = 0; + for (;;) { + dns_rpz_prefix_t dbit; + if (cur == NULL) { + /* + * No child so we cannot go down. + * Quit with whatever we already found + * or add the target as a child of the current parent. + */ + if (!create) { + return (find_result); + } + child = new_node(rpzs, tgt_ip, tgt_prefix, NULL); + if (child == NULL) { + return (ISC_R_NOMEMORY); + } + if (parent == NULL) { + rpzs->cidr = child; + } else { + parent->child[cur_num] = child; + } + child->parent = parent; + child->set.client_ip |= tgt_set->client_ip; + child->set.ip |= tgt_set->ip; + child->set.nsip |= tgt_set->nsip; + set_sum_pair(child); + *found = child; + return (ISC_R_SUCCESS); + } + + if ((cur->sum.client_ip & set.client_ip) == 0 && + (cur->sum.ip & set.ip) == 0 && + (cur->sum.nsip & set.nsip) == 0) + { + /* + * This node has no relevant data + * and is in none of the target trees. + * Pretend it does not exist if we are not adding. + * + * If we are adding, continue down to eventually add + * a node and mark/put this node in the correct tree. + */ + if (!create) { + return (find_result); + } + } + + dbit = diff_keys(tgt_ip, tgt_prefix, &cur->ip, cur->prefix); + /* + * dbit <= tgt_prefix and dbit <= cur->prefix always. + * We are finished searching if we matched all of the target. + */ + if (dbit == tgt_prefix) { + if (tgt_prefix == cur->prefix) { + /* + * The node's key matches the target exactly. + */ + if ((cur->set.client_ip & set.client_ip) != 0 || + (cur->set.ip & set.ip) != 0 || + (cur->set.nsip & set.nsip) != 0) + { + /* + * It is the answer if it has data. + */ + *found = cur; + if (create) { + find_result = ISC_R_EXISTS; + } else { + find_result = ISC_R_SUCCESS; + } + } else if (create) { + /* + * The node lacked relevant data, + * but will have it now. + */ + cur->set.client_ip |= + tgt_set->client_ip; + cur->set.ip |= tgt_set->ip; + cur->set.nsip |= tgt_set->nsip; + set_sum_pair(cur); + *found = cur; + find_result = ISC_R_SUCCESS; + } + return (find_result); + } + + /* + * We know tgt_prefix < cur->prefix which means that + * the target is shorter than the current node. + * Add the target as the current node's parent. + */ + if (!create) { + return (find_result); + } + + new_parent = new_node(rpzs, tgt_ip, tgt_prefix, cur); + if (new_parent == NULL) { + return (ISC_R_NOMEMORY); + } + new_parent->parent = parent; + if (parent == NULL) { + rpzs->cidr = new_parent; + } else { + parent->child[cur_num] = new_parent; + } + child_num = DNS_RPZ_IP_BIT(&cur->ip, tgt_prefix); + new_parent->child[child_num] = cur; + cur->parent = new_parent; + new_parent->set = *tgt_set; + set_sum_pair(new_parent); + *found = new_parent; + return (ISC_R_SUCCESS); + } + + if (dbit == cur->prefix) { + if ((cur->set.client_ip & set.client_ip) != 0 || + (cur->set.ip & set.ip) != 0 || + (cur->set.nsip & set.nsip) != 0) + { + /* + * We have a partial match between of all of the + * current node but only part of the target. + * Continue searching for other hits in the + * same or lower numbered trees. + */ + find_result = DNS_R_PARTIALMATCH; + *found = cur; + set.client_ip = trim_zbits(set.client_ip, + cur->set.client_ip); + set.ip = trim_zbits(set.ip, cur->set.ip); + set.nsip = trim_zbits(set.nsip, cur->set.nsip); + } + parent = cur; + cur_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); + cur = cur->child[cur_num]; + continue; + } + + /* + * dbit < tgt_prefix and dbit < cur->prefix, + * so we failed to match both the target and the current node. + * Insert a fork of a parent above the current node and + * add the target as a sibling of the current node + */ + if (!create) { + return (find_result); + } + + sibling = new_node(rpzs, tgt_ip, tgt_prefix, NULL); + if (sibling == NULL) { + return (ISC_R_NOMEMORY); + } + new_parent = new_node(rpzs, tgt_ip, dbit, cur); + if (new_parent == NULL) { + isc_mem_put(rpzs->mctx, sibling, sizeof(*sibling)); + return (ISC_R_NOMEMORY); + } + new_parent->parent = parent; + if (parent == NULL) { + rpzs->cidr = new_parent; + } else { + parent->child[cur_num] = new_parent; + } + child_num = DNS_RPZ_IP_BIT(tgt_ip, dbit); + new_parent->child[child_num] = sibling; + new_parent->child[1 - child_num] = cur; + cur->parent = new_parent; + sibling->parent = new_parent; + sibling->set = *tgt_set; + set_sum_pair(sibling); + *found = sibling; + return (ISC_R_SUCCESS); + } +} + +/* + * Add an IP address to the radix tree. + */ +static isc_result_t +add_cidr(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name) { + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_prefix_t tgt_prefix; + dns_rpz_addr_zbits_t set; + dns_rpz_cidr_node_t *found = NULL; + isc_result_t result; + + result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpz, rpz_type, src_name, + &tgt_ip, &tgt_prefix, &set); + /* + * Log complaints about bad owner names but let the zone load. + */ + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + result = search(rpz->rpzs, &tgt_ip, tgt_prefix, &set, true, &found); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * Do not worry if the radix tree already exists, + * because diff_apply() likes to add nodes before deleting. + */ + if (result == ISC_R_EXISTS) { + return (ISC_R_SUCCESS); + } + + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz add_cidr(%s) failed: %s", namebuf, + isc_result_totext(result)); + return (result); + } + + adj_trigger_cnt(rpz, rpz_type, &tgt_ip, tgt_prefix, true); + return (result); +} + +static isc_result_t +add_nm(dns_rpz_zones_t *rpzs, dns_name_t *trig_name, + const dns_rpz_nm_data_t *new_data) { + dns_rbtnode_t *nmnode = NULL; + dns_rpz_nm_data_t *nm_data = NULL; + isc_result_t result; + + nmnode = NULL; + result = dns_rbt_addnode(rpzs->rbt, trig_name, &nmnode); + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_EXISTS: + nm_data = nmnode->data; + if (nm_data == NULL) { + nm_data = isc_mem_get(rpzs->mctx, sizeof(*nm_data)); + *nm_data = *new_data; + nmnode->data = nm_data; + return (ISC_R_SUCCESS); + } + break; + default: + return (result); + } + + /* + * Do not count bits that are already present + */ + if ((nm_data->set.qname & new_data->set.qname) != 0 || + (nm_data->set.ns & new_data->set.ns) != 0 || + (nm_data->wild.qname & new_data->wild.qname) != 0 || + (nm_data->wild.ns & new_data->wild.ns) != 0) + { + return (ISC_R_EXISTS); + } + + nm_data->set.qname |= new_data->set.qname; + nm_data->set.ns |= new_data->set.ns; + nm_data->wild.qname |= new_data->wild.qname; + nm_data->wild.ns |= new_data->wild.ns; + return (ISC_R_SUCCESS); +} + +static isc_result_t +add_name(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name) { + dns_rpz_nm_data_t new_data; + dns_fixedname_t trig_namef; + dns_name_t *trig_name = NULL; + isc_result_t result; + + /* + * We need a summary database of names even with 1 policy zone, + * because wildcard triggers are handled differently. + */ + + trig_name = dns_fixedname_initname(&trig_namef); + name2data(rpz, rpz_type, src_name, trig_name, &new_data); + + result = add_nm(rpz->rpzs, trig_name, &new_data); + + /* + * Do not worry if the node already exists, + * because diff_apply() likes to add nodes before deleting. + */ + if (result == ISC_R_EXISTS) { + return (ISC_R_SUCCESS); + } + if (result == ISC_R_SUCCESS) { + adj_trigger_cnt(rpz, rpz_type, NULL, 0, true); + } + return (result); +} + +/* + * Callback to free the data for a node in the summary RBT database. + */ +static void +rpz_node_deleter(void *nm_data, void *mctx) { + isc_mem_put(mctx, nm_data, sizeof(dns_rpz_nm_data_t)); +} + +/* + * Get ready for a new set of policy zones for a view. + */ +isc_result_t +dns_rpz_new_zones(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, char *rps_cstr, + size_t rps_cstr_size, dns_rpz_zones_t **rpzsp) { + dns_rpz_zones_t *rpzs = NULL; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + + rpzs = isc_mem_get(mctx, sizeof(*rpzs)); + *rpzs = (dns_rpz_zones_t){ + .rps_cstr = rps_cstr, + .rps_cstr_size = rps_cstr_size, + .taskmgr = taskmgr, + .timermgr = timermgr, + .magic = DNS_RPZ_ZONES_MAGIC, + }; + + isc_rwlock_init(&rpzs->search_lock, 0, 0); + isc_mutex_init(&rpzs->maint_lock); + isc_refcount_init(&rpzs->references, 1); + +#ifdef USE_DNSRPS + if (rps_cstr != NULL) { + result = dns_dnsrps_view_init(rpzs, rps_cstr); + if (result != ISC_R_SUCCESS) { + goto cleanup_rbt; + } + } +#else /* ifdef USE_DNSRPS */ + INSIST(!rpzs->p.dnsrps_enabled); +#endif /* ifdef USE_DNSRPS */ + if (!rpzs->p.dnsrps_enabled) { + result = dns_rbt_create(mctx, rpz_node_deleter, mctx, + &rpzs->rbt); + } + + if (result != ISC_R_SUCCESS) { + goto cleanup_rbt; + } + + result = isc_taskmgr_excltask(taskmgr, &rpzs->updater); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + + isc_mem_attach(mctx, &rpzs->mctx); + + *rpzsp = rpzs; + return (ISC_R_SUCCESS); + +cleanup_task: + dns_rbt_destroy(&rpzs->rbt); + +cleanup_rbt: + isc_refcount_decrementz(&rpzs->references); + isc_refcount_destroy(&rpzs->references); + isc_mutex_destroy(&rpzs->maint_lock); + isc_rwlock_destroy(&rpzs->search_lock); + isc_mem_put(mctx, rpzs, sizeof(*rpzs)); + + return (result); +} + +isc_result_t +dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) { + isc_result_t result; + dns_rpz_zone_t *rpz = NULL; + + REQUIRE(DNS_RPZ_ZONES_VALID(rpzs)); + REQUIRE(rpzp != NULL && *rpzp == NULL); + + if (rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) { + return (ISC_R_NOSPACE); + } + + result = dns__rpz_shuttingdown(rpzs); + if (result != ISC_R_SUCCESS) { + return (result); + } + + rpz = isc_mem_get(rpzs->mctx, sizeof(*rpz)); + *rpz = (dns_rpz_zone_t){ + .addsoa = true, + .magic = DNS_RPZ_ZONE_MAGIC, + .rpzs = rpzs, + }; + + result = isc_timer_create(rpzs->timermgr, isc_timertype_inactive, NULL, + NULL, rpzs->updater, dns__rpz_timer_cb, rpz, + &rpz->updatetimer); + if (result != ISC_R_SUCCESS) { + goto cleanup_timer; + } + + /* + * This will never be used, but costs us nothing and + * simplifies dns__rpz_timer_cb(). + */ + + isc_ht_init(&rpz->nodes, rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE); + + dns_name_init(&rpz->origin, NULL); + dns_name_init(&rpz->client_ip, NULL); + dns_name_init(&rpz->ip, NULL); + dns_name_init(&rpz->nsdname, NULL); + dns_name_init(&rpz->nsip, NULL); + dns_name_init(&rpz->passthru, NULL); + dns_name_init(&rpz->drop, NULL); + dns_name_init(&rpz->tcp_only, NULL); + dns_name_init(&rpz->cname, NULL); + + isc_time_settoepoch(&rpz->lastupdated); + + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, NULL, 0, + NULL, NULL, NULL, NULL, NULL); + + rpz->num = rpzs->p.num_zones++; + rpzs->zones[rpz->num] = rpz; + + *rpzp = rpz; + + return (ISC_R_SUCCESS); + +cleanup_timer: + isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz)); + + return (result); +} + +isc_result_t +dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg) { + dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)fn_arg; + isc_time_t now; + isc_result_t result = ISC_R_SUCCESS; + char dname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_RPZ_ZONE_VALID(rpz)); + + LOCK(&rpz->rpzs->maint_lock); + + if (rpz->rpzs->shuttingdown) { + result = ISC_R_SHUTTINGDOWN; + goto unlock; + } + + /* New zone came as AXFR */ + if (rpz->db != NULL && rpz->db != db) { + /* We need to clean up the old DB */ + if (rpz->dbversion != NULL) { + dns_db_closeversion(rpz->db, &rpz->dbversion, false); + } + dns_db_updatenotify_unregister(rpz->db, + dns_rpz_dbupdate_callback, rpz); + dns_db_detach(&rpz->db); + } + + if (rpz->db == NULL) { + RUNTIME_CHECK(rpz->dbversion == NULL); + dns_db_attach(db, &rpz->db); + } + + dns_name_format(&rpz->origin, dname, DNS_NAME_FORMATSIZE); + + if (!rpz->updatepending && !rpz->updaterunning) { + uint64_t tdiff; + + rpz->updatepending = true; + + isc_time_now(&now); + tdiff = isc_time_microdiff(&now, &rpz->lastupdated) / 1000000; + if (tdiff < rpz->min_update_interval) { + uint64_t defer = rpz->min_update_interval - tdiff; + isc_interval_t interval; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "rpz: %s: new zone version came " + "too soon, deferring update for " + "%" PRIu64 " seconds", + dname, defer); + isc_interval_set(&interval, (unsigned int)defer, 0); + dns_db_currentversion(rpz->db, &rpz->dbversion); + (void)isc_timer_reset(rpz->updatetimer, + isc_timertype_once, NULL, + &interval, true); + } else { + isc_event_t *event = NULL; + + dns_db_currentversion(rpz->db, &rpz->dbversion); + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, + sizeof(rpz->updateevent), 0, NULL, + DNS_EVENT_RPZUPDATED, dns__rpz_timer_cb, + rpz, rpz, NULL, NULL); + event = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &event); + } + } else { + rpz->updatepending = true; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "rpz: %s: update already queued or running", + dname); + if (rpz->dbversion != NULL) { + dns_db_closeversion(rpz->db, &rpz->dbversion, false); + } + dns_db_currentversion(rpz->db, &rpz->dbversion); + } + +unlock: + UNLOCK(&rpz->rpzs->maint_lock); + + return (result); +} + +static void +update_rpz_done_cb(void *data, isc_result_t result) { + dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)data; + char dname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_RPZ_ZONE_VALID(rpz)); + + if (result == ISC_R_SUCCESS && rpz->updateresult != ISC_R_SUCCESS) { + result = rpz->updateresult; + } + + LOCK(&rpz->rpzs->maint_lock); + rpz->updaterunning = false; + + dns_name_format(&rpz->origin, dname, DNS_NAME_FORMATSIZE); + + /* If there's no update pending, or if shutting down, finish. */ + if (!rpz->updatepending || rpz->rpzs->shuttingdown) { + goto done; + } + + /* If there's an update pending, schedule it */ + if (rpz->min_update_interval > 0) { + uint64_t defer = rpz->min_update_interval; + isc_interval_t interval; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "rpz: %s: new zone version came " + "too soon, deferring update for " + "%" PRIu64 " seconds", + dname, defer); + isc_interval_set(&interval, (unsigned int)defer, 0); + (void)isc_timer_reset(rpz->updatetimer, isc_timertype_once, + NULL, &interval, true); + } else { + isc_event_t *event = NULL; + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, + NULL, DNS_EVENT_RPZUPDATED, dns__rpz_timer_cb, + rpz, rpz, NULL, NULL); + event = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &event); + } + +done: + dns_db_closeversion(rpz->updb, &rpz->updbversion, false); + dns_db_detach(&rpz->updb); + + UNLOCK(&rpz->rpzs->maint_lock); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, "rpz: %s: reload done: %s", dname, + isc_result_totext(result)); + + dns_rpz_unref_rpzs(rpz->rpzs); +} + +static isc_result_t +update_nodes(dns_rpz_zone_t *rpz, isc_ht_t *newnodes) { + isc_result_t result; + dns_dbiterator_t *updbit = NULL; + dns_name_t *name = NULL; + dns_fixedname_t fixname; + char domain[DNS_NAME_FORMATSIZE]; + + dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE); + + name = dns_fixedname_initname(&fixname); + + result = dns_db_createiterator(rpz->updb, DNS_DB_NONSEC3, &updbit); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: failed to create DB iterator - %s", + domain, isc_result_totext(result)); + return (result); + } + + result = dns_dbiterator_first(updbit); + if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: failed to get db iterator - %s", domain, + isc_result_totext(result)); + goto cleanup; + } + + while (result == ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rdatasetiter_t *rdsiter = NULL; + dns_dbnode_t *node = NULL; + + result = dns__rpz_shuttingdown(rpz->rpzs); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_dbiterator_current(updbit, &node, name); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: failed to get dbiterator - %s", + domain, isc_result_totext(result)); + goto cleanup; + } + + result = dns_dbiterator_pause(updbit); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = dns_db_allrdatasets(rpz->updb, node, rpz->updbversion, + 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: failed to fetch " + "rrdatasets - %s", + domain, isc_result_totext(result)); + dns_db_detachnode(rpz->updb, &node); + goto cleanup; + } + + result = dns_rdatasetiter_first(rdsiter); + + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(rpz->updb, &node); + + if (result != ISC_R_SUCCESS) { /* skip empty non-terminal */ + if (result != ISC_R_NOMORE) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: error %s while creating " + "rdatasetiter", + domain, isc_result_totext(result)); + } + goto next; + } + + dns_name_downcase(name, name, NULL); + + /* Add entry to the new nodes table */ + result = isc_ht_add(newnodes, name->ndata, name->length, rpz); + if (result != ISC_R_SUCCESS) { + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s, adding node %s to HT error %s", + domain, namebuf, + isc_result_totext(result)); + goto next; + } + + /* Does the entry exist in the old nodes table? */ + result = isc_ht_find(rpz->nodes, name->ndata, name->length, + NULL); + if (result == ISC_R_SUCCESS) { /* found */ + isc_ht_delete(rpz->nodes, name->ndata, name->length); + goto next; + } + + /* + * Only the single rpz updates are serialized, so we need to + * lock here because we can be processing more updates to + * different rpz zones at the same time + */ + LOCK(&rpz->rpzs->maint_lock); + result = rpz_add(rpz, name); + UNLOCK(&rpz->rpzs->maint_lock); + + if (result != ISC_R_SUCCESS) { + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: adding node %s " + "to RPZ error %s", + domain, namebuf, + isc_result_totext(result)); + } else if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "rpz: %s: adding node %s", domain, + namebuf); + } + + next: + result = dns_dbiterator_next(updbit); + } + INSIST(result != ISC_R_SUCCESS); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + dns_dbiterator_destroy(&updbit); + + return (result); +} + +static isc_result_t +cleanup_nodes(dns_rpz_zone_t *rpz) { + isc_result_t result; + isc_ht_iter_t *iter = NULL; + dns_name_t *name = NULL; + dns_fixedname_t fixname; + + name = dns_fixedname_initname(&fixname); + + isc_ht_iter_create(rpz->nodes, &iter); + + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + isc_region_t region; + unsigned char *key = NULL; + size_t keysize; + + result = dns__rpz_shuttingdown(rpz->rpzs); + if (result != ISC_R_SUCCESS) { + break; + } + + isc_ht_iter_currentkey(iter, &key, &keysize); + region.base = key; + region.length = (unsigned int)keysize; + dns_name_fromregion(name, ®ion); + + LOCK(&rpz->rpzs->maint_lock); + rpz_del(rpz, name); + UNLOCK(&rpz->rpzs->maint_lock); + } + INSIST(result != ISC_R_SUCCESS); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + isc_ht_iter_destroy(&iter); + + return (result); +} + +static isc_result_t +dns__rpz_shuttingdown(dns_rpz_zones_t *rpzs) { + bool shuttingdown = false; + + LOCK(&rpzs->maint_lock); + shuttingdown = rpzs->shuttingdown; + UNLOCK(&rpzs->maint_lock); + + if (shuttingdown) { + return (ISC_R_SHUTTINGDOWN); + } + + return (ISC_R_SUCCESS); +} + +static void +update_rpz_cb(void *data) { + dns_rpz_zone_t *rpz = (dns_rpz_zone_t *)data; + isc_result_t result = ISC_R_SUCCESS; + isc_ht_t *newnodes = NULL; + + REQUIRE(rpz->nodes != NULL); + + result = dns__rpz_shuttingdown(rpz->rpzs); + if (result != ISC_R_SUCCESS) { + goto shuttingdown; + } + + isc_ht_init(&newnodes, rpz->rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE); + + result = update_nodes(rpz, newnodes); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = cleanup_nodes(rpz); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Finalize the update */ + ISC_SWAP(rpz->nodes, newnodes); + +cleanup: + isc_ht_destroy(&newnodes); + +shuttingdown: + rpz->updateresult = result; +} + +static void +dns__rpz_timer_cb(isc_task_t *task, isc_event_t *event) { + char domain[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_rpz_zone_t *rpz = NULL; + + UNUSED(task); + REQUIRE(event != NULL); + REQUIRE(event->ev_arg != NULL); + + rpz = (dns_rpz_zone_t *)event->ev_arg; + isc_event_free(&event); + + REQUIRE(isc_nm_tid() >= 0); + REQUIRE(DNS_RPZ_ZONE_VALID(rpz)); + + LOCK(&rpz->rpzs->maint_lock); + + if (rpz->rpzs->shuttingdown) { + goto unlock; + } + + rpz->updatepending = false; + rpz->updaterunning = true; + rpz->updateresult = ISC_R_UNSET; + + INSIST(rpz->updb == NULL); + INSIST(rpz->updbversion == NULL); + INSIST(rpz->dbversion != NULL); + INSIST(DNS_DB_VALID(rpz->db)); + dns_db_attach(rpz->db, &rpz->updb); + rpz->updbversion = rpz->dbversion; + rpz->dbversion = NULL; + + dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, "rpz: %s: reload start", domain); + + dns_rpz_ref_rpzs(rpz->rpzs); + isc_nm_work_offload(isc_task_getnetmgr(rpz->rpzs->updater), + update_rpz_cb, update_rpz_done_cb, rpz); + + result = isc_time_now(&rpz->lastupdated); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +unlock: + UNLOCK(&rpz->rpzs->maint_lock); +} + +/* + * Free the radix tree of a response policy database. + */ +static void +cidr_free(dns_rpz_zones_t *rpzs) { + dns_rpz_cidr_node_t *cur = NULL, *child = NULL, *parent = NULL; + + cur = rpzs->cidr; + while (cur != NULL) { + /* Depth first. */ + child = cur->child[0]; + if (child != NULL) { + cur = child; + continue; + } + child = cur->child[1]; + if (child != NULL) { + cur = child; + continue; + } + + /* Delete this leaf and go up. */ + parent = cur->parent; + if (parent == NULL) { + rpzs->cidr = NULL; + } else { + parent->child[parent->child[1] == cur] = NULL; + } + isc_mem_put(rpzs->mctx, cur, sizeof(*cur)); + cur = parent; + } +} + +static void +dns__rpz_shutdown(dns_rpz_zone_t *rpz) { + /* maint_lock must be locked */ + if (rpz->updatetimer != NULL) { + isc_result_t result; + + /* Don't wait for timer to trigger for shutdown */ + result = isc_timer_reset(rpz->updatetimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +} + +static void +dns_rpz_zone_destroy(dns_rpz_zone_t **rpzp) { + dns_rpz_zone_t *rpz = NULL; + dns_rpz_zones_t *rpzs; + + rpz = *rpzp; + *rpzp = NULL; + + rpzs = rpz->rpzs; + rpz->rpzs = NULL; + + if (dns_name_dynamic(&rpz->origin)) { + dns_name_free(&rpz->origin, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->client_ip)) { + dns_name_free(&rpz->client_ip, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->ip)) { + dns_name_free(&rpz->ip, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->nsdname)) { + dns_name_free(&rpz->nsdname, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->nsip)) { + dns_name_free(&rpz->nsip, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->passthru)) { + dns_name_free(&rpz->passthru, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->drop)) { + dns_name_free(&rpz->drop, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->tcp_only)) { + dns_name_free(&rpz->tcp_only, rpzs->mctx); + } + if (dns_name_dynamic(&rpz->cname)) { + dns_name_free(&rpz->cname, rpzs->mctx); + } + if (rpz->db != NULL) { + if (rpz->dbversion != NULL) { + dns_db_closeversion(rpz->db, &rpz->dbversion, false); + } + dns_db_updatenotify_unregister(rpz->db, + dns_rpz_dbupdate_callback, rpz); + dns_db_detach(&rpz->db); + } + INSIST(!rpz->updaterunning); + + isc_timer_reset(rpz->updatetimer, isc_timertype_inactive, NULL, NULL, + true); + isc_timer_destroy(&rpz->updatetimer); + + isc_ht_destroy(&rpz->nodes); + + isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz)); +} + +static void +dns__rpz_zones_destroy(dns_rpz_zones_t *rpzs) { + REQUIRE(rpzs->shuttingdown); + + isc_refcount_destroy(&rpzs->references); + + for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) + { + if (rpzs->zones[rpz_num] == NULL) { + continue; + } + + dns_rpz_zone_destroy(&rpzs->zones[rpz_num]); + } + + if (rpzs->rps_cstr_size != 0) { +#ifdef USE_DNSRPS + librpz->client_detach(&rpzs->rps_client); +#endif /* ifdef USE_DNSRPS */ + isc_mem_put(rpzs->mctx, rpzs->rps_cstr, rpzs->rps_cstr_size); + } + + cidr_free(rpzs); + if (rpzs->rbt != NULL) { + dns_rbt_destroy(&rpzs->rbt); + } + isc_task_detach(&rpzs->updater); + isc_mutex_destroy(&rpzs->maint_lock); + isc_rwlock_destroy(&rpzs->search_lock); + isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs)); +} + +void +dns_rpz_zones_shutdown(dns_rpz_zones_t *rpzs) { + REQUIRE(DNS_RPZ_ZONES_VALID(rpzs)); + /* + * Forget the last of the view's rpz machinery when shutting down. + */ + + LOCK(&rpzs->maint_lock); + if (rpzs->shuttingdown) { + UNLOCK(&rpzs->maint_lock); + return; + } + + rpzs->shuttingdown = true; + + for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) + { + if (rpzs->zones[rpz_num] == NULL) { + continue; + } + + dns__rpz_shutdown(rpzs->zones[rpz_num]); + } + UNLOCK(&rpzs->maint_lock); +} + +#ifdef DNS_RPZ_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_rpz_zones, dns__rpz_zones_destroy); +#else +ISC_REFCOUNT_IMPL(dns_rpz_zones, dns__rpz_zones_destroy); +#endif + +/* + * Add an IP address to the radix tree or a name to the summary database. + */ +static isc_result_t +rpz_add(dns_rpz_zone_t *rpz, const dns_name_t *src_name) { + dns_rpz_type_t rpz_type; + isc_result_t result = ISC_R_FAILURE; + dns_rpz_zones_t *rpzs = NULL; + dns_rpz_num_t rpz_num; + + REQUIRE(rpz != NULL); + + rpzs = rpz->rpzs; + rpz_num = rpz->num; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + rpz_type = type_from_name(rpzs, rpz, src_name); + + switch (rpz_type) { + case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_NSDNAME: + result = add_name(rpz, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + result = add_cidr(rpz, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_BAD: + break; + } + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + return (result); +} + +/* + * Remove an IP address from the radix tree. + */ +static void +del_cidr(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name) { + isc_result_t result; + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_prefix_t tgt_prefix; + dns_rpz_addr_zbits_t tgt_set; + dns_rpz_cidr_node_t *tgt = NULL, *parent = NULL, *child = NULL; + + /* + * Do not worry about invalid rpz IP address names. If we + * are here, then something relevant was added and so was + * valid. Invalid names here are usually internal RBTDB nodes. + */ + result = name2ipkey(DNS_RPZ_DEBUG_QUIET, rpz, rpz_type, src_name, + &tgt_ip, &tgt_prefix, &tgt_set); + if (result != ISC_R_SUCCESS) { + return; + } + + result = search(rpz->rpzs, &tgt_ip, tgt_prefix, &tgt_set, false, &tgt); + if (result != ISC_R_SUCCESS) { + INSIST(result == ISC_R_NOTFOUND || + result == DNS_R_PARTIALMATCH); + /* + * Do not worry about missing summary RBT nodes that probably + * correspond to RBTDB nodes that were implicit RBT nodes + * that were later added for (often empty) wildcards + * and then to the RBTDB deferred cleanup list. + */ + return; + } + + /* + * Mark the node and its parents to reflect the deleted IP address. + * Do not count bits that are already clear for internal RBTDB nodes. + */ + tgt_set.client_ip &= tgt->set.client_ip; + tgt_set.ip &= tgt->set.ip; + tgt_set.nsip &= tgt->set.nsip; + tgt->set.client_ip &= ~tgt_set.client_ip; + tgt->set.ip &= ~tgt_set.ip; + tgt->set.nsip &= ~tgt_set.nsip; + set_sum_pair(tgt); + + adj_trigger_cnt(rpz, rpz_type, &tgt_ip, tgt_prefix, false); + + /* + * We might need to delete 2 nodes. + */ + do { + /* + * The node is now useless if it has no data of its own + * and 0 or 1 children. We are finished if it is not + * useless. + */ + if ((child = tgt->child[0]) != NULL) { + if (tgt->child[1] != NULL) { + break; + } + } else { + child = tgt->child[1]; + } + if (tgt->set.client_ip != 0 || tgt->set.ip != 0 || + tgt->set.nsip != 0) + { + break; + } + + /* + * Replace the pointer to this node in the parent with + * the remaining child or NULL. + */ + parent = tgt->parent; + if (parent == NULL) { + rpz->rpzs->cidr = child; + } else { + parent->child[parent->child[1] == tgt] = child; + } + + /* + * If the child exists fix up its parent pointer. + */ + if (child != NULL) { + child->parent = parent; + } + isc_mem_put(rpz->rpzs->mctx, tgt, sizeof(*tgt)); + + tgt = parent; + } while (tgt != NULL); +} + +static void +del_name(dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type, + const dns_name_t *src_name) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t trig_namef; + dns_name_t *trig_name = NULL; + dns_rbtnode_t *nmnode = NULL; + dns_rpz_nm_data_t *nm_data = NULL; + dns_rpz_nm_data_t del_data; + isc_result_t result; + bool exists; + + /* + * We need a summary database of names even with 1 policy zone, + * because wildcard triggers are handled differently. + */ + + trig_name = dns_fixedname_initname(&trig_namef); + name2data(rpz, rpz_type, src_name, trig_name, &del_data); + + nmnode = NULL; + result = dns_rbt_findnode(rpz->rpzs->rbt, trig_name, NULL, &nmnode, + NULL, 0, NULL, NULL); + if (result != ISC_R_SUCCESS) { + /* + * Do not worry about missing summary RBT nodes that probably + * correspond to RBTDB nodes that were implicit RBT nodes + * that were later added for (often empty) wildcards + * and then to the RBTDB deferred cleanup list. + */ + if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) { + return; + } + dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz del_name(%s) node search failed: %s", + namebuf, isc_result_totext(result)); + return; + } + + nm_data = nmnode->data; + INSIST(nm_data != NULL); + + /* + * Do not count bits that next existed for RBT nodes that would we + * would not have found in a summary for a single RBTDB tree. + */ + del_data.set.qname &= nm_data->set.qname; + del_data.set.ns &= nm_data->set.ns; + del_data.wild.qname &= nm_data->wild.qname; + del_data.wild.ns &= nm_data->wild.ns; + + exists = (del_data.set.qname != 0 || del_data.set.ns != 0 || + del_data.wild.qname != 0 || del_data.wild.ns != 0); + + nm_data->set.qname &= ~del_data.set.qname; + nm_data->set.ns &= ~del_data.set.ns; + nm_data->wild.qname &= ~del_data.wild.qname; + nm_data->wild.ns &= ~del_data.wild.ns; + + if (nm_data->set.qname == 0 && nm_data->set.ns == 0 && + nm_data->wild.qname == 0 && nm_data->wild.ns == 0) + { + result = dns_rbt_deletenode(rpz->rpzs->rbt, nmnode, false); + if (result != ISC_R_SUCCESS) { + /* + * bin/tests/system/rpz/tests.sh looks for + * "rpz.*failed". + */ + dns_name_format(src_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz del_name(%s) node delete " + "failed: %s", + namebuf, isc_result_totext(result)); + } + } + + if (exists) { + adj_trigger_cnt(rpz, rpz_type, NULL, 0, false); + } +} + +/* + * Remove an IP address from the radix tree or a name from the summary database. + */ +static void +rpz_del(dns_rpz_zone_t *rpz, const dns_name_t *src_name) { + dns_rpz_type_t rpz_type; + dns_rpz_zones_t *rpzs = NULL; + dns_rpz_num_t rpz_num; + + REQUIRE(rpz != NULL); + + rpzs = rpz->rpzs; + rpz_num = rpz->num; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + rpz_type = type_from_name(rpzs, rpz, src_name); + + switch (rpz_type) { + case DNS_RPZ_TYPE_QNAME: + case DNS_RPZ_TYPE_NSDNAME: + del_name(rpz, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + del_cidr(rpz, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_BAD: + break; + } + + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write); +} + +/* + * Search the summary radix tree to get a relative owner name in a + * policy zone relevant to a triggering IP address. + * rpz_type and zbits limit the search for IP address netaddr + * return the policy zone's number or DNS_RPZ_INVALID_NUM + * ip_name is the relative owner name found and + * *prefixp is its prefix length. + */ +dns_rpz_num_t +dns_rpz_find_ip(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, const isc_netaddr_t *netaddr, + dns_name_t *ip_name, dns_rpz_prefix_t *prefixp) { + dns_rpz_cidr_key_t tgt_ip; + dns_rpz_addr_zbits_t tgt_set; + dns_rpz_cidr_node_t *found = NULL; + isc_result_t result; + dns_rpz_num_t rpz_num = 0; + dns_rpz_have_t have; + int i; + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + have = rpzs->have; + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + + /* + * Convert IP address to CIDR tree key. + */ + if (netaddr->family == AF_INET) { + tgt_ip.w[0] = 0; + tgt_ip.w[1] = 0; + tgt_ip.w[2] = ADDR_V4MAPPED; + tgt_ip.w[3] = ntohl(netaddr->type.in.s_addr); + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits &= have.client_ipv4; + break; + case DNS_RPZ_TYPE_IP: + zbits &= have.ipv4; + break; + case DNS_RPZ_TYPE_NSIP: + zbits &= have.nsipv4; + break; + default: + UNREACHABLE(); + break; + } + } else if (netaddr->family == AF_INET6) { + dns_rpz_cidr_key_t src_ip6; + + /* + * Given the int aligned struct in_addr member of netaddr->type + * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *, + * but some people object. + */ + memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w)); + for (i = 0; i < 4; i++) { + tgt_ip.w[i] = ntohl(src_ip6.w[i]); + } + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits &= have.client_ipv6; + break; + case DNS_RPZ_TYPE_IP: + zbits &= have.ipv6; + break; + case DNS_RPZ_TYPE_NSIP: + zbits &= have.nsipv6; + break; + default: + UNREACHABLE(); + break; + } + } else { + return (DNS_RPZ_INVALID_NUM); + } + + if (zbits == 0) { + return (DNS_RPZ_INVALID_NUM); + } + make_addr_set(&tgt_set, zbits, rpz_type); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + result = search(rpzs, &tgt_ip, 128, &tgt_set, false, &found); + if (result == ISC_R_NOTFOUND) { + /* + * There are no eligible zones for this IP address. + */ + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + return (DNS_RPZ_INVALID_NUM); + } + + /* + * Construct the trigger name for the longest matching trigger + * in the first eligible zone with a match. + */ + *prefixp = found->prefix; + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip); + break; + case DNS_RPZ_TYPE_IP: + rpz_num = zbit_to_num(found->set.ip & tgt_set.ip); + break; + case DNS_RPZ_TYPE_NSIP: + rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip); + break; + default: + UNREACHABLE(); + } + result = ip2name(&found->ip, found->prefix, dns_rootname, ip_name); + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS) { + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz ip2name() failed: %s", + isc_result_totext(result)); + return (DNS_RPZ_INVALID_NUM); + } + return (rpz_num); +} + +/* + * Search the summary radix tree for policy zones with triggers matching + * a name. + */ +dns_rpz_zbits_t +dns_rpz_find_name(dns_rpz_zones_t *rpzs, dns_rpz_type_t rpz_type, + dns_rpz_zbits_t zbits, dns_name_t *trig_name) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rbtnode_t *nmnode = NULL; + const dns_rpz_nm_data_t *nm_data = NULL; + dns_rpz_zbits_t found_zbits; + dns_rbtnodechain_t chain; + isc_result_t result; + int i; + + if (zbits == 0) { + return (0); + } + + found_zbits = 0; + + dns_rbtnodechain_init(&chain); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + + nmnode = NULL; + result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, &chain, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + + switch (result) { + case ISC_R_SUCCESS: + nm_data = nmnode->data; + if (nm_data != NULL) { + if (rpz_type == DNS_RPZ_TYPE_QNAME) { + found_zbits = nm_data->set.qname; + } else { + found_zbits = nm_data->set.ns; + } + } + FALLTHROUGH; + + case DNS_R_PARTIALMATCH: + i = chain.level_matches; + nmnode = chain.levels[chain.level_matches]; + + /* + * Whenever an exact match is found by dns_rbt_findnode(), + * the highest level node in the chain will not be put into + * chain->levels[] array, but instead the chain->end + * pointer will be adjusted to point to that node. + * + * Suppose we have the following entries in a rpz zone: + * example.com CNAME rpz-passthru. + * *.example.com CNAME rpz-passthru. + * + * A query for www.example.com would result in the + * following chain object returned by dns_rbt_findnode(): + * chain->level_count = 2 + * chain->level_matches = 2 + * chain->levels[0] = . + * chain->levels[1] = example.com + * chain->levels[2] = NULL + * chain->end = www + * + * Since exact matches only care for testing rpz set bits, + * we need to test for rpz wild bits through iterating the + * nodechain, and that includes testing the rpz wild bits + * in the highest level node found. In the case of an exact + * match, chain->levels[chain->level_matches] will be NULL, + * to address that we must use chain->end as the start + * point, then iterate over the remaining levels in the + * chain. + */ + if (nmnode == NULL) { + --i; + nmnode = chain.end; + } + + while (nmnode != NULL) { + nm_data = nmnode->data; + if (nm_data != NULL) { + if (rpz_type == DNS_RPZ_TYPE_QNAME) { + found_zbits |= nm_data->wild.qname; + } else { + found_zbits |= nm_data->wild.ns; + } + } + + if (i >= 0) { + nmnode = chain.levels[i]; + --i; + } else { + break; + } + } + break; + + case ISC_R_NOTFOUND: + break; + + default: + /* + * bin/tests/system/rpz/tests.sh looks for "rpz.*failed". + */ + dns_name_format(trig_name, namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dns_rpz_find_name(%s) failed: %s", namebuf, + isc_result_totext(result)); + break; + } + + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + + dns_rbtnodechain_invalidate(&chain); + + return (zbits & found_zbits); +} + +/* + * Translate CNAME rdata to a QNAME response policy action. + */ +dns_rpz_policy_t +dns_rpz_decode_cname(dns_rpz_zone_t *rpz, dns_rdataset_t *rdataset, + dns_name_t *selfname) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_cname_t cname; + isc_result_t result; + + result = dns_rdataset_first(rdataset); + INSIST(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + INSIST(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + /* + * CNAME . means NXDOMAIN + */ + if (dns_name_equal(&cname.cname, dns_rootname)) { + return (DNS_RPZ_POLICY_NXDOMAIN); + } + + if (dns_name_iswildcard(&cname.cname)) { + /* + * CNAME *. means NODATA + */ + if (dns_name_countlabels(&cname.cname) == 2) { + return (DNS_RPZ_POLICY_NODATA); + } + + /* + * A qname of www.evil.com and a policy of + * *.evil.com CNAME *.garden.net + * gives a result of + * evil.com CNAME evil.com.garden.net + */ + if (dns_name_countlabels(&cname.cname) > 2) { + return (DNS_RPZ_POLICY_WILDCNAME); + } + } + + /* + * CNAME rpz-tcp-only. means "send truncated UDP responses." + */ + if (dns_name_equal(&cname.cname, &rpz->tcp_only)) { + return (DNS_RPZ_POLICY_TCP_ONLY); + } + + /* + * CNAME rpz-drop. means "do not respond." + */ + if (dns_name_equal(&cname.cname, &rpz->drop)) { + return (DNS_RPZ_POLICY_DROP); + } + + /* + * CNAME rpz-passthru. means "do not rewrite." + */ + if (dns_name_equal(&cname.cname, &rpz->passthru)) { + return (DNS_RPZ_POLICY_PASSTHRU); + } + + /* + * 128.1.0.127.rpz-ip CNAME 128.1.0.0.127. is obsolete PASSTHRU + */ + if (selfname != NULL && dns_name_equal(&cname.cname, selfname)) { + return (DNS_RPZ_POLICY_PASSTHRU); + } + + /* + * Any other rdata gives a response consisting of the rdata. + */ + return (DNS_RPZ_POLICY_RECORD); +} diff --git a/lib/dns/rriterator.c b/lib/dns/rriterator.c new file mode 100644 index 0000000..93548bc --- /dev/null +++ b/lib/dns/rriterator.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/*** + *** RRiterator methods + ***/ + +isc_result_t +dns_rriterator_init(dns_rriterator_t *it, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now) { + isc_result_t result; + it->magic = RRITERATOR_MAGIC; + it->db = db; + it->dbit = NULL; + it->ver = ver; + it->now = now; + it->node = NULL; + result = dns_db_createiterator(it->db, 0, &it->dbit); + if (result != ISC_R_SUCCESS) { + return (result); + } + it->rdatasetit = NULL; + dns_rdata_init(&it->rdata); + dns_rdataset_init(&it->rdataset); + dns_fixedname_init(&it->fixedname); + INSIST(!dns_rdataset_isassociated(&it->rdataset)); + it->result = ISC_R_SUCCESS; + return (it->result); +} + +isc_result_t +dns_rriterator_first(dns_rriterator_t *it) { + REQUIRE(VALID_RRITERATOR(it)); + /* Reset state */ + if (dns_rdataset_isassociated(&it->rdataset)) { + dns_rdataset_disassociate(&it->rdataset); + } + if (it->rdatasetit != NULL) { + dns_rdatasetiter_destroy(&it->rdatasetit); + } + if (it->node != NULL) { + dns_db_detachnode(it->db, &it->node); + } + it->result = dns_dbiterator_first(it->dbit); + + /* + * The top node may be empty when out of zone glue exists. + * Walk the tree to find the first node with data. + */ + while (it->result == ISC_R_SUCCESS) { + it->result = dns_dbiterator_current( + it->dbit, &it->node, + dns_fixedname_name(&it->fixedname)); + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + + it->result = dns_db_allrdatasets(it->db, it->node, it->ver, 0, + it->now, &it->rdatasetit); + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + + it->result = dns_rdatasetiter_first(it->rdatasetit); + if (it->result != ISC_R_SUCCESS) { + /* + * This node is empty. Try next node. + */ + dns_rdatasetiter_destroy(&it->rdatasetit); + dns_db_detachnode(it->db, &it->node); + it->result = dns_dbiterator_next(it->dbit); + continue; + } + dns_rdatasetiter_current(it->rdatasetit, &it->rdataset); + dns_rdataset_getownercase(&it->rdataset, + dns_fixedname_name(&it->fixedname)); + it->rdataset.attributes |= DNS_RDATASETATTR_LOADORDER; + it->result = dns_rdataset_first(&it->rdataset); + return (it->result); + } + return (it->result); +} + +isc_result_t +dns_rriterator_nextrrset(dns_rriterator_t *it) { + REQUIRE(VALID_RRITERATOR(it)); + if (dns_rdataset_isassociated(&it->rdataset)) { + dns_rdataset_disassociate(&it->rdataset); + } + it->result = dns_rdatasetiter_next(it->rdatasetit); + /* + * The while loop body is executed more than once + * only when an empty dbnode needs to be skipped. + */ + while (it->result == ISC_R_NOMORE) { + dns_rdatasetiter_destroy(&it->rdatasetit); + dns_db_detachnode(it->db, &it->node); + it->result = dns_dbiterator_next(it->dbit); + if (it->result == ISC_R_NOMORE) { + /* We are at the end of the entire database. */ + return (it->result); + } + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + it->result = dns_dbiterator_current( + it->dbit, &it->node, + dns_fixedname_name(&it->fixedname)); + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + it->result = dns_db_allrdatasets(it->db, it->node, it->ver, 0, + it->now, &it->rdatasetit); + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + it->result = dns_rdatasetiter_first(it->rdatasetit); + } + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + dns_rdatasetiter_current(it->rdatasetit, &it->rdataset); + dns_rdataset_getownercase(&it->rdataset, + dns_fixedname_name(&it->fixedname)); + it->rdataset.attributes |= DNS_RDATASETATTR_LOADORDER; + it->result = dns_rdataset_first(&it->rdataset); + return (it->result); +} + +isc_result_t +dns_rriterator_next(dns_rriterator_t *it) { + REQUIRE(VALID_RRITERATOR(it)); + if (it->result != ISC_R_SUCCESS) { + return (it->result); + } + + INSIST(it->dbit != NULL); + INSIST(it->node != NULL); + INSIST(it->rdatasetit != NULL); + + it->result = dns_rdataset_next(&it->rdataset); + if (it->result == ISC_R_NOMORE) { + return (dns_rriterator_nextrrset(it)); + } + return (it->result); +} + +void +dns_rriterator_pause(dns_rriterator_t *it) { + REQUIRE(VALID_RRITERATOR(it)); + RUNTIME_CHECK(dns_dbiterator_pause(it->dbit) == ISC_R_SUCCESS); +} + +void +dns_rriterator_destroy(dns_rriterator_t *it) { + REQUIRE(VALID_RRITERATOR(it)); + if (dns_rdataset_isassociated(&it->rdataset)) { + dns_rdataset_disassociate(&it->rdataset); + } + if (it->rdatasetit != NULL) { + dns_rdatasetiter_destroy(&it->rdatasetit); + } + if (it->node != NULL) { + dns_db_detachnode(it->db, &it->node); + } + dns_dbiterator_destroy(&it->dbit); +} + +void +dns_rriterator_current(dns_rriterator_t *it, dns_name_t **name, uint32_t *ttl, + dns_rdataset_t **rdataset, dns_rdata_t **rdata) { + REQUIRE(name != NULL && *name == NULL); + REQUIRE(VALID_RRITERATOR(it)); + REQUIRE(it->result == ISC_R_SUCCESS); + REQUIRE(rdataset == NULL || *rdataset == NULL); + REQUIRE(rdata == NULL || *rdata == NULL); + + *name = dns_fixedname_name(&it->fixedname); + *ttl = it->rdataset.ttl; + + dns_rdata_reset(&it->rdata); + dns_rdataset_current(&it->rdataset, &it->rdata); + + if (rdataset != NULL) { + *rdataset = &it->rdataset; + } + + if (rdata != NULL) { + *rdata = &it->rdata; + } +} diff --git a/lib/dns/rrl.c b/lib/dns/rrl.c new file mode 100644 index 0000000..3be91b6 --- /dev/null +++ b/lib/dns/rrl.c @@ -0,0 +1,1367 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/* + * Rate limit DNS responses. + */ + +/* #define ISC_LIST_CHECKINIT */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static void +log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf, + unsigned int log_buf_len); + +/* + * Get a modulus for a hash function that is tolerably likely to be + * relatively prime to most inputs. Of course, we get a prime for for initial + * values not larger than the square of the last prime. We often get a prime + * after that. + * This works well in practice for hash tables up to at least 100 + * times the square of the last prime and better than a multiplicative hash. + */ +static int +hash_divisor(unsigned int initial) { + static uint16_t primes[] = { + 3, + 5, + 7, + 11, + 13, + 17, + 19, + 23, + 29, + 31, + 37, + 41, + 43, + 47, + 53, + 59, + 61, + 67, + 71, + 73, + 79, + 83, + 89, + 97, +#if 0 + 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, + 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, + 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, + 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, + 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, + 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, + 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, + 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, + 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, +#endif /* if 0 */ + }; + int divisions, tries; + unsigned int result; + uint16_t *pp, p; + + result = initial; + + if (primes[sizeof(primes) / sizeof(primes[0]) - 1] >= result) { + pp = primes; + while (*pp < result) { + ++pp; + } + return (*pp); + } + + if ((result & 1) == 0) { + ++result; + } + + divisions = 0; + tries = 1; + pp = primes; + do { + p = *pp++; + ++divisions; + if ((result % p) == 0) { + ++tries; + result += 2; + pp = primes; + } + } while (pp < &primes[sizeof(primes) / sizeof(primes[0])]); + + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3, + "%d hash_divisor() divisions in %d tries" + " to get %d from %d", + divisions, tries, result, initial); + } + + return (result); +} + +/* + * Convert a timestamp to a number of seconds in the past. + */ +static int +delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) { + int delta; + + delta = now - ts; + if (delta >= 0) { + return (delta); + } + + /* + * The timestamp is in the future. That future might result from + * re-ordered requests, because we use timestamps on requests + * instead of consulting a clock. Timestamps in the distant future are + * assumed to result from clock changes. When the clock changes to + * the past, make existing timestamps appear to be in the past. + */ + if (delta < -DNS_RRL_MAX_TIME_TRAVEL) { + return (DNS_RRL_FOREVER); + } + return (0); +} + +static int +get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) { + if (!e->ts_valid) { + return (DNS_RRL_FOREVER); + } + return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now)); +} + +static void +set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) { + dns_rrl_entry_t *e_old; + unsigned int ts_gen; + int i, ts; + + ts_gen = rrl->ts_gen; + ts = now - rrl->ts_bases[ts_gen]; + if (ts < 0) { + if (ts < -DNS_RRL_MAX_TIME_TRAVEL) { + ts = DNS_RRL_FOREVER; + } else { + ts = 0; + } + } + + /* + * Make a new timestamp base if the current base is too old. + * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient, + * useless history. Their timestamps can be treated as if they are + * all the same. + * We only do arithmetic on more recent timestamps, so bases for + * older timestamps can be recycled provided the old timestamps are + * marked as ancient history. + * This loop is almost always very short because most entries are + * recycled after one second and any entries that need to be marked + * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds. + */ + if (ts >= DNS_RRL_MAX_TS) { + ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES; + for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0; + e_old != NULL && (e_old->ts_gen == ts_gen || + !ISC_LINK_LINKED(e_old, hlink)); + e_old = ISC_LIST_PREV(e_old, lru), ++i) + { + e_old->ts_valid = false; + } + if (i != 0) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, + "rrl new time base scanned %d entries" + " at %d for %d %d %d %d", + i, now, rrl->ts_bases[ts_gen], + rrl->ts_bases[(ts_gen + 1) % DNS_RRL_TS_BASES], + rrl->ts_bases[(ts_gen + 2) % DNS_RRL_TS_BASES], + rrl->ts_bases[(ts_gen + 3) % DNS_RRL_TS_BASES]); + } + rrl->ts_gen = ts_gen; + rrl->ts_bases[ts_gen] = now; + ts = 0; + } + + e->ts_gen = ts_gen; + e->ts = ts; + e->ts_valid = true; +} + +static isc_result_t +expand_entries(dns_rrl_t *rrl, int newsize) { + unsigned int bsize; + dns_rrl_block_t *b; + dns_rrl_entry_t *e; + double rate; + int i; + + if (rrl->num_entries + newsize >= rrl->max_entries && + rrl->max_entries != 0) + { + newsize = rrl->max_entries - rrl->num_entries; + if (newsize <= 0) { + return (ISC_R_SUCCESS); + } + } + + /* + * Log expansions so that the user can tune max-table-size + * and min-table-size. + */ + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && rrl->hash != NULL) { + rate = rrl->probes; + if (rrl->searches != 0) { + rate /= rrl->searches; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, + "increase from %d to %d RRL entries with" + " %d bins; average search length %.1f", + rrl->num_entries, rrl->num_entries + newsize, + rrl->hash->length, rate); + } + + bsize = sizeof(dns_rrl_block_t) + + (newsize - 1) * sizeof(dns_rrl_entry_t); + b = isc_mem_get(rrl->mctx, bsize); + memset(b, 0, bsize); + b->size = bsize; + + e = b->entries; + for (i = 0; i < newsize; ++i, ++e) { + ISC_LINK_INIT(e, hlink); + ISC_LIST_INITANDAPPEND(rrl->lru, e, lru); + } + rrl->num_entries += newsize; + ISC_LIST_INITANDAPPEND(rrl->blocks, b, link); + + return (ISC_R_SUCCESS); +} + +static dns_rrl_bin_t * +get_bin(dns_rrl_hash_t *hash, unsigned int hval) { + INSIST(hash != NULL); + return (&hash->bins[hval % hash->length]); +} + +static void +free_old_hash(dns_rrl_t *rrl) { + dns_rrl_hash_t *old_hash; + dns_rrl_bin_t *old_bin; + dns_rrl_entry_t *e, *e_next; + + old_hash = rrl->old_hash; + for (old_bin = &old_hash->bins[0]; + old_bin < &old_hash->bins[old_hash->length]; ++old_bin) + { + for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) { + e_next = ISC_LIST_NEXT(e, hlink); + ISC_LINK_INIT(e, hlink); + } + } + + isc_mem_put(rrl->mctx, old_hash, + sizeof(*old_hash) + + (old_hash->length - 1) * sizeof(old_hash->bins[0])); + rrl->old_hash = NULL; +} + +static isc_result_t +expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) { + dns_rrl_hash_t *hash; + int old_bins, new_bins, hsize; + double rate; + + if (rrl->old_hash != NULL) { + free_old_hash(rrl); + } + + /* + * Most searches fail and so go to the end of the chain. + * Use a small hash table load factor. + */ + old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length; + new_bins = old_bins / 8 + old_bins; + if (new_bins < rrl->num_entries) { + new_bins = rrl->num_entries; + } + new_bins = hash_divisor(new_bins); + + hsize = sizeof(dns_rrl_hash_t) + (new_bins - 1) * sizeof(hash->bins[0]); + hash = isc_mem_get(rrl->mctx, hsize); + memset(hash, 0, hsize); + hash->length = new_bins; + rrl->hash_gen ^= 1; + hash->gen = rrl->hash_gen; + + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) { + rate = rrl->probes; + if (rrl->searches != 0) { + rate /= rrl->searches; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, + "increase from %d to %d RRL bins for" + " %d entries; average search length %.1f", + old_bins, new_bins, rrl->num_entries, rate); + } + + rrl->old_hash = rrl->hash; + if (rrl->old_hash != NULL) { + rrl->old_hash->check_time = now; + } + rrl->hash = hash; + + return (ISC_R_SUCCESS); +} + +static void +ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) { + /* + * Make the entry most recently used. + */ + if (ISC_LIST_HEAD(rrl->lru) != e) { + if (e == rrl->last_logged) { + rrl->last_logged = ISC_LIST_PREV(e, lru); + } + ISC_LIST_UNLINK(rrl->lru, e, lru); + ISC_LIST_PREPEND(rrl->lru, e, lru); + } + + /* + * Expand the hash table if it is time and necessary. + * This will leave the newly referenced entry in a chain in the + * old hash table. It will migrate to the new hash table the next + * time it is used or be cut loose when the old hash table is destroyed. + */ + rrl->probes += probes; + ++rrl->searches; + if (rrl->searches > 100 && + delta_rrl_time(rrl->hash->check_time, now) > 1) + { + if (rrl->probes / rrl->searches > 2) { + expand_rrl_hash(rrl, now); + } + rrl->hash->check_time = now; + rrl->probes = 0; + rrl->searches = 0; + } +} + +static bool +key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) { + if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0) { + return (true); + } + return (false); +} + +static uint32_t +hash_key(const dns_rrl_key_t *key) { + uint32_t hval; + int i; + + hval = key->w[0]; + for (i = sizeof(key->w) / sizeof(key->w[0]) - 1; i >= 0; --i) { + hval = key->w[i] + (hval << 1); + } + return (hval); +} + +/* + * Construct the hash table key. + * Use a hash of the DNS query name to save space in the database. + * Collisions result in legitimate rate limiting responses for one + * query name also limiting responses for other names to the + * same client. This is rare and benign enough given the large + * space costs compared to keeping the entire name in the database + * entry or the time costs of dynamic allocation. + */ +static void +make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key, + const isc_sockaddr_t *client_addr, dns_zone_t *zone, + dns_rdatatype_t qtype, const dns_name_t *qname, + dns_rdataclass_t qclass, dns_rrl_rtype_t rtype) { + int i; + + memset(key, 0, sizeof(*key)); + + key->s.rtype = rtype; + if (rtype == DNS_RRL_RTYPE_QUERY) { + key->s.qtype = qtype; + key->s.qclass = qclass & 0xff; + } else if (rtype == DNS_RRL_RTYPE_REFERRAL || + rtype == DNS_RRL_RTYPE_NODATA) + { + /* + * Because there is no qtype in the empty answer sections of + * referral and NODATA responses, count them as the same. + */ + key->s.qclass = qclass & 0xff; + } + + if (qname != NULL && qname->labels != 0) { + dns_name_t *origin = NULL; + + if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 && + zone != NULL && (origin = dns_zone_getorigin(zone)) != NULL) + { + dns_fixedname_t fixed; + dns_name_t *wild; + isc_result_t result; + + /* + * Put all wildcard names in one bucket using the zone's + * origin name concatenated to the "*" name. + */ + wild = dns_fixedname_initname(&fixed); + result = dns_name_concatenate(dns_wildcardname, origin, + wild, NULL); + if (result != ISC_R_SUCCESS) { + /* + * Fallback to use the zone's origin name + * instead of the concatenated name. + */ + wild = origin; + } + key->s.qname_hash = dns_name_fullhash(wild, false); + } else { + key->s.qname_hash = dns_name_fullhash(qname, false); + } + } + + switch (client_addr->type.sa.sa_family) { + case AF_INET: + key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr & + rrl->ipv4_mask); + break; + case AF_INET6: + key->s.ipv6 = true; + memmove(key->s.ip, &client_addr->type.sin6.sin6_addr, + sizeof(key->s.ip)); + for (i = 0; i < DNS_RRL_MAX_PREFIX / 32; ++i) { + key->s.ip[i] &= rrl->ipv6_mask[i]; + } + break; + } +} + +static dns_rrl_rate_t * +get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) { + switch (rtype) { + case DNS_RRL_RTYPE_QUERY: + return (&rrl->responses_per_second); + case DNS_RRL_RTYPE_REFERRAL: + return (&rrl->referrals_per_second); + case DNS_RRL_RTYPE_NODATA: + return (&rrl->nodata_per_second); + case DNS_RRL_RTYPE_NXDOMAIN: + return (&rrl->nxdomains_per_second); + case DNS_RRL_RTYPE_ERROR: + return (&rrl->errors_per_second); + case DNS_RRL_RTYPE_ALL: + return (&rrl->all_per_second); + default: + UNREACHABLE(); + } +} + +static int +response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) { + dns_rrl_rate_t *ratep; + int balance, rate; + + if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) { + rate = 1; + } else { + ratep = get_rate(rrl, e->key.s.rtype); + rate = ratep->scaled; + } + + balance = e->responses + age * rate; + if (balance > rate) { + balance = rate; + } + return (balance); +} + +/* + * Search for an entry for a response and optionally create it. + */ +static dns_rrl_entry_t * +get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr, dns_zone_t *zone, + dns_rdataclass_t qclass, dns_rdatatype_t qtype, + const dns_name_t *qname, dns_rrl_rtype_t rtype, isc_stdtime_t now, + bool create, char *log_buf, unsigned int log_buf_len) { + dns_rrl_key_t key; + uint32_t hval; + dns_rrl_entry_t *e; + dns_rrl_hash_t *hash; + dns_rrl_bin_t *new_bin, *old_bin; + int probes, age; + + make_key(rrl, &key, client_addr, zone, qtype, qname, qclass, rtype); + hval = hash_key(&key); + + /* + * Look for the entry in the current hash table. + */ + new_bin = get_bin(rrl->hash, hval); + probes = 1; + e = ISC_LIST_HEAD(*new_bin); + while (e != NULL) { + if (key_cmp(&e->key, &key)) { + ref_entry(rrl, e, probes, now); + return (e); + } + ++probes; + e = ISC_LIST_NEXT(e, hlink); + } + + /* + * Look in the old hash table. + */ + if (rrl->old_hash != NULL) { + old_bin = get_bin(rrl->old_hash, hval); + e = ISC_LIST_HEAD(*old_bin); + while (e != NULL) { + if (key_cmp(&e->key, &key)) { + ISC_LIST_UNLINK(*old_bin, e, hlink); + ISC_LIST_PREPEND(*new_bin, e, hlink); + e->hash_gen = rrl->hash_gen; + ref_entry(rrl, e, probes, now); + return (e); + } + e = ISC_LIST_NEXT(e, hlink); + } + + /* + * Discard previous hash table when all of its entries are old. + */ + age = delta_rrl_time(rrl->old_hash->check_time, now); + if (age > rrl->window) { + free_old_hash(rrl); + } + } + + if (!create) { + return (NULL); + } + + /* + * The entry does not exist, so create it by finding a free entry. + * Keep currently penalized and logged entries. + * Try to make more entries if none are idle. + * Steal the oldest entry if we cannot create more. + */ + for (e = ISC_LIST_TAIL(rrl->lru); e != NULL; e = ISC_LIST_PREV(e, lru)) + { + if (!ISC_LINK_LINKED(e, hlink)) { + break; + } + age = get_age(rrl, e, now); + if (age <= 1) { + e = NULL; + break; + } + if (!e->logged && response_balance(rrl, e, age) > 0) { + break; + } + } + if (e == NULL) { + expand_entries(rrl, ISC_MIN((rrl->num_entries + 1) / 2, 1000)); + e = ISC_LIST_TAIL(rrl->lru); + } + if (e->logged) { + log_end(rrl, e, true, log_buf, log_buf_len); + } + if (ISC_LINK_LINKED(e, hlink)) { + if (e->hash_gen == rrl->hash_gen) { + hash = rrl->hash; + } else { + hash = rrl->old_hash; + } + old_bin = get_bin(hash, hash_key(&e->key)); + ISC_LIST_UNLINK(*old_bin, e, hlink); + } + ISC_LIST_PREPEND(*new_bin, e, hlink); + e->hash_gen = rrl->hash_gen; + e->key = key; + e->ts_valid = false; + ref_entry(rrl, e, probes, now); + return (e); +} + +static void +debit_log(const dns_rrl_entry_t *e, int age, const char *action) { + char buf[sizeof("age=2147483647")]; + const char *age_str; + + if (age == DNS_RRL_FOREVER) { + age_str = ""; + } else { + snprintf(buf, sizeof(buf), "age=%d", age); + age_str = buf; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, DNS_LOGMODULE_REQUEST, + DNS_RRL_LOG_DEBUG3, "rrl %08x %6s responses=%-3d %s", + hash_key(&e->key), age_str, e->responses, action); +} + +static dns_rrl_result_t +debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale, + const isc_sockaddr_t *client_addr, isc_stdtime_t now, + char *log_buf, unsigned int log_buf_len) { + int rate, new_rate, slip, new_slip, age, log_secs, min; + dns_rrl_rate_t *ratep; + dns_rrl_entry_t const *credit_e; + + /* + * Pick the rate counter. + * Optionally adjust the rate by the estimated query/second rate. + */ + ratep = get_rate(rrl, e->key.s.rtype); + rate = ratep->r; + if (rate == 0) { + return (DNS_RRL_RESULT_OK); + } + + if (scale < 1.0) { + /* + * The limit for clients that have used TCP is not scaled. + */ + credit_e = get_entry( + rrl, client_addr, NULL, 0, dns_rdatatype_none, NULL, + DNS_RRL_RTYPE_TCP, now, false, log_buf, log_buf_len); + if (credit_e != NULL) { + age = get_age(rrl, e, now); + if (age < rrl->window) { + scale = 1.0; + } + } + } + if (scale < 1.0) { + new_rate = (int)(rate * scale); + if (new_rate < 1) { + new_rate = 1; + } + if (ratep->scaled != new_rate) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, + "%d qps scaled %s by %.2f" + " from %d to %d", + (int)qps, ratep->str, scale, rate, + new_rate); + rate = new_rate; + ratep->scaled = rate; + } + } + + min = -rrl->window * rate; + + /* + * Treat time jumps into the recent past as no time. + * Treat entries older than the window as if they were just created + * Credit other entries. + */ + age = get_age(rrl, e, now); + if (age > 0) { + /* + * Credit tokens earned during elapsed time. + */ + if (age > rrl->window) { + e->responses = rate; + e->slip_cnt = 0; + } else { + e->responses += rate * age; + if (e->responses > rate) { + e->responses = rate; + e->slip_cnt = 0; + } + } + /* + * Find the seconds since last log message without overflowing + * small counter. This counter is reset when an entry is + * created. It is not necessarily reset when some requests + * are answered provided other requests continue to be dropped + * or slipped. This can happen when the request rate is just + * at the limit. + */ + if (e->logged) { + log_secs = e->log_secs; + log_secs += age; + if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0) { + log_secs = DNS_RRL_MAX_LOG_SECS; + } + e->log_secs = log_secs; + } + } + set_age(rrl, e, now); + + /* + * Debit the entry for this response. + */ + if (--e->responses >= 0) { + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { + debit_log(e, age, ""); + } + return (DNS_RRL_RESULT_OK); + } + + if (e->responses < min) { + e->responses = min; + } + + /* + * Drop this response unless it should slip or leak. + */ + slip = rrl->slip.r; + if (slip > 2 && scale < 1.0) { + new_slip = (int)(slip * scale); + if (new_slip < 2) { + new_slip = 2; + } + if (rrl->slip.scaled != new_slip) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, + "%d qps scaled slip" + " by %.2f from %d to %d", + (int)qps, scale, slip, new_slip); + slip = new_slip; + rrl->slip.scaled = slip; + } + } + if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) { + if (e->slip_cnt++ == 0) { + if ((int)e->slip_cnt >= slip) { + e->slip_cnt = 0; + } + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { + debit_log(e, age, "slip"); + } + return (DNS_RRL_RESULT_SLIP); + } else if ((int)e->slip_cnt >= slip) { + e->slip_cnt = 0; + } + } + + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { + debit_log(e, age, "drop"); + } + return (DNS_RRL_RESULT_DROP); +} + +static dns_rrl_qname_buf_t * +get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) { + dns_rrl_qname_buf_t *qbuf; + + qbuf = rrl->qnames[e->log_qname]; + if (qbuf == NULL || qbuf->e != e) { + return (NULL); + } + return (qbuf); +} + +static void +free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) { + dns_rrl_qname_buf_t *qbuf; + + qbuf = get_qname(rrl, e); + if (qbuf != NULL) { + qbuf->e = NULL; + ISC_LIST_APPEND(rrl->qname_free, qbuf, link); + } +} + +static void +add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) { + isc_region_t region; + + isc_buffer_availableregion(lb, ®ion); + if (str_len >= region.length) { + if (region.length == 0U) { + return; + } + str_len = region.length; + } + memmove(region.base, str, str_len); + isc_buffer_add(lb, str_len); +} + +#define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s) - 1) + +/* + * Build strings for the logs + */ +static void +make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e, const char *str1, + const char *str2, bool plural, const dns_name_t *qname, + bool save_qname, dns_rrl_result_t rrl_result, + isc_result_t resp_result, char *log_buf, + unsigned int log_buf_len) { + isc_buffer_t lb; + dns_rrl_qname_buf_t *qbuf; + isc_netaddr_t cidr; + char strbuf[ISC_MAX(sizeof("/123"), sizeof(" (12345678)"))]; + const char *rstr; + isc_result_t msg_result; + + if (log_buf_len <= 1) { + if (log_buf_len == 1) { + log_buf[0] = '\0'; + } + return; + } + isc_buffer_init(&lb, log_buf, log_buf_len - 1); + + if (str1 != NULL) { + add_log_str(&lb, str1, strlen(str1)); + } + if (str2 != NULL) { + add_log_str(&lb, str2, strlen(str2)); + } + + switch (rrl_result) { + case DNS_RRL_RESULT_OK: + break; + case DNS_RRL_RESULT_DROP: + ADD_LOG_CSTR(&lb, "drop "); + break; + case DNS_RRL_RESULT_SLIP: + ADD_LOG_CSTR(&lb, "slip "); + break; + default: + UNREACHABLE(); + } + + switch (e->key.s.rtype) { + case DNS_RRL_RTYPE_QUERY: + break; + case DNS_RRL_RTYPE_REFERRAL: + ADD_LOG_CSTR(&lb, "referral "); + break; + case DNS_RRL_RTYPE_NODATA: + ADD_LOG_CSTR(&lb, "NODATA "); + break; + case DNS_RRL_RTYPE_NXDOMAIN: + ADD_LOG_CSTR(&lb, "NXDOMAIN "); + break; + case DNS_RRL_RTYPE_ERROR: + if (resp_result == ISC_R_SUCCESS) { + ADD_LOG_CSTR(&lb, "error "); + } else { + rstr = isc_result_totext(resp_result); + add_log_str(&lb, rstr, strlen(rstr)); + ADD_LOG_CSTR(&lb, " error "); + } + break; + case DNS_RRL_RTYPE_ALL: + ADD_LOG_CSTR(&lb, "all "); + break; + default: + UNREACHABLE(); + } + + if (plural) { + ADD_LOG_CSTR(&lb, "responses to "); + } else { + ADD_LOG_CSTR(&lb, "response to "); + } + + memset(&cidr, 0, sizeof(cidr)); + if (e->key.s.ipv6) { + snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen); + cidr.family = AF_INET6; + memset(&cidr.type.in6, 0, sizeof(cidr.type.in6)); + memmove(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip)); + } else { + snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen); + cidr.family = AF_INET; + cidr.type.in.s_addr = e->key.s.ip[0]; + } + msg_result = isc_netaddr_totext(&cidr, &lb); + if (msg_result != ISC_R_SUCCESS) { + ADD_LOG_CSTR(&lb, "?"); + } + add_log_str(&lb, strbuf, strlen(strbuf)); + + if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY || + e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL || + e->key.s.rtype == DNS_RRL_RTYPE_NODATA || + e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) + { + qbuf = get_qname(rrl, e); + if (save_qname && qbuf == NULL && qname != NULL && + dns_name_isabsolute(qname)) + { + /* + * Capture the qname for the "stop limiting" message. + */ + qbuf = ISC_LIST_TAIL(rrl->qname_free); + if (qbuf != NULL) { + ISC_LIST_UNLINK(rrl->qname_free, qbuf, link); + } else if (rrl->num_qnames < DNS_RRL_QNAMES) { + qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf)); + { + memset(qbuf, 0, sizeof(*qbuf)); + ISC_LINK_INIT(qbuf, link); + qbuf->index = rrl->num_qnames; + rrl->qnames[rrl->num_qnames++] = qbuf; + } + } + if (qbuf != NULL) { + e->log_qname = qbuf->index; + qbuf->e = e; + dns_fixedname_init(&qbuf->qname); + dns_name_copy(qname, + dns_fixedname_name(&qbuf->qname)); + } + } + if (qbuf != NULL) { + qname = dns_fixedname_name(&qbuf->qname); + } + if (qname != NULL) { + ADD_LOG_CSTR(&lb, " for "); + (void)dns_name_totext(qname, true, &lb); + } else { + ADD_LOG_CSTR(&lb, " for (?)"); + } + if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) { + ADD_LOG_CSTR(&lb, " "); + (void)dns_rdataclass_totext(e->key.s.qclass, &lb); + if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) { + ADD_LOG_CSTR(&lb, " "); + (void)dns_rdatatype_totext(e->key.s.qtype, &lb); + } + } + snprintf(strbuf, sizeof(strbuf), " (%08" PRIx32 ")", + e->key.s.qname_hash); + add_log_str(&lb, strbuf, strlen(strbuf)); + } + + /* + * We saved room for '\0'. + */ + log_buf[isc_buffer_usedlength(&lb)] = '\0'; +} + +static void +log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf, + unsigned int log_buf_len) { + if (e->logged) { + make_log_buf(rrl, e, early ? "*" : NULL, + rrl->log_only ? "would stop limiting " + : "stop limiting ", + true, NULL, false, DNS_RRL_RESULT_OK, + ISC_R_SUCCESS, log_buf, log_buf_len); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s", + log_buf); + free_qname(rrl, e); + e->logged = false; + --rrl->num_logged; + } +} + +/* + * Log messages for streams that have stopped being rate limited. + */ +static void +log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit, char *log_buf, + unsigned int log_buf_len) { + dns_rrl_entry_t *e; + int age; + + for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) { + if (!e->logged) { + continue; + } + if (now != 0) { + age = get_age(rrl, e, now); + if (age < DNS_RRL_STOP_LOG_SECS || + response_balance(rrl, e, age) < 0) + { + break; + } + } + + log_end(rrl, e, now == 0, log_buf, log_buf_len); + if (rrl->num_logged <= 0) { + break; + } + + /* + * Too many messages could stall real work. + */ + if (--limit < 0) { + rrl->last_logged = ISC_LIST_PREV(e, lru); + return; + } + } + if (e == NULL) { + INSIST(rrl->num_logged == 0); + rrl->log_stops_time = now; + } + rrl->last_logged = e; +} + +/* + * Main rate limit interface. + */ +dns_rrl_result_t +dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr, + bool is_tcp, dns_rdataclass_t qclass, dns_rdatatype_t qtype, + const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now, + bool wouldlog, char *log_buf, unsigned int log_buf_len) { + dns_rrl_t *rrl; + dns_rrl_rtype_t rtype; + dns_rrl_entry_t *e; + isc_netaddr_t netclient; + int secs; + double qps, scale; + int exempt_match; + isc_result_t result; + dns_rrl_result_t rrl_result; + + INSIST(log_buf != NULL && log_buf_len > 0); + + rrl = view->rrl; + if (rrl->exempt != NULL) { + isc_netaddr_fromsockaddr(&netclient, client_addr); + result = dns_acl_match(&netclient, NULL, rrl->exempt, + view->aclenv, &exempt_match, NULL); + if (result == ISC_R_SUCCESS && exempt_match > 0) { + return (DNS_RRL_RESULT_OK); + } + } + + LOCK(&rrl->lock); + + /* + * Estimate total query per second rate when scaling by qps. + */ + if (rrl->qps_scale == 0) { + qps = 0.0; + scale = 1.0; + } else { + ++rrl->qps_responses; + secs = delta_rrl_time(rrl->qps_time, now); + if (secs <= 0) { + qps = rrl->qps; + } else { + qps = (1.0 * rrl->qps_responses) / secs; + if (secs >= rrl->window) { + if (isc_log_wouldlog(dns_lctx, + DNS_RRL_LOG_DEBUG3)) + { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, + DNS_RRL_LOG_DEBUG3, + "%d responses/%d seconds" + " = %d qps", + rrl->qps_responses, secs, + (int)qps); + } + rrl->qps = qps; + rrl->qps_responses = 0; + rrl->qps_time = now; + } else if (qps < rrl->qps) { + qps = rrl->qps; + } + } + scale = rrl->qps_scale / qps; + } + + /* + * Do maintenance once per second. + */ + if (rrl->num_logged > 0 && rrl->log_stops_time != now) { + log_stops(rrl, now, 8, log_buf, log_buf_len); + } + + /* + * Notice TCP responses when scaling limits by qps. + * Do not try to rate limit TCP responses. + */ + if (is_tcp) { + if (scale < 1.0) { + e = get_entry(rrl, client_addr, NULL, 0, + dns_rdatatype_none, NULL, + DNS_RRL_RTYPE_TCP, now, true, log_buf, + log_buf_len); + if (e != NULL) { + e->responses = -(rrl->window + 1); + set_age(rrl, e, now); + } + } + UNLOCK(&rrl->lock); + return (DNS_RRL_RESULT_OK); + } + + /* + * Find the right kind of entry, creating it if necessary. + * If that is impossible, then nothing more can be done + */ + switch (resp_result) { + case ISC_R_SUCCESS: + rtype = DNS_RRL_RTYPE_QUERY; + break; + case DNS_R_DELEGATION: + rtype = DNS_RRL_RTYPE_REFERRAL; + break; + case DNS_R_NXRRSET: + rtype = DNS_RRL_RTYPE_NODATA; + break; + case DNS_R_NXDOMAIN: + rtype = DNS_RRL_RTYPE_NXDOMAIN; + break; + default: + rtype = DNS_RRL_RTYPE_ERROR; + break; + } + e = get_entry(rrl, client_addr, zone, qclass, qtype, qname, rtype, now, + true, log_buf, log_buf_len); + if (e == NULL) { + UNLOCK(&rrl->lock); + return (DNS_RRL_RESULT_OK); + } + + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) { + /* + * Do not worry about speed or releasing the lock. + * This message appears before messages from debit_rrl_entry(). + */ + make_log_buf(rrl, e, "consider limiting ", NULL, false, qname, + false, DNS_RRL_RESULT_OK, resp_result, log_buf, + log_buf_len); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, "%s", + log_buf); + } + + rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now, + log_buf, log_buf_len); + + if (rrl->all_per_second.r != 0) { + /* + * We must debit the all-per-second token bucket if we have + * an all-per-second limit for the IP address. + * The all-per-second limit determines the log message + * when both limits are hit. + * The response limiting must continue if the + * all-per-second limiting lapses. + */ + dns_rrl_entry_t *e_all; + dns_rrl_result_t rrl_all_result; + + e_all = get_entry(rrl, client_addr, zone, 0, dns_rdatatype_none, + NULL, DNS_RRL_RTYPE_ALL, now, true, log_buf, + log_buf_len); + if (e_all == NULL) { + UNLOCK(&rrl->lock); + return (DNS_RRL_RESULT_OK); + } + rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale, + client_addr, now, log_buf, + log_buf_len); + if (rrl_all_result != DNS_RRL_RESULT_OK) { + e = e_all; + rrl_result = rrl_all_result; + if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) { + make_log_buf(rrl, e, + "prefer all-per-second limiting ", + NULL, true, qname, false, + DNS_RRL_RESULT_OK, resp_result, + log_buf, log_buf_len); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, + DNS_RRL_LOG_DEBUG1, "%s", + log_buf); + } + } + } + + if (rrl_result == DNS_RRL_RESULT_OK) { + UNLOCK(&rrl->lock); + return (DNS_RRL_RESULT_OK); + } + + /* + * Log occasionally in the rate-limit category. + */ + if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) && + isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP)) + { + make_log_buf(rrl, e, rrl->log_only ? "would " : NULL, + e->logged ? "continue limiting " : "limit ", true, + qname, true, DNS_RRL_RESULT_OK, resp_result, + log_buf, log_buf_len); + if (!e->logged) { + e->logged = true; + if (++rrl->num_logged <= 1) { + rrl->last_logged = e; + } + } + e->log_secs = 0; + + /* + * Avoid holding the lock. + */ + if (!wouldlog) { + UNLOCK(&rrl->lock); + e = NULL; + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s", + log_buf); + } + + /* + * Make a log message for the caller. + */ + if (wouldlog) { + make_log_buf(rrl, e, + rrl->log_only ? "would rate limit " + : "rate limit ", + NULL, false, qname, false, rrl_result, resp_result, + log_buf, log_buf_len); + } + + if (e != NULL) { + /* + * Do not save the qname unless we might need it for + * the ending log message. + */ + if (!e->logged) { + free_qname(rrl, e); + } + UNLOCK(&rrl->lock); + } + + return (rrl_result); +} + +void +dns_rrl_view_destroy(dns_view_t *view) { + dns_rrl_t *rrl; + dns_rrl_block_t *b; + dns_rrl_hash_t *h; + char log_buf[DNS_RRL_LOG_BUF_LEN]; + int i; + + rrl = view->rrl; + if (rrl == NULL) { + return; + } + view->rrl = NULL; + + /* + * Assume the caller takes care of locking the view and anything else. + */ + + if (rrl->num_logged > 0) { + log_stops(rrl, 0, INT32_MAX, log_buf, sizeof(log_buf)); + } + + for (i = 0; i < DNS_RRL_QNAMES; ++i) { + if (rrl->qnames[i] == NULL) { + break; + } + isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i])); + } + + if (rrl->exempt != NULL) { + dns_acl_detach(&rrl->exempt); + } + + isc_mutex_destroy(&rrl->lock); + + while (!ISC_LIST_EMPTY(rrl->blocks)) { + b = ISC_LIST_HEAD(rrl->blocks); + ISC_LIST_UNLINK(rrl->blocks, b, link); + isc_mem_put(rrl->mctx, b, b->size); + } + + h = rrl->hash; + if (h != NULL) { + isc_mem_put(rrl->mctx, h, + sizeof(*h) + (h->length - 1) * sizeof(h->bins[0])); + } + + h = rrl->old_hash; + if (h != NULL) { + isc_mem_put(rrl->mctx, h, + sizeof(*h) + (h->length - 1) * sizeof(h->bins[0])); + } + + isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl)); +} + +isc_result_t +dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) { + dns_rrl_t *rrl; + isc_result_t result; + + *rrlp = NULL; + + rrl = isc_mem_get(view->mctx, sizeof(*rrl)); + memset(rrl, 0, sizeof(*rrl)); + isc_mem_attach(view->mctx, &rrl->mctx); + isc_mutex_init(&rrl->lock); + isc_stdtime_get(&rrl->ts_bases[0]); + + view->rrl = rrl; + + result = expand_entries(rrl, min_entries); + if (result != ISC_R_SUCCESS) { + dns_rrl_view_destroy(view); + return (result); + } + result = expand_rrl_hash(rrl, 0); + if (result != ISC_R_SUCCESS) { + dns_rrl_view_destroy(view); + return (result); + } + + *rrlp = rrl; + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c new file mode 100644 index 0000000..317eeb0 --- /dev/null +++ b/lib/dns/sdb.c @@ -0,0 +1,1598 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdatalist_p.h" + +struct dns_sdbimplementation { + const dns_sdbmethods_t *methods; + void *driverdata; + unsigned int flags; + isc_mem_t *mctx; + isc_mutex_t driverlock; + dns_dbimplementation_t *dbimp; +}; + +struct dns_sdb { + /* Unlocked */ + dns_db_t common; + char *zone; + dns_sdbimplementation_t *implementation; + void *dbdata; + + /* Atomic */ + isc_refcount_t references; +}; + +struct dns_sdblookup { + /* Unlocked */ + unsigned int magic; + dns_sdb_t *sdb; + ISC_LIST(dns_rdatalist_t) lists; + ISC_LIST(isc_buffer_t) buffers; + dns_name_t *name; + ISC_LINK(dns_sdblookup_t) link; + dns_rdatacallbacks_t callbacks; + + /* Atomic */ + isc_refcount_t references; +}; + +typedef struct dns_sdblookup dns_sdbnode_t; + +struct dns_sdballnodes { + dns_dbiterator_t common; + ISC_LIST(dns_sdbnode_t) nodelist; + dns_sdbnode_t *current; + dns_sdbnode_t *origin; +}; + +typedef dns_sdballnodes_t sdb_dbiterator_t; + +typedef struct sdb_rdatasetiter { + dns_rdatasetiter_t common; + dns_rdatalist_t *current; +} sdb_rdatasetiter_t; + +#define SDB_MAGIC ISC_MAGIC('S', 'D', 'B', '-') + +/*% + * Note that "impmagic" is not the first four bytes of the struct, so + * ISC_MAGIC_VALID cannot be used. + */ +#define VALID_SDB(sdb) ((sdb) != NULL && (sdb)->common.impmagic == SDB_MAGIC) + +#define SDBLOOKUP_MAGIC ISC_MAGIC('S', 'D', 'B', 'L') +#define VALID_SDBLOOKUP(sdbl) ISC_MAGIC_VALID(sdbl, SDBLOOKUP_MAGIC) +#define VALID_SDBNODE(sdbn) VALID_SDBLOOKUP(sdbn) + +/* These values are taken from RFC1537 */ +#define SDB_DEFAULT_REFRESH 28800U /* 8 hours */ +#define SDB_DEFAULT_RETRY 7200U /* 2 hours */ +#define SDB_DEFAULT_EXPIRE 604800U /* 7 days */ +#define SDB_DEFAULT_MINIMUM 86400U /* 1 day */ + +/* This is a reasonable value */ +#define SDB_DEFAULT_TTL (60 * 60 * 24) + +#ifdef __COVERITY__ +#define MAYBE_LOCK(sdb) LOCK(&sdb->implementation->driverlock) +#define MAYBE_UNLOCK(sdb) UNLOCK(&sdb->implementation->driverlock) +#else /* ifdef __COVERITY__ */ +#define MAYBE_LOCK(sdb) \ + do { \ + unsigned int flags = sdb->implementation->flags; \ + if ((flags & DNS_SDBFLAG_THREADSAFE) == 0) \ + LOCK(&sdb->implementation->driverlock); \ + } while (0) + +#define MAYBE_UNLOCK(sdb) \ + do { \ + unsigned int flags = sdb->implementation->flags; \ + if ((flags & DNS_SDBFLAG_THREADSAFE) == 0) \ + UNLOCK(&sdb->implementation->driverlock); \ + } while (0) +#endif /* ifdef __COVERITY__ */ + +static int dummy; + +static isc_result_t +dns_sdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type, + dns_rdataclass_t rdclass, unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp); + +static isc_result_t +findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); + +static isc_result_t +createnode(dns_sdb_t *sdb, dns_sdbnode_t **nodep); + +static void +destroynode(dns_sdbnode_t *node); + +static void +detachnode(dns_db_t *db, dns_dbnode_t **targetp); + +static void +list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node, + dns_rdataset_t *rdataset); + +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp); +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name); +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name); +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name); + +static dns_dbiteratormethods_t dbiterator_methods = { + dbiterator_destroy, dbiterator_first, dbiterator_last, + dbiterator_seek, dbiterator_prev, dbiterator_next, + dbiterator_current, dbiterator_pause, dbiterator_origin +}; + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp); +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator); +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator); +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset); + +static dns_rdatasetitermethods_t rdatasetiter_methods = { + rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next, + rdatasetiter_current +}; + +/* + * Functions used by implementors of simple databases + */ +isc_result_t +dns_sdb_register(const char *drivername, const dns_sdbmethods_t *methods, + void *driverdata, unsigned int flags, isc_mem_t *mctx, + dns_sdbimplementation_t **sdbimp) { + dns_sdbimplementation_t *imp; + isc_result_t result; + + REQUIRE(drivername != NULL); + REQUIRE(methods != NULL); + REQUIRE(methods->lookup != NULL || methods->lookup2 != NULL); + REQUIRE(mctx != NULL); + REQUIRE(sdbimp != NULL && *sdbimp == NULL); + REQUIRE((flags & + ~(DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA | + DNS_SDBFLAG_THREADSAFE | DNS_SDBFLAG_DNS64)) == 0); + + imp = isc_mem_get(mctx, sizeof(dns_sdbimplementation_t)); + imp->methods = methods; + imp->driverdata = driverdata; + imp->flags = flags; + imp->mctx = NULL; + isc_mem_attach(mctx, &imp->mctx); + isc_mutex_init(&imp->driverlock); + + imp->dbimp = NULL; + result = dns_db_register(drivername, dns_sdb_create, imp, mctx, + &imp->dbimp); + if (result != ISC_R_SUCCESS) { + goto cleanup_mutex; + } + *sdbimp = imp; + + return (ISC_R_SUCCESS); + +cleanup_mutex: + isc_mutex_destroy(&imp->driverlock); + isc_mem_put(mctx, imp, sizeof(dns_sdbimplementation_t)); + return (result); +} + +void +dns_sdb_unregister(dns_sdbimplementation_t **sdbimp) { + dns_sdbimplementation_t *imp; + + REQUIRE(sdbimp != NULL && *sdbimp != NULL); + + imp = *sdbimp; + *sdbimp = NULL; + dns_db_unregister(&imp->dbimp); + isc_mutex_destroy(&imp->driverlock); + + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdbimplementation_t)); +} + +static unsigned int +initial_size(unsigned int len) { + unsigned int size; + + for (size = 1024; size < (64 * 1024); size *= 2) { + if (len < size) { + return (size); + } + } + return (65535); +} + +isc_result_t +dns_sdb_putrdata(dns_sdblookup_t *lookup, dns_rdatatype_t typeval, + dns_ttl_t ttl, const unsigned char *rdatap, + unsigned int rdlen) { + dns_rdatalist_t *rdatalist; + dns_rdata_t *rdata; + isc_buffer_t *rdatabuf = NULL; + isc_mem_t *mctx; + isc_region_t region; + + mctx = lookup->sdb->common.mctx; + + rdatalist = ISC_LIST_HEAD(lookup->lists); + while (rdatalist != NULL) { + if (rdatalist->type == typeval) { + break; + } + rdatalist = ISC_LIST_NEXT(rdatalist, link); + } + + if (rdatalist == NULL) { + rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t)); + dns_rdatalist_init(rdatalist); + rdatalist->rdclass = lookup->sdb->common.rdclass; + rdatalist->type = typeval; + rdatalist->ttl = ttl; + ISC_LIST_APPEND(lookup->lists, rdatalist, link); + } else if (rdatalist->ttl != ttl) { + return (DNS_R_BADTTL); + } + + rdata = isc_mem_get(mctx, sizeof(dns_rdata_t)); + + isc_buffer_allocate(mctx, &rdatabuf, rdlen); + DE_CONST(rdatap, region.base); + region.length = rdlen; + isc_buffer_copyregion(rdatabuf, ®ion); + isc_buffer_usedregion(rdatabuf, ®ion); + dns_rdata_init(rdata); + dns_rdata_fromregion(rdata, rdatalist->rdclass, rdatalist->type, + ®ion); + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + ISC_LIST_APPEND(lookup->buffers, rdatabuf, link); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_sdb_putrr(dns_sdblookup_t *lookup, const char *type, dns_ttl_t ttl, + const char *data) { + unsigned int datalen; + dns_rdatatype_t typeval; + isc_textregion_t r; + isc_lex_t *lex = NULL; + isc_result_t result; + unsigned char *p = NULL; + unsigned int size = 0; /* Init to suppress compiler warning */ + isc_mem_t *mctx; + dns_sdbimplementation_t *imp; + const dns_name_t *origin; + isc_buffer_t b; + isc_buffer_t rb; + + REQUIRE(VALID_SDBLOOKUP(lookup)); + REQUIRE(type != NULL); + REQUIRE(data != NULL); + + mctx = lookup->sdb->common.mctx; + + DE_CONST(type, r.base); + r.length = strlen(type); + result = dns_rdatatype_fromtext(&typeval, &r); + if (result != ISC_R_SUCCESS) { + return (result); + } + + imp = lookup->sdb->implementation; + if ((imp->flags & DNS_SDBFLAG_RELATIVERDATA) != 0) { + origin = &lookup->sdb->common.origin; + } else { + origin = dns_rootname; + } + + result = isc_lex_create(mctx, 64, &lex); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + datalen = strlen(data); + size = initial_size(datalen); + do { + isc_buffer_constinit(&b, data, datalen); + isc_buffer_add(&b, datalen); + result = isc_lex_openbuffer(lex, &b); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + if (size >= 65535) { + size = 65535; + } + p = isc_mem_get(mctx, size); + isc_buffer_init(&rb, p, size); + result = dns_rdata_fromtext(NULL, lookup->sdb->common.rdclass, + typeval, lex, origin, 0, mctx, &rb, + &lookup->callbacks); + if (result != ISC_R_NOSPACE) { + break; + } + + /* + * Is the RR too big? + */ + if (size >= 65535) { + break; + } + isc_mem_put(mctx, p, size); + p = NULL; + size *= 2; + } while (result == ISC_R_NOSPACE); + + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_sdb_putrdata(lookup, typeval, ttl, isc_buffer_base(&rb), + isc_buffer_usedlength(&rb)); +failure: + if (p != NULL) { + isc_mem_put(mctx, p, size); + } + if (lex != NULL) { + isc_lex_destroy(&lex); + } + + return (result); +} + +static isc_result_t +getnode(dns_sdballnodes_t *allnodes, const char *name, dns_sdbnode_t **nodep) { + dns_name_t *newname; + const dns_name_t *origin; + dns_fixedname_t fnewname; + dns_sdb_t *sdb = (dns_sdb_t *)allnodes->common.db; + dns_sdbimplementation_t *imp = sdb->implementation; + dns_sdbnode_t *sdbnode; + isc_mem_t *mctx = sdb->common.mctx; + isc_buffer_t b; + isc_result_t result; + + newname = dns_fixedname_initname(&fnewname); + + if ((imp->flags & DNS_SDBFLAG_RELATIVERDATA) != 0) { + origin = &sdb->common.origin; + } else { + origin = dns_rootname; + } + isc_buffer_constinit(&b, name, strlen(name)); + isc_buffer_add(&b, strlen(name)); + + result = dns_name_fromtext(newname, &b, origin, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (allnodes->common.relative_names) { + /* All names are relative to the root */ + unsigned int nlabels = dns_name_countlabels(newname); + dns_name_getlabelsequence(newname, 0, nlabels - 1, newname); + } + + sdbnode = ISC_LIST_HEAD(allnodes->nodelist); + if (sdbnode == NULL || !dns_name_equal(sdbnode->name, newname)) { + sdbnode = NULL; + result = createnode(sdb, &sdbnode); + if (result != ISC_R_SUCCESS) { + return (result); + } + sdbnode->name = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(sdbnode->name, NULL); + dns_name_dup(newname, mctx, sdbnode->name); + ISC_LIST_PREPEND(allnodes->nodelist, sdbnode, link); + if (allnodes->origin == NULL && + dns_name_equal(newname, &sdb->common.origin)) + { + allnodes->origin = sdbnode; + } + } + *nodep = sdbnode; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_sdb_putnamedrr(dns_sdballnodes_t *allnodes, const char *name, + const char *type, dns_ttl_t ttl, const char *data) { + isc_result_t result; + dns_sdbnode_t *sdbnode = NULL; + result = getnode(allnodes, name, &sdbnode); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (dns_sdb_putrr(sdbnode, type, ttl, data)); +} + +isc_result_t +dns_sdb_putnamedrdata(dns_sdballnodes_t *allnodes, const char *name, + dns_rdatatype_t type, dns_ttl_t ttl, const void *rdata, + unsigned int rdlen) { + isc_result_t result; + dns_sdbnode_t *sdbnode = NULL; + result = getnode(allnodes, name, &sdbnode); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (dns_sdb_putrdata(sdbnode, type, ttl, rdata, rdlen)); +} + +isc_result_t +dns_sdb_putsoa(dns_sdblookup_t *lookup, const char *mname, const char *rname, + uint32_t serial) { + char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7]; + int n; + + REQUIRE(mname != NULL); + REQUIRE(rname != NULL); + + n = snprintf(str, sizeof(str), "%s %s %u %u %u %u %u", mname, rname, + serial, SDB_DEFAULT_REFRESH, SDB_DEFAULT_RETRY, + SDB_DEFAULT_EXPIRE, SDB_DEFAULT_MINIMUM); + if (n >= (int)sizeof(str) || n < 0) { + return (ISC_R_NOSPACE); + } + return (dns_sdb_putrr(lookup, "SOA", SDB_DEFAULT_TTL, str)); +} + +/* + * DB routines + */ + +static void +attach(dns_db_t *source, dns_db_t **targetp) { + dns_sdb_t *sdb = (dns_sdb_t *)source; + + REQUIRE(VALID_SDB(sdb)); + + isc_refcount_increment(&sdb->references); + + *targetp = source; +} + +static void +destroy(dns_sdb_t *sdb) { + dns_sdbimplementation_t *imp = sdb->implementation; + + isc_refcount_destroy(&sdb->references); + + if (imp->methods->destroy != NULL) { + MAYBE_LOCK(sdb); + imp->methods->destroy(sdb->zone, imp->driverdata, &sdb->dbdata); + MAYBE_UNLOCK(sdb); + } + + isc_mem_free(sdb->common.mctx, sdb->zone); + + sdb->common.magic = 0; + sdb->common.impmagic = 0; + + dns_name_free(&sdb->common.origin, sdb->common.mctx); + + isc_mem_putanddetach(&sdb->common.mctx, sdb, sizeof(dns_sdb_t)); +} + +static void +detach(dns_db_t **dbp) { + dns_sdb_t *sdb = (dns_sdb_t *)(*dbp); + + REQUIRE(VALID_SDB(sdb)); + + *dbp = NULL; + + if (isc_refcount_decrement(&sdb->references) == 1) { + destroy(sdb); + } +} + +static isc_result_t +beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + UNUSED(db); + UNUSED(callbacks); + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + UNUSED(db); + UNUSED(callbacks); + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +dump(dns_db_t *db, dns_dbversion_t *version, const char *filename, + dns_masterformat_t masterformat) { + UNUSED(db); + UNUSED(version); + UNUSED(filename); + UNUSED(masterformat); + return (ISC_R_NOTIMPLEMENTED); +} + +static void +currentversion(dns_db_t *db, dns_dbversion_t **versionp) { + REQUIRE(versionp != NULL && *versionp == NULL); + + UNUSED(db); + + *versionp = (void *)&dummy; + return; +} + +static isc_result_t +newversion(dns_db_t *db, dns_dbversion_t **versionp) { + UNUSED(db); + UNUSED(versionp); + + return (ISC_R_NOTIMPLEMENTED); +} + +static void +attachversion(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp) { + REQUIRE(source != NULL && source == (void *)&dummy); + REQUIRE(targetp != NULL && *targetp == NULL); + + UNUSED(db); + *targetp = source; + return; +} + +static void +closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) { + REQUIRE(versionp != NULL && *versionp == (void *)&dummy); + REQUIRE(!commit); + + UNUSED(db); + UNUSED(commit); + + *versionp = NULL; +} + +static isc_result_t +createnode(dns_sdb_t *sdb, dns_sdbnode_t **nodep) { + dns_sdbnode_t *node; + + node = isc_mem_get(sdb->common.mctx, sizeof(dns_sdbnode_t)); + + node->sdb = NULL; + attach((dns_db_t *)sdb, (dns_db_t **)&node->sdb); + ISC_LIST_INIT(node->lists); + ISC_LIST_INIT(node->buffers); + ISC_LINK_INIT(node, link); + node->name = NULL; + dns_rdatacallbacks_init(&node->callbacks); + + isc_refcount_init(&node->references, 1); + + node->magic = SDBLOOKUP_MAGIC; + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static void +destroynode(dns_sdbnode_t *node) { + dns_rdatalist_t *list; + dns_rdata_t *rdata; + isc_buffer_t *b; + dns_sdb_t *sdb; + isc_mem_t *mctx; + + sdb = node->sdb; + mctx = sdb->common.mctx; + + while (!ISC_LIST_EMPTY(node->lists)) { + list = ISC_LIST_HEAD(node->lists); + while (!ISC_LIST_EMPTY(list->rdata)) { + rdata = ISC_LIST_HEAD(list->rdata); + ISC_LIST_UNLINK(list->rdata, rdata, link); + isc_mem_put(mctx, rdata, sizeof(dns_rdata_t)); + } + ISC_LIST_UNLINK(node->lists, list, link); + isc_mem_put(mctx, list, sizeof(dns_rdatalist_t)); + } + + while (!ISC_LIST_EMPTY(node->buffers)) { + b = ISC_LIST_HEAD(node->buffers); + ISC_LIST_UNLINK(node->buffers, b, link); + isc_buffer_free(&b); + } + + if (node->name != NULL) { + dns_name_free(node->name, mctx); + isc_mem_put(mctx, node->name, sizeof(dns_name_t)); + } + + node->magic = 0; + isc_mem_put(mctx, node, sizeof(dns_sdbnode_t)); + detach((dns_db_t **)(void *)&sdb); +} + +static isc_result_t +getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + dns_sdbnode_t *node = NULL; + isc_result_t result; + isc_buffer_t b; + char namestr[DNS_NAME_MAXTEXT + 1]; + dns_sdbimplementation_t *imp; + dns_name_t relname; + dns_name_t *name; + + REQUIRE(VALID_SDB(sdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + imp = sdb->implementation; + name = &sdb->common.origin; + + if (imp->methods->lookup2 != NULL) { + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + dns_name_init(&relname, NULL); + name = &relname; + } + } else { + isc_buffer_init(&b, namestr, sizeof(namestr)); + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + dns_name_init(&relname, NULL); + result = dns_name_totext(&relname, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } else { + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + isc_buffer_putuint8(&b, 0); + } + + result = createnode(sdb, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + MAYBE_LOCK(sdb); + if (imp->methods->lookup2 != NULL) { + result = imp->methods->lookup2(&sdb->common.origin, name, + sdb->dbdata, node, NULL, NULL); + } else { + result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata, + node, NULL, NULL); + } + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS && + !(result == ISC_R_NOTFOUND && imp->methods->authority != NULL)) + { + destroynode(node); + return (result); + } + + if (imp->methods->authority != NULL) { + MAYBE_LOCK(sdb); + result = imp->methods->authority(sdb->zone, sdb->dbdata, node); + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS) { + destroynode(node); + return (result); + } + } + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnodeext(dns_db_t *db, const dns_name_t *name, bool create, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_dbnode_t **nodep) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + dns_sdbnode_t *node = NULL; + isc_result_t result; + isc_buffer_t b; + char namestr[DNS_NAME_MAXTEXT + 1]; + bool isorigin; + dns_sdbimplementation_t *imp; + dns_name_t relname; + unsigned int labels; + + REQUIRE(VALID_SDB(sdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + UNUSED(name); + UNUSED(create); + + imp = sdb->implementation; + + isorigin = dns_name_equal(name, &sdb->common.origin); + + if (imp->methods->lookup2 != NULL) { + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + labels = dns_name_countlabels(name) - + dns_name_countlabels(&db->origin); + dns_name_init(&relname, NULL); + dns_name_getlabelsequence(name, 0, labels, &relname); + name = &relname; + } + } else { + isc_buffer_init(&b, namestr, sizeof(namestr)); + if ((imp->flags & DNS_SDBFLAG_RELATIVEOWNER) != 0) { + labels = dns_name_countlabels(name) - + dns_name_countlabels(&db->origin); + dns_name_init(&relname, NULL); + dns_name_getlabelsequence(name, 0, labels, &relname); + result = dns_name_totext(&relname, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } else { + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + isc_buffer_putuint8(&b, 0); + } + + result = createnode(sdb, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + MAYBE_LOCK(sdb); + if (imp->methods->lookup2 != NULL) { + result = imp->methods->lookup2(&sdb->common.origin, name, + sdb->dbdata, node, methods, + clientinfo); + } else { + result = imp->methods->lookup(sdb->zone, namestr, sdb->dbdata, + node, methods, clientinfo); + } + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS && !(result == ISC_R_NOTFOUND && isorigin && + imp->methods->authority != NULL)) + { + destroynode(node); + return (result); + } + + if (isorigin && imp->methods->authority != NULL) { + MAYBE_LOCK(sdb); + result = imp->methods->authority(sdb->zone, sdb->dbdata, node); + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS) { + destroynode(node); + return (result); + } + } + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static isc_result_t +findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + dns_dbnode_t *node = NULL; + dns_fixedname_t fname; + dns_rdataset_t xrdataset; + dns_name_t *xname; + unsigned int nlabels, olabels; + isc_result_t result; + unsigned int i; + unsigned int flags; + + REQUIRE(VALID_SDB(sdb)); + REQUIRE(nodep == NULL || *nodep == NULL); + REQUIRE(version == NULL || version == (void *)&dummy); + + UNUSED(options); + + if (!dns_name_issubdomain(name, &db->origin)) { + return (DNS_R_NXDOMAIN); + } + + olabels = dns_name_countlabels(&db->origin); + nlabels = dns_name_countlabels(name); + + xname = dns_fixedname_initname(&fname); + + if (rdataset == NULL) { + dns_rdataset_init(&xrdataset); + rdataset = &xrdataset; + } + + result = DNS_R_NXDOMAIN; + flags = sdb->implementation->flags; + i = (flags & DNS_SDBFLAG_DNS64) != 0 ? nlabels : olabels; + for (; i <= nlabels; i++) { + /* + * Look up the next label. + */ + dns_name_getlabelsequence(name, nlabels - i, i, xname); + result = findnodeext(db, xname, false, methods, clientinfo, + &node); + if (result == ISC_R_NOTFOUND) { + /* + * No data at zone apex? + */ + if (i == olabels) { + return (DNS_R_BADDB); + } + result = DNS_R_NXDOMAIN; + continue; + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * DNS64 zone's don't have DNAME or NS records. + */ + if ((flags & DNS_SDBFLAG_DNS64) != 0) { + goto skip; + } + + /* + * DNS64 zone's don't have DNAME or NS records. + */ + if ((flags & DNS_SDBFLAG_DNS64) != 0) { + goto skip; + } + + /* + * Look for a DNAME at the current label, unless this is + * the qname. + */ + if (i < nlabels) { + result = findrdataset(db, node, version, + dns_rdatatype_dname, 0, now, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + result = DNS_R_DNAME; + break; + } + } + + /* + * Look for an NS at the current label, unless this is the + * origin or glue is ok. + */ + if (i != olabels && (options & DNS_DBFIND_GLUEOK) == 0) { + result = findrdataset(db, node, version, + dns_rdatatype_ns, 0, now, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + if (i == nlabels && type == dns_rdatatype_any) { + result = DNS_R_ZONECUT; + dns_rdataset_disassociate(rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated( + sigrdataset)) + { + dns_rdataset_disassociate( + sigrdataset); + } + } else { + result = DNS_R_DELEGATION; + } + break; + } + } + + /* + * If the current name is not the qname, add another label + * and try again. + */ + if (i < nlabels) { + destroynode(node); + node = NULL; + continue; + } + + skip: + /* + * If we're looking for ANY, we're done. + */ + if (type == dns_rdatatype_any) { + result = ISC_R_SUCCESS; + break; + } + + /* + * Look for the qtype. + */ + result = findrdataset(db, node, version, type, 0, now, rdataset, + sigrdataset); + if (result == ISC_R_SUCCESS) { + break; + } + + /* + * Look for a CNAME + */ + if (type != dns_rdatatype_cname) { + result = findrdataset(db, node, version, + dns_rdatatype_cname, 0, now, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + result = DNS_R_CNAME; + break; + } + } + + result = DNS_R_NXRRSET; + break; + } + + if (rdataset == &xrdataset && dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + + if (foundname != NULL) { + dns_name_copy(xname, foundname); + } + + if (nodep != NULL) { + *nodep = node; + } else if (node != NULL) { + detachnode(db, &node); + } + + return (result); +} + +static isc_result_t +findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_name_t *dcname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(dcname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + return (ISC_R_NOTIMPLEMENTED); +} + +static void +attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + dns_sdbnode_t *node = (dns_sdbnode_t *)source; + + REQUIRE(VALID_SDB(sdb)); + + UNUSED(sdb); + + isc_refcount_increment(&node->references); + + *targetp = source; +} + +static void +detachnode(dns_db_t *db, dns_dbnode_t **targetp) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + dns_sdbnode_t *node; + + REQUIRE(VALID_SDB(sdb)); + REQUIRE(targetp != NULL && *targetp != NULL); + + UNUSED(sdb); + + node = (dns_sdbnode_t *)(*targetp); + + *targetp = NULL; + + if (isc_refcount_decrement(&node->references) == 1) { + destroynode(node); + } +} + +static isc_result_t +expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + UNUSED(db); + UNUSED(node); + UNUSED(now); + UNREACHABLE(); +} + +static void +printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) { + UNUSED(db); + UNUSED(node); + UNUSED(out); + return; +} + +static isc_result_t +createiterator(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp) { + dns_sdb_t *sdb = (dns_sdb_t *)db; + REQUIRE(VALID_SDB(sdb)); + + sdb_dbiterator_t *sdbiter; + isc_result_t result; + dns_sdbimplementation_t *imp = sdb->implementation; + + if (imp->methods->allnodes == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if ((options & DNS_DB_NSEC3ONLY) != 0 || + (options & DNS_DB_NONSEC3) != 0) + { + return (ISC_R_NOTIMPLEMENTED); + } + + sdbiter = isc_mem_get(sdb->common.mctx, sizeof(sdb_dbiterator_t)); + + sdbiter->common.methods = &dbiterator_methods; + sdbiter->common.db = NULL; + dns_db_attach(db, &sdbiter->common.db); + sdbiter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) != + 0); + sdbiter->common.magic = DNS_DBITERATOR_MAGIC; + ISC_LIST_INIT(sdbiter->nodelist); + sdbiter->current = NULL; + sdbiter->origin = NULL; + + MAYBE_LOCK(sdb); + result = imp->methods->allnodes(sdb->zone, sdb->dbdata, sdbiter); + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS) { + dbiterator_destroy((dns_dbiterator_t **)(void *)&sdbiter); + return (result); + } + + if (sdbiter->origin != NULL) { + ISC_LIST_UNLINK(sdbiter->nodelist, sdbiter->origin, link); + ISC_LIST_PREPEND(sdbiter->nodelist, sdbiter->origin, link); + } + + *iteratorp = (dns_dbiterator_t *)sdbiter; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + REQUIRE(VALID_SDBNODE(node)); + + dns_rdatalist_t *list; + dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node; + + UNUSED(db); + UNUSED(version); + UNUSED(covers); + UNUSED(now); + UNUSED(sigrdataset); + + if (type == dns_rdatatype_rrsig) { + return (ISC_R_NOTIMPLEMENTED); + } + + list = ISC_LIST_HEAD(sdbnode->lists); + while (list != NULL) { + if (list->type == type) { + break; + } + list = ISC_LIST_NEXT(list, link); + } + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + + list_tordataset(list, db, node, rdataset); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp) { + sdb_rdatasetiter_t *iterator; + + REQUIRE(version == NULL || version == &dummy); + + UNUSED(version); + UNUSED(now); + + iterator = isc_mem_get(db->mctx, sizeof(sdb_rdatasetiter_t)); + + iterator->common.magic = DNS_RDATASETITER_MAGIC; + iterator->common.methods = &rdatasetiter_methods; + iterator->common.db = db; + iterator->common.node = NULL; + attachnode(db, node, &iterator->common.node); + iterator->common.version = version; + iterator->common.options = options; + iterator->common.now = now; + + *iteratorp = (dns_rdatasetiter_t *)iterator; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *addedrdataset) { + UNUSED(db); + UNUSED(node); + UNUSED(version); + UNUSED(now); + UNUSED(rdataset); + UNUSED(options); + UNUSED(addedrdataset); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *newrdataset) { + UNUSED(db); + UNUSED(node); + UNUSED(version); + UNUSED(rdataset); + UNUSED(options); + UNUSED(newrdataset); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers) { + UNUSED(db); + UNUSED(node); + UNUSED(version); + UNUSED(type); + UNUSED(covers); + + return (ISC_R_NOTIMPLEMENTED); +} + +static bool +issecure(dns_db_t *db) { + UNUSED(db); + + return (false); +} + +static unsigned int +nodecount(dns_db_t *db, dns_dbtree_t tree) { + UNUSED(db); + UNUSED(tree); + + return (0); +} + +static bool +ispersistent(dns_db_t *db) { + UNUSED(db); + return (true); +} + +static void +overmem(dns_db_t *db, bool over) { + UNUSED(db); + UNUSED(over); +} + +static void +settask(dns_db_t *db, isc_task_t *task) { + UNUSED(db); + UNUSED(task); +} + +static dns_dbmethods_t sdb_methods = { + attach, detach, + beginload, endload, + dump, currentversion, + newversion, attachversion, + closeversion, NULL, /* findnode */ + NULL, /* find */ + findzonecut, attachnode, + detachnode, expirenode, + printnode, createiterator, + findrdataset, allrdatasets, + addrdataset, subtractrdataset, + deleterdataset, issecure, + nodecount, ispersistent, + overmem, settask, + getoriginnode, /* getoriginnode */ + NULL, /* transfernode */ + NULL, /* getnsec3parameters */ + NULL, /* findnsec3node */ + NULL, /* setsigningtime */ + NULL, /* getsigningtime */ + NULL, /* resigned */ + NULL, /* isdnssec */ + NULL, /* getrrsetstats */ + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ + findnodeext, findext, + NULL, /* setcachestats */ + NULL, /* hashsize */ + NULL, /* nodefullname */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL, /* getservestalettl */ + NULL, /* setservestalerefresh */ + NULL, /* getservestalerefresh */ + NULL, /* setgluecachestats */ +}; + +static isc_result_t +dns_sdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type, + dns_rdataclass_t rdclass, unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp) { + dns_sdb_t *sdb; + isc_result_t result; + char zonestr[DNS_NAME_MAXTEXT + 1]; + isc_buffer_t b; + dns_sdbimplementation_t *imp; + + REQUIRE(driverarg != NULL); + + imp = driverarg; + + if (type != dns_dbtype_zone) { + return (ISC_R_NOTIMPLEMENTED); + } + + sdb = isc_mem_get(mctx, sizeof(dns_sdb_t)); + memset(sdb, 0, sizeof(dns_sdb_t)); + + dns_name_init(&sdb->common.origin, NULL); + sdb->common.attributes = 0; + sdb->common.methods = &sdb_methods; + sdb->common.rdclass = rdclass; + sdb->common.mctx = NULL; + sdb->implementation = imp; + + isc_mem_attach(mctx, &sdb->common.mctx); + + result = dns_name_dupwithoffsets(origin, mctx, &sdb->common.origin); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + isc_buffer_init(&b, zonestr, sizeof(zonestr)); + result = dns_name_totext(origin, true, &b); + if (result != ISC_R_SUCCESS) { + goto cleanup_origin; + } + isc_buffer_putuint8(&b, 0); + + sdb->zone = isc_mem_strdup(mctx, zonestr); + + sdb->dbdata = NULL; + if (imp->methods->create != NULL) { + MAYBE_LOCK(sdb); + result = imp->methods->create(sdb->zone, argc, argv, + imp->driverdata, &sdb->dbdata); + MAYBE_UNLOCK(sdb); + if (result != ISC_R_SUCCESS) { + goto cleanup_zonestr; + } + } + + isc_refcount_init(&sdb->references, 1); + + sdb->common.magic = DNS_DB_MAGIC; + sdb->common.impmagic = SDB_MAGIC; + + *dbp = (dns_db_t *)sdb; + + return (ISC_R_SUCCESS); + +cleanup_zonestr: + isc_mem_free(mctx, sdb->zone); +cleanup_origin: + dns_name_free(&sdb->common.origin, mctx); +cleanup_lock: + isc_mem_putanddetach(&mctx, sdb, sizeof(dns_sdb_t)); + + return (result); +} + +/* + * Rdataset Methods + */ + +static void +disassociate(dns_rdataset_t *rdataset) { + dns_dbnode_t *node = rdataset->private5; + dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node; + dns_db_t *db = (dns_db_t *)sdbnode->sdb; + + detachnode(db, &node); + isc__rdatalist_disassociate(rdataset); +} + +static void +rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + dns_dbnode_t *node = source->private5; + dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node; + dns_db_t *db = (dns_db_t *)sdbnode->sdb; + dns_dbnode_t *tempdb = NULL; + + isc__rdatalist_clone(source, target); + attachnode(db, node, &tempdb); + source->private5 = tempdb; +} + +static dns_rdatasetmethods_t sdb_rdataset_methods = { + disassociate, + isc__rdatalist_first, + isc__rdatalist_next, + isc__rdatalist_current, + rdataset_clone, + isc__rdatalist_count, + isc__rdatalist_addnoqname, + isc__rdatalist_getnoqname, + NULL, /* addclosest */ + NULL, /* getclosest */ + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL, /* getownercase */ + NULL /* addglue */ +}; + +static void +list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node, + dns_rdataset_t *rdataset) { + /* + * The sdb rdataset is an rdatalist with some additions. + * - private1 & private2 are used by the rdatalist. + * - private3 & private 4 are unused. + * - private5 is the node. + */ + + /* This should never fail. */ + RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == + ISC_R_SUCCESS); + + rdataset->methods = &sdb_rdataset_methods; + dns_db_attachnode(db, node, &rdataset->private5); +} + +/* + * Database Iterator Methods + */ +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)(*iteratorp); + dns_sdb_t *sdb = (dns_sdb_t *)sdbiter->common.db; + + while (!ISC_LIST_EMPTY(sdbiter->nodelist)) { + dns_sdbnode_t *node; + node = ISC_LIST_HEAD(sdbiter->nodelist); + ISC_LIST_UNLINK(sdbiter->nodelist, node, link); + destroynode(node); + } + + dns_db_detach(&sdbiter->common.db); + isc_mem_put(sdb->common.mctx, sdbiter, sizeof(sdb_dbiterator_t)); + + *iteratorp = NULL; +} + +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist); + if (sdbiter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + sdbiter->current = ISC_LIST_TAIL(sdbiter->nodelist); + if (sdbiter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + sdbiter->current = ISC_LIST_HEAD(sdbiter->nodelist); + while (sdbiter->current != NULL) { + if (dns_name_equal(sdbiter->current->name, name)) { + return (ISC_R_SUCCESS); + } + sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + sdbiter->current = ISC_LIST_PREV(sdbiter->current, link); + if (sdbiter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + sdbiter->current = ISC_LIST_NEXT(sdbiter->current, link); + if (sdbiter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name) { + sdb_dbiterator_t *sdbiter = (sdb_dbiterator_t *)iterator; + + attachnode(iterator->db, sdbiter->current, nodep); + if (name != NULL) { + dns_name_copy(sdbiter->current->name, name); + return (ISC_R_SUCCESS); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator) { + UNUSED(iterator); + return (ISC_R_SUCCESS); +} + +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) { + UNUSED(iterator); + dns_name_copy(dns_rootname, name); + return (ISC_R_SUCCESS); +} + +/* + * Rdataset Iterator Methods + */ + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)(*iteratorp); + detachnode(sdbiterator->common.db, &sdbiterator->common.node); + isc_mem_put(sdbiterator->common.db->mctx, sdbiterator, + sizeof(sdb_rdatasetiter_t)); + *iteratorp = NULL; +} + +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator) { + sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator; + dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)iterator->node; + + if (ISC_LIST_EMPTY(sdbnode->lists)) { + return (ISC_R_NOMORE); + } + sdbiterator->current = ISC_LIST_HEAD(sdbnode->lists); + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator) { + sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator; + + sdbiterator->current = ISC_LIST_NEXT(sdbiterator->current, link); + if (sdbiterator->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) { + sdb_rdatasetiter_t *sdbiterator = (sdb_rdatasetiter_t *)iterator; + + list_tordataset(sdbiterator->current, iterator->db, iterator->node, + rdataset); +} diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c new file mode 100644 index 0000000..7ab08f6 --- /dev/null +++ b/lib/dns/sdlz.c @@ -0,0 +1,2086 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdatalist_p.h" + +/* + * Private Types + */ + +struct dns_sdlzimplementation { + const dns_sdlzmethods_t *methods; + isc_mem_t *mctx; + void *driverarg; + unsigned int flags; + isc_mutex_t driverlock; + dns_dlzimplementation_t *dlz_imp; +}; + +struct dns_sdlz_db { + /* Unlocked */ + dns_db_t common; + void *dbdata; + dns_sdlzimplementation_t *dlzimp; + + /* Atomic */ + isc_refcount_t references; + + /* Locked */ + dns_dbversion_t *future_version; + int dummy_version; +}; + +struct dns_sdlzlookup { + /* Unlocked */ + unsigned int magic; + dns_sdlz_db_t *sdlz; + ISC_LIST(dns_rdatalist_t) lists; + ISC_LIST(isc_buffer_t) buffers; + dns_name_t *name; + ISC_LINK(dns_sdlzlookup_t) link; + dns_rdatacallbacks_t callbacks; + + /* Atomic */ + isc_refcount_t references; +}; + +typedef struct dns_sdlzlookup dns_sdlznode_t; + +struct dns_sdlzallnodes { + dns_dbiterator_t common; + ISC_LIST(dns_sdlznode_t) nodelist; + dns_sdlznode_t *current; + dns_sdlznode_t *origin; +}; + +typedef dns_sdlzallnodes_t sdlz_dbiterator_t; + +typedef struct sdlz_rdatasetiter { + dns_rdatasetiter_t common; + dns_rdatalist_t *current; +} sdlz_rdatasetiter_t; + +#define SDLZDB_MAGIC ISC_MAGIC('D', 'L', 'Z', 'S') + +/* + * Note that "impmagic" is not the first four bytes of the struct, so + * ISC_MAGIC_VALID cannot be used. + */ + +#define VALID_SDLZDB(sdlzdb) \ + ((sdlzdb) != NULL && (sdlzdb)->common.impmagic == SDLZDB_MAGIC) + +#define SDLZLOOKUP_MAGIC ISC_MAGIC('D', 'L', 'Z', 'L') +#define VALID_SDLZLOOKUP(sdlzl) ISC_MAGIC_VALID(sdlzl, SDLZLOOKUP_MAGIC) +#define VALID_SDLZNODE(sdlzn) VALID_SDLZLOOKUP(sdlzn) + +/* These values are taken from RFC 1537 */ +#define SDLZ_DEFAULT_REFRESH 28800U /* 8 hours */ +#define SDLZ_DEFAULT_RETRY 7200U /* 2 hours */ +#define SDLZ_DEFAULT_EXPIRE 604800U /* 7 days */ +#define SDLZ_DEFAULT_MINIMUM 86400U /* 1 day */ + +/* This is a reasonable value */ +#define SDLZ_DEFAULT_TTL (60 * 60 * 24) + +#ifdef __COVERITY__ +#define MAYBE_LOCK(imp) LOCK(&imp->driverlock) +#define MAYBE_UNLOCK(imp) UNLOCK(&imp->driverlock) +#else /* ifdef __COVERITY__ */ +#define MAYBE_LOCK(imp) \ + do { \ + unsigned int flags = imp->flags; \ + if ((flags & DNS_SDLZFLAG_THREADSAFE) == 0) \ + LOCK(&imp->driverlock); \ + } while (0) + +#define MAYBE_UNLOCK(imp) \ + do { \ + unsigned int flags = imp->flags; \ + if ((flags & DNS_SDLZFLAG_THREADSAFE) == 0) \ + UNLOCK(&imp->driverlock); \ + } while (0) +#endif /* ifdef __COVERITY__ */ + +/* + * Forward references. + */ +static isc_result_t +getnodedata(dns_db_t *db, const dns_name_t *name, bool create, + unsigned int options, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep); + +static void +list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node, + dns_rdataset_t *rdataset); + +static void +detachnode(dns_db_t *db, dns_dbnode_t **targetp); + +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp); +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name); +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name); +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator); +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name); + +static dns_dbiteratormethods_t dbiterator_methods = { + dbiterator_destroy, dbiterator_first, dbiterator_last, + dbiterator_seek, dbiterator_prev, dbiterator_next, + dbiterator_current, dbiterator_pause, dbiterator_origin +}; + +/* + * Utility functions + */ + +/* + * Log a message at the given level + */ +static void +sdlz_log(int level, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(level), fmt, ap); + va_end(ap); +} + +/*% Converts the input string to lowercase, in place. */ +static void +dns_sdlz_tolower(char *str) { + unsigned int len = strlen(str); + unsigned int i; + + for (i = 0; i < len; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + str[i] += 32; + } + } +} + +static unsigned int +initial_size(const char *data) { + unsigned int len = (strlen(data) / 64) + 1; + return (len * 64 + 64); +} + +/* + * Rdataset Iterator Methods. These methods were "borrowed" from the SDB + * driver interface. See the SDB driver interface documentation for more info. + */ + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)(*iteratorp); + + detachnode(sdlziterator->common.db, &sdlziterator->common.node); + isc_mem_put(sdlziterator->common.db->mctx, sdlziterator, + sizeof(sdlz_rdatasetiter_t)); + *iteratorp = NULL; +} + +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator) { + sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator; + dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)iterator->node; + + if (ISC_LIST_EMPTY(sdlznode->lists)) { + return (ISC_R_NOMORE); + } + sdlziterator->current = ISC_LIST_HEAD(sdlznode->lists); + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator) { + sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator; + + sdlziterator->current = ISC_LIST_NEXT(sdlziterator->current, link); + if (sdlziterator->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) { + sdlz_rdatasetiter_t *sdlziterator = (sdlz_rdatasetiter_t *)iterator; + + list_tordataset(sdlziterator->current, iterator->db, iterator->node, + rdataset); +} + +static dns_rdatasetitermethods_t rdatasetiter_methods = { + rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next, + rdatasetiter_current +}; + +/* + * DB routines. These methods were "borrowed" from the SDB driver interface. + * See the SDB driver interface documentation for more info. + */ + +static void +attach(dns_db_t *source, dns_db_t **targetp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)source; + + REQUIRE(VALID_SDLZDB(sdlz)); + + isc_refcount_increment(&sdlz->references); + + *targetp = source; +} + +static void +destroy(dns_sdlz_db_t *sdlz) { + sdlz->common.magic = 0; + sdlz->common.impmagic = 0; + + dns_name_free(&sdlz->common.origin, sdlz->common.mctx); + + isc_refcount_destroy(&sdlz->references); + isc_mem_putanddetach(&sdlz->common.mctx, sdlz, sizeof(dns_sdlz_db_t)); +} + +static void +detach(dns_db_t **dbp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)(*dbp); + + REQUIRE(VALID_SDLZDB(sdlz)); + + *dbp = NULL; + + if (isc_refcount_decrement(&sdlz->references) == 1) { + destroy(sdlz); + } +} + +static isc_result_t +beginload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + UNUSED(db); + UNUSED(callbacks); + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +endload(dns_db_t *db, dns_rdatacallbacks_t *callbacks) { + UNUSED(db); + UNUSED(callbacks); + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +dump(dns_db_t *db, dns_dbversion_t *version, const char *filename, + dns_masterformat_t masterformat) { + UNUSED(db); + UNUSED(version); + UNUSED(filename); + UNUSED(masterformat); + return (ISC_R_NOTIMPLEMENTED); +} + +static void +currentversion(dns_db_t *db, dns_dbversion_t **versionp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(versionp != NULL && *versionp == NULL); + + *versionp = (void *)&sdlz->dummy_version; + return; +} + +static isc_result_t +newversion(dns_db_t *db, dns_dbversion_t **versionp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + char origin[DNS_NAME_MAXTEXT + 1]; + isc_result_t result; + + REQUIRE(VALID_SDLZDB(sdlz)); + + if (sdlz->dlzimp->methods->newversion == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + dns_name_format(&sdlz->common.origin, origin, sizeof(origin)); + + result = sdlz->dlzimp->methods->newversion( + origin, sdlz->dlzimp->driverarg, sdlz->dbdata, versionp); + if (result != ISC_R_SUCCESS) { + sdlz_log(ISC_LOG_ERROR, + "sdlz newversion on origin %s failed : %s", origin, + isc_result_totext(result)); + return (result); + } + + sdlz->future_version = *versionp; + return (ISC_R_SUCCESS); +} + +static void +attachversion(dns_db_t *db, dns_dbversion_t *source, + dns_dbversion_t **targetp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(source != NULL && source == (void *)&sdlz->dummy_version); + + *targetp = source; +} + +static void +closeversion(dns_db_t *db, dns_dbversion_t **versionp, bool commit) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + char origin[DNS_NAME_MAXTEXT + 1]; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(versionp != NULL); + + if (*versionp == (void *)&sdlz->dummy_version) { + *versionp = NULL; + return; + } + + REQUIRE(*versionp == sdlz->future_version); + REQUIRE(sdlz->dlzimp->methods->closeversion != NULL); + + dns_name_format(&sdlz->common.origin, origin, sizeof(origin)); + + sdlz->dlzimp->methods->closeversion(origin, commit, + sdlz->dlzimp->driverarg, + sdlz->dbdata, versionp); + if (*versionp != NULL) { + sdlz_log(ISC_LOG_ERROR, "sdlz closeversion on origin %s failed", + origin); + } + + sdlz->future_version = NULL; +} + +static isc_result_t +createnode(dns_sdlz_db_t *sdlz, dns_sdlznode_t **nodep) { + dns_sdlznode_t *node; + + node = isc_mem_get(sdlz->common.mctx, sizeof(dns_sdlznode_t)); + + node->sdlz = NULL; + attach((dns_db_t *)sdlz, (dns_db_t **)&node->sdlz); + ISC_LIST_INIT(node->lists); + ISC_LIST_INIT(node->buffers); + ISC_LINK_INIT(node, link); + node->name = NULL; + dns_rdatacallbacks_init(&node->callbacks); + + isc_refcount_init(&node->references, 1); + node->magic = SDLZLOOKUP_MAGIC; + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static void +destroynode(dns_sdlznode_t *node) { + dns_rdatalist_t *list; + dns_rdata_t *rdata; + isc_buffer_t *b; + dns_sdlz_db_t *sdlz; + dns_db_t *db; + isc_mem_t *mctx; + + isc_refcount_destroy(&node->references); + + sdlz = node->sdlz; + mctx = sdlz->common.mctx; + + while (!ISC_LIST_EMPTY(node->lists)) { + list = ISC_LIST_HEAD(node->lists); + while (!ISC_LIST_EMPTY(list->rdata)) { + rdata = ISC_LIST_HEAD(list->rdata); + ISC_LIST_UNLINK(list->rdata, rdata, link); + isc_mem_put(mctx, rdata, sizeof(dns_rdata_t)); + } + ISC_LIST_UNLINK(node->lists, list, link); + isc_mem_put(mctx, list, sizeof(dns_rdatalist_t)); + } + + while (!ISC_LIST_EMPTY(node->buffers)) { + b = ISC_LIST_HEAD(node->buffers); + ISC_LIST_UNLINK(node->buffers, b, link); + isc_buffer_free(&b); + } + + if (node->name != NULL) { + dns_name_free(node->name, mctx); + isc_mem_put(mctx, node->name, sizeof(dns_name_t)); + } + + node->magic = 0; + isc_mem_put(mctx, node, sizeof(dns_sdlznode_t)); + db = &sdlz->common; + detach(&db); +} + +static isc_result_t +getnodedata(dns_db_t *db, const dns_name_t *name, bool create, + unsigned int options, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_dbnode_t **nodep) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + dns_sdlznode_t *node = NULL; + isc_result_t result; + isc_buffer_t b; + char namestr[DNS_NAME_MAXTEXT + 1]; + isc_buffer_t b2; + char zonestr[DNS_NAME_MAXTEXT + 1]; + bool isorigin; + dns_sdlzauthorityfunc_t authority; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(nodep != NULL && *nodep == NULL); + + if (sdlz->dlzimp->methods->newversion == NULL) { + REQUIRE(!create); + } + + isc_buffer_init(&b, namestr, sizeof(namestr)); + if ((sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVEOWNER) != 0) { + dns_name_t relname; + unsigned int labels; + + labels = dns_name_countlabels(name) - + dns_name_countlabels(&sdlz->common.origin); + dns_name_init(&relname, NULL); + dns_name_getlabelsequence(name, 0, labels, &relname); + result = dns_name_totext(&relname, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } else { + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + isc_buffer_putuint8(&b, 0); + + isc_buffer_init(&b2, zonestr, sizeof(zonestr)); + result = dns_name_totext(&sdlz->common.origin, true, &b2); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_putuint8(&b2, 0); + + result = createnode(sdlz, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isorigin = dns_name_equal(name, &sdlz->common.origin); + + /* make sure strings are always lowercase */ + dns_sdlz_tolower(zonestr); + dns_sdlz_tolower(namestr); + + MAYBE_LOCK(sdlz->dlzimp); + + /* try to lookup the host (namestr) */ + result = sdlz->dlzimp->methods->lookup( + zonestr, namestr, sdlz->dlzimp->driverarg, sdlz->dbdata, node, + methods, clientinfo); + + /* + * If the name was not found and DNS_DBFIND_NOWILD is not + * set, then we try to find a wildcard entry. + * + * If DNS_DBFIND_NOZONECUT is set and there are multiple + * levels between the host and the zone origin, we also look + * for wildcards at each level. + */ + if (result == ISC_R_NOTFOUND && !create && + (options & DNS_DBFIND_NOWILD) == 0) + { + unsigned int i, dlabels, nlabels; + + nlabels = dns_name_countlabels(name); + dlabels = nlabels - dns_name_countlabels(&sdlz->common.origin); + for (i = 0; i < dlabels; i++) { + char wildstr[DNS_NAME_MAXTEXT + 1]; + dns_fixedname_t fixed; + const dns_name_t *wild; + + dns_fixedname_init(&fixed); + if (i == dlabels - 1) { + wild = dns_wildcardname; + } else { + dns_name_t *fname; + fname = dns_fixedname_name(&fixed); + dns_name_getlabelsequence( + name, i + 1, dlabels - i - 1, fname); + result = dns_name_concatenate( + dns_wildcardname, fname, fname, NULL); + if (result != ISC_R_SUCCESS) { + MAYBE_UNLOCK(sdlz->dlzimp); + return (result); + } + wild = fname; + } + + isc_buffer_init(&b, wildstr, sizeof(wildstr)); + result = dns_name_totext(wild, true, &b); + if (result != ISC_R_SUCCESS) { + MAYBE_UNLOCK(sdlz->dlzimp); + return (result); + } + isc_buffer_putuint8(&b, 0); + + result = sdlz->dlzimp->methods->lookup( + zonestr, wildstr, sdlz->dlzimp->driverarg, + sdlz->dbdata, node, methods, clientinfo); + if (result == ISC_R_SUCCESS) { + break; + } + } + } + + MAYBE_UNLOCK(sdlz->dlzimp); + + if (result == ISC_R_NOTFOUND && (isorigin || create)) { + result = ISC_R_SUCCESS; + } + + if (result != ISC_R_SUCCESS) { + isc_refcount_decrementz(&node->references); + destroynode(node); + return (result); + } + + if (isorigin && sdlz->dlzimp->methods->authority != NULL) { + MAYBE_LOCK(sdlz->dlzimp); + authority = sdlz->dlzimp->methods->authority; + result = (*authority)(zonestr, sdlz->dlzimp->driverarg, + sdlz->dbdata, node); + MAYBE_UNLOCK(sdlz->dlzimp); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + isc_refcount_decrementz(&node->references); + destroynode(node); + return (result); + } + } + + if (node->name == NULL) { + node->name = isc_mem_get(sdlz->common.mctx, sizeof(dns_name_t)); + dns_name_init(node->name, NULL); + dns_name_dup(name, sdlz->common.mctx, node->name); + } + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnodeext(dns_db_t *db, const dns_name_t *name, bool create, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_dbnode_t **nodep) { + return (getnodedata(db, name, create, 0, methods, clientinfo, nodep)); +} + +static isc_result_t +findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + return (getnodedata(db, name, create, 0, NULL, NULL, nodep)); +} + +static isc_result_t +findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_name_t *dcname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(dcname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + return (ISC_R_NOTIMPLEMENTED); +} + +static void +attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + dns_sdlznode_t *node = (dns_sdlznode_t *)source; + + REQUIRE(VALID_SDLZDB(sdlz)); + + UNUSED(sdlz); + + isc_refcount_increment(&node->references); + + *targetp = source; +} + +static void +detachnode(dns_db_t *db, dns_dbnode_t **targetp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + dns_sdlznode_t *node; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(targetp != NULL && *targetp != NULL); + + UNUSED(sdlz); + + node = (dns_sdlznode_t *)(*targetp); + *targetp = NULL; + + if (isc_refcount_decrement(&node->references) == 1) { + destroynode(node); + } +} + +static isc_result_t +expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + UNUSED(db); + UNUSED(node); + UNUSED(now); + UNREACHABLE(); +} + +static void +printnode(dns_db_t *db, dns_dbnode_t *node, FILE *out) { + UNUSED(db); + UNUSED(node); + UNUSED(out); + return; +} + +static isc_result_t +createiterator(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + sdlz_dbiterator_t *sdlziter; + isc_result_t result; + isc_buffer_t b; + char zonestr[DNS_NAME_MAXTEXT + 1]; + + REQUIRE(VALID_SDLZDB(sdlz)); + + if (sdlz->dlzimp->methods->allnodes == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if ((options & DNS_DB_NSEC3ONLY) != 0 || + (options & DNS_DB_NONSEC3) != 0) + { + return (ISC_R_NOTIMPLEMENTED); + } + + isc_buffer_init(&b, zonestr, sizeof(zonestr)); + result = dns_name_totext(&sdlz->common.origin, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_putuint8(&b, 0); + + sdlziter = isc_mem_get(sdlz->common.mctx, sizeof(sdlz_dbiterator_t)); + + sdlziter->common.methods = &dbiterator_methods; + sdlziter->common.db = NULL; + dns_db_attach(db, &sdlziter->common.db); + sdlziter->common.relative_names = ((options & DNS_DB_RELATIVENAMES) != + 0); + sdlziter->common.magic = DNS_DBITERATOR_MAGIC; + ISC_LIST_INIT(sdlziter->nodelist); + sdlziter->current = NULL; + sdlziter->origin = NULL; + + /* make sure strings are always lowercase */ + dns_sdlz_tolower(zonestr); + + MAYBE_LOCK(sdlz->dlzimp); + result = sdlz->dlzimp->methods->allnodes( + zonestr, sdlz->dlzimp->driverarg, sdlz->dbdata, sdlziter); + MAYBE_UNLOCK(sdlz->dlzimp); + if (result != ISC_R_SUCCESS) { + dns_dbiterator_t *iter = &sdlziter->common; + dbiterator_destroy(&iter); + return (result); + } + + if (sdlziter->origin != NULL) { + ISC_LIST_UNLINK(sdlziter->nodelist, sdlziter->origin, link); + ISC_LIST_PREPEND(sdlziter->nodelist, sdlziter->origin, link); + } + + *iteratorp = (dns_dbiterator_t *)sdlziter; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + REQUIRE(VALID_SDLZNODE(node)); + dns_rdatalist_t *list; + dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node; + + UNUSED(db); + UNUSED(version); + UNUSED(covers); + UNUSED(now); + UNUSED(sigrdataset); + + if (type == dns_rdatatype_sig || type == dns_rdatatype_rrsig) { + return (ISC_R_NOTIMPLEMENTED); + } + + list = ISC_LIST_HEAD(sdlznode->lists); + while (list != NULL) { + if (list->type == type) { + break; + } + list = ISC_LIST_NEXT(list, link); + } + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + + list_tordataset(list, db, node, rdataset); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +findext(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + dns_dbnode_t *node = NULL; + dns_fixedname_t fname; + dns_rdataset_t xrdataset; + dns_name_t *xname; + unsigned int nlabels, olabels; + isc_result_t result; + unsigned int i; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(nodep == NULL || *nodep == NULL); + REQUIRE(version == NULL || version == (void *)&sdlz->dummy_version || + version == sdlz->future_version); + + UNUSED(sdlz); + + if (!dns_name_issubdomain(name, &db->origin)) { + return (DNS_R_NXDOMAIN); + } + + olabels = dns_name_countlabels(&db->origin); + nlabels = dns_name_countlabels(name); + + xname = dns_fixedname_initname(&fname); + + if (rdataset == NULL) { + dns_rdataset_init(&xrdataset); + rdataset = &xrdataset; + } + + result = DNS_R_NXDOMAIN; + + /* + * If we're not walking down searching for zone + * cuts, we can cut straight to the chase + */ + if ((options & DNS_DBFIND_NOZONECUT) != 0) { + i = nlabels; + goto search; + } + + for (i = olabels; i <= nlabels; i++) { + search: + /* + * Look up the next label. + */ + dns_name_getlabelsequence(name, nlabels - i, i, xname); + result = getnodedata(db, xname, false, options, methods, + clientinfo, &node); + if (result == ISC_R_NOTFOUND) { + result = DNS_R_NXDOMAIN; + continue; + } else if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Look for a DNAME at the current label, unless this is + * the qname. + */ + if (i < nlabels) { + result = findrdataset(db, node, version, + dns_rdatatype_dname, 0, now, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + result = DNS_R_DNAME; + break; + } + } + + /* + * Look for an NS at the current label, unless this is the + * origin, glue is ok, or there are known to be no zone cuts. + */ + if (i != olabels && (options & DNS_DBFIND_GLUEOK) == 0 && + (options & DNS_DBFIND_NOZONECUT) == 0) + { + result = findrdataset(db, node, version, + dns_rdatatype_ns, 0, now, + rdataset, sigrdataset); + + if (result == ISC_R_SUCCESS && i == nlabels && + type == dns_rdatatype_any) + { + result = DNS_R_ZONECUT; + dns_rdataset_disassociate(rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + break; + } else if (result == ISC_R_SUCCESS) { + result = DNS_R_DELEGATION; + break; + } + } + + /* + * If the current name is not the qname, add another label + * and try again. + */ + if (i < nlabels) { + detachnode(db, &node); + node = NULL; + continue; + } + + /* + * If we're looking for ANY, we're done. + */ + if (type == dns_rdatatype_any) { + result = ISC_R_SUCCESS; + break; + } + + /* + * Look for the qtype. + */ + result = findrdataset(db, node, version, type, 0, now, rdataset, + sigrdataset); + if (result == ISC_R_SUCCESS) { + break; + } + + /* + * Look for a CNAME + */ + if (type != dns_rdatatype_cname) { + result = findrdataset(db, node, version, + dns_rdatatype_cname, 0, now, + rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + result = DNS_R_CNAME; + break; + } + } + + result = DNS_R_NXRRSET; + break; + } + + if (rdataset == &xrdataset && dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + + if (foundname != NULL) { + dns_name_copy(xname, foundname); + } + + if (nodep != NULL) { + *nodep = node; + } else if (node != NULL) { + detachnode(db, &node); + } + + return (result); +} + +static isc_result_t +find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, + dns_rdatatype_t type, unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + return (findext(db, name, version, type, options, now, nodep, foundname, + NULL, NULL, rdataset, sigrdataset)); +} + +static isc_result_t +allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int options, isc_stdtime_t now, + dns_rdatasetiter_t **iteratorp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + sdlz_rdatasetiter_t *iterator; + + REQUIRE(VALID_SDLZDB(sdlz)); + + REQUIRE(version == NULL || version == (void *)&sdlz->dummy_version || + version == sdlz->future_version); + + UNUSED(version); + UNUSED(now); + + iterator = isc_mem_get(db->mctx, sizeof(sdlz_rdatasetiter_t)); + + iterator->common.magic = DNS_RDATASETITER_MAGIC; + iterator->common.methods = &rdatasetiter_methods; + iterator->common.db = db; + iterator->common.node = NULL; + attachnode(db, node, &iterator->common.node); + iterator->common.version = version; + iterator->common.options = options; + iterator->common.now = now; + + *iteratorp = (dns_rdatasetiter_t *)iterator; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +modrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdataset_t *rdataset, unsigned int options, + dns_sdlzmodrdataset_t mod_function) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + dns_master_style_t *style = NULL; + isc_result_t result; + isc_buffer_t *buffer = NULL; + isc_mem_t *mctx; + dns_sdlznode_t *sdlznode; + char *rdatastr = NULL; + char name[DNS_NAME_MAXTEXT + 1]; + + REQUIRE(VALID_SDLZDB(sdlz)); + + if (mod_function == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + sdlznode = (dns_sdlznode_t *)node; + + UNUSED(options); + + dns_name_format(sdlznode->name, name, sizeof(name)); + + mctx = sdlz->common.mctx; + + isc_buffer_allocate(mctx, &buffer, 1024); + + result = dns_master_stylecreate(&style, 0, 0, 0, 0, 0, 0, 1, 0xffffffff, + mctx); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_master_rdatasettotext(sdlznode->name, rdataset, style, + NULL, buffer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (isc_buffer_usedlength(buffer) < 1) { + result = ISC_R_BADADDRESSFORM; + goto cleanup; + } + + rdatastr = isc_buffer_base(buffer); + if (rdatastr == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + rdatastr[isc_buffer_usedlength(buffer) - 1] = 0; + + MAYBE_LOCK(sdlz->dlzimp); + result = mod_function(name, rdatastr, sdlz->dlzimp->driverarg, + sdlz->dbdata, version); + MAYBE_UNLOCK(sdlz->dlzimp); + +cleanup: + isc_buffer_free(&buffer); + if (style != NULL) { + dns_master_styledestroy(&style, mctx); + } + + return (result); +} + +static isc_result_t +addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *addedrdataset) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + isc_result_t result; + + UNUSED(now); + UNUSED(addedrdataset); + REQUIRE(VALID_SDLZDB(sdlz)); + + if (sdlz->dlzimp->methods->addrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + result = modrdataset(db, node, version, rdataset, options, + sdlz->dlzimp->methods->addrdataset); + return (result); +} + +static isc_result_t +subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdataset_t *rdataset, unsigned int options, + dns_rdataset_t *newrdataset) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + isc_result_t result; + + UNUSED(newrdataset); + REQUIRE(VALID_SDLZDB(sdlz)); + + if (sdlz->dlzimp->methods->subtractrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + result = modrdataset(db, node, version, rdataset, options, + sdlz->dlzimp->methods->subtractrdataset); + return (result); +} + +static isc_result_t +deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdatatype_t type, dns_rdatatype_t covers) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + char name[DNS_NAME_MAXTEXT + 1]; + char b_type[DNS_RDATATYPE_FORMATSIZE]; + dns_sdlznode_t *sdlznode; + isc_result_t result; + + UNUSED(covers); + + REQUIRE(VALID_SDLZDB(sdlz)); + + if (sdlz->dlzimp->methods->delrdataset == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + sdlznode = (dns_sdlznode_t *)node; + dns_name_format(sdlznode->name, name, sizeof(name)); + dns_rdatatype_format(type, b_type, sizeof(b_type)); + + MAYBE_LOCK(sdlz->dlzimp); + result = sdlz->dlzimp->methods->delrdataset( + name, b_type, sdlz->dlzimp->driverarg, sdlz->dbdata, version); + MAYBE_UNLOCK(sdlz->dlzimp); + + return (result); +} + +static bool +issecure(dns_db_t *db) { + UNUSED(db); + + return (false); +} + +static unsigned int +nodecount(dns_db_t *db, dns_dbtree_t tree) { + UNUSED(db); + UNUSED(tree); + + return (0); +} + +static bool +ispersistent(dns_db_t *db) { + UNUSED(db); + return (true); +} + +static void +overmem(dns_db_t *db, bool over) { + UNUSED(db); + UNUSED(over); +} + +static void +settask(dns_db_t *db, isc_task_t *task) { + UNUSED(db); + UNUSED(task); +} + +/* + * getoriginnode() is used by the update code to find the + * dns_rdatatype_dnskey record for a zone + */ +static isc_result_t +getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db; + isc_result_t result; + + REQUIRE(VALID_SDLZDB(sdlz)); + if (sdlz->dlzimp->methods->newversion == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + result = getnodedata(db, &sdlz->common.origin, false, 0, NULL, NULL, + nodep); + if (result != ISC_R_SUCCESS) { + sdlz_log(ISC_LOG_ERROR, "sdlz getoriginnode failed: %s", + isc_result_totext(result)); + } + return (result); +} + +static dns_dbmethods_t sdlzdb_methods = { + attach, detach, beginload, + endload, dump, currentversion, + newversion, attachversion, closeversion, + findnode, find, findzonecut, + attachnode, detachnode, expirenode, + printnode, createiterator, findrdataset, + allrdatasets, addrdataset, subtractrdataset, + deleterdataset, issecure, nodecount, + ispersistent, overmem, settask, + getoriginnode, NULL, /* transfernode */ + NULL, /* getnsec3parameters */ + NULL, /* findnsec3node */ + NULL, /* setsigningtime */ + NULL, /* getsigningtime */ + NULL, /* resigned */ + NULL, /* isdnssec */ + NULL, /* getrrsetstats */ + NULL, /* rpz_attach */ + NULL, /* rpz_ready */ + findnodeext, findext, NULL, /* setcachestats */ + NULL, /* hashsize */ + NULL, /* nodefullname */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL, /* getservestalettl */ + NULL, /* setservestalerefresh */ + NULL, /* getservestalerefresh */ + NULL, /* setgluecachestats */ +}; + +/* + * Database Iterator Methods. These methods were "borrowed" from the SDB + * driver interface. See the SDB driver interface documentation for more info. + */ + +static void +dbiterator_destroy(dns_dbiterator_t **iteratorp) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)(*iteratorp); + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)sdlziter->common.db; + + while (!ISC_LIST_EMPTY(sdlziter->nodelist)) { + dns_sdlznode_t *node; + node = ISC_LIST_HEAD(sdlziter->nodelist); + ISC_LIST_UNLINK(sdlziter->nodelist, node, link); + isc_refcount_decrementz(&node->references); + destroynode(node); + } + + dns_db_detach(&sdlziter->common.db); + isc_mem_put(sdlz->common.mctx, sdlziter, sizeof(sdlz_dbiterator_t)); + + *iteratorp = NULL; +} + +static isc_result_t +dbiterator_first(dns_dbiterator_t *iterator) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist); + if (sdlziter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_last(dns_dbiterator_t *iterator) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + sdlziter->current = ISC_LIST_TAIL(sdlziter->nodelist); + if (sdlziter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_seek(dns_dbiterator_t *iterator, const dns_name_t *name) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + sdlziter->current = ISC_LIST_HEAD(sdlziter->nodelist); + while (sdlziter->current != NULL) { + if (dns_name_equal(sdlziter->current->name, name)) { + return (ISC_R_SUCCESS); + } + sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +dbiterator_prev(dns_dbiterator_t *iterator) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + sdlziter->current = ISC_LIST_PREV(sdlziter->current, link); + if (sdlziter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_next(dns_dbiterator_t *iterator) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + sdlziter->current = ISC_LIST_NEXT(sdlziter->current, link); + if (sdlziter->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static isc_result_t +dbiterator_current(dns_dbiterator_t *iterator, dns_dbnode_t **nodep, + dns_name_t *name) { + sdlz_dbiterator_t *sdlziter = (sdlz_dbiterator_t *)iterator; + + attachnode(iterator->db, sdlziter->current, nodep); + if (name != NULL) { + dns_name_copy(sdlziter->current->name, name); + return (ISC_R_SUCCESS); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +dbiterator_pause(dns_dbiterator_t *iterator) { + UNUSED(iterator); + return (ISC_R_SUCCESS); +} + +static isc_result_t +dbiterator_origin(dns_dbiterator_t *iterator, dns_name_t *name) { + UNUSED(iterator); + dns_name_copy(dns_rootname, name); + return (ISC_R_SUCCESS); +} + +/* + * Rdataset Methods. These methods were "borrowed" from the SDB driver + * interface. See the SDB driver interface documentation for more info. + */ + +static void +disassociate(dns_rdataset_t *rdataset) { + dns_dbnode_t *node = rdataset->private5; + dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node; + dns_db_t *db = (dns_db_t *)sdlznode->sdlz; + + detachnode(db, &node); + isc__rdatalist_disassociate(rdataset); +} + +static void +rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + dns_dbnode_t *node = source->private5; + dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node; + dns_db_t *db = (dns_db_t *)sdlznode->sdlz; + dns_dbnode_t *tempdb = NULL; + + isc__rdatalist_clone(source, target); + attachnode(db, node, &tempdb); + source->private5 = tempdb; +} + +static dns_rdatasetmethods_t rdataset_methods = { + disassociate, + isc__rdatalist_first, + isc__rdatalist_next, + isc__rdatalist_current, + rdataset_clone, + isc__rdatalist_count, + isc__rdatalist_addnoqname, + isc__rdatalist_getnoqname, + NULL, /* addclosest */ + NULL, /* getclosest */ + NULL, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL, /* getownercase */ + NULL /* addglue */ +}; + +static void +list_tordataset(dns_rdatalist_t *rdatalist, dns_db_t *db, dns_dbnode_t *node, + dns_rdataset_t *rdataset) { + /* + * The sdlz rdataset is an rdatalist with some additions. + * - private1 & private2 are used by the rdatalist. + * - private3 & private 4 are unused. + * - private5 is the node. + */ + + /* This should never fail. */ + RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == + ISC_R_SUCCESS); + + rdataset->methods = &rdataset_methods; + dns_db_attachnode(db, node, &rdataset->private5); +} + +/* + * SDLZ core methods. This is the core of the new DLZ functionality. + */ + +/*% + * Build a 'bind' database driver structure to be returned by + * either the find zone or the allow zone transfer method. + * This method is only available in this source file, it is + * not made available anywhere else. + */ + +static isc_result_t +dns_sdlzcreateDBP(isc_mem_t *mctx, void *driverarg, void *dbdata, + const dns_name_t *name, dns_rdataclass_t rdclass, + dns_db_t **dbp) { + isc_result_t result; + dns_sdlz_db_t *sdlzdb; + dns_sdlzimplementation_t *imp; + + /* check that things are as we expect */ + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(name != NULL); + + imp = (dns_sdlzimplementation_t *)driverarg; + + /* allocate and zero memory for driver structure */ + sdlzdb = isc_mem_get(mctx, sizeof(dns_sdlz_db_t)); + memset(sdlzdb, 0, sizeof(dns_sdlz_db_t)); + + /* initialize and set origin */ + dns_name_init(&sdlzdb->common.origin, NULL); + result = dns_name_dupwithoffsets(name, mctx, &sdlzdb->common.origin); + if (result != ISC_R_SUCCESS) { + goto mem_cleanup; + } + + /* set the rest of the database structure attributes */ + sdlzdb->dlzimp = imp; + sdlzdb->common.methods = &sdlzdb_methods; + sdlzdb->common.attributes = 0; + sdlzdb->common.rdclass = rdclass; + sdlzdb->common.mctx = NULL; + sdlzdb->dbdata = dbdata; + isc_refcount_init(&sdlzdb->references, 1); + + /* attach to the memory context */ + isc_mem_attach(mctx, &sdlzdb->common.mctx); + + /* mark structure as valid */ + sdlzdb->common.magic = DNS_DB_MAGIC; + sdlzdb->common.impmagic = SDLZDB_MAGIC; + *dbp = (dns_db_t *)sdlzdb; + + return (result); +mem_cleanup: + isc_mem_put(mctx, sdlzdb, sizeof(dns_sdlz_db_t)); + return (result); +} + +static isc_result_t +dns_sdlzallowzonexfr(void *driverarg, void *dbdata, isc_mem_t *mctx, + dns_rdataclass_t rdclass, const dns_name_t *name, + const isc_sockaddr_t *clientaddr, dns_db_t **dbp) { + isc_buffer_t b; + isc_buffer_t b2; + char namestr[DNS_NAME_MAXTEXT + 1]; + char clientstr[(sizeof "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255." + "255") + + 1]; + isc_netaddr_t netaddr; + isc_result_t result; + dns_sdlzimplementation_t *imp; + + /* + * Perform checks to make sure data is as we expect it to be. + */ + REQUIRE(driverarg != NULL); + REQUIRE(name != NULL); + REQUIRE(clientaddr != NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + + imp = (dns_sdlzimplementation_t *)driverarg; + + /* Convert DNS name to ascii text */ + isc_buffer_init(&b, namestr, sizeof(namestr)); + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_putuint8(&b, 0); + + /* convert client address to ascii text */ + isc_buffer_init(&b2, clientstr, sizeof(clientstr)); + isc_netaddr_fromsockaddr(&netaddr, clientaddr); + result = isc_netaddr_totext(&netaddr, &b2); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_putuint8(&b2, 0); + + /* make sure strings are always lowercase */ + dns_sdlz_tolower(namestr); + dns_sdlz_tolower(clientstr); + + /* Call SDLZ driver's find zone method */ + if (imp->methods->allowzonexfr != NULL) { + isc_result_t rresult = ISC_R_SUCCESS; + + MAYBE_LOCK(imp); + result = imp->methods->allowzonexfr(imp->driverarg, dbdata, + namestr, clientstr); + MAYBE_UNLOCK(imp); + /* + * if zone is supported and transfers are (or might be) + * allowed, build a 'bind' database driver + */ + if (result == ISC_R_SUCCESS || result == ISC_R_DEFAULT) { + rresult = dns_sdlzcreateDBP(mctx, driverarg, dbdata, + name, rdclass, dbp); + } + if (rresult != ISC_R_SUCCESS) { + result = rresult; + } + return (result); + } + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +dns_sdlzcreate(isc_mem_t *mctx, const char *dlzname, unsigned int argc, + char *argv[], void *driverarg, void **dbdata) { + dns_sdlzimplementation_t *imp; + isc_result_t result = ISC_R_NOTFOUND; + + /* Write debugging message to log */ + sdlz_log(ISC_LOG_DEBUG(2), "Loading SDLZ driver."); + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(driverarg != NULL); + REQUIRE(dlzname != NULL); + REQUIRE(dbdata != NULL); + UNUSED(mctx); + + imp = driverarg; + + /* If the create method exists, call it. */ + if (imp->methods->create != NULL) { + MAYBE_LOCK(imp); + result = imp->methods->create(dlzname, argc, argv, + imp->driverarg, dbdata); + MAYBE_UNLOCK(imp); + } + + /* Write debugging message to log */ + if (result == ISC_R_SUCCESS) { + sdlz_log(ISC_LOG_DEBUG(2), "SDLZ driver loaded successfully."); + } else { + sdlz_log(ISC_LOG_ERROR, "SDLZ driver failed to load."); + } + + return (result); +} + +static void +dns_sdlzdestroy(void *driverdata, void **dbdata) { + dns_sdlzimplementation_t *imp; + + /* Write debugging message to log */ + sdlz_log(ISC_LOG_DEBUG(2), "Unloading SDLZ driver."); + + imp = driverdata; + + /* If the destroy method exists, call it. */ + if (imp->methods->destroy != NULL) { + MAYBE_LOCK(imp); + imp->methods->destroy(imp->driverarg, dbdata); + MAYBE_UNLOCK(imp); + } +} + +static isc_result_t +dns_sdlzfindzone(void *driverarg, void *dbdata, isc_mem_t *mctx, + dns_rdataclass_t rdclass, const dns_name_t *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo, + dns_db_t **dbp) { + isc_buffer_t b; + char namestr[DNS_NAME_MAXTEXT + 1]; + isc_result_t result; + dns_sdlzimplementation_t *imp; + + /* + * Perform checks to make sure data is as we expect it to be. + */ + REQUIRE(driverarg != NULL); + REQUIRE(name != NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + + imp = (dns_sdlzimplementation_t *)driverarg; + + /* Convert DNS name to ascii text */ + isc_buffer_init(&b, namestr, sizeof(namestr)); + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_putuint8(&b, 0); + + /* make sure strings are always lowercase */ + dns_sdlz_tolower(namestr); + + /* Call SDLZ driver's find zone method */ + MAYBE_LOCK(imp); + result = imp->methods->findzone(imp->driverarg, dbdata, namestr, + methods, clientinfo); + MAYBE_UNLOCK(imp); + + /* + * if zone is supported build a 'bind' database driver + * structure to return + */ + if (result == ISC_R_SUCCESS) { + result = dns_sdlzcreateDBP(mctx, driverarg, dbdata, name, + rdclass, dbp); + } + + return (result); +} + +static isc_result_t +dns_sdlzconfigure(void *driverarg, void *dbdata, dns_view_t *view, + dns_dlzdb_t *dlzdb) { + isc_result_t result; + dns_sdlzimplementation_t *imp; + + REQUIRE(driverarg != NULL); + + imp = (dns_sdlzimplementation_t *)driverarg; + + /* Call SDLZ driver's configure method */ + if (imp->methods->configure != NULL) { + MAYBE_LOCK(imp); + result = imp->methods->configure(view, dlzdb, imp->driverarg, + dbdata); + MAYBE_UNLOCK(imp); + } else { + result = ISC_R_SUCCESS; + } + + return (result); +} + +static bool +dns_sdlzssumatch(const dns_name_t *signer, const dns_name_t *name, + const isc_netaddr_t *tcpaddr, dns_rdatatype_t type, + const dst_key_t *key, void *driverarg, void *dbdata) { + dns_sdlzimplementation_t *imp; + char b_signer[DNS_NAME_FORMATSIZE]; + char b_name[DNS_NAME_FORMATSIZE]; + char b_addr[ISC_NETADDR_FORMATSIZE]; + char b_type[DNS_RDATATYPE_FORMATSIZE]; + char b_key[DST_KEY_FORMATSIZE]; + isc_buffer_t *tkey_token = NULL; + isc_region_t token_region = { NULL, 0 }; + uint32_t token_len = 0; + bool ret; + + REQUIRE(driverarg != NULL); + + imp = (dns_sdlzimplementation_t *)driverarg; + if (imp->methods->ssumatch == NULL) { + return (false); + } + + /* + * Format the request elements. sdlz operates on strings, not + * structures + */ + if (signer != NULL) { + dns_name_format(signer, b_signer, sizeof(b_signer)); + } else { + b_signer[0] = 0; + } + + dns_name_format(name, b_name, sizeof(b_name)); + + if (tcpaddr != NULL) { + isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr)); + } else { + b_addr[0] = 0; + } + + dns_rdatatype_format(type, b_type, sizeof(b_type)); + + if (key != NULL) { + dst_key_format(key, b_key, sizeof(b_key)); + tkey_token = dst_key_tkeytoken(key); + } else { + b_key[0] = 0; + } + + if (tkey_token != NULL) { + isc_buffer_region(tkey_token, &token_region); + token_len = token_region.length; + } + + MAYBE_LOCK(imp); + ret = imp->methods->ssumatch(b_signer, b_name, b_addr, b_type, b_key, + token_len, + token_len != 0 ? token_region.base : NULL, + imp->driverarg, dbdata); + MAYBE_UNLOCK(imp); + return (ret); +} + +static dns_dlzmethods_t sdlzmethods = { dns_sdlzcreate, dns_sdlzdestroy, + dns_sdlzfindzone, dns_sdlzallowzonexfr, + dns_sdlzconfigure, dns_sdlzssumatch }; + +/* + * Public functions. + */ + +isc_result_t +dns_sdlz_putrr(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl, + const char *data) { + dns_rdatalist_t *rdatalist; + dns_rdata_t *rdata; + dns_rdatatype_t typeval; + isc_consttextregion_t r; + isc_buffer_t b; + isc_buffer_t *rdatabuf = NULL; + isc_lex_t *lex; + isc_result_t result; + unsigned int size; + isc_mem_t *mctx; + const dns_name_t *origin; + + REQUIRE(VALID_SDLZLOOKUP(lookup)); + REQUIRE(type != NULL); + REQUIRE(data != NULL); + + mctx = lookup->sdlz->common.mctx; + + r.base = type; + r.length = strlen(type); + result = dns_rdatatype_fromtext(&typeval, (void *)&r); + if (result != ISC_R_SUCCESS) { + return (result); + } + + rdatalist = ISC_LIST_HEAD(lookup->lists); + while (rdatalist != NULL) { + if (rdatalist->type == typeval) { + break; + } + rdatalist = ISC_LIST_NEXT(rdatalist, link); + } + + if (rdatalist == NULL) { + rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t)); + dns_rdatalist_init(rdatalist); + rdatalist->rdclass = lookup->sdlz->common.rdclass; + rdatalist->type = typeval; + rdatalist->ttl = ttl; + ISC_LIST_APPEND(lookup->lists, rdatalist, link); + } else if (rdatalist->ttl > ttl) { + /* + * BIND9 doesn't enforce all RRs in an RRset + * having the same TTL, as per RFC 2136, + * section 7.12. If a DLZ backend has + * different TTLs, then the best + * we can do is return the lowest. + */ + rdatalist->ttl = ttl; + } + + rdata = isc_mem_get(mctx, sizeof(dns_rdata_t)); + dns_rdata_init(rdata); + + if ((lookup->sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) { + origin = &lookup->sdlz->common.origin; + } else { + origin = dns_rootname; + } + + lex = NULL; + result = isc_lex_create(mctx, 64, &lex); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + size = initial_size(data); + do { + isc_buffer_constinit(&b, data, strlen(data)); + isc_buffer_add(&b, strlen(data)); + + result = isc_lex_openbuffer(lex, &b); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + rdatabuf = NULL; + isc_buffer_allocate(mctx, &rdatabuf, size); + + result = dns_rdata_fromtext(rdata, rdatalist->rdclass, + rdatalist->type, lex, origin, false, + mctx, rdatabuf, &lookup->callbacks); + if (result != ISC_R_SUCCESS) { + isc_buffer_free(&rdatabuf); + } + if (size >= 65535) { + break; + } + size *= 2; + if (size >= 65535) { + size = 65535; + } + } while (result == ISC_R_NOSPACE); + + if (result != ISC_R_SUCCESS) { + result = DNS_R_SERVFAIL; + goto failure; + } + + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + ISC_LIST_APPEND(lookup->buffers, rdatabuf, link); + + if (lex != NULL) { + isc_lex_destroy(&lex); + } + + return (ISC_R_SUCCESS); + +failure: + if (rdatabuf != NULL) { + isc_buffer_free(&rdatabuf); + } + if (lex != NULL) { + isc_lex_destroy(&lex); + } + isc_mem_put(mctx, rdata, sizeof(dns_rdata_t)); + + return (result); +} + +isc_result_t +dns_sdlz_putnamedrr(dns_sdlzallnodes_t *allnodes, const char *name, + const char *type, dns_ttl_t ttl, const char *data) { + dns_name_t *newname; + const dns_name_t *origin; + dns_fixedname_t fnewname; + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)allnodes->common.db; + dns_sdlznode_t *sdlznode; + isc_mem_t *mctx = sdlz->common.mctx; + isc_buffer_t b; + isc_result_t result; + + newname = dns_fixedname_initname(&fnewname); + + if ((sdlz->dlzimp->flags & DNS_SDLZFLAG_RELATIVERDATA) != 0) { + origin = &sdlz->common.origin; + } else { + origin = dns_rootname; + } + isc_buffer_constinit(&b, name, strlen(name)); + isc_buffer_add(&b, strlen(name)); + + result = dns_name_fromtext(newname, &b, origin, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (allnodes->common.relative_names) { + /* All names are relative to the root */ + unsigned int nlabels = dns_name_countlabels(newname); + dns_name_getlabelsequence(newname, 0, nlabels - 1, newname); + } + + sdlznode = ISC_LIST_HEAD(allnodes->nodelist); + if (sdlznode == NULL || !dns_name_equal(sdlznode->name, newname)) { + sdlznode = NULL; + result = createnode(sdlz, &sdlznode); + if (result != ISC_R_SUCCESS) { + return (result); + } + sdlznode->name = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(sdlznode->name, NULL); + dns_name_dup(newname, mctx, sdlznode->name); + ISC_LIST_PREPEND(allnodes->nodelist, sdlznode, link); + if (allnodes->origin == NULL && + dns_name_equal(newname, &sdlz->common.origin)) + { + allnodes->origin = sdlznode; + } + } + return (dns_sdlz_putrr(sdlznode, type, ttl, data)); +} + +isc_result_t +dns_sdlz_putsoa(dns_sdlzlookup_t *lookup, const char *mname, const char *rname, + uint32_t serial) { + char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7]; + int n; + + REQUIRE(mname != NULL); + REQUIRE(rname != NULL); + + n = snprintf(str, sizeof str, "%s %s %u %u %u %u %u", mname, rname, + serial, SDLZ_DEFAULT_REFRESH, SDLZ_DEFAULT_RETRY, + SDLZ_DEFAULT_EXPIRE, SDLZ_DEFAULT_MINIMUM); + if (n >= (int)sizeof(str) || n < 0) { + return (ISC_R_NOSPACE); + } + return (dns_sdlz_putrr(lookup, "SOA", SDLZ_DEFAULT_TTL, str)); +} + +isc_result_t +dns_sdlzregister(const char *drivername, const dns_sdlzmethods_t *methods, + void *driverarg, unsigned int flags, isc_mem_t *mctx, + dns_sdlzimplementation_t **sdlzimp) { + dns_sdlzimplementation_t *imp; + isc_result_t result; + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(drivername != NULL); + REQUIRE(methods != NULL); + REQUIRE(methods->findzone != NULL); + REQUIRE(methods->lookup != NULL); + REQUIRE(mctx != NULL); + REQUIRE(sdlzimp != NULL && *sdlzimp == NULL); + REQUIRE((flags & + ~(DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE)) == 0); + + /* Write debugging message to log */ + sdlz_log(ISC_LOG_DEBUG(2), "Registering SDLZ driver '%s'", drivername); + + /* + * Allocate memory for a sdlz_implementation object. Error if + * we cannot. + */ + imp = isc_mem_get(mctx, sizeof(dns_sdlzimplementation_t)); + + /* Make sure memory region is set to all 0's */ + memset(imp, 0, sizeof(dns_sdlzimplementation_t)); + + /* Store the data passed into this method */ + imp->methods = methods; + imp->driverarg = driverarg; + imp->flags = flags; + imp->mctx = NULL; + + /* attach the new sdlz_implementation object to a memory context */ + isc_mem_attach(mctx, &imp->mctx); + + /* + * initialize the driver lock, error if we cannot + * (used if a driver does not support multiple threads) + */ + isc_mutex_init(&imp->driverlock); + + imp->dlz_imp = NULL; + + /* + * register the DLZ driver. Pass in our "extra" sdlz information as + * a driverarg. (that's why we stored the passed in driver arg in our + * sdlz_implementation structure) Also, store the dlz_implementation + * structure in our sdlz_implementation. + */ + result = dns_dlzregister(drivername, &sdlzmethods, imp, mctx, + &imp->dlz_imp); + + /* if registration fails, cleanup and get outta here. */ + if (result != ISC_R_SUCCESS) { + goto cleanup_mutex; + } + + *sdlzimp = imp; + + return (ISC_R_SUCCESS); + +cleanup_mutex: + /* destroy the driver lock, we don't need it anymore */ + isc_mutex_destroy(&imp->driverlock); + + /* + * return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t)); + return (result); +} + +void +dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp) { + dns_sdlzimplementation_t *imp; + + /* Write debugging message to log */ + sdlz_log(ISC_LOG_DEBUG(2), "Unregistering SDLZ driver."); + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(sdlzimp != NULL && *sdlzimp != NULL); + + imp = *sdlzimp; + *sdlzimp = NULL; + + /* Unregister the DLZ driver implementation */ + dns_dlzunregister(&imp->dlz_imp); + + /* destroy the driver lock, we don't need it anymore */ + isc_mutex_destroy(&imp->driverlock); + + /* + * return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dns_sdlzimplementation_t)); +} + +isc_result_t +dns_sdlz_setdb(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass, + const dns_name_t *name, dns_db_t **dbp) { + isc_result_t result; + + result = dns_sdlzcreateDBP(dlzdatabase->mctx, + dlzdatabase->implementation->driverarg, + dlzdatabase->dbdata, name, rdclass, dbp); + return (result); +} diff --git a/lib/dns/soa.c b/lib/dns/soa.c new file mode 100644 index 0000000..ca5ca88 --- /dev/null +++ b/lib/dns/soa.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include + +#include +#include +#include + +static uint32_t +decode_uint32(unsigned char *p) { + return (((uint32_t)p[0] << 24) + ((uint32_t)p[1] << 16) + + ((uint32_t)p[2] << 8) + ((uint32_t)p[3] << 0)); +} + +static void +encode_uint32(uint32_t val, unsigned char *p) { + p[0] = (uint8_t)(val >> 24); + p[1] = (uint8_t)(val >> 16); + p[2] = (uint8_t)(val >> 8); + p[3] = (uint8_t)(val >> 0); +} + +static uint32_t +soa_get(dns_rdata_t *rdata, int offset) { + INSIST(rdata->type == dns_rdatatype_soa); + /* + * Locate the field within the SOA RDATA based + * on its position relative to the end of the data. + * + * This is a bit of a kludge, but the alternative approach of + * using dns_rdata_tostruct() and dns_rdata_fromstruct() would + * involve a lot of unnecessary work (like building domain + * names and allocating temporary memory) when all we really + * want to do is to get 32 bits of fixed-sized data. + */ + INSIST(rdata->length >= 20); + INSIST(offset >= 0 && offset <= 16); + return (decode_uint32(rdata->data + rdata->length - 20 + offset)); +} + +isc_result_t +dns_soa_buildrdata(const dns_name_t *origin, const dns_name_t *contact, + dns_rdataclass_t rdclass, uint32_t serial, uint32_t refresh, + uint32_t retry, uint32_t expire, uint32_t minimum, + unsigned char *buffer, dns_rdata_t *rdata) { + dns_rdata_soa_t soa; + isc_buffer_t rdatabuf; + + REQUIRE(origin != NULL); + REQUIRE(contact != NULL); + + memset(buffer, 0, DNS_SOA_BUFFERSIZE); + isc_buffer_init(&rdatabuf, buffer, DNS_SOA_BUFFERSIZE); + + soa.common.rdtype = dns_rdatatype_soa; + soa.common.rdclass = rdclass; + soa.mctx = NULL; + soa.serial = serial; + soa.refresh = refresh; + soa.retry = retry; + soa.expire = expire; + soa.minimum = minimum; + dns_name_init(&soa.origin, NULL); + dns_name_clone(origin, &soa.origin); + dns_name_init(&soa.contact, NULL); + dns_name_clone(contact, &soa.contact); + + return (dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_soa, &soa, + &rdatabuf)); +} + +uint32_t +dns_soa_getserial(dns_rdata_t *rdata) { + return (soa_get(rdata, 0)); +} +uint32_t +dns_soa_getrefresh(dns_rdata_t *rdata) { + return (soa_get(rdata, 4)); +} +uint32_t +dns_soa_getretry(dns_rdata_t *rdata) { + return (soa_get(rdata, 8)); +} +uint32_t +dns_soa_getexpire(dns_rdata_t *rdata) { + return (soa_get(rdata, 12)); +} +uint32_t +dns_soa_getminimum(dns_rdata_t *rdata) { + return (soa_get(rdata, 16)); +} + +static void +soa_set(dns_rdata_t *rdata, uint32_t val, int offset) { + INSIST(rdata->type == dns_rdatatype_soa); + INSIST(rdata->length >= 20); + INSIST(offset >= 0 && offset <= 16); + encode_uint32(val, rdata->data + rdata->length - 20 + offset); +} + +void +dns_soa_setserial(uint32_t val, dns_rdata_t *rdata) { + soa_set(rdata, val, 0); +} +void +dns_soa_setrefresh(uint32_t val, dns_rdata_t *rdata) { + soa_set(rdata, val, 4); +} +void +dns_soa_setretry(uint32_t val, dns_rdata_t *rdata) { + soa_set(rdata, val, 8); +} +void +dns_soa_setexpire(uint32_t val, dns_rdata_t *rdata) { + soa_set(rdata, val, 12); +} +void +dns_soa_setminimum(uint32_t val, dns_rdata_t *rdata) { + soa_set(rdata, val, 16); +} diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c new file mode 100644 index 0000000..581cdcd --- /dev/null +++ b/lib/dns/ssu.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define SSUTABLEMAGIC ISC_MAGIC('S', 'S', 'U', 'T') +#define VALID_SSUTABLE(table) ISC_MAGIC_VALID(table, SSUTABLEMAGIC) + +#define SSURULEMAGIC ISC_MAGIC('S', 'S', 'U', 'R') +#define VALID_SSURULE(table) ISC_MAGIC_VALID(table, SSURULEMAGIC) + +struct dns_ssurule { + unsigned int magic; + bool grant; /*%< is this a grant or a deny? */ + dns_ssumatchtype_t matchtype; /*%< which type of pattern match? */ + dns_name_t *identity; /*%< the identity to match */ + dns_name_t *name; /*%< the name being updated */ + unsigned int ntypes; /*%< number of data types covered */ + dns_ssuruletype_t *types; /*%< the data types. Can include */ + /* ANY. if NULL, defaults to all */ + /* types except SIG, SOA, and NS */ + ISC_LINK(dns_ssurule_t) link; +}; + +struct dns_ssutable { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + dns_dlzdb_t *dlzdatabase; + ISC_LIST(dns_ssurule_t) rules; +}; + +void +dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) { + dns_ssutable_t *table; + + REQUIRE(tablep != NULL && *tablep == NULL); + REQUIRE(mctx != NULL); + + table = isc_mem_get(mctx, sizeof(dns_ssutable_t)); + isc_refcount_init(&table->references, 1); + table->mctx = NULL; + isc_mem_attach(mctx, &table->mctx); + ISC_LIST_INIT(table->rules); + table->magic = SSUTABLEMAGIC; + *tablep = table; +} + +static void +destroy(dns_ssutable_t *table) { + isc_mem_t *mctx; + + REQUIRE(VALID_SSUTABLE(table)); + + mctx = table->mctx; + while (!ISC_LIST_EMPTY(table->rules)) { + dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules); + if (rule->identity != NULL) { + dns_name_free(rule->identity, mctx); + isc_mem_put(mctx, rule->identity, + sizeof(*rule->identity)); + } + if (rule->name != NULL) { + dns_name_free(rule->name, mctx); + isc_mem_put(mctx, rule->name, sizeof(*rule->name)); + } + if (rule->types != NULL) { + isc_mem_put(mctx, rule->types, + rule->ntypes * sizeof(*rule->types)); + } + ISC_LIST_UNLINK(table->rules, rule, link); + rule->magic = 0; + isc_mem_put(mctx, rule, sizeof(dns_ssurule_t)); + } + isc_refcount_destroy(&table->references); + table->magic = 0; + isc_mem_putanddetach(&table->mctx, table, sizeof(dns_ssutable_t)); +} + +void +dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp) { + REQUIRE(VALID_SSUTABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_ssutable_detach(dns_ssutable_t **tablep) { + dns_ssutable_t *table; + + REQUIRE(tablep != NULL); + table = *tablep; + *tablep = NULL; + REQUIRE(VALID_SSUTABLE(table)); + + if (isc_refcount_decrement(&table->references) == 1) { + destroy(table); + } +} + +void +dns_ssutable_addrule(dns_ssutable_t *table, bool grant, + const dns_name_t *identity, dns_ssumatchtype_t matchtype, + const dns_name_t *name, unsigned int ntypes, + dns_ssuruletype_t *types) { + dns_ssurule_t *rule; + isc_mem_t *mctx; + + REQUIRE(VALID_SSUTABLE(table)); + REQUIRE(dns_name_isabsolute(identity)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(matchtype <= dns_ssumatchtype_max); + if (matchtype == dns_ssumatchtype_wildcard) { + REQUIRE(dns_name_iswildcard(name)); + } + if (ntypes > 0) { + REQUIRE(types != NULL); + } + + mctx = table->mctx; + rule = isc_mem_get(mctx, sizeof(*rule)); + + rule->identity = NULL; + rule->name = NULL; + rule->types = NULL; + + rule->grant = grant; + + rule->identity = isc_mem_get(mctx, sizeof(*rule->identity)); + dns_name_init(rule->identity, NULL); + dns_name_dup(identity, mctx, rule->identity); + + rule->name = isc_mem_get(mctx, sizeof(*rule->name)); + dns_name_init(rule->name, NULL); + dns_name_dup(name, mctx, rule->name); + + rule->matchtype = matchtype; + + rule->ntypes = ntypes; + if (ntypes > 0) { + rule->types = isc_mem_get(mctx, ntypes * sizeof(*rule->types)); + memmove(rule->types, types, ntypes * sizeof(*rule->types)); + } else { + rule->types = NULL; + } + + rule->magic = SSURULEMAGIC; + ISC_LIST_INITANDAPPEND(table->rules, rule, link); +} + +static bool +isusertype(dns_rdatatype_t type) { + return (type != dns_rdatatype_ns && type != dns_rdatatype_soa && + type != dns_rdatatype_rrsig); +} + +static void +reverse_from_address(dns_name_t *tcpself, const isc_netaddr_t *tcpaddr) { + char buf[16 * 4 + sizeof("IP6.ARPA.")]; + isc_result_t result; + const unsigned char *ap; + isc_buffer_t b; + unsigned long l; + + switch (tcpaddr->family) { + case AF_INET: + l = ntohl(tcpaddr->type.in.s_addr); + result = snprintf(buf, sizeof(buf), + "%lu.%lu.%lu.%lu.IN-ADDR.ARPA.", + (l >> 0) & 0xff, (l >> 8) & 0xff, + (l >> 16) & 0xff, (l >> 24) & 0xff); + RUNTIME_CHECK(result < sizeof(buf)); + break; + case AF_INET6: + ap = tcpaddr->type.in6.s6_addr; + result = snprintf( + buf, sizeof(buf), + "%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x." + "IP6.ARPA.", + ap[15] & 0x0f, (ap[15] >> 4) & 0x0f, ap[14] & 0x0f, + (ap[14] >> 4) & 0x0f, ap[13] & 0x0f, + (ap[13] >> 4) & 0x0f, ap[12] & 0x0f, + (ap[12] >> 4) & 0x0f, ap[11] & 0x0f, + (ap[11] >> 4) & 0x0f, ap[10] & 0x0f, + (ap[10] >> 4) & 0x0f, ap[9] & 0x0f, (ap[9] >> 4) & 0x0f, + ap[8] & 0x0f, (ap[8] >> 4) & 0x0f, ap[7] & 0x0f, + (ap[7] >> 4) & 0x0f, ap[6] & 0x0f, (ap[6] >> 4) & 0x0f, + ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f, + (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f, + ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f, + (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f); + RUNTIME_CHECK(result < sizeof(buf)); + break; + default: + UNREACHABLE(); + } + isc_buffer_init(&b, buf, strlen(buf)); + isc_buffer_add(&b, strlen(buf)); + result = dns_name_fromtext(tcpself, &b, dns_rootname, 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +stf_from_address(dns_name_t *stfself, const isc_netaddr_t *tcpaddr) { + char buf[sizeof("X.X.X.X.Y.Y.Y.Y.2.0.0.2.IP6.ARPA.")]; + isc_result_t result; + const unsigned char *ap; + isc_buffer_t b; + unsigned long l; + + switch (tcpaddr->family) { + case AF_INET: + l = ntohl(tcpaddr->type.in.s_addr); + result = snprintf(buf, sizeof(buf), + "%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx" + "2.0.0.2.IP6.ARPA.", + l & 0xf, (l >> 4) & 0xf, (l >> 8) & 0xf, + (l >> 12) & 0xf, (l >> 16) & 0xf, + (l >> 20) & 0xf, (l >> 24) & 0xf, + (l >> 28) & 0xf); + RUNTIME_CHECK(result < sizeof(buf)); + break; + case AF_INET6: + ap = tcpaddr->type.in6.s6_addr; + result = snprintf( + buf, sizeof(buf), + "%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.IP6.ARPA.", + ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f, + (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f, + ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f, + (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f); + RUNTIME_CHECK(result < sizeof(buf)); + break; + default: + UNREACHABLE(); + } + isc_buffer_init(&b, buf, strlen(buf)); + isc_buffer_add(&b, strlen(buf)); + result = dns_name_fromtext(stfself, &b, dns_rootname, 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +bool +dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *addr, + bool tcp, dns_aclenv_t *env, dns_rdatatype_t type, + const dns_name_t *target, const dst_key_t *key, + const dns_ssurule_t **rulep) { + dns_fixedname_t fixed; + dns_name_t *stfself; + dns_name_t *tcpself; + dns_name_t *wildcard; + dns_ssurule_t *rule; + const dns_name_t *tname; + int match; + isc_result_t result; + unsigned int i; + + REQUIRE(VALID_SSUTABLE(table)); + REQUIRE(signer == NULL || dns_name_isabsolute(signer)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(addr == NULL || env != NULL); + + if (signer == NULL && addr == NULL) { + return (false); + } + + for (rule = ISC_LIST_HEAD(table->rules); rule != NULL; + rule = ISC_LIST_NEXT(rule, link)) + { + switch (rule->matchtype) { + case dns_ssumatchtype_local: + case dns_ssumatchtype_name: + case dns_ssumatchtype_self: + case dns_ssumatchtype_selfsub: + case dns_ssumatchtype_selfwild: + case dns_ssumatchtype_subdomain: + case dns_ssumatchtype_wildcard: + if (signer == NULL) { + continue; + } + if (dns_name_iswildcard(rule->identity)) { + if (!dns_name_matcheswildcard(signer, + rule->identity)) + { + continue; + } + } else { + if (!dns_name_equal(signer, rule->identity)) { + continue; + } + } + break; + case dns_ssumatchtype_selfkrb5: + case dns_ssumatchtype_selfms: + case dns_ssumatchtype_selfsubkrb5: + case dns_ssumatchtype_selfsubms: + case dns_ssumatchtype_subdomainkrb5: + case dns_ssumatchtype_subdomainms: + case dns_ssumatchtype_subdomainselfkrb5rhs: + case dns_ssumatchtype_subdomainselfmsrhs: + if (signer == NULL) { + continue; + } + break; + case dns_ssumatchtype_tcpself: + case dns_ssumatchtype_6to4self: + if (!tcp || addr == NULL) { + continue; + } + break; + case dns_ssumatchtype_external: + case dns_ssumatchtype_dlz: + break; + } + + switch (rule->matchtype) { + case dns_ssumatchtype_name: + if (!dns_name_equal(name, rule->name)) { + continue; + } + break; + case dns_ssumatchtype_subdomain: + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + break; + case dns_ssumatchtype_local: + if (addr == NULL) { + continue; + } + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + RWLOCK(&env->rwlock, isc_rwlocktype_read); + dns_acl_match(addr, NULL, env->localhost, NULL, &match, + NULL); + RWUNLOCK(&env->rwlock, isc_rwlocktype_read); + if (match == 0) { + if (signer != NULL) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_SSU, + ISC_LOG_WARNING, + "update-policy local: " + "match on session " + "key not from " + "localhost"); + } + continue; + } + break; + case dns_ssumatchtype_wildcard: + if (!dns_name_matcheswildcard(name, rule->name)) { + continue; + } + break; + case dns_ssumatchtype_self: + if (!dns_name_equal(signer, name)) { + continue; + } + break; + case dns_ssumatchtype_selfsub: + if (!dns_name_issubdomain(name, signer)) { + continue; + } + break; + case dns_ssumatchtype_selfwild: + wildcard = dns_fixedname_initname(&fixed); + result = dns_name_concatenate(dns_wildcardname, signer, + wildcard, NULL); + if (result != ISC_R_SUCCESS) { + continue; + } + if (!dns_name_matcheswildcard(name, wildcard)) { + continue; + } + break; + case dns_ssumatchtype_selfkrb5: + if (dst_gssapi_identitymatchesrealmkrb5( + signer, name, rule->identity, false)) + { + break; + } + continue; + case dns_ssumatchtype_selfms: + if (dst_gssapi_identitymatchesrealmms( + signer, name, rule->identity, false)) + { + break; + } + continue; + case dns_ssumatchtype_selfsubkrb5: + if (dst_gssapi_identitymatchesrealmkrb5( + signer, name, rule->identity, true)) + { + break; + } + continue; + case dns_ssumatchtype_selfsubms: + if (dst_gssapi_identitymatchesrealmms( + signer, name, rule->identity, true)) + { + break; + } + continue; + case dns_ssumatchtype_subdomainkrb5: + case dns_ssumatchtype_subdomainselfkrb5rhs: + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + tname = NULL; + switch (rule->matchtype) { + case dns_ssumatchtype_subdomainselfkrb5rhs: + if (type == dns_rdatatype_ptr) { + tname = target; + } + if (type == dns_rdatatype_srv) { + tname = target; + } + break; + default: + break; + } + if (dst_gssapi_identitymatchesrealmkrb5( + signer, tname, rule->identity, false)) + { + break; + } + continue; + case dns_ssumatchtype_subdomainms: + case dns_ssumatchtype_subdomainselfmsrhs: + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + tname = NULL; + switch (rule->matchtype) { + case dns_ssumatchtype_subdomainselfmsrhs: + if (type == dns_rdatatype_ptr) { + tname = target; + } + if (type == dns_rdatatype_srv) { + tname = target; + } + break; + default: + break; + } + if (dst_gssapi_identitymatchesrealmms( + signer, tname, rule->identity, false)) + { + break; + } + continue; + case dns_ssumatchtype_tcpself: + tcpself = dns_fixedname_initname(&fixed); + reverse_from_address(tcpself, addr); + if (dns_name_iswildcard(rule->identity)) { + if (!dns_name_matcheswildcard(tcpself, + rule->identity)) + { + continue; + } + } else { + if (!dns_name_equal(tcpself, rule->identity)) { + continue; + } + } + if (!dns_name_equal(tcpself, name)) { + continue; + } + break; + case dns_ssumatchtype_6to4self: + stfself = dns_fixedname_initname(&fixed); + stf_from_address(stfself, addr); + if (dns_name_iswildcard(rule->identity)) { + if (!dns_name_matcheswildcard(stfself, + rule->identity)) + { + continue; + } + } else { + if (!dns_name_equal(stfself, rule->identity)) { + continue; + } + } + if (!dns_name_equal(stfself, name)) { + continue; + } + break; + case dns_ssumatchtype_external: + if (!dns_ssu_external_match(rule->identity, signer, + name, addr, type, key, + table->mctx)) + { + continue; + } + break; + case dns_ssumatchtype_dlz: + if (!dns_dlz_ssumatch(table->dlzdatabase, signer, name, + addr, type, key)) + { + continue; + } + break; + } + + if (rule->ntypes == 0) { + /* + * If this is a DLZ rule, then the DLZ ssu + * checks will have already checked the type. + */ + if (rule->matchtype != dns_ssumatchtype_dlz && + !isusertype(type)) + { + continue; + } + } else { + for (i = 0; i < rule->ntypes; i++) { + if (rule->types[i].type == dns_rdatatype_any || + rule->types[i].type == type) + { + break; + } + } + if (i == rule->ntypes) { + continue; + } + } + if (rule->grant && rulep != NULL) { + *rulep = rule; + } + return (rule->grant); + } + + return (false); +} + +bool +dns_ssurule_isgrant(const dns_ssurule_t *rule) { + REQUIRE(VALID_SSURULE(rule)); + return (rule->grant); +} + +dns_name_t * +dns_ssurule_identity(const dns_ssurule_t *rule) { + REQUIRE(VALID_SSURULE(rule)); + return (rule->identity); +} + +unsigned int +dns_ssurule_matchtype(const dns_ssurule_t *rule) { + REQUIRE(VALID_SSURULE(rule)); + return (rule->matchtype); +} + +dns_name_t * +dns_ssurule_name(const dns_ssurule_t *rule) { + REQUIRE(VALID_SSURULE(rule)); + return (rule->name); +} + +unsigned int +dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) { + REQUIRE(VALID_SSURULE(rule)); + REQUIRE(types != NULL && *types != NULL); + *types = rule->types; + return (rule->ntypes); +} + +unsigned int +dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type) { + unsigned int i; + unsigned int max = 0; + + REQUIRE(VALID_SSURULE(rule)); + + for (i = 0; i < rule->ntypes; i++) { + if (rule->types[i].type == dns_rdatatype_any) { + max = rule->types[i].max; + } + if (rule->types[i].type == type) { + return (rule->types[i].max); + } + } + return (max); +} + +isc_result_t +dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) { + REQUIRE(VALID_SSUTABLE(table)); + REQUIRE(rule != NULL && *rule == NULL); + *rule = ISC_LIST_HEAD(table->rules); + return (*rule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE); +} + +isc_result_t +dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule) { + REQUIRE(VALID_SSURULE(rule)); + REQUIRE(nextrule != NULL && *nextrule == NULL); + *nextrule = ISC_LIST_NEXT(rule, link); + return (*nextrule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE); +} + +/* + * Create a specialised SSU table that points at an external DLZ database + */ +void +dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, + dns_dlzdb_t *dlzdatabase) { + dns_ssurule_t *rule; + dns_ssutable_t *table = NULL; + + REQUIRE(tablep != NULL && *tablep == NULL); + + dns_ssutable_create(mctx, &table); + + table->dlzdatabase = dlzdatabase; + + rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t)); + + rule->identity = NULL; + rule->name = NULL; + rule->grant = true; + rule->matchtype = dns_ssumatchtype_dlz; + rule->ntypes = 0; + rule->types = NULL; + rule->magic = SSURULEMAGIC; + + ISC_LIST_INITANDAPPEND(table->rules, rule, link); + *tablep = table; +} + +isc_result_t +dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype) { + REQUIRE(str != NULL); + REQUIRE(mtype != NULL); + + if (strcasecmp(str, "name") == 0) { + *mtype = dns_ssumatchtype_name; + } else if (strcasecmp(str, "subdomain") == 0) { + *mtype = dns_ssumatchtype_subdomain; + } else if (strcasecmp(str, "wildcard") == 0) { + *mtype = dns_ssumatchtype_wildcard; + } else if (strcasecmp(str, "self") == 0) { + *mtype = dns_ssumatchtype_self; + } else if (strcasecmp(str, "selfsub") == 0) { + *mtype = dns_ssumatchtype_selfsub; + } else if (strcasecmp(str, "selfwild") == 0) { + *mtype = dns_ssumatchtype_selfwild; + } else if (strcasecmp(str, "ms-self") == 0) { + *mtype = dns_ssumatchtype_selfms; + } else if (strcasecmp(str, "ms-selfsub") == 0) { + *mtype = dns_ssumatchtype_selfsubms; + } else if (strcasecmp(str, "krb5-self") == 0) { + *mtype = dns_ssumatchtype_selfkrb5; + } else if (strcasecmp(str, "krb5-selfsub") == 0) { + *mtype = dns_ssumatchtype_selfsubkrb5; + } else if (strcasecmp(str, "ms-subdomain") == 0) { + *mtype = dns_ssumatchtype_subdomainms; + } else if (strcasecmp(str, "ms-subdomain-self-rhs") == 0) { + *mtype = dns_ssumatchtype_subdomainselfmsrhs; + } else if (strcasecmp(str, "krb5-subdomain") == 0) { + *mtype = dns_ssumatchtype_subdomainkrb5; + } else if (strcasecmp(str, "krb5-subdomain-self-rhs") == 0) { + *mtype = dns_ssumatchtype_subdomainselfkrb5rhs; + } else if (strcasecmp(str, "tcp-self") == 0) { + *mtype = dns_ssumatchtype_tcpself; + } else if (strcasecmp(str, "6to4-self") == 0) { + *mtype = dns_ssumatchtype_6to4self; + } else if (strcasecmp(str, "zonesub") == 0) { + *mtype = dns_ssumatchtype_subdomain; + } else if (strcasecmp(str, "external") == 0) { + *mtype = dns_ssumatchtype_external; + } else { + return (ISC_R_NOTFOUND); + } + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/ssu_external.c b/lib/dns/ssu_external.c new file mode 100644 index 0000000..905bbb7 --- /dev/null +++ b/lib/dns/ssu_external.c @@ -0,0 +1,255 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This implements external update-policy rules. This allows permission + * to update a zone to be checked by consulting an external daemon (e.g., + * kerberos). + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +static void +ssu_e_log(int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_SECURITY, DNS_LOGMODULE_ZONE, + ISC_LOG_DEBUG(level), fmt, ap); + va_end(ap); +} + +/* + * Connect to a UNIX domain socket. + */ +static int +ux_socket_connect(const char *path) { + int fd = -1; + struct sockaddr_un addr; + + REQUIRE(path != NULL); + + if (strlen(path) > sizeof(addr.sun_path)) { + ssu_e_log(3, + "ssu_external: socket path '%s' " + "longer than system maximum %zu", + path, sizeof(addr.sun_path)); + return (-1); + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + ssu_e_log(3, "ssu_external: unable to create socket - %s", + strbuf); + return (-1); + } + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + ssu_e_log(3, + "ssu_external: unable to connect to " + "socket '%s' - %s", + path, strbuf); + close(fd); + return (-1); + } + return (fd); +} + +/* Change this version if you update the format of the request */ +#define SSU_EXTERNAL_VERSION 1 + +/* + * Perform an update-policy rule check against an external application + * over a socket. + * + * This currently only supports local: for unix domain datagram sockets. + * + * Note that by using a datagram socket and creating a new socket each + * time we avoid the need for locking and allow for parallel access to + * the authorization server. + */ +bool +dns_ssu_external_match(const dns_name_t *identity, const dns_name_t *signer, + const dns_name_t *name, const isc_netaddr_t *tcpaddr, + dns_rdatatype_t type, const dst_key_t *key, + isc_mem_t *mctx) { + char b_identity[DNS_NAME_FORMATSIZE]; + char b_signer[DNS_NAME_FORMATSIZE]; + char b_name[DNS_NAME_FORMATSIZE]; + char b_addr[ISC_NETADDR_FORMATSIZE]; + char b_type[DNS_RDATATYPE_FORMATSIZE]; + char b_key[DST_KEY_FORMATSIZE]; + isc_buffer_t *tkey_token = NULL; + int fd; + const char *sock_path; + unsigned int req_len; + isc_region_t token_region = { NULL, 0 }; + unsigned char *data; + isc_buffer_t buf; + uint32_t token_len = 0; + uint32_t reply; + ssize_t ret; + + /* The identity contains local:/path/to/socket */ + dns_name_format(identity, b_identity, sizeof(b_identity)); + + /* For now only local: is supported */ + if (strncmp(b_identity, "local:", 6) != 0) { + ssu_e_log(3, "ssu_external: invalid socket path '%s'", + b_identity); + return (false); + } + sock_path = &b_identity[6]; + + fd = ux_socket_connect(sock_path); + if (fd == -1) { + return (false); + } + + if (key != NULL) { + dst_key_format(key, b_key, sizeof(b_key)); + tkey_token = dst_key_tkeytoken(key); + } else { + b_key[0] = 0; + } + + if (tkey_token != NULL) { + isc_buffer_region(tkey_token, &token_region); + token_len = token_region.length; + } + + /* Format the request elements */ + if (signer != NULL) { + dns_name_format(signer, b_signer, sizeof(b_signer)); + } else { + b_signer[0] = 0; + } + + dns_name_format(name, b_name, sizeof(b_name)); + + if (tcpaddr != NULL) { + isc_netaddr_format(tcpaddr, b_addr, sizeof(b_addr)); + } else { + b_addr[0] = 0; + } + + dns_rdatatype_format(type, b_type, sizeof(b_type)); + + /* Work out how big the request will be */ + req_len = sizeof(uint32_t) + /* Format version */ + sizeof(uint32_t) + /* Length */ + strlen(b_signer) + 1 + /* Signer */ + strlen(b_name) + 1 + /* Name */ + strlen(b_addr) + 1 + /* Address */ + strlen(b_type) + 1 + /* Type */ + strlen(b_key) + 1 + /* Key */ + sizeof(uint32_t) + /* tkey_token length */ + token_len; /* tkey_token */ + + /* format the buffer */ + data = isc_mem_allocate(mctx, req_len); + + isc_buffer_init(&buf, data, req_len); + isc_buffer_putuint32(&buf, SSU_EXTERNAL_VERSION); + isc_buffer_putuint32(&buf, req_len); + + /* Strings must be null-terminated */ + isc_buffer_putstr(&buf, b_signer); + isc_buffer_putuint8(&buf, 0); + isc_buffer_putstr(&buf, b_name); + isc_buffer_putuint8(&buf, 0); + isc_buffer_putstr(&buf, b_addr); + isc_buffer_putuint8(&buf, 0); + isc_buffer_putstr(&buf, b_type); + isc_buffer_putuint8(&buf, 0); + isc_buffer_putstr(&buf, b_key); + isc_buffer_putuint8(&buf, 0); + + isc_buffer_putuint32(&buf, token_len); + if (tkey_token && token_len != 0) { + isc_buffer_putmem(&buf, token_region.base, token_len); + } + + ENSURE(isc_buffer_availablelength(&buf) == 0); + + /* Send the request */ + ret = write(fd, data, req_len); + isc_mem_free(mctx, data); + if (ret != (ssize_t)req_len) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + ssu_e_log(3, "ssu_external: unable to send request - %s", + strbuf); + close(fd); + return (false); + } + + /* Receive the reply */ + ret = read(fd, &reply, sizeof(uint32_t)); + if (ret != (ssize_t)sizeof(uint32_t)) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + ssu_e_log(3, "ssu_external: unable to receive reply - %s", + strbuf); + close(fd); + return (false); + } + + close(fd); + + reply = ntohl(reply); + + if (reply == 0) { + ssu_e_log(3, "ssu_external: denied external auth for '%s'", + b_name); + return (false); + } else if (reply == 1) { + ssu_e_log(3, "ssu_external: allowed external auth for '%s'", + b_name); + return (true); + } + + ssu_e_log(3, "ssu_external: invalid reply 0x%08x", reply); + + return (false); +} diff --git a/lib/dns/stats.c b/lib/dns/stats.c new file mode 100644 index 0000000..390a397 --- /dev/null +++ b/lib/dns/stats.c @@ -0,0 +1,653 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DNS_STATS_MAGIC ISC_MAGIC('D', 's', 't', 't') +#define DNS_STATS_VALID(x) ISC_MAGIC_VALID(x, DNS_STATS_MAGIC) + +/*% + * Statistics types. + */ +typedef enum { + dns_statstype_general = 0, + dns_statstype_rdtype = 1, + dns_statstype_rdataset = 2, + dns_statstype_opcode = 3, + dns_statstype_rcode = 4, + dns_statstype_dnssec = 5 +} dns_statstype_t; + +/*% + * It doesn't make sense to have 2^16 counters for all possible types since + * most of them won't be used. We have counters for the first 256 types. + * + * A rdtypecounter is now 8 bits for RRtypes and 3 bits for flags: + * + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | | | | | | S |NX| RRType | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * If the 8 bits for RRtype are all zero, this is an Other RRtype. + */ +#define RDTYPECOUNTER_MAXTYPE 0x00ff + +/* + * + * Bit 7 is the NXRRSET (NX) flag and indicates whether this is a + * positive (0) or a negative (1) RRset. + */ +#define RDTYPECOUNTER_NXRRSET 0x0100 + +/* + * Then bit 5 and 6 mostly tell you if this counter is for an active, + * stale, or ancient RRtype: + * + * S = 0 (0b00) means Active + * S = 1 (0b01) means Stale + * S = 2 (0b10) means Ancient + * + * Since a counter cannot be stale and ancient at the same time, we + * treat S = 0b11 as a special case to deal with NXDOMAIN counters. + */ +#define RDTYPECOUNTER_STALE (1 << 9) +#define RDTYPECOUNTER_ANCIENT (1 << 10) +#define RDTYPECOUNTER_NXDOMAIN ((1 << 9) | (1 << 10)) + +/* + * S = 0b11 indicates an NXDOMAIN counter and in this case the RRtype + * field signals the expiry of this cached item: + * + * RRType = 0 (0b00) means Active + * RRType = 1 (0b01) means Stale + * RRType = 2 (0b02) means Ancient + * + */ +#define RDTYPECOUNTER_NXDOMAIN_STALE 1 +#define RDTYPECOUNTER_NXDOMAIN_ANCIENT 2 + +/* + * The maximum value for rdtypecounter is for an ancient NXDOMAIN. + */ +#define RDTYPECOUNTER_MAXVAL 0x0602 + +/* + * DNSSEC sign statistics. + * + * Per key we maintain 3 counters. The first is actually no counter but + * a key id reference. The second is the number of signatures the key created. + * The third is the number of signatures refreshed by the key. + */ + +/* Maximum number of keys to keep track of for DNSSEC signing statistics. */ +static int dnssecsign_num_keys = 4; +static int dnssecsign_block_size = 3; +/* Key id mask */ +#define DNSSECSIGNSTATS_KEY_ID_MASK 0x0000FFFF + +struct dns_stats { + unsigned int magic; + dns_statstype_t type; + isc_mem_t *mctx; + isc_stats_t *counters; + isc_refcount_t references; +}; + +typedef struct rdatadumparg { + dns_rdatatypestats_dumper_t fn; + void *arg; +} rdatadumparg_t; + +typedef struct opcodedumparg { + dns_opcodestats_dumper_t fn; + void *arg; +} opcodedumparg_t; + +typedef struct rcodedumparg { + dns_rcodestats_dumper_t fn; + void *arg; +} rcodedumparg_t; +typedef struct dnssecsigndumparg { + dns_dnssecsignstats_dumper_t fn; + void *arg; +} dnssecsigndumparg_t; + +void +dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp) { + REQUIRE(DNS_STATS_VALID(stats)); + REQUIRE(statsp != NULL && *statsp == NULL); + + isc_refcount_increment(&stats->references); + + *statsp = stats; +} + +void +dns_stats_detach(dns_stats_t **statsp) { + dns_stats_t *stats; + + REQUIRE(statsp != NULL && DNS_STATS_VALID(*statsp)); + + stats = *statsp; + *statsp = NULL; + + if (isc_refcount_decrement(&stats->references) == 1) { + isc_refcount_destroy(&stats->references); + isc_stats_detach(&stats->counters); + isc_mem_putanddetach(&stats->mctx, stats, sizeof(*stats)); + } +} + +/*% + * Create methods + */ +static isc_result_t +create_stats(isc_mem_t *mctx, dns_statstype_t type, int ncounters, + dns_stats_t **statsp) { + dns_stats_t *stats; + isc_result_t result; + + stats = isc_mem_get(mctx, sizeof(*stats)); + + stats->counters = NULL; + isc_refcount_init(&stats->references, 1); + + result = isc_stats_create(mctx, &stats->counters, ncounters); + if (result != ISC_R_SUCCESS) { + goto clean_mutex; + } + + stats->magic = DNS_STATS_MAGIC; + stats->type = type; + stats->mctx = NULL; + isc_mem_attach(mctx, &stats->mctx); + *statsp = stats; + + return (ISC_R_SUCCESS); + +clean_mutex: + isc_mem_put(mctx, stats, sizeof(*stats)); + + return (result); +} + +isc_result_t +dns_generalstats_create(isc_mem_t *mctx, dns_stats_t **statsp, int ncounters) { + REQUIRE(statsp != NULL && *statsp == NULL); + + return (create_stats(mctx, dns_statstype_general, ncounters, statsp)); +} + +isc_result_t +dns_rdatatypestats_create(isc_mem_t *mctx, dns_stats_t **statsp) { + REQUIRE(statsp != NULL && *statsp == NULL); + + /* + * Create rdtype statistics for the first 255 RRtypes, + * plus one additional for other RRtypes. + */ + return (create_stats(mctx, dns_statstype_rdtype, + (RDTYPECOUNTER_MAXTYPE + 1), statsp)); +} + +isc_result_t +dns_rdatasetstats_create(isc_mem_t *mctx, dns_stats_t **statsp) { + REQUIRE(statsp != NULL && *statsp == NULL); + + return (create_stats(mctx, dns_statstype_rdataset, + (RDTYPECOUNTER_MAXVAL + 1), statsp)); +} + +isc_result_t +dns_opcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp) { + REQUIRE(statsp != NULL && *statsp == NULL); + + return (create_stats(mctx, dns_statstype_opcode, 16, statsp)); +} + +isc_result_t +dns_rcodestats_create(isc_mem_t *mctx, dns_stats_t **statsp) { + REQUIRE(statsp != NULL && *statsp == NULL); + + return (create_stats(mctx, dns_statstype_rcode, dns_rcode_badcookie + 1, + statsp)); +} + +isc_result_t +dns_dnssecsignstats_create(isc_mem_t *mctx, dns_stats_t **statsp) { + REQUIRE(statsp != NULL && *statsp == NULL); + + /* + * Create two counters per key, one is the key id, the other two are + * the actual counters for creating and refreshing signatures. + */ + return (create_stats(mctx, dns_statstype_dnssec, + dnssecsign_num_keys * dnssecsign_block_size, + statsp)); +} + +/*% + * Increment/Decrement methods + */ +void +dns_generalstats_increment(dns_stats_t *stats, isc_statscounter_t counter) { + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_general); + + isc_stats_increment(stats->counters, counter); +} + +static isc_statscounter_t +rdatatype2counter(dns_rdatatype_t type) { + if (type > (dns_rdatatype_t)RDTYPECOUNTER_MAXTYPE) { + return (0); + } + return ((isc_statscounter_t)type); +} + +void +dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type) { + isc_statscounter_t counter; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rdtype); + + counter = rdatatype2counter(type); + isc_stats_increment(stats->counters, counter); +} + +static void +update_rdatasetstats(dns_stats_t *stats, dns_rdatastatstype_t rrsettype, + bool increment) { + isc_statscounter_t counter; + + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) != 0) + { + counter = RDTYPECOUNTER_NXDOMAIN; + + /* + * This is an NXDOMAIN counter, save the expiry value + * (active, stale, or ancient) value in the RRtype part. + */ + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0) + { + counter |= RDTYPECOUNTER_NXDOMAIN_ANCIENT; + } else if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_STALE) != 0) + { + counter += RDTYPECOUNTER_NXDOMAIN_STALE; + } + } else { + counter = rdatatype2counter(DNS_RDATASTATSTYPE_BASE(rrsettype)); + + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_NXRRSET) != 0) + { + counter |= RDTYPECOUNTER_NXRRSET; + } + + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0) + { + counter |= RDTYPECOUNTER_ANCIENT; + } else if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_STALE) != 0) + { + counter |= RDTYPECOUNTER_STALE; + } + } + + if (increment) { + isc_stats_increment(stats->counters, counter); + } else { + isc_stats_decrement(stats->counters, counter); + } +} + +void +dns_rdatasetstats_increment(dns_stats_t *stats, + dns_rdatastatstype_t rrsettype) { + REQUIRE(DNS_STATS_VALID(stats) && + stats->type == dns_statstype_rdataset); + + update_rdatasetstats(stats, rrsettype, true); +} + +void +dns_rdatasetstats_decrement(dns_stats_t *stats, + dns_rdatastatstype_t rrsettype) { + REQUIRE(DNS_STATS_VALID(stats) && + stats->type == dns_statstype_rdataset); + + update_rdatasetstats(stats, rrsettype, false); +} + +void +dns_opcodestats_increment(dns_stats_t *stats, dns_opcode_t code) { + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_opcode); + + isc_stats_increment(stats->counters, (isc_statscounter_t)code); +} + +void +dns_rcodestats_increment(dns_stats_t *stats, dns_rcode_t code) { + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rcode); + + if (code <= dns_rcode_badcookie) { + isc_stats_increment(stats->counters, (isc_statscounter_t)code); + } +} + +void +dns_dnssecsignstats_increment(dns_stats_t *stats, dns_keytag_t id, uint8_t alg, + dnssecsignstats_type_t operation) { + uint32_t kval; + int num_keys = isc_stats_ncounters(stats->counters) / + dnssecsign_block_size; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec); + + /* Shift algorithm in front of key tag, which is 16 bits */ + kval = (uint32_t)(alg << 16 | id); + + /* Look up correct counter. */ + for (int i = 0; i < num_keys; i++) { + int idx = i * dnssecsign_block_size; + uint32_t counter = isc_stats_get_counter(stats->counters, idx); + if (counter == kval) { + /* Match */ + isc_stats_increment(stats->counters, (idx + operation)); + return; + } + } + + /* No match found. Store key in unused slot. */ + for (int i = 0; i < num_keys; i++) { + int idx = i * dnssecsign_block_size; + uint32_t counter = isc_stats_get_counter(stats->counters, idx); + if (counter == 0) { + isc_stats_set(stats->counters, kval, idx); + isc_stats_increment(stats->counters, (idx + operation)); + return; + } + } + + /* No room, grow stats storage. */ + isc_stats_resize(&stats->counters, + (num_keys * dnssecsign_block_size * 2)); + + /* Reset counters for new key (new index, nidx). */ + int nidx = num_keys * dnssecsign_block_size; + isc_stats_set(stats->counters, kval, nidx); + isc_stats_set(stats->counters, 0, (nidx + dns_dnssecsignstats_sign)); + isc_stats_set(stats->counters, 0, (nidx + dns_dnssecsignstats_refresh)); + + /* And increment the counter for the given operation. */ + isc_stats_increment(stats->counters, (nidx + operation)); +} + +void +dns_dnssecsignstats_clear(dns_stats_t *stats, dns_keytag_t id, uint8_t alg) { + uint32_t kval; + int num_keys = isc_stats_ncounters(stats->counters) / + dnssecsign_block_size; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec); + + /* Shift algorithm in front of key tag, which is 16 bits */ + kval = (uint32_t)(alg << 16 | id); + + /* Look up correct counter. */ + for (int i = 0; i < num_keys; i++) { + int idx = i * dnssecsign_block_size; + uint32_t counter = isc_stats_get_counter(stats->counters, idx); + if (counter == kval) { + /* Match */ + isc_stats_set(stats->counters, 0, idx); + isc_stats_set(stats->counters, 0, + (idx + dns_dnssecsignstats_sign)); + isc_stats_set(stats->counters, 0, + (idx + dns_dnssecsignstats_refresh)); + return; + } + } +} + +/*% + * Dump methods + */ +void +dns_generalstats_dump(dns_stats_t *stats, dns_generalstats_dumper_t dump_fn, + void *arg, unsigned int options) { + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_general); + + isc_stats_dump(stats->counters, (isc_stats_dumper_t)dump_fn, arg, + options); +} + +static void +dump_rdentry(int rdcounter, uint64_t value, dns_rdatastatstype_t attributes, + dns_rdatatypestats_dumper_t dump_fn, void *arg) { + dns_rdatatype_t rdtype = dns_rdatatype_none; /* sentinel */ + dns_rdatastatstype_t type; + + if ((rdcounter & RDTYPECOUNTER_MAXTYPE) == 0) { + attributes |= DNS_RDATASTATSTYPE_ATTR_OTHERTYPE; + } else { + rdtype = (dns_rdatatype_t)(rdcounter & RDTYPECOUNTER_MAXTYPE); + } + type = DNS_RDATASTATSTYPE_VALUE((dns_rdatastatstype_t)rdtype, + attributes); + dump_fn(type, value, arg); +} + +static void +rdatatype_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) { + rdatadumparg_t *rdatadumparg = arg; + + dump_rdentry(counter, value, 0, rdatadumparg->fn, rdatadumparg->arg); +} + +void +dns_rdatatypestats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn, + void *arg0, unsigned int options) { + rdatadumparg_t arg; + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rdtype); + + arg.fn = dump_fn; + arg.arg = arg0; + isc_stats_dump(stats->counters, rdatatype_dumpcb, &arg, options); +} + +static void +rdataset_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) { + rdatadumparg_t *rdatadumparg = arg; + unsigned int attributes = 0; + + if ((counter & RDTYPECOUNTER_NXDOMAIN) == RDTYPECOUNTER_NXDOMAIN) { + attributes |= DNS_RDATASTATSTYPE_ATTR_NXDOMAIN; + + /* + * This is an NXDOMAIN counter, check the RRtype part for the + * expiry value (active, stale, or ancient). + */ + if ((counter & RDTYPECOUNTER_MAXTYPE) == + RDTYPECOUNTER_NXDOMAIN_STALE) + { + attributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + } else if ((counter & RDTYPECOUNTER_MAXTYPE) == + RDTYPECOUNTER_NXDOMAIN_ANCIENT) + { + attributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT; + } + } else { + if ((counter & RDTYPECOUNTER_MAXTYPE) == 0) { + attributes |= DNS_RDATASTATSTYPE_ATTR_OTHERTYPE; + } + if ((counter & RDTYPECOUNTER_NXRRSET) != 0) { + attributes |= DNS_RDATASTATSTYPE_ATTR_NXRRSET; + } + + if ((counter & RDTYPECOUNTER_STALE) != 0) { + attributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + } else if ((counter & RDTYPECOUNTER_ANCIENT) != 0) { + attributes |= DNS_RDATASTATSTYPE_ATTR_ANCIENT; + } + } + + dump_rdentry(counter, value, attributes, rdatadumparg->fn, + rdatadumparg->arg); +} + +void +dns_rdatasetstats_dump(dns_stats_t *stats, dns_rdatatypestats_dumper_t dump_fn, + void *arg0, unsigned int options) { + rdatadumparg_t arg; + + REQUIRE(DNS_STATS_VALID(stats) && + stats->type == dns_statstype_rdataset); + + arg.fn = dump_fn; + arg.arg = arg0; + isc_stats_dump(stats->counters, rdataset_dumpcb, &arg, options); +} + +static void +dnssec_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) { + dnssecsigndumparg_t *dnssecarg = arg; + + dnssecarg->fn((dns_keytag_t)counter, value, dnssecarg->arg); +} + +static void +dnssec_statsdump(isc_stats_t *stats, dnssecsignstats_type_t operation, + isc_stats_dumper_t dump_fn, void *arg, unsigned int options) { + int i, num_keys; + + num_keys = isc_stats_ncounters(stats) / dnssecsign_block_size; + for (i = 0; i < num_keys; i++) { + int idx = dnssecsign_block_size * i; + uint32_t kval, val; + dns_keytag_t id; + + kval = isc_stats_get_counter(stats, idx); + if (kval == 0) { + continue; + } + + val = isc_stats_get_counter(stats, (idx + operation)); + if ((options & ISC_STATSDUMP_VERBOSE) == 0 && val == 0) { + continue; + } + + id = (dns_keytag_t)kval & DNSSECSIGNSTATS_KEY_ID_MASK; + + dump_fn((isc_statscounter_t)id, val, arg); + } +} + +void +dns_dnssecsignstats_dump(dns_stats_t *stats, dnssecsignstats_type_t operation, + dns_dnssecsignstats_dumper_t dump_fn, void *arg0, + unsigned int options) { + dnssecsigndumparg_t arg; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_dnssec); + + arg.fn = dump_fn; + arg.arg = arg0; + + dnssec_statsdump(stats->counters, operation, dnssec_dumpcb, &arg, + options); +} + +static void +opcode_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) { + opcodedumparg_t *opcodearg = arg; + + opcodearg->fn((dns_opcode_t)counter, value, opcodearg->arg); +} + +static void +rcode_dumpcb(isc_statscounter_t counter, uint64_t value, void *arg) { + rcodedumparg_t *rcodearg = arg; + + rcodearg->fn((dns_rcode_t)counter, value, rcodearg->arg); +} + +void +dns_opcodestats_dump(dns_stats_t *stats, dns_opcodestats_dumper_t dump_fn, + void *arg0, unsigned int options) { + opcodedumparg_t arg; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_opcode); + + arg.fn = dump_fn; + arg.arg = arg0; + isc_stats_dump(stats->counters, opcode_dumpcb, &arg, options); +} + +void +dns_rcodestats_dump(dns_stats_t *stats, dns_rcodestats_dumper_t dump_fn, + void *arg0, unsigned int options) { + rcodedumparg_t arg; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rcode); + + arg.fn = dump_fn; + arg.arg = arg0; + isc_stats_dump(stats->counters, rcode_dumpcb, &arg, options); +} + +/*** + *** Obsolete variables and functions follow: + ***/ +const char *dns_statscounter_names[DNS_STATS_NCOUNTERS] = { + "success", "referral", "nxrrset", "nxdomain", + "recursion", "failure", "duplicate", "dropped" +}; + +isc_result_t +dns_stats_alloccounters(isc_mem_t *mctx, uint64_t **ctrp) { + int i; + uint64_t *p = isc_mem_get(mctx, DNS_STATS_NCOUNTERS * sizeof(uint64_t)); + if (p == NULL) { + return (ISC_R_NOMEMORY); + } + for (i = 0; i < DNS_STATS_NCOUNTERS; i++) { + p[i] = 0; + } + *ctrp = p; + return (ISC_R_SUCCESS); +} + +void +dns_stats_freecounters(isc_mem_t *mctx, uint64_t **ctrp) { + isc_mem_put(mctx, *ctrp, DNS_STATS_NCOUNTERS * sizeof(uint64_t)); + *ctrp = NULL; +} diff --git a/lib/dns/time.c b/lib/dns/time.c new file mode 100644 index 0000000..d110ace --- /dev/null +++ b/lib/dns/time.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include + +static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +isc_result_t +dns_time64_totext(int64_t t, isc_buffer_t *target) { + struct tm tm; + char buf[sizeof("!!!!!!YYYY!!!!!!!!MM!!!!!!!!DD!!!!!!!!HH!!!!!!!!MM!!!!" + "!!!!SS")]; + int secs; + unsigned int l; + isc_region_t region; + +/* + * Warning. Do NOT use arguments with side effects with these macros. + */ +#define is_leap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) +#define year_secs(y) ((is_leap(y) ? 366 : 365) * 86400) +#define month_secs(m, y) ((days[m] + ((m == 1 && is_leap(y)) ? 1 : 0)) * 86400) + + tm.tm_year = 70; + while (t < 0) { + if (tm.tm_year == 0) { + return (ISC_R_RANGE); + } + tm.tm_year--; + secs = year_secs(tm.tm_year + 1900); + t += secs; + } + while ((secs = year_secs(tm.tm_year + 1900)) <= t) { + t -= secs; + tm.tm_year++; + if (tm.tm_year + 1900 > 9999) { + return (ISC_R_RANGE); + } + } + tm.tm_mon = 0; + while ((secs = month_secs(tm.tm_mon, tm.tm_year + 1900)) <= t) { + t -= secs; + tm.tm_mon++; + } + tm.tm_mday = 1; + while (86400 <= t) { + t -= 86400; + tm.tm_mday++; + } + tm.tm_hour = 0; + while (3600 <= t) { + t -= 3600; + tm.tm_hour++; + } + tm.tm_min = 0; + while (60 <= t) { + t -= 60; + tm.tm_min++; + } + tm.tm_sec = (int)t; + /* yyyy mm dd HH MM SS */ + snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + + isc_buffer_availableregion(target, ®ion); + l = strlen(buf); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, buf, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +int64_t +dns_time64_from32(uint32_t value) { + isc_stdtime_t now; + int64_t start; + int64_t t; + + /* + * Adjust the time to the closest epoch. This should be changed + * to use a 64-bit counterpart to isc_stdtime_get() if one ever + * is defined, but even the current code is good until the year + * 2106. + */ + isc_stdtime_get(&now); + start = (int64_t)now; + if (isc_serial_gt(value, now)) { + t = start + (value - now); + } else { + t = start - (now - value); + } + + return (t); +} + +isc_result_t +dns_time32_totext(uint32_t value, isc_buffer_t *target) { + return (dns_time64_totext(dns_time64_from32(value), target)); +} + +isc_result_t +dns_time64_fromtext(const char *source, int64_t *target) { + int year, month, day, hour, minute, second; + int64_t value; + int secs; + int i; + +#define RANGE(min, max, value) \ + do { \ + if (value < (min) || value > (max)) \ + return ((ISC_R_RANGE)); \ + } while (0) + + if (strlen(source) != 14U) { + return (DNS_R_SYNTAX); + } + /* + * Confirm the source only consists digits. sscanf() allows some + * minor exceptions. + */ + for (i = 0; i < 14; i++) { + if (!isdigit((unsigned char)source[i])) { + return (DNS_R_SYNTAX); + } + } + if (sscanf(source, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, + &minute, &second) != 6) + { + return (DNS_R_SYNTAX); + } + + RANGE(0, 9999, year); + RANGE(1, 12, month); + RANGE(1, days[month - 1] + ((month == 2 && is_leap(year)) ? 1 : 0), + day); +#ifdef __COVERITY__ + /* + * Use a simplified range to silence Coverity warning (in + * arithmetic with day below). + */ + RANGE(1, 31, day); +#endif /* __COVERITY__ */ + + RANGE(0, 23, hour); + RANGE(0, 59, minute); + RANGE(0, 60, second); /* 60 == leap second. */ + + /* + * Calculate seconds from epoch. + * Note: this uses a idealized calendar. + */ + value = second + (60 * minute) + (3600 * hour) + ((day - 1) * 86400); + for (i = 0; i < (month - 1); i++) { + value += days[i] * 86400; + } + if (is_leap(year) && month > 2) { + value += 86400; + } + if (year < 1970) { + for (i = 1969; i >= year; i--) { + secs = (is_leap(i) ? 366 : 365) * 86400; + value -= secs; + } + } else { + for (i = 1970; i < year; i++) { + secs = (is_leap(i) ? 366 : 365) * 86400; + value += secs; + } + } + + *target = value; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_time32_fromtext(const char *source, uint32_t *target) { + int64_t value64; + isc_result_t result; + result = dns_time64_fromtext(source, &value64); + if (result != ISC_R_SUCCESS) { + return (result); + } + *target = (uint32_t)value64; + + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c new file mode 100644 index 0000000..d60f65d --- /dev/null +++ b/lib/dns/tkey.c @@ -0,0 +1,1605 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#if HAVE_GSSAPI_GSSAPI_H +#include +#elif HAVE_GSSAPI_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" + +#define TEMP_BUFFER_SZ 8192 +#define TKEY_RANDOM_AMOUNT 16 + +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +static void +tkey_log(const char *fmt, ...) ISC_FORMAT_PRINTF(1, 2); + +static void +tkey_log(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST, + ISC_LOG_DEBUG(4), fmt, ap); + va_end(ap); +} + +static void +dumpmessage(dns_message_t *msg) { + isc_buffer_t outbuf; + unsigned char *output; + int len = TEMP_BUFFER_SZ; + isc_result_t result; + + for (;;) { + output = isc_mem_get(msg->mctx, len); + + isc_buffer_init(&outbuf, output, len); + result = dns_message_totext(msg, &dns_master_style_debug, 0, + &outbuf); + if (result == ISC_R_NOSPACE) { + isc_mem_put(msg->mctx, output, len); + len *= 2; + continue; + } + + if (result == ISC_R_SUCCESS) { + tkey_log("%.*s", (int)isc_buffer_usedlength(&outbuf), + (char *)isc_buffer_base(&outbuf)); + } else { + tkey_log("Warning: dns_message_totext: %s", + isc_result_totext(result)); + } + break; + } + + if (output != NULL) { + isc_mem_put(msg->mctx, output, len); + } +} + +isc_result_t +dns_tkeyctx_create(isc_mem_t *mctx, dns_tkeyctx_t **tctxp) { + dns_tkeyctx_t *tctx; + + REQUIRE(mctx != NULL); + REQUIRE(tctxp != NULL && *tctxp == NULL); + + tctx = isc_mem_get(mctx, sizeof(dns_tkeyctx_t)); + tctx->mctx = NULL; + isc_mem_attach(mctx, &tctx->mctx); + tctx->dhkey = NULL; + tctx->domain = NULL; + tctx->gsscred = NULL; + tctx->gssapi_keytab = NULL; + + *tctxp = tctx; + return (ISC_R_SUCCESS); +} + +void +dns_tkeyctx_destroy(dns_tkeyctx_t **tctxp) { + isc_mem_t *mctx; + dns_tkeyctx_t *tctx; + + REQUIRE(tctxp != NULL && *tctxp != NULL); + + tctx = *tctxp; + *tctxp = NULL; + mctx = tctx->mctx; + + if (tctx->dhkey != NULL) { + dst_key_free(&tctx->dhkey); + } + if (tctx->domain != NULL) { + if (dns_name_dynamic(tctx->domain)) { + dns_name_free(tctx->domain, mctx); + } + isc_mem_put(mctx, tctx->domain, sizeof(dns_name_t)); + } + if (tctx->gssapi_keytab != NULL) { + isc_mem_free(mctx, tctx->gssapi_keytab); + } + if (tctx->gsscred != NULL) { + dst_gssapi_releasecred(&tctx->gsscred); + } + isc_mem_putanddetach(&mctx, tctx, sizeof(dns_tkeyctx_t)); +} + +static isc_result_t +add_rdata_to_list(dns_message_t *msg, dns_name_t *name, dns_rdata_t *rdata, + uint32_t ttl, dns_namelist_t *namelist) { + isc_result_t result; + isc_region_t r, newr; + dns_rdata_t *newrdata = NULL; + dns_name_t *newname = NULL; + dns_rdatalist_t *newlist = NULL; + dns_rdataset_t *newset = NULL; + isc_buffer_t *tmprdatabuf = NULL; + + RETERR(dns_message_gettemprdata(msg, &newrdata)); + + dns_rdata_toregion(rdata, &r); + isc_buffer_allocate(msg->mctx, &tmprdatabuf, r.length); + isc_buffer_availableregion(tmprdatabuf, &newr); + memmove(newr.base, r.base, r.length); + dns_rdata_fromregion(newrdata, rdata->rdclass, rdata->type, &newr); + dns_message_takebuffer(msg, &tmprdatabuf); + + RETERR(dns_message_gettempname(msg, &newname)); + dns_name_copy(name, newname); + + RETERR(dns_message_gettemprdatalist(msg, &newlist)); + newlist->rdclass = newrdata->rdclass; + newlist->type = newrdata->type; + newlist->ttl = ttl; + ISC_LIST_APPEND(newlist->rdata, newrdata, link); + + RETERR(dns_message_gettemprdataset(msg, &newset)); + RETERR(dns_rdatalist_tordataset(newlist, newset)); + + ISC_LIST_INIT(newname->list); + ISC_LIST_APPEND(newname->list, newset, link); + + ISC_LIST_APPEND(*namelist, newname, link); + + return (ISC_R_SUCCESS); + +failure: + if (newrdata != NULL) { + if (ISC_LINK_LINKED(newrdata, link)) { + INSIST(newlist != NULL); + ISC_LIST_UNLINK(newlist->rdata, newrdata, link); + } + dns_message_puttemprdata(msg, &newrdata); + } + if (newname != NULL) { + dns_message_puttempname(msg, &newname); + } + if (newset != NULL) { + dns_rdataset_disassociate(newset); + dns_message_puttemprdataset(msg, &newset); + } + if (newlist != NULL) { + dns_message_puttemprdatalist(msg, &newlist); + } + return (result); +} + +static void +free_namelist(dns_message_t *msg, dns_namelist_t *namelist) { + dns_name_t *name; + dns_rdataset_t *set; + + while (!ISC_LIST_EMPTY(*namelist)) { + name = ISC_LIST_HEAD(*namelist); + ISC_LIST_UNLINK(*namelist, name, link); + while (!ISC_LIST_EMPTY(name->list)) { + set = ISC_LIST_HEAD(name->list); + ISC_LIST_UNLINK(name->list, set, link); + if (dns_rdataset_isassociated(set)) { + dns_rdataset_disassociate(set); + } + dns_message_puttemprdataset(msg, &set); + } + dns_message_puttempname(msg, &name); + } +} + +static isc_result_t +compute_secret(isc_buffer_t *shared, isc_region_t *queryrandomness, + isc_region_t *serverrandomness, isc_buffer_t *secret) { + isc_md_t *md; + isc_region_t r, r2; + unsigned char digests[ISC_MAX_MD_SIZE * 2]; + unsigned char *digest1, *digest2; + unsigned int digestslen, digestlen1 = 0, digestlen2 = 0; + unsigned int i; + isc_result_t result; + + isc_buffer_usedregion(shared, &r); + + md = isc_md_new(); + if (md == NULL) { + return (ISC_R_NOSPACE); + } + + /* + * MD5 ( query data | DH value ). + */ + digest1 = digests; + + result = isc_md_init(md, ISC_MD_MD5); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_update(md, queryrandomness->base, + queryrandomness->length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_update(md, r.base, r.length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_final(md, digest1, &digestlen1); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_reset(md); + if (result != ISC_R_SUCCESS) { + goto end; + } + + /* + * MD5 ( server data | DH value ). + */ + digest2 = digests + digestlen1; + + result = isc_md_init(md, ISC_MD_MD5); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_update(md, serverrandomness->base, + serverrandomness->length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_update(md, r.base, r.length); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = isc_md_final(md, digest2, &digestlen2); + if (result != ISC_R_SUCCESS) { + goto end; + } + + isc_md_free(md); + md = NULL; + + digestslen = digestlen1 + digestlen2; + + /* + * XOR ( DH value, MD5-1 | MD5-2). + */ + isc_buffer_availableregion(secret, &r); + isc_buffer_usedregion(shared, &r2); + if (r.length < digestslen || r.length < r2.length) { + return (ISC_R_NOSPACE); + } + if (r2.length > digestslen) { + memmove(r.base, r2.base, r2.length); + for (i = 0; i < digestslen; i++) { + r.base[i] ^= digests[i]; + } + isc_buffer_add(secret, r2.length); + } else { + memmove(r.base, digests, digestslen); + for (i = 0; i < r2.length; i++) { + r.base[i] ^= r2.base[i]; + } + isc_buffer_add(secret, digestslen); + } + result = ISC_R_SUCCESS; +end: + if (md != NULL) { + isc_md_free(md); + } + return (result); +} + +static isc_result_t +process_dhtkey(dns_message_t *msg, dns_name_t *signer, dns_name_t *name, + dns_rdata_tkey_t *tkeyin, dns_tkeyctx_t *tctx, + dns_rdata_tkey_t *tkeyout, dns_tsig_keyring_t *ring, + dns_namelist_t *namelist) { + isc_result_t result = ISC_R_SUCCESS; + dns_name_t *keyname, ourname; + dns_rdataset_t *keyset = NULL; + dns_rdata_t keyrdata = DNS_RDATA_INIT, ourkeyrdata = DNS_RDATA_INIT; + bool found_key = false, found_incompatible = false; + dst_key_t *pubkey = NULL; + isc_buffer_t ourkeybuf, *shared = NULL; + isc_region_t r, r2, ourkeyr; + unsigned char keydata[DST_KEY_MAXSIZE]; + unsigned int sharedsize; + isc_buffer_t secret; + unsigned char *randomdata = NULL, secretdata[256]; + dns_ttl_t ttl = 0; + + if (tctx->dhkey == NULL) { + tkey_log("process_dhtkey: tkey-dhkey not defined"); + tkeyout->error = dns_tsigerror_badalg; + return (DNS_R_REFUSED); + } + + if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_HMACMD5_NAME)) { + tkey_log("process_dhtkey: algorithms other than " + "hmac-md5 are not supported"); + tkeyout->error = dns_tsigerror_badalg; + return (ISC_R_SUCCESS); + } + + /* + * Look for a DH KEY record that will work with ours. + */ + for (result = dns_message_firstname(msg, DNS_SECTION_ADDITIONAL); + result == ISC_R_SUCCESS && !found_key; + result = dns_message_nextname(msg, DNS_SECTION_ADDITIONAL)) + { + keyname = NULL; + dns_message_currentname(msg, DNS_SECTION_ADDITIONAL, &keyname); + keyset = NULL; + result = dns_message_findtype(keyname, dns_rdatatype_key, 0, + &keyset); + if (result != ISC_R_SUCCESS) { + continue; + } + + for (result = dns_rdataset_first(keyset); + result == ISC_R_SUCCESS && !found_key; + result = dns_rdataset_next(keyset)) + { + dns_rdataset_current(keyset, &keyrdata); + pubkey = NULL; + result = dns_dnssec_keyfromrdata(keyname, &keyrdata, + msg->mctx, &pubkey); + if (result != ISC_R_SUCCESS) { + dns_rdata_reset(&keyrdata); + continue; + } + if (dst_key_alg(pubkey) == DNS_KEYALG_DH) { + if (dst_key_paramcompare(pubkey, tctx->dhkey)) { + found_key = true; + ttl = keyset->ttl; + break; + } else { + found_incompatible = true; + } + } + dst_key_free(&pubkey); + dns_rdata_reset(&keyrdata); + } + } + + if (!found_key) { + if (found_incompatible) { + tkey_log("process_dhtkey: found an incompatible key"); + tkeyout->error = dns_tsigerror_badkey; + return (ISC_R_SUCCESS); + } else { + tkey_log("process_dhtkey: failed to find a key"); + return (DNS_R_FORMERR); + } + } + + RETERR(add_rdata_to_list(msg, keyname, &keyrdata, ttl, namelist)); + + isc_buffer_init(&ourkeybuf, keydata, sizeof(keydata)); + RETERR(dst_key_todns(tctx->dhkey, &ourkeybuf)); + isc_buffer_usedregion(&ourkeybuf, &ourkeyr); + dns_rdata_fromregion(&ourkeyrdata, dns_rdataclass_any, + dns_rdatatype_key, &ourkeyr); + + dns_name_init(&ourname, NULL); + dns_name_clone(dst_key_name(tctx->dhkey), &ourname); + + /* + * XXXBEW The TTL should be obtained from the database, if it exists. + */ + RETERR(add_rdata_to_list(msg, &ourname, &ourkeyrdata, 0, namelist)); + + RETERR(dst_key_secretsize(tctx->dhkey, &sharedsize)); + isc_buffer_allocate(msg->mctx, &shared, sharedsize); + + result = dst_key_computesecret(pubkey, tctx->dhkey, shared); + if (result != ISC_R_SUCCESS) { + tkey_log("process_dhtkey: failed to compute shared secret: %s", + isc_result_totext(result)); + goto failure; + } + dst_key_free(&pubkey); + + isc_buffer_init(&secret, secretdata, sizeof(secretdata)); + + randomdata = isc_mem_get(tkeyout->mctx, TKEY_RANDOM_AMOUNT); + + isc_nonce_buf(randomdata, TKEY_RANDOM_AMOUNT); + + r.base = randomdata; + r.length = TKEY_RANDOM_AMOUNT; + r2.base = tkeyin->key; + r2.length = tkeyin->keylen; + RETERR(compute_secret(shared, &r2, &r, &secret)); + isc_buffer_free(&shared); + + RETERR(dns_tsigkey_create( + name, &tkeyin->algorithm, isc_buffer_base(&secret), + isc_buffer_usedlength(&secret), true, signer, tkeyin->inception, + tkeyin->expire, ring->mctx, ring, NULL)); + + /* This key is good for a long time */ + tkeyout->inception = tkeyin->inception; + tkeyout->expire = tkeyin->expire; + + tkeyout->key = randomdata; + tkeyout->keylen = TKEY_RANDOM_AMOUNT; + + return (ISC_R_SUCCESS); + +failure: + if (!ISC_LIST_EMPTY(*namelist)) { + free_namelist(msg, namelist); + } + if (shared != NULL) { + isc_buffer_free(&shared); + } + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (randomdata != NULL) { + isc_mem_put(tkeyout->mctx, randomdata, TKEY_RANDOM_AMOUNT); + } + return (result); +} + +static isc_result_t +process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin, + dns_tkeyctx_t *tctx, dns_rdata_tkey_t *tkeyout, + dns_tsig_keyring_t *ring) { + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *dstkey = NULL; + dns_tsigkey_t *tsigkey = NULL; + dns_fixedname_t fixed; + dns_name_t *principal; + isc_stdtime_t now; + isc_region_t intoken; + isc_buffer_t *outtoken = NULL; + dns_gss_ctx_id_t gss_ctx = NULL; + + /* + * You have to define either a gss credential (principal) to + * accept with tkey-gssapi-credential, or you have to + * configure a specific keytab (with tkey-gssapi-keytab) in + * order to use gsstkey. + */ + if (tctx->gsscred == NULL && tctx->gssapi_keytab == NULL) { + tkey_log("process_gsstkey(): no tkey-gssapi-credential " + "or tkey-gssapi-keytab configured"); + return (DNS_R_REFUSED); + } + + if (!dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPI_NAME) && + !dns_name_equal(&tkeyin->algorithm, DNS_TSIG_GSSAPIMS_NAME)) + { + tkeyout->error = dns_tsigerror_badalg; + tkey_log("process_gsstkey(): dns_tsigerror_badalg"); /* XXXSRA + */ + return (ISC_R_SUCCESS); + } + + /* + * XXXDCL need to check for key expiry per 4.1.1 + * XXXDCL need a way to check fully established, perhaps w/key_flags + */ + + intoken.base = tkeyin->key; + intoken.length = tkeyin->keylen; + + result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring); + if (result == ISC_R_SUCCESS) { + gss_ctx = dst_key_getgssctx(tsigkey->key); + } + + principal = dns_fixedname_initname(&fixed); + + /* + * Note that tctx->gsscred may be NULL if tctx->gssapi_keytab is set + */ + result = dst_gssapi_acceptctx(tctx->gsscred, tctx->gssapi_keytab, + &intoken, &outtoken, &gss_ctx, principal, + tctx->mctx); + if (result == DNS_R_INVALIDTKEY) { + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } + tkeyout->error = dns_tsigerror_badkey; + tkey_log("process_gsstkey(): dns_tsigerror_badkey"); /* XXXSRA + */ + return (ISC_R_SUCCESS); + } + if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) { + goto failure; + } + /* + * XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times. + */ + + isc_stdtime_get(&now); + + if (dns_name_countlabels(principal) == 0U) { + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } + } else if (tsigkey == NULL) { +#if HAVE_GSSAPI + OM_uint32 gret, minor, lifetime; +#endif /* HAVE_GSSAPI */ + uint32_t expire; + + RETERR(dst_key_fromgssapi(name, gss_ctx, ring->mctx, &dstkey, + &intoken)); + /* + * Limit keys to 1 hour or the context's lifetime whichever + * is smaller. + */ + expire = now + 3600; +#if HAVE_GSSAPI + gret = gss_context_time(&minor, gss_ctx, &lifetime); + if (gret == GSS_S_COMPLETE && now + lifetime < expire) { + expire = now + lifetime; + } +#endif /* HAVE_GSSAPI */ + RETERR(dns_tsigkey_createfromkey( + name, &tkeyin->algorithm, dstkey, true, principal, now, + expire, ring->mctx, ring, &tsigkey)); + dst_key_free(&dstkey); + tkeyout->inception = now; + tkeyout->expire = expire; + } else { + tkeyout->inception = tsigkey->inception; + tkeyout->expire = tsigkey->expire; + } + + if (outtoken) { + tkeyout->key = isc_mem_get(tkeyout->mctx, + isc_buffer_usedlength(outtoken)); + tkeyout->keylen = isc_buffer_usedlength(outtoken); + memmove(tkeyout->key, isc_buffer_base(outtoken), + isc_buffer_usedlength(outtoken)); + isc_buffer_free(&outtoken); + } else { + tkeyout->key = isc_mem_get(tkeyout->mctx, tkeyin->keylen); + tkeyout->keylen = tkeyin->keylen; + memmove(tkeyout->key, tkeyin->key, tkeyin->keylen); + } + + tkeyout->error = dns_rcode_noerror; + + tkey_log("process_gsstkey(): dns_tsigerror_noerror"); /* XXXSRA */ + + /* + * We found a TKEY to respond with. If the request is not TSIG signed, + * we need to make sure the response is signed (see RFC 3645, Section + * 2.2). + */ + if (tsigkey != NULL) { + if (msg->tsigkey == NULL && msg->sig0key == NULL) { + dns_message_settsigkey(msg, tsigkey); + } + dns_tsigkey_detach(&tsigkey); + } + + return (ISC_R_SUCCESS); + +failure: + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } + + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + + if (outtoken != NULL) { + isc_buffer_free(&outtoken); + } + + tkey_log("process_gsstkey(): %s", isc_result_totext(result)); /* XXXSRA + */ + + return (result); +} + +static isc_result_t +process_deletetkey(dns_name_t *signer, dns_name_t *name, + dns_rdata_tkey_t *tkeyin, dns_rdata_tkey_t *tkeyout, + dns_tsig_keyring_t *ring) { + isc_result_t result; + dns_tsigkey_t *tsigkey = NULL; + const dns_name_t *identity; + + result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring); + if (result != ISC_R_SUCCESS) { + tkeyout->error = dns_tsigerror_badname; + return (ISC_R_SUCCESS); + } + + /* + * Only allow a delete if the identity that created the key is the + * same as the identity that signed the message. + */ + identity = dns_tsigkey_identity(tsigkey); + if (identity == NULL || !dns_name_equal(identity, signer)) { + dns_tsigkey_detach(&tsigkey); + return (DNS_R_REFUSED); + } + + /* + * Set the key to be deleted when no references are left. If the key + * was not generated with TKEY and is in the config file, it may be + * reloaded later. + */ + dns_tsigkey_setdeleted(tsigkey); + + /* Release the reference */ + dns_tsigkey_detach(&tsigkey); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, + dns_tsig_keyring_t *ring) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_tkey_t tkeyin, tkeyout; + bool freetkeyin = false; + dns_name_t *qname, *name, *keyname, *signer, tsigner; + dns_fixedname_t fkeyname; + dns_rdataset_t *tkeyset; + dns_rdata_t rdata; + dns_namelist_t namelist; + char tkeyoutdata[512]; + isc_buffer_t tkeyoutbuf; + + REQUIRE(msg != NULL); + REQUIRE(tctx != NULL); + REQUIRE(ring != NULL); + + ISC_LIST_INIT(namelist); + + /* + * Interpret the question section. + */ + result = dns_message_firstname(msg, DNS_SECTION_QUESTION); + if (result != ISC_R_SUCCESS) { + return (DNS_R_FORMERR); + } + + qname = NULL; + dns_message_currentname(msg, DNS_SECTION_QUESTION, &qname); + + /* + * Look for a TKEY record that matches the question. + */ + tkeyset = NULL; + name = NULL; + result = dns_message_findname(msg, DNS_SECTION_ADDITIONAL, qname, + dns_rdatatype_tkey, 0, &name, &tkeyset); + if (result != ISC_R_SUCCESS) { + /* + * Try the answer section, since that's where Win2000 + * puts it. + */ + name = NULL; + if (dns_message_findname(msg, DNS_SECTION_ANSWER, qname, + dns_rdatatype_tkey, 0, &name, + &tkeyset) != ISC_R_SUCCESS) + { + result = DNS_R_FORMERR; + tkey_log("dns_tkey_processquery: couldn't find a TKEY " + "matching the question"); + goto failure; + } + } + result = dns_rdataset_first(tkeyset); + if (result != ISC_R_SUCCESS) { + result = DNS_R_FORMERR; + goto failure; + } + dns_rdata_init(&rdata); + dns_rdataset_current(tkeyset, &rdata); + + RETERR(dns_rdata_tostruct(&rdata, &tkeyin, NULL)); + freetkeyin = true; + + if (tkeyin.error != dns_rcode_noerror) { + result = DNS_R_FORMERR; + goto failure; + } + + /* + * Before we go any farther, verify that the message was signed. + * GSSAPI TKEY doesn't require a signature, the rest do. + */ + dns_name_init(&tsigner, NULL); + result = dns_message_signer(msg, &tsigner); + if (result != ISC_R_SUCCESS) { + if (tkeyin.mode == DNS_TKEYMODE_GSSAPI && + result == ISC_R_NOTFOUND) + { + signer = NULL; + } else { + tkey_log("dns_tkey_processquery: query was not " + "properly signed - rejecting"); + result = DNS_R_FORMERR; + goto failure; + } + } else { + signer = &tsigner; + } + + tkeyout.common.rdclass = tkeyin.common.rdclass; + tkeyout.common.rdtype = tkeyin.common.rdtype; + ISC_LINK_INIT(&tkeyout.common, link); + tkeyout.mctx = msg->mctx; + + dns_name_init(&tkeyout.algorithm, NULL); + dns_name_clone(&tkeyin.algorithm, &tkeyout.algorithm); + + tkeyout.inception = tkeyout.expire = 0; + tkeyout.mode = tkeyin.mode; + tkeyout.error = 0; + tkeyout.keylen = tkeyout.otherlen = 0; + tkeyout.key = tkeyout.other = NULL; + + /* + * A delete operation must have a fully specified key name. If this + * is not a delete, we do the following: + * if (qname != ".") + * keyname = qname + defaultdomain + * else + * keyname = + defaultdomain + */ + if (tkeyin.mode != DNS_TKEYMODE_DELETE) { + dns_tsigkey_t *tsigkey = NULL; + + if (tctx->domain == NULL && tkeyin.mode != DNS_TKEYMODE_GSSAPI) + { + tkey_log("dns_tkey_processquery: tkey-domain not set"); + result = DNS_R_REFUSED; + goto failure; + } + + keyname = dns_fixedname_initname(&fkeyname); + + if (!dns_name_equal(qname, dns_rootname)) { + unsigned int n = dns_name_countlabels(qname); + dns_name_copy(qname, keyname); + dns_name_getlabelsequence(keyname, 0, n - 1, keyname); + } else { + static char hexdigits[16] = { '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' }; + unsigned char randomdata[16]; + char randomtext[32]; + isc_buffer_t b; + unsigned int i, j; + + isc_nonce_buf(randomdata, sizeof(randomdata)); + + for (i = 0, j = 0; i < sizeof(randomdata); i++) { + unsigned char val = randomdata[i]; + randomtext[j++] = hexdigits[val >> 4]; + randomtext[j++] = hexdigits[val & 0xF]; + } + isc_buffer_init(&b, randomtext, sizeof(randomtext)); + isc_buffer_add(&b, sizeof(randomtext)); + result = dns_name_fromtext(keyname, &b, NULL, 0, NULL); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + if (tkeyin.mode == DNS_TKEYMODE_GSSAPI) { + /* Yup. This is a hack */ + result = dns_name_concatenate(keyname, dns_rootname, + keyname, NULL); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } else { + result = dns_name_concatenate(keyname, tctx->domain, + keyname, NULL); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + result = dns_tsigkey_find(&tsigkey, keyname, NULL, ring); + + if (result == ISC_R_SUCCESS) { + tkeyout.error = dns_tsigerror_badname; + dns_tsigkey_detach(&tsigkey); + goto failure_with_tkey; + } else if (result != ISC_R_NOTFOUND) { + goto failure; + } + } else { + keyname = qname; + } + + switch (tkeyin.mode) { + case DNS_TKEYMODE_DIFFIEHELLMAN: + tkeyout.error = dns_rcode_noerror; + RETERR(process_dhtkey(msg, signer, keyname, &tkeyin, tctx, + &tkeyout, ring, &namelist)); + break; + case DNS_TKEYMODE_GSSAPI: + tkeyout.error = dns_rcode_noerror; + RETERR(process_gsstkey(msg, keyname, &tkeyin, tctx, &tkeyout, + ring)); + break; + case DNS_TKEYMODE_DELETE: + tkeyout.error = dns_rcode_noerror; + RETERR(process_deletetkey(signer, keyname, &tkeyin, &tkeyout, + ring)); + break; + case DNS_TKEYMODE_SERVERASSIGNED: + case DNS_TKEYMODE_RESOLVERASSIGNED: + result = DNS_R_NOTIMP; + goto failure; + default: + tkeyout.error = dns_tsigerror_badmode; + } + +failure_with_tkey: + + dns_rdata_init(&rdata); + isc_buffer_init(&tkeyoutbuf, tkeyoutdata, sizeof(tkeyoutdata)); + result = dns_rdata_fromstruct(&rdata, tkeyout.common.rdclass, + tkeyout.common.rdtype, &tkeyout, + &tkeyoutbuf); + + if (freetkeyin) { + dns_rdata_freestruct(&tkeyin); + freetkeyin = false; + } + + if (tkeyout.key != NULL) { + isc_mem_put(tkeyout.mctx, tkeyout.key, tkeyout.keylen); + } + if (tkeyout.other != NULL) { + isc_mem_put(tkeyout.mctx, tkeyout.other, tkeyout.otherlen); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + RETERR(add_rdata_to_list(msg, keyname, &rdata, 0, &namelist)); + + RETERR(dns_message_reply(msg, true)); + + name = ISC_LIST_HEAD(namelist); + while (name != NULL) { + dns_name_t *next = ISC_LIST_NEXT(name, link); + ISC_LIST_UNLINK(namelist, name, link); + dns_message_addname(msg, name, DNS_SECTION_ANSWER); + name = next; + } + + return (ISC_R_SUCCESS); + +failure: + + if (freetkeyin) { + dns_rdata_freestruct(&tkeyin); + } + if (!ISC_LIST_EMPTY(namelist)) { + free_namelist(msg, &namelist); + } + return (result); +} + +static isc_result_t +buildquery(dns_message_t *msg, const dns_name_t *name, dns_rdata_tkey_t *tkey, + bool win2k) { + dns_name_t *qname = NULL, *aname = NULL; + dns_rdataset_t *question = NULL, *tkeyset = NULL; + dns_rdatalist_t *tkeylist = NULL; + dns_rdata_t *rdata = NULL; + isc_buffer_t *dynbuf = NULL; + isc_result_t result; + unsigned int len; + + REQUIRE(msg != NULL); + REQUIRE(name != NULL); + REQUIRE(tkey != NULL); + + RETERR(dns_message_gettempname(msg, &qname)); + RETERR(dns_message_gettempname(msg, &aname)); + + RETERR(dns_message_gettemprdataset(msg, &question)); + dns_rdataset_makequestion(question, dns_rdataclass_any, + dns_rdatatype_tkey); + + len = 16 + tkey->algorithm.length + tkey->keylen + tkey->otherlen; + isc_buffer_allocate(msg->mctx, &dynbuf, len); + RETERR(dns_message_gettemprdata(msg, &rdata)); + + RETERR(dns_rdata_fromstruct(rdata, dns_rdataclass_any, + dns_rdatatype_tkey, tkey, dynbuf)); + dns_message_takebuffer(msg, &dynbuf); + + RETERR(dns_message_gettemprdatalist(msg, &tkeylist)); + tkeylist->rdclass = dns_rdataclass_any; + tkeylist->type = dns_rdatatype_tkey; + ISC_LIST_APPEND(tkeylist->rdata, rdata, link); + + RETERR(dns_message_gettemprdataset(msg, &tkeyset)); + RETERR(dns_rdatalist_tordataset(tkeylist, tkeyset)); + + dns_name_copy(name, qname); + dns_name_copy(name, aname); + + ISC_LIST_APPEND(qname->list, question, link); + ISC_LIST_APPEND(aname->list, tkeyset, link); + + dns_message_addname(msg, qname, DNS_SECTION_QUESTION); + + /* + * Windows 2000 needs this in the answer section, not the additional + * section where the RFC specifies. + */ + if (win2k) { + dns_message_addname(msg, aname, DNS_SECTION_ANSWER); + } else { + dns_message_addname(msg, aname, DNS_SECTION_ADDITIONAL); + } + + return (ISC_R_SUCCESS); + +failure: + if (qname != NULL) { + dns_message_puttempname(msg, &qname); + } + if (aname != NULL) { + dns_message_puttempname(msg, &aname); + } + if (question != NULL) { + dns_rdataset_disassociate(question); + dns_message_puttemprdataset(msg, &question); + } + if (dynbuf != NULL) { + isc_buffer_free(&dynbuf); + } + if (rdata != NULL) { + dns_message_puttemprdata(msg, &rdata); + } + if (tkeylist != NULL) { + dns_message_puttemprdatalist(msg, &tkeylist); + } + if (tkeyset != NULL) { + if (dns_rdataset_isassociated(tkeyset)) { + dns_rdataset_disassociate(tkeyset); + } + dns_message_puttemprdataset(msg, &tkeyset); + } + return (result); +} + +isc_result_t +dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key, + const dns_name_t *name, const dns_name_t *algorithm, + isc_buffer_t *nonce, uint32_t lifetime) { + dns_rdata_tkey_t tkey; + dns_rdata_t *rdata = NULL; + isc_buffer_t *dynbuf = NULL; + isc_region_t r; + dns_name_t keyname; + dns_namelist_t namelist; + isc_result_t result; + isc_stdtime_t now; + dns_name_t *item; + + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH); + REQUIRE(dst_key_isprivate(key)); + REQUIRE(name != NULL); + REQUIRE(algorithm != NULL); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = msg->mctx; + dns_name_init(&tkey.algorithm, NULL); + dns_name_clone(algorithm, &tkey.algorithm); + isc_stdtime_get(&now); + tkey.inception = now; + tkey.expire = now + lifetime; + tkey.mode = DNS_TKEYMODE_DIFFIEHELLMAN; + if (nonce != NULL) { + isc_buffer_usedregion(nonce, &r); + } else { + r.base = NULL; + r.length = 0; + } + tkey.error = 0; + tkey.key = r.base; + tkey.keylen = r.length; + tkey.other = NULL; + tkey.otherlen = 0; + + RETERR(buildquery(msg, name, &tkey, false)); + + RETERR(dns_message_gettemprdata(msg, &rdata)); + isc_buffer_allocate(msg->mctx, &dynbuf, 1024); + RETERR(dst_key_todns(key, dynbuf)); + isc_buffer_usedregion(dynbuf, &r); + dns_rdata_fromregion(rdata, dns_rdataclass_any, dns_rdatatype_key, &r); + dns_message_takebuffer(msg, &dynbuf); + + dns_name_init(&keyname, NULL); + dns_name_clone(dst_key_name(key), &keyname); + + ISC_LIST_INIT(namelist); + RETERR(add_rdata_to_list(msg, &keyname, rdata, 0, &namelist)); + item = ISC_LIST_HEAD(namelist); + while (item != NULL) { + dns_name_t *next = ISC_LIST_NEXT(item, link); + ISC_LIST_UNLINK(namelist, item, link); + dns_message_addname(msg, item, DNS_SECTION_ADDITIONAL); + item = next; + } + + return (ISC_R_SUCCESS); + +failure: + + if (dynbuf != NULL) { + isc_buffer_free(&dynbuf); + } + return (result); +} + +isc_result_t +dns_tkey_buildgssquery(dns_message_t *msg, const dns_name_t *name, + const dns_name_t *gname, isc_buffer_t *intoken, + uint32_t lifetime, dns_gss_ctx_id_t *context, bool win2k, + isc_mem_t *mctx, char **err_message) { + dns_rdata_tkey_t tkey; + isc_result_t result; + isc_stdtime_t now; + isc_buffer_t token; + unsigned char array[TEMP_BUFFER_SZ]; + + UNUSED(intoken); + + REQUIRE(msg != NULL); + REQUIRE(name != NULL); + REQUIRE(gname != NULL); + REQUIRE(context != NULL); + REQUIRE(mctx != NULL); + + isc_buffer_init(&token, array, sizeof(array)); + result = dst_gssapi_initctx(gname, NULL, &token, context, mctx, + err_message); + if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) { + return (result); + } + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = NULL; + dns_name_init(&tkey.algorithm, NULL); + + if (win2k) { + dns_name_clone(DNS_TSIG_GSSAPIMS_NAME, &tkey.algorithm); + } else { + dns_name_clone(DNS_TSIG_GSSAPI_NAME, &tkey.algorithm); + } + + isc_stdtime_get(&now); + tkey.inception = now; + tkey.expire = now + lifetime; + tkey.mode = DNS_TKEYMODE_GSSAPI; + tkey.error = 0; + tkey.key = isc_buffer_base(&token); + tkey.keylen = isc_buffer_usedlength(&token); + tkey.other = NULL; + tkey.otherlen = 0; + + return (buildquery(msg, name, &tkey, win2k)); +} + +isc_result_t +dns_tkey_builddeletequery(dns_message_t *msg, dns_tsigkey_t *key) { + dns_rdata_tkey_t tkey; + + REQUIRE(msg != NULL); + REQUIRE(key != NULL); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = msg->mctx; + dns_name_init(&tkey.algorithm, NULL); + dns_name_clone(key->algorithm, &tkey.algorithm); + tkey.inception = tkey.expire = 0; + tkey.mode = DNS_TKEYMODE_DELETE; + tkey.error = 0; + tkey.keylen = tkey.otherlen = 0; + tkey.key = tkey.other = NULL; + + return (buildquery(msg, &key->name, &tkey, false)); +} + +static isc_result_t +find_tkey(dns_message_t *msg, dns_name_t **name, dns_rdata_t *rdata, + int section) { + dns_rdataset_t *tkeyset; + isc_result_t result; + + result = dns_message_firstname(msg, section); + while (result == ISC_R_SUCCESS) { + *name = NULL; + dns_message_currentname(msg, section, name); + tkeyset = NULL; + result = dns_message_findtype(*name, dns_rdatatype_tkey, 0, + &tkeyset); + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_first(tkeyset); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdataset_current(tkeyset, rdata); + return (ISC_R_SUCCESS); + } + result = dns_message_nextname(msg, section); + } + if (result == ISC_R_NOMORE) { + return (ISC_R_NOTFOUND); + } + return (result); +} + +isc_result_t +dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dst_key_t *key, isc_buffer_t *nonce, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring) { + dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT; + dns_name_t keyname, *tkeyname, *theirkeyname, *ourkeyname, *tempname; + dns_rdataset_t *theirkeyset = NULL, *ourkeyset = NULL; + dns_rdata_t theirkeyrdata = DNS_RDATA_INIT; + dst_key_t *theirkey = NULL; + dns_rdata_tkey_t qtkey, rtkey; + unsigned char secretdata[256]; + unsigned int sharedsize; + isc_buffer_t *shared = NULL, secret; + isc_region_t r, r2; + isc_result_t result; + bool freertkey = false; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + REQUIRE(key != NULL); + REQUIRE(dst_key_alg(key) == DNS_KEYALG_DH); + REQUIRE(dst_key_isprivate(key)); + if (outkey != NULL) { + REQUIRE(*outkey == NULL); + } + + if (rmsg->rcode != dns_rcode_noerror) { + return (dns_result_fromrcode(rmsg->rcode)); + } + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + freertkey = true; + + RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, DNS_SECTION_ADDITIONAL)); + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_DIFFIEHELLMAN || + rtkey.mode != qtkey.mode || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) || + rmsg->rcode != dns_rcode_noerror) + { + tkey_log("dns_tkey_processdhresponse: tkey mode invalid " + "or error set(1)"); + result = DNS_R_INVALIDTKEY; + dns_rdata_freestruct(&qtkey); + goto failure; + } + + dns_rdata_freestruct(&qtkey); + + dns_name_init(&keyname, NULL); + dns_name_clone(dst_key_name(key), &keyname); + + ourkeyname = NULL; + ourkeyset = NULL; + RETERR(dns_message_findname(rmsg, DNS_SECTION_ANSWER, &keyname, + dns_rdatatype_key, 0, &ourkeyname, + &ourkeyset)); + + result = dns_message_firstname(rmsg, DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + theirkeyname = NULL; + dns_message_currentname(rmsg, DNS_SECTION_ANSWER, + &theirkeyname); + if (dns_name_equal(theirkeyname, ourkeyname)) { + goto next; + } + theirkeyset = NULL; + result = dns_message_findtype(theirkeyname, dns_rdatatype_key, + 0, &theirkeyset); + if (result == ISC_R_SUCCESS) { + RETERR(dns_rdataset_first(theirkeyset)); + break; + } + next: + result = dns_message_nextname(rmsg, DNS_SECTION_ANSWER); + } + + if (theirkeyset == NULL) { + tkey_log("dns_tkey_processdhresponse: failed to find server " + "key"); + result = ISC_R_NOTFOUND; + goto failure; + } + + dns_rdataset_current(theirkeyset, &theirkeyrdata); + RETERR(dns_dnssec_keyfromrdata(theirkeyname, &theirkeyrdata, rmsg->mctx, + &theirkey)); + + RETERR(dst_key_secretsize(key, &sharedsize)); + isc_buffer_allocate(rmsg->mctx, &shared, sharedsize); + + RETERR(dst_key_computesecret(theirkey, key, shared)); + + isc_buffer_init(&secret, secretdata, sizeof(secretdata)); + + r.base = rtkey.key; + r.length = rtkey.keylen; + if (nonce != NULL) { + isc_buffer_usedregion(nonce, &r2); + } else { + r2.base = NULL; + r2.length = 0; + } + RETERR(compute_secret(shared, &r2, &r, &secret)); + + isc_buffer_usedregion(&secret, &r); + result = dns_tsigkey_create(tkeyname, &rtkey.algorithm, r.base, + r.length, true, NULL, rtkey.inception, + rtkey.expire, rmsg->mctx, ring, outkey); + isc_buffer_free(&shared); + dns_rdata_freestruct(&rtkey); + dst_key_free(&theirkey); + return (result); + +failure: + if (shared != NULL) { + isc_buffer_free(&shared); + } + + if (theirkey != NULL) { + dst_key_free(&theirkey); + } + + if (freertkey) { + dns_rdata_freestruct(&rtkey); + } + + return (result); +} + +isc_result_t +dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg, + const dns_name_t *gname, dns_gss_ctx_id_t *context, + isc_buffer_t *outtoken, dns_tsigkey_t **outkey, + dns_tsig_keyring_t *ring, char **err_message) { + dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT; + dns_name_t *tkeyname; + dns_rdata_tkey_t rtkey, qtkey; + dst_key_t *dstkey = NULL; + isc_buffer_t intoken; + isc_result_t result; + unsigned char array[TEMP_BUFFER_SZ]; + + REQUIRE(outtoken != NULL); + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + REQUIRE(gname != NULL); + REQUIRE(ring != NULL); + if (outkey != NULL) { + REQUIRE(*outkey == NULL); + } + + if (rmsg->rcode != dns_rcode_noerror) { + return (dns_result_fromrcode(rmsg->rcode)); + } + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + + /* + * Win2k puts the item in the ANSWER section, while the RFC + * specifies it should be in the ADDITIONAL section. Check first + * where it should be, and then where it may be. + */ + result = find_tkey(qmsg, &tkeyname, &qtkeyrdata, + DNS_SECTION_ADDITIONAL); + if (result == ISC_R_NOTFOUND) { + result = find_tkey(qmsg, &tkeyname, &qtkeyrdata, + DNS_SECTION_ANSWER); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_GSSAPI || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm)) + { + tkey_log("dns_tkey_processgssresponse: tkey mode invalid " + "or error set(2) %d", + rtkey.error); + dumpmessage(qmsg); + dumpmessage(rmsg); + result = DNS_R_INVALIDTKEY; + goto failure; + } + + isc_buffer_init(outtoken, array, sizeof(array)); + isc_buffer_init(&intoken, rtkey.key, rtkey.keylen); + RETERR(dst_gssapi_initctx(gname, &intoken, outtoken, context, + ring->mctx, err_message)); + + RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx, &dstkey, + NULL)); + + RETERR(dns_tsigkey_createfromkey( + tkeyname, DNS_TSIG_GSSAPI_NAME, dstkey, false, NULL, + rtkey.inception, rtkey.expire, ring->mctx, ring, outkey)); + dst_key_free(&dstkey); + dns_rdata_freestruct(&rtkey); + return (result); + +failure: + /* + * XXXSRA This probably leaks memory from rtkey and qtkey. + */ + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} + +isc_result_t +dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg, + dns_tsig_keyring_t *ring) { + dns_rdata_t qtkeyrdata = DNS_RDATA_INIT, rtkeyrdata = DNS_RDATA_INIT; + dns_name_t *tkeyname, *tempname; + dns_rdata_tkey_t qtkey, rtkey; + dns_tsigkey_t *tsigkey = NULL; + isc_result_t result; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + + if (rmsg->rcode != dns_rcode_noerror) { + return (dns_result_fromrcode(rmsg->rcode)); + } + + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + + RETERR(find_tkey(qmsg, &tempname, &qtkeyrdata, DNS_SECTION_ADDITIONAL)); + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_DELETE || rtkey.mode != qtkey.mode || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm) || + rmsg->rcode != dns_rcode_noerror) + { + tkey_log("dns_tkey_processdeleteresponse: tkey mode invalid " + "or error set(3)"); + result = DNS_R_INVALIDTKEY; + dns_rdata_freestruct(&qtkey); + dns_rdata_freestruct(&rtkey); + goto failure; + } + + dns_rdata_freestruct(&qtkey); + + RETERR(dns_tsigkey_find(&tsigkey, tkeyname, &rtkey.algorithm, ring)); + + dns_rdata_freestruct(&rtkey); + + /* + * Mark the key as deleted. + */ + dns_tsigkey_setdeleted(tsigkey); + /* + * Release the reference. + */ + dns_tsigkey_detach(&tsigkey); + +failure: + return (result); +} + +isc_result_t +dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg, + const dns_name_t *server, dns_gss_ctx_id_t *context, + dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring, + bool win2k, char **err_message) { + dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT; + dns_name_t *tkeyname; + dns_rdata_tkey_t rtkey, qtkey, tkey; + isc_buffer_t intoken, outtoken; + dst_key_t *dstkey = NULL; + isc_result_t result; + unsigned char array[TEMP_BUFFER_SZ]; + bool freertkey = false; + + REQUIRE(qmsg != NULL); + REQUIRE(rmsg != NULL); + REQUIRE(server != NULL); + if (outkey != NULL) { + REQUIRE(*outkey == NULL); + } + + if (rmsg->rcode != dns_rcode_noerror) { + return (dns_result_fromrcode(rmsg->rcode)); + } + + RETERR(find_tkey(rmsg, &tkeyname, &rtkeyrdata, DNS_SECTION_ANSWER)); + RETERR(dns_rdata_tostruct(&rtkeyrdata, &rtkey, NULL)); + freertkey = true; + + if (win2k) { + RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata, + DNS_SECTION_ANSWER)); + } else { + RETERR(find_tkey(qmsg, &tkeyname, &qtkeyrdata, + DNS_SECTION_ADDITIONAL)); + } + + RETERR(dns_rdata_tostruct(&qtkeyrdata, &qtkey, NULL)); + + if (rtkey.error != dns_rcode_noerror || + rtkey.mode != DNS_TKEYMODE_GSSAPI || + !dns_name_equal(&rtkey.algorithm, &qtkey.algorithm)) + { + tkey_log("dns_tkey_processdhresponse: tkey mode invalid " + "or error set(4)"); + result = DNS_R_INVALIDTKEY; + goto failure; + } + + isc_buffer_init(&intoken, rtkey.key, rtkey.keylen); + isc_buffer_init(&outtoken, array, sizeof(array)); + + result = dst_gssapi_initctx(server, &intoken, &outtoken, context, + ring->mctx, err_message); + if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) { + return (result); + } + + if (result == DNS_R_CONTINUE) { + dns_fixedname_t fixed; + + dns_fixedname_init(&fixed); + dns_name_copy(tkeyname, dns_fixedname_name(&fixed)); + tkeyname = dns_fixedname_name(&fixed); + + tkey.common.rdclass = dns_rdataclass_any; + tkey.common.rdtype = dns_rdatatype_tkey; + ISC_LINK_INIT(&tkey.common, link); + tkey.mctx = NULL; + dns_name_init(&tkey.algorithm, NULL); + + if (win2k) { + dns_name_clone(DNS_TSIG_GSSAPIMS_NAME, &tkey.algorithm); + } else { + dns_name_clone(DNS_TSIG_GSSAPI_NAME, &tkey.algorithm); + } + + tkey.inception = qtkey.inception; + tkey.expire = qtkey.expire; + tkey.mode = DNS_TKEYMODE_GSSAPI; + tkey.error = 0; + tkey.key = isc_buffer_base(&outtoken); + tkey.keylen = isc_buffer_usedlength(&outtoken); + tkey.other = NULL; + tkey.otherlen = 0; + + dns_message_reset(qmsg, DNS_MESSAGE_INTENTRENDER); + RETERR(buildquery(qmsg, tkeyname, &tkey, win2k)); + return (DNS_R_CONTINUE); + } + + RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx, &dstkey, + NULL)); + + /* + * XXXSRA This seems confused. If we got CONTINUE from initctx, + * the GSS negotiation hasn't completed yet, so we can't sign + * anything yet. + */ + + RETERR(dns_tsigkey_createfromkey( + tkeyname, + (win2k ? DNS_TSIG_GSSAPIMS_NAME : DNS_TSIG_GSSAPI_NAME), dstkey, + true, NULL, rtkey.inception, rtkey.expire, ring->mctx, ring, + outkey)); + dst_key_free(&dstkey); + dns_rdata_freestruct(&rtkey); + return (result); + +failure: + /* + * XXXSRA This probably leaks memory from qtkey. + */ + if (freertkey) { + dns_rdata_freestruct(&rtkey); + } + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} diff --git a/lib/dns/transport.c b/lib/dns/transport.c new file mode 100644 index 0000000..ae1ab74 --- /dev/null +++ b/lib/dns/transport.c @@ -0,0 +1,474 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TRANSPORT_MAGIC ISC_MAGIC('T', 'r', 'n', 's') +#define VALID_TRANSPORT(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_MAGIC) + +#define TRANSPORT_LIST_MAGIC ISC_MAGIC('T', 'r', 'L', 's') +#define VALID_TRANSPORT_LIST(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_LIST_MAGIC) + +struct dns_transport_list { + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + isc_rwlock_t lock; + dns_rbt_t *transports[DNS_TRANSPORT_COUNT]; +}; + +typedef enum ternary { ter_none = 0, ter_true = 1, ter_false = 2 } ternary_t; + +struct dns_transport { + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + dns_transport_type_t type; + struct { + char *tlsname; + char *certfile; + char *keyfile; + char *cafile; + char *remote_hostname; + char *ciphers; + uint32_t protocol_versions; + ternary_t prefer_server_ciphers; + } tls; + struct { + char *endpoint; + dns_http_mode_t mode; + } doh; +}; + +static void +free_dns_transport(void *node, void *arg) { + dns_transport_t *transport = node; + + REQUIRE(node != NULL); + + UNUSED(arg); + + dns_transport_detach(&transport); +} + +static isc_result_t +list_add(dns_transport_list_t *list, const dns_name_t *name, + const dns_transport_type_t type, dns_transport_t *transport) { + isc_result_t result; + dns_rbt_t *rbt = NULL; + + RWLOCK(&list->lock, isc_rwlocktype_write); + rbt = list->transports[type]; + INSIST(rbt != NULL); + + result = dns_rbt_addname(rbt, name, transport); + + RWUNLOCK(&list->lock, isc_rwlocktype_write); + + return (result); +} + +dns_transport_type_t +dns_transport_get_type(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->type); +} + +char * +dns_transport_get_certfile(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.certfile); +} + +char * +dns_transport_get_keyfile(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.keyfile); +} + +char * +dns_transport_get_cafile(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.cafile); +} + +char * +dns_transport_get_remote_hostname(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.remote_hostname); +} + +char * +dns_transport_get_endpoint(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->doh.endpoint); +} + +dns_http_mode_t +dns_transport_get_mode(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->doh.mode); +} + +dns_transport_t * +dns_transport_new(const dns_name_t *name, dns_transport_type_t type, + dns_transport_list_t *list) { + dns_transport_t *transport = isc_mem_get(list->mctx, + sizeof(*transport)); + *transport = (dns_transport_t){ .type = type }; + isc_refcount_init(&transport->references, 1); + isc_mem_attach(list->mctx, &transport->mctx); + transport->magic = TRANSPORT_MAGIC; + + list_add(list, name, type, transport); + + return (transport); +} + +void +dns_transport_set_certfile(dns_transport_t *transport, const char *certfile) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.certfile != NULL) { + isc_mem_free(transport->mctx, transport->tls.certfile); + } + + if (certfile != NULL) { + transport->tls.certfile = isc_mem_strdup(transport->mctx, + certfile); + } +} + +void +dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.keyfile != NULL) { + isc_mem_free(transport->mctx, transport->tls.keyfile); + } + + if (keyfile != NULL) { + transport->tls.keyfile = isc_mem_strdup(transport->mctx, + keyfile); + } +} + +void +dns_transport_set_cafile(dns_transport_t *transport, const char *cafile) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.cafile != NULL) { + isc_mem_free(transport->mctx, transport->tls.cafile); + } + + if (cafile != NULL) { + transport->tls.cafile = isc_mem_strdup(transport->mctx, cafile); + } +} + +void +dns_transport_set_remote_hostname(dns_transport_t *transport, + const char *hostname) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.remote_hostname != NULL) { + isc_mem_free(transport->mctx, transport->tls.remote_hostname); + } + + if (hostname != NULL) { + transport->tls.remote_hostname = isc_mem_strdup(transport->mctx, + hostname); + } +} + +void +dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_HTTP); + + if (transport->doh.endpoint != NULL) { + isc_mem_free(transport->mctx, transport->doh.endpoint); + } + + if (endpoint != NULL) { + transport->doh.endpoint = isc_mem_strdup(transport->mctx, + endpoint); + } +} + +void +dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_HTTP); + + transport->doh.mode = mode; +} + +void +dns_transport_set_tls_versions(dns_transport_t *transport, + const uint32_t tls_versions) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_HTTP || + transport->type == DNS_TRANSPORT_TLS); + + transport->tls.protocol_versions = tls_versions; +} + +uint32_t +dns_transport_get_tls_versions(const dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.protocol_versions); +} + +void +dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.ciphers != NULL) { + isc_mem_free(transport->mctx, transport->tls.ciphers); + } + + if (ciphers != NULL) { + transport->tls.ciphers = isc_mem_strdup(transport->mctx, + ciphers); + } +} + +void +dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.tlsname != NULL) { + isc_mem_free(transport->mctx, transport->tls.tlsname); + } + + if (tlsname != NULL) { + transport->tls.tlsname = isc_mem_strdup(transport->mctx, + tlsname); + } +} + +char * +dns_transport_get_ciphers(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.ciphers); +} + +char * +dns_transport_get_tlsname(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.tlsname); +} + +void +dns_transport_set_prefer_server_ciphers(dns_transport_t *transport, + const bool prefer) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + transport->tls.prefer_server_ciphers = prefer ? ter_true : ter_false; +} + +bool +dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport, + bool *preferp) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(preferp != NULL); + if (transport->tls.prefer_server_ciphers == ter_none) { + return (false); + } else if (transport->tls.prefer_server_ciphers == ter_true) { + *preferp = true; + return (true); + } else if (transport->tls.prefer_server_ciphers == ter_false) { + *preferp = false; + return (true); + } + + UNREACHABLE(); + return false; +} + +static void +transport_destroy(dns_transport_t *transport) { + isc_refcount_destroy(&transport->references); + transport->magic = 0; + + if (transport->doh.endpoint != NULL) { + isc_mem_free(transport->mctx, transport->doh.endpoint); + } + if (transport->tls.remote_hostname != NULL) { + isc_mem_free(transport->mctx, transport->tls.remote_hostname); + } + if (transport->tls.cafile != NULL) { + isc_mem_free(transport->mctx, transport->tls.cafile); + } + if (transport->tls.keyfile != NULL) { + isc_mem_free(transport->mctx, transport->tls.keyfile); + } + if (transport->tls.certfile != NULL) { + isc_mem_free(transport->mctx, transport->tls.certfile); + } + if (transport->tls.ciphers != NULL) { + isc_mem_free(transport->mctx, transport->tls.ciphers); + } + + if (transport->tls.tlsname != NULL) { + isc_mem_free(transport->mctx, transport->tls.tlsname); + } + + isc_mem_putanddetach(&transport->mctx, transport, sizeof(*transport)); +} + +void +dns_transport_attach(dns_transport_t *source, dns_transport_t **targetp) { + REQUIRE(source != NULL); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_transport_detach(dns_transport_t **transportp) { + dns_transport_t *transport = NULL; + + REQUIRE(transportp != NULL); + REQUIRE(VALID_TRANSPORT(*transportp)); + + transport = *transportp; + *transportp = NULL; + + if (isc_refcount_decrement(&transport->references) == 1) { + transport_destroy(transport); + } +} + +dns_transport_t * +dns_transport_find(const dns_transport_type_t type, const dns_name_t *name, + dns_transport_list_t *list) { + isc_result_t result; + dns_transport_t *transport = NULL; + dns_rbt_t *rbt = NULL; + + REQUIRE(VALID_TRANSPORT_LIST(list)); + REQUIRE(list->transports[type] != NULL); + + rbt = list->transports[type]; + + RWLOCK(&list->lock, isc_rwlocktype_read); + result = dns_rbt_findname(rbt, name, 0, NULL, (void *)&transport); + if (result == ISC_R_SUCCESS) { + isc_refcount_increment(&transport->references); + } + RWUNLOCK(&list->lock, isc_rwlocktype_read); + + return (transport); +} + +dns_transport_list_t * +dns_transport_list_new(isc_mem_t *mctx) { + dns_transport_list_t *list = isc_mem_get(mctx, sizeof(*list)); + + *list = (dns_transport_list_t){ 0 }; + + isc_rwlock_init(&list->lock, 0, 0); + + isc_mem_attach(mctx, &list->mctx); + isc_refcount_init(&list->references, 1); + + list->magic = TRANSPORT_LIST_MAGIC; + + for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) { + isc_result_t result; + result = dns_rbt_create(list->mctx, free_dns_transport, NULL, + &list->transports[type]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + return (list); +} + +void +dns_transport_list_attach(dns_transport_list_t *source, + dns_transport_list_t **targetp) { + REQUIRE(VALID_TRANSPORT_LIST(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +transport_list_destroy(dns_transport_list_t *list) { + isc_refcount_destroy(&list->references); + list->magic = 0; + + for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) { + if (list->transports[type] != NULL) { + dns_rbt_destroy(&list->transports[type]); + } + } + isc_rwlock_destroy(&list->lock); + isc_mem_putanddetach(&list->mctx, list, sizeof(*list)); +} + +void +dns_transport_list_detach(dns_transport_list_t **listp) { + dns_transport_list_t *list = NULL; + + REQUIRE(listp != NULL); + REQUIRE(VALID_TRANSPORT_LIST(*listp)); + + list = *listp; + *listp = NULL; + + if (isc_refcount_decrement(&list->references) == 1) { + transport_list_destroy(list); + } +} diff --git a/lib/dns/tsec.c b/lib/dns/tsec.c new file mode 100644 index 0000000..fcb7613 --- /dev/null +++ b/lib/dns/tsec.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include + +#include +#include + +#include + +#define DNS_TSEC_MAGIC ISC_MAGIC('T', 's', 'e', 'c') +#define DNS_TSEC_VALID(t) ISC_MAGIC_VALID(t, DNS_TSEC_MAGIC) + +/*% + * DNS Transaction Security object. We assume this is not shared by + * multiple threads, and so the structure does not contain a lock. + */ +struct dns_tsec { + unsigned int magic; + dns_tsectype_t type; + isc_mem_t *mctx; + union { + dns_tsigkey_t *tsigkey; + dst_key_t *key; + } ukey; +}; + +isc_result_t +dns_tsec_create(isc_mem_t *mctx, dns_tsectype_t type, dst_key_t *key, + dns_tsec_t **tsecp) { + isc_result_t result; + dns_tsec_t *tsec; + dns_tsigkey_t *tsigkey = NULL; + const dns_name_t *algname; + + REQUIRE(mctx != NULL); + REQUIRE(tsecp != NULL && *tsecp == NULL); + + tsec = isc_mem_get(mctx, sizeof(*tsec)); + + tsec->type = type; + tsec->mctx = mctx; + + switch (type) { + case dns_tsectype_tsig: + switch (dst_key_alg(key)) { + case DST_ALG_HMACMD5: + algname = dns_tsig_hmacmd5_name; + break; + case DST_ALG_HMACSHA1: + algname = dns_tsig_hmacsha1_name; + break; + case DST_ALG_HMACSHA224: + algname = dns_tsig_hmacsha224_name; + break; + case DST_ALG_HMACSHA256: + algname = dns_tsig_hmacsha256_name; + break; + case DST_ALG_HMACSHA384: + algname = dns_tsig_hmacsha384_name; + break; + case DST_ALG_HMACSHA512: + algname = dns_tsig_hmacsha512_name; + break; + default: + isc_mem_put(mctx, tsec, sizeof(*tsec)); + return (DNS_R_BADALG); + } + result = dns_tsigkey_createfromkey(dst_key_name(key), algname, + key, false, NULL, 0, 0, mctx, + NULL, &tsigkey); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, tsec, sizeof(*tsec)); + return (result); + } + tsec->ukey.tsigkey = tsigkey; + break; + case dns_tsectype_sig0: + tsec->ukey.key = key; + break; + default: + UNREACHABLE(); + } + + tsec->magic = DNS_TSEC_MAGIC; + + *tsecp = tsec; + return (ISC_R_SUCCESS); +} + +void +dns_tsec_destroy(dns_tsec_t **tsecp) { + dns_tsec_t *tsec; + + REQUIRE(tsecp != NULL && *tsecp != NULL); + tsec = *tsecp; + *tsecp = NULL; + REQUIRE(DNS_TSEC_VALID(tsec)); + + switch (tsec->type) { + case dns_tsectype_tsig: + dns_tsigkey_detach(&tsec->ukey.tsigkey); + break; + case dns_tsectype_sig0: + dst_key_free(&tsec->ukey.key); + break; + default: + UNREACHABLE(); + } + + tsec->magic = 0; + isc_mem_put(tsec->mctx, tsec, sizeof(*tsec)); +} + +dns_tsectype_t +dns_tsec_gettype(dns_tsec_t *tsec) { + REQUIRE(DNS_TSEC_VALID(tsec)); + + return (tsec->type); +} + +void +dns_tsec_getkey(dns_tsec_t *tsec, void *keyp) { + REQUIRE(DNS_TSEC_VALID(tsec)); + REQUIRE(keyp != NULL); + + switch (tsec->type) { + case dns_tsectype_tsig: + dns_tsigkey_attach(tsec->ukey.tsigkey, (dns_tsigkey_t **)keyp); + break; + case dns_tsectype_sig0: + *(dst_key_t **)keyp = tsec->ukey.key; + break; + default: + UNREACHABLE(); + } +} diff --git a/lib/dns/tsig.c b/lib/dns/tsig.c new file mode 100644 index 0000000..857ec4c --- /dev/null +++ b/lib/dns/tsig.c @@ -0,0 +1,1919 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tsig_p.h" + +#define TSIG_MAGIC ISC_MAGIC('T', 'S', 'I', 'G') +#define VALID_TSIG_KEY(x) ISC_MAGIC_VALID(x, TSIG_MAGIC) + +#ifndef DNS_TSIG_MAXGENERATEDKEYS +#define DNS_TSIG_MAXGENERATEDKEYS 4096 +#endif /* ifndef DNS_TSIG_MAXGENERATEDKEYS */ + +#define is_response(msg) ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) + +#define BADTIMELEN 6 + +static unsigned char hmacmd5_ndata[] = "\010hmac-md5\007sig-alg\003reg\003int"; +static unsigned char hmacmd5_offsets[] = { 0, 9, 17, 21, 25 }; + +static dns_name_t const hmacmd5 = DNS_NAME_INITABSOLUTE(hmacmd5_ndata, + hmacmd5_offsets); +const dns_name_t *dns_tsig_hmacmd5_name = &hmacmd5; + +static unsigned char gsstsig_ndata[] = "\010gss-tsig"; +static unsigned char gsstsig_offsets[] = { 0, 9 }; +static dns_name_t const gsstsig = DNS_NAME_INITABSOLUTE(gsstsig_ndata, + gsstsig_offsets); +const dns_name_t *dns_tsig_gssapi_name = &gsstsig; + +/* + * Since Microsoft doesn't follow its own standard, we will use this + * alternate name as a second guess. + */ +static unsigned char gsstsigms_ndata[] = "\003gss\011microsoft\003com"; +static unsigned char gsstsigms_offsets[] = { 0, 4, 14, 18 }; +static dns_name_t const gsstsigms = DNS_NAME_INITABSOLUTE(gsstsigms_ndata, + gsstsigms_offsets); +const dns_name_t *dns_tsig_gssapims_name = &gsstsigms; + +static unsigned char hmacsha1_ndata[] = "\011hmac-sha1"; +static unsigned char hmacsha1_offsets[] = { 0, 10 }; +static dns_name_t const hmacsha1 = DNS_NAME_INITABSOLUTE(hmacsha1_ndata, + hmacsha1_offsets); +const dns_name_t *dns_tsig_hmacsha1_name = &hmacsha1; + +static unsigned char hmacsha224_ndata[] = "\013hmac-sha224"; +static unsigned char hmacsha224_offsets[] = { 0, 12 }; +static dns_name_t const hmacsha224 = DNS_NAME_INITABSOLUTE(hmacsha224_ndata, + hmacsha224_offsets); +const dns_name_t *dns_tsig_hmacsha224_name = &hmacsha224; + +static unsigned char hmacsha256_ndata[] = "\013hmac-sha256"; +static unsigned char hmacsha256_offsets[] = { 0, 12 }; +static dns_name_t const hmacsha256 = DNS_NAME_INITABSOLUTE(hmacsha256_ndata, + hmacsha256_offsets); +const dns_name_t *dns_tsig_hmacsha256_name = &hmacsha256; + +static unsigned char hmacsha384_ndata[] = "\013hmac-sha384"; +static unsigned char hmacsha384_offsets[] = { 0, 12 }; +static dns_name_t const hmacsha384 = DNS_NAME_INITABSOLUTE(hmacsha384_ndata, + hmacsha384_offsets); +const dns_name_t *dns_tsig_hmacsha384_name = &hmacsha384; + +static unsigned char hmacsha512_ndata[] = "\013hmac-sha512"; +static unsigned char hmacsha512_offsets[] = { 0, 12 }; +static dns_name_t const hmacsha512 = DNS_NAME_INITABSOLUTE(hmacsha512_ndata, + hmacsha512_offsets); +const dns_name_t *dns_tsig_hmacsha512_name = &hmacsha512; + +static const struct { + const dns_name_t *name; + unsigned int dstalg; +} known_algs[] = { { &hmacmd5, DST_ALG_HMACMD5 }, + { &gsstsig, DST_ALG_GSSAPI }, + { &gsstsigms, DST_ALG_GSSAPI }, + { &hmacsha1, DST_ALG_HMACSHA1 }, + { &hmacsha224, DST_ALG_HMACSHA224 }, + { &hmacsha256, DST_ALG_HMACSHA256 }, + { &hmacsha384, DST_ALG_HMACSHA384 }, + { &hmacsha512, DST_ALG_HMACSHA512 } }; + +static isc_result_t +tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg); + +static void +tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +static void +cleanup_ring(dns_tsig_keyring_t *ring); +static void +tsigkey_free(dns_tsigkey_t *key); + +bool +dns__tsig_algvalid(unsigned int alg) { + return (alg == DST_ALG_HMACMD5 || alg == DST_ALG_HMACSHA1 || + alg == DST_ALG_HMACSHA224 || alg == DST_ALG_HMACSHA256 || + alg == DST_ALG_HMACSHA384 || alg == DST_ALG_HMACSHA512); +} + +static void +tsig_log(dns_tsigkey_t *key, int level, const char *fmt, ...) { + va_list ap; + char message[4096]; + char namestr[DNS_NAME_FORMATSIZE]; + char creatorstr[DNS_NAME_FORMATSIZE]; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + if (key != NULL) { + dns_name_format(&key->name, namestr, sizeof(namestr)); + } else { + strlcpy(namestr, "", sizeof(namestr)); + } + + if (key != NULL && key->generated && key->creator) { + dns_name_format(key->creator, creatorstr, sizeof(creatorstr)); + } else { + strlcpy(creatorstr, "", sizeof(creatorstr)); + } + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + if (key != NULL && key->generated) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_TSIG, level, + "tsig key '%s' (%s): %s", namestr, creatorstr, + message); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_TSIG, level, "tsig key '%s': %s", + namestr, message); + } +} + +static void +remove_fromring(dns_tsigkey_t *tkey) { + if (tkey->generated) { + ISC_LIST_UNLINK(tkey->ring->lru, tkey, link); + tkey->ring->generated--; + } + (void)dns_rbt_deletename(tkey->ring->keys, &tkey->name, false); +} + +static void +adjust_lru(dns_tsigkey_t *tkey) { + if (tkey->generated) { + RWLOCK(&tkey->ring->lock, isc_rwlocktype_write); + /* + * We may have been removed from the LRU list between + * removing the read lock and acquiring the write lock. + */ + if (ISC_LINK_LINKED(tkey, link) && tkey->ring->lru.tail != tkey) + { + ISC_LIST_UNLINK(tkey->ring->lru, tkey, link); + ISC_LIST_APPEND(tkey->ring->lru, tkey, link); + } + RWUNLOCK(&tkey->ring->lock, isc_rwlocktype_write); + } +} + +/* + * A supplemental routine just to add a key to ring. Note that reference + * counter should be counted separately because we may be adding the key + * as part of creation of the key, in which case the reference counter was + * already initialized. Also note we don't need RWLOCK for the reference + * counter: it's protected by a separate lock. + */ +static isc_result_t +keyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name, + dns_tsigkey_t *tkey) { + isc_result_t result; + + RWLOCK(&ring->lock, isc_rwlocktype_write); + ring->writecount++; + + /* + * Do on the fly cleaning. Find some nodes we might not + * want around any more. + */ + if (ring->writecount > 10) { + cleanup_ring(ring); + ring->writecount = 0; + } + + result = dns_rbt_addname(ring->keys, name, tkey); + if (result == ISC_R_SUCCESS) { + if (tkey->generated) { + /* + * Add the new key to the LRU list and remove the + * least recently used key if there are too many + * keys on the list. + */ + ISC_LIST_APPEND(ring->lru, tkey, link); + if (ring->generated++ > ring->maxgenerated) { + remove_fromring(ISC_LIST_HEAD(ring->lru)); + } + } + + tkey->ring = ring; + } + + RWUNLOCK(&ring->lock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_tsigkey_createfromkey(const dns_name_t *name, const dns_name_t *algorithm, + dst_key_t *dstkey, bool generated, + const dns_name_t *creator, isc_stdtime_t inception, + isc_stdtime_t expire, isc_mem_t *mctx, + dns_tsig_keyring_t *ring, dns_tsigkey_t **key) { + dns_tsigkey_t *tkey; + isc_result_t ret; + unsigned int refs = 0; + unsigned int dstalg = 0; + + REQUIRE(key == NULL || *key == NULL); + REQUIRE(name != NULL); + REQUIRE(algorithm != NULL); + REQUIRE(mctx != NULL); + REQUIRE(key != NULL || ring != NULL); + + tkey = isc_mem_get(mctx, sizeof(dns_tsigkey_t)); + + dns_name_init(&tkey->name, NULL); + dns_name_dup(name, mctx, &tkey->name); + (void)dns_name_downcase(&tkey->name, &tkey->name, NULL); + + /* Check against known algorithm names */ + dstalg = dns__tsig_algfromname(algorithm); + if (dstalg != 0) { + /* + * 'algorithm' must be set to a static pointer + * so that dns__tsig_algallocated() can compare them. + */ + tkey->algorithm = dns__tsig_algnamefromname(algorithm); + if (dstkey != NULL && dst_key_alg(dstkey) != dstalg) { + ret = DNS_R_BADALG; + goto cleanup_name; + } + } else { + dns_name_t *tmpname; + if (dstkey != NULL) { + ret = DNS_R_BADALG; + goto cleanup_name; + } + tmpname = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(tmpname, NULL); + dns_name_dup(algorithm, mctx, tmpname); + (void)dns_name_downcase(tmpname, tmpname, NULL); + tkey->algorithm = tmpname; + } + + if (creator != NULL) { + tkey->creator = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(tkey->creator, NULL); + dns_name_dup(creator, mctx, tkey->creator); + } else { + tkey->creator = NULL; + } + + tkey->key = NULL; + if (dstkey != NULL) { + dst_key_attach(dstkey, &tkey->key); + } + tkey->ring = ring; + + if (key != NULL) { + refs = 1; + } + if (ring != NULL) { + refs++; + } + + isc_refcount_init(&tkey->refs, refs); + + tkey->generated = generated; + tkey->inception = inception; + tkey->expire = expire; + tkey->mctx = NULL; + isc_mem_attach(mctx, &tkey->mctx); + ISC_LINK_INIT(tkey, link); + + tkey->magic = TSIG_MAGIC; + + if (ring != NULL) { + ret = keyring_add(ring, name, tkey); + if (ret != ISC_R_SUCCESS) { + goto cleanup_refs; + } + } + + /* + * Ignore this if it's a GSS key, since the key size is meaningless. + */ + if (dstkey != NULL && dst_key_size(dstkey) < 64 && + dstalg != DST_ALG_GSSAPI) + { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_TSIG, ISC_LOG_INFO, + "the key '%s' is too short to be secure", + namestr); + } + + if (key != NULL) { + *key = tkey; + } + + return (ISC_R_SUCCESS); + +cleanup_refs: + tkey->magic = 0; + while (refs-- > 0) { + isc_refcount_decrement0(&tkey->refs); + } + isc_refcount_destroy(&tkey->refs); + + if (tkey->key != NULL) { + dst_key_free(&tkey->key); + } + if (tkey->creator != NULL) { + dns_name_free(tkey->creator, mctx); + isc_mem_put(mctx, tkey->creator, sizeof(dns_name_t)); + } + if (dns__tsig_algallocated(tkey->algorithm)) { + dns_name_t *tmpname; + DE_CONST(tkey->algorithm, tmpname); + if (dns_name_dynamic(tmpname)) { + dns_name_free(tmpname, mctx); + } + isc_mem_put(mctx, tmpname, sizeof(dns_name_t)); + } +cleanup_name: + dns_name_free(&tkey->name, mctx); + isc_mem_put(mctx, tkey, sizeof(dns_tsigkey_t)); + + return (ret); +} + +/* + * Find a few nodes to destroy if possible. + */ +static void +cleanup_ring(dns_tsig_keyring_t *ring) { + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + isc_stdtime_t now; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + + /* + * Start up a new iterator each time. + */ + isc_stdtime_get(&now); + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + +again: + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return; + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + if (tkey != NULL) { + if (tkey->generated && + isc_refcount_current(&tkey->refs) == 1 && + tkey->inception != tkey->expire && + tkey->expire < now) + { + tsig_log(tkey, 2, "tsig expire: deleting"); + /* delete the key */ + dns_rbtnodechain_invalidate(&chain); + remove_fromring(tkey); + goto again; + } + } + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return; + } + } +} + +static void +destroyring(dns_tsig_keyring_t *ring) { + isc_refcount_destroy(&ring->references); + dns_rbt_destroy(&ring->keys); + isc_rwlock_destroy(&ring->lock); + isc_mem_putanddetach(&ring->mctx, ring, sizeof(dns_tsig_keyring_t)); +} + +/* + * Look up the DST_ALG_ constant for a given name. + */ +unsigned int +dns__tsig_algfromname(const dns_name_t *algorithm) { + int i; + int n = sizeof(known_algs) / sizeof(*known_algs); + for (i = 0; i < n; ++i) { + const dns_name_t *name = known_algs[i].name; + if (algorithm == name || dns_name_equal(algorithm, name)) { + return (known_algs[i].dstalg); + } + } + return (0); +} + +/* + * Convert an algorithm name into a pointer to the + * corresponding pre-defined dns_name_t structure. + */ +const dns_name_t * +dns__tsig_algnamefromname(const dns_name_t *algorithm) { + int i; + int n = sizeof(known_algs) / sizeof(*known_algs); + for (i = 0; i < n; ++i) { + const dns_name_t *name = known_algs[i].name; + if (algorithm == name || dns_name_equal(algorithm, name)) { + return (name); + } + } + return (NULL); +} + +/* + * Test whether the passed algorithm is NOT a pointer to one of the + * pre-defined known algorithms (and therefore one that has been + * dynamically allocated). + * + * This will return an incorrect result if passed a dynamically allocated + * dns_name_t that happens to match one of the pre-defined names. + */ +bool +dns__tsig_algallocated(const dns_name_t *algorithm) { + int i; + int n = sizeof(known_algs) / sizeof(*known_algs); + for (i = 0; i < n; ++i) { + const dns_name_t *name = known_algs[i].name; + if (algorithm == name) { + return (false); + } + } + return (true); +} + +static isc_result_t +restore_key(dns_tsig_keyring_t *ring, isc_stdtime_t now, FILE *fp) { + dst_key_t *dstkey = NULL; + char namestr[1024]; + char creatorstr[1024]; + char algorithmstr[1024]; + char keystr[4096]; + unsigned int inception, expire; + int n; + isc_buffer_t b; + dns_name_t *name, *creator, *algorithm; + dns_fixedname_t fname, fcreator, falgorithm; + isc_result_t result; + unsigned int dstalg; + + n = fscanf(fp, "%1023s %1023s %u %u %1023s %4095s\n", namestr, + creatorstr, &inception, &expire, algorithmstr, keystr); + if (n == EOF) { + return (ISC_R_NOMORE); + } + if (n != 6) { + return (ISC_R_FAILURE); + } + + if (isc_serial_lt(expire, now)) { + return (DNS_R_EXPIRED); + } + + name = dns_fixedname_initname(&fname); + isc_buffer_init(&b, namestr, strlen(namestr)); + isc_buffer_add(&b, strlen(namestr)); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + creator = dns_fixedname_initname(&fcreator); + isc_buffer_init(&b, creatorstr, strlen(creatorstr)); + isc_buffer_add(&b, strlen(creatorstr)); + result = dns_name_fromtext(creator, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + algorithm = dns_fixedname_initname(&falgorithm); + isc_buffer_init(&b, algorithmstr, strlen(algorithmstr)); + isc_buffer_add(&b, strlen(algorithmstr)); + result = dns_name_fromtext(algorithm, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dstalg = dns__tsig_algfromname(algorithm); + if (dstalg == 0) { + return (DNS_R_BADALG); + } + + result = dst_key_restore(name, dstalg, DNS_KEYOWNER_ENTITY, + DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, + ring->mctx, keystr, &dstkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_tsigkey_createfromkey(name, algorithm, dstkey, true, + creator, inception, expire, + ring->mctx, ring, NULL); + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} + +static void +dump_key(dns_tsigkey_t *tkey, FILE *fp) { + char *buffer = NULL; + int length = 0; + char namestr[DNS_NAME_FORMATSIZE]; + char creatorstr[DNS_NAME_FORMATSIZE]; + char algorithmstr[DNS_NAME_FORMATSIZE]; + isc_result_t result; + + REQUIRE(tkey != NULL); + REQUIRE(fp != NULL); + + dns_name_format(&tkey->name, namestr, sizeof(namestr)); + dns_name_format(tkey->creator, creatorstr, sizeof(creatorstr)); + dns_name_format(tkey->algorithm, algorithmstr, sizeof(algorithmstr)); + result = dst_key_dump(tkey->key, tkey->mctx, &buffer, &length); + if (result == ISC_R_SUCCESS) { + fprintf(fp, "%s %s %u %u %s %.*s\n", namestr, creatorstr, + tkey->inception, tkey->expire, algorithmstr, length, + buffer); + } + if (buffer != NULL) { + isc_mem_put(tkey->mctx, buffer, length); + } +} + +isc_result_t +dns_tsigkeyring_dumpanddetach(dns_tsig_keyring_t **ringp, FILE *fp) { + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + isc_stdtime_t now; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + dns_tsig_keyring_t *ring; + + REQUIRE(ringp != NULL && *ringp != NULL); + + ring = *ringp; + *ringp = NULL; + + if (isc_refcount_decrement(&ring->references) > 1) { + return (DNS_R_CONTINUE); + } + + isc_stdtime_get(&now); + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, origin); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + goto destroy; + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + if (tkey != NULL && tkey->generated && tkey->expire >= now) { + dump_key(tkey, fp); + } + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + goto destroy; + } + } + +destroy: + destroyring(ring); + return (result); +} + +const dns_name_t * +dns_tsigkey_identity(const dns_tsigkey_t *tsigkey) { + REQUIRE(tsigkey == NULL || VALID_TSIG_KEY(tsigkey)); + + if (tsigkey == NULL) { + return (NULL); + } + if (tsigkey->generated) { + return (tsigkey->creator); + } else { + return (&tsigkey->name); + } +} + +isc_result_t +dns_tsigkey_create(const dns_name_t *name, const dns_name_t *algorithm, + unsigned char *secret, int length, bool generated, + const dns_name_t *creator, isc_stdtime_t inception, + isc_stdtime_t expire, isc_mem_t *mctx, + dns_tsig_keyring_t *ring, dns_tsigkey_t **key) { + dst_key_t *dstkey = NULL; + isc_result_t result; + unsigned int dstalg = 0; + + REQUIRE(length >= 0); + if (length > 0) { + REQUIRE(secret != NULL); + } + + dstalg = dns__tsig_algfromname(algorithm); + if (dns__tsig_algvalid(dstalg)) { + if (secret != NULL) { + isc_buffer_t b; + + isc_buffer_init(&b, secret, length); + isc_buffer_add(&b, length); + result = dst_key_frombuffer( + name, dstalg, DNS_KEYOWNER_ENTITY, + DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, &b, + mctx, &dstkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else if (length > 0) { + return (DNS_R_BADALG); + } + + result = dns_tsigkey_createfromkey(name, algorithm, dstkey, generated, + creator, inception, expire, mctx, + ring, key); + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} + +void +dns_tsigkey_attach(dns_tsigkey_t *source, dns_tsigkey_t **targetp) { + REQUIRE(VALID_TSIG_KEY(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->refs); + *targetp = source; +} + +static void +tsigkey_free(dns_tsigkey_t *key) { + REQUIRE(VALID_TSIG_KEY(key)); + + key->magic = 0; + dns_name_free(&key->name, key->mctx); + if (dns__tsig_algallocated(key->algorithm)) { + dns_name_t *name; + DE_CONST(key->algorithm, name); + dns_name_free(name, key->mctx); + isc_mem_put(key->mctx, name, sizeof(dns_name_t)); + } + if (key->key != NULL) { + dst_key_free(&key->key); + } + if (key->creator != NULL) { + dns_name_free(key->creator, key->mctx); + isc_mem_put(key->mctx, key->creator, sizeof(dns_name_t)); + } + isc_mem_putanddetach(&key->mctx, key, sizeof(dns_tsigkey_t)); +} + +void +dns_tsigkey_detach(dns_tsigkey_t **keyp) { + REQUIRE(keyp != NULL && VALID_TSIG_KEY(*keyp)); + dns_tsigkey_t *key = *keyp; + *keyp = NULL; + + if (isc_refcount_decrement(&key->refs) == 1) { + isc_refcount_destroy(&key->refs); + tsigkey_free(key); + } +} + +void +dns_tsigkey_setdeleted(dns_tsigkey_t *key) { + REQUIRE(VALID_TSIG_KEY(key)); + REQUIRE(key->ring != NULL); + + RWLOCK(&key->ring->lock, isc_rwlocktype_write); + remove_fromring(key); + RWUNLOCK(&key->ring->lock, isc_rwlocktype_write); +} + +isc_result_t +dns_tsig_sign(dns_message_t *msg) { + dns_tsigkey_t *key = NULL; + dns_rdata_any_tsig_t tsig, querytsig; + unsigned char data[128]; + isc_buffer_t databuf, sigbuf; + isc_buffer_t *dynbuf = NULL; + dns_name_t *owner = NULL; + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *datalist = NULL; + dns_rdataset_t *dataset = NULL; + isc_region_t r; + isc_stdtime_t now; + isc_mem_t *mctx; + dst_context_t *ctx = NULL; + isc_result_t ret; + unsigned char badtimedata[BADTIMELEN]; + unsigned int sigsize = 0; + bool response; + + REQUIRE(msg != NULL); + key = dns_message_gettsigkey(msg); + REQUIRE(VALID_TSIG_KEY(key)); + + /* + * If this is a response, there should be a TSIG in the query with the + * the exception if this is a TKEY request (see RFC 3645, Section 2.2). + */ + response = is_response(msg); + if (response && msg->querytsig == NULL) { + if (msg->tkey != 1) { + return (DNS_R_EXPECTEDTSIG); + } + } + + mctx = msg->mctx; + + tsig.mctx = mctx; + tsig.common.rdclass = dns_rdataclass_any; + tsig.common.rdtype = dns_rdatatype_tsig; + ISC_LINK_INIT(&tsig.common, link); + dns_name_init(&tsig.algorithm, NULL); + dns_name_clone(key->algorithm, &tsig.algorithm); + + if (msg->fuzzing) { + now = msg->fuzztime; + } else { + isc_stdtime_get(&now); + } + + tsig.timesigned = now + msg->timeadjust; + tsig.fudge = DNS_TSIG_FUDGE; + + tsig.originalid = msg->id; + + isc_buffer_init(&databuf, data, sizeof(data)); + + if (response) { + tsig.error = msg->querytsigstatus; + } else { + tsig.error = dns_rcode_noerror; + } + + if (tsig.error != dns_tsigerror_badtime) { + tsig.otherlen = 0; + tsig.other = NULL; + } else { + isc_buffer_t otherbuf; + + tsig.otherlen = BADTIMELEN; + tsig.other = badtimedata; + isc_buffer_init(&otherbuf, tsig.other, tsig.otherlen); + isc_buffer_putuint48(&otherbuf, tsig.timesigned); + } + + if ((key->key != NULL) && (tsig.error != dns_tsigerror_badsig) && + (tsig.error != dns_tsigerror_badkey)) + { + unsigned char header[DNS_MESSAGE_HEADERLEN]; + isc_buffer_t headerbuf; + uint16_t digestbits; + bool querytsig_ok = false; + + /* + * If it is a response, we assume that the request MAC + * has validated at this point. This is why we include a + * MAC length > 0 in the reply. + */ + ret = dst_context_create(key->key, mctx, DNS_LOGCATEGORY_DNSSEC, + true, 0, &ctx); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + /* + * If this is a response, and if there was a TSIG in + * the query, digest the request's MAC. + * + * (Note: querytsig should be non-NULL for all + * responses except TKEY responses. Those may be signed + * with the newly-negotiated TSIG key even if the query + * wasn't signed.) + */ + if (response && msg->querytsig != NULL) { + dns_rdata_t querytsigrdata = DNS_RDATA_INIT; + + INSIST(msg->verified_sig); + + ret = dns_rdataset_first(msg->querytsig); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + dns_rdataset_current(msg->querytsig, &querytsigrdata); + ret = dns_rdata_tostruct(&querytsigrdata, &querytsig, + NULL); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + isc_buffer_putuint16(&databuf, querytsig.siglen); + if (isc_buffer_availablelength(&databuf) < + querytsig.siglen) + { + ret = ISC_R_NOSPACE; + goto cleanup_context; + } + isc_buffer_putmem(&databuf, querytsig.signature, + querytsig.siglen); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + querytsig_ok = true; + } + + /* + * Digest the header. + */ + isc_buffer_init(&headerbuf, header, sizeof(header)); + dns_message_renderheader(msg, &headerbuf); + isc_buffer_usedregion(&headerbuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest the remainder of the message. + */ + isc_buffer_usedregion(msg->buffer, &r); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + if (msg->tcp_continuation == 0) { + /* + * Digest the name, class, ttl, alg. + */ + dns_name_toregion(&key->name, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + isc_buffer_clear(&databuf); + isc_buffer_putuint16(&databuf, dns_rdataclass_any); + isc_buffer_putuint32(&databuf, 0); /* ttl */ + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + dns_name_toregion(&tsig.algorithm, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + } + /* Digest the timesigned and fudge */ + isc_buffer_clear(&databuf); + if (tsig.error == dns_tsigerror_badtime && querytsig_ok) { + tsig.timesigned = querytsig.timesigned; + } + isc_buffer_putuint48(&databuf, tsig.timesigned); + isc_buffer_putuint16(&databuf, tsig.fudge); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + if (msg->tcp_continuation == 0) { + /* + * Digest the error and other data length. + */ + isc_buffer_clear(&databuf); + isc_buffer_putuint16(&databuf, tsig.error); + isc_buffer_putuint16(&databuf, tsig.otherlen); + + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest other data. + */ + if (tsig.otherlen > 0) { + r.length = tsig.otherlen; + r.base = tsig.other; + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + } + } + + ret = dst_key_sigsize(key->key, &sigsize); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + tsig.signature = isc_mem_get(mctx, sigsize); + + isc_buffer_init(&sigbuf, tsig.signature, sigsize); + ret = dst_context_sign(ctx, &sigbuf); + if (ret != ISC_R_SUCCESS) { + goto cleanup_signature; + } + dst_context_destroy(&ctx); + digestbits = dst_key_getbits(key->key); + if (digestbits != 0) { + unsigned int bytes = (digestbits + 7) / 8; + if (querytsig_ok && bytes < querytsig.siglen) { + bytes = querytsig.siglen; + } + if (bytes > isc_buffer_usedlength(&sigbuf)) { + bytes = isc_buffer_usedlength(&sigbuf); + } + tsig.siglen = bytes; + } else { + tsig.siglen = isc_buffer_usedlength(&sigbuf); + } + } else { + tsig.siglen = 0; + tsig.signature = NULL; + } + + ret = dns_message_gettemprdata(msg, &rdata); + if (ret != ISC_R_SUCCESS) { + goto cleanup_signature; + } + isc_buffer_allocate(msg->mctx, &dynbuf, 512); + ret = dns_rdata_fromstruct(rdata, dns_rdataclass_any, + dns_rdatatype_tsig, &tsig, dynbuf); + if (ret != ISC_R_SUCCESS) { + goto cleanup_dynbuf; + } + + dns_message_takebuffer(msg, &dynbuf); + + if (tsig.signature != NULL) { + isc_mem_put(mctx, tsig.signature, sigsize); + tsig.signature = NULL; + } + + ret = dns_message_gettempname(msg, &owner); + if (ret != ISC_R_SUCCESS) { + goto cleanup_rdata; + } + dns_name_copy(&key->name, owner); + + ret = dns_message_gettemprdatalist(msg, &datalist); + if (ret != ISC_R_SUCCESS) { + goto cleanup_owner; + } + + ret = dns_message_gettemprdataset(msg, &dataset); + if (ret != ISC_R_SUCCESS) { + goto cleanup_rdatalist; + } + datalist->rdclass = dns_rdataclass_any; + datalist->type = dns_rdatatype_tsig; + ISC_LIST_APPEND(datalist->rdata, rdata, link); + RUNTIME_CHECK(dns_rdatalist_tordataset(datalist, dataset) == + ISC_R_SUCCESS); + msg->tsig = dataset; + msg->tsigname = owner; + + /* Windows does not like the tsig name being compressed. */ + msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; + + return (ISC_R_SUCCESS); + +cleanup_rdatalist: + dns_message_puttemprdatalist(msg, &datalist); +cleanup_owner: + dns_message_puttempname(msg, &owner); + goto cleanup_rdata; +cleanup_dynbuf: + isc_buffer_free(&dynbuf); +cleanup_rdata: + dns_message_puttemprdata(msg, &rdata); +cleanup_signature: + if (tsig.signature != NULL) { + isc_mem_put(mctx, tsig.signature, sigsize); + } +cleanup_context: + if (ctx != NULL) { + dst_context_destroy(&ctx); + } + return (ret); +} + +isc_result_t +dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg, + dns_tsig_keyring_t *ring1, dns_tsig_keyring_t *ring2) { + dns_rdata_any_tsig_t tsig, querytsig; + isc_region_t r, source_r, header_r, sig_r; + isc_buffer_t databuf; + unsigned char data[32]; + dns_name_t *keyname; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_stdtime_t now; + isc_result_t ret; + dns_tsigkey_t *tsigkey; + dst_key_t *key = NULL; + unsigned char header[DNS_MESSAGE_HEADERLEN]; + dst_context_t *ctx = NULL; + isc_mem_t *mctx; + uint16_t addcount, id; + unsigned int siglen; + unsigned int alg; + bool response; + + REQUIRE(source != NULL); + REQUIRE(DNS_MESSAGE_VALID(msg)); + tsigkey = dns_message_gettsigkey(msg); + response = is_response(msg); + + REQUIRE(tsigkey == NULL || VALID_TSIG_KEY(tsigkey)); + + msg->verify_attempted = 1; + msg->verified_sig = 0; + msg->tsigstatus = dns_tsigerror_badsig; + + if (msg->tcp_continuation) { + if (tsigkey == NULL || msg->querytsig == NULL) { + return (DNS_R_UNEXPECTEDTSIG); + } + return (tsig_verify_tcp(source, msg)); + } + + /* + * There should be a TSIG record... + */ + if (msg->tsig == NULL) { + return (DNS_R_EXPECTEDTSIG); + } + + /* + * If this is a response and there's no key or query TSIG, there + * shouldn't be one on the response. + */ + if (response && (tsigkey == NULL || msg->querytsig == NULL)) { + return (DNS_R_UNEXPECTEDTSIG); + } + + mctx = msg->mctx; + + /* + * If we're here, we know the message is well formed and contains a + * TSIG record. + */ + + keyname = msg->tsigname; + ret = dns_rdataset_first(msg->tsig); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + dns_rdataset_current(msg->tsig, &rdata); + ret = dns_rdata_tostruct(&rdata, &tsig, NULL); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + dns_rdata_reset(&rdata); + if (response) { + ret = dns_rdataset_first(msg->querytsig); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + dns_rdataset_current(msg->querytsig, &rdata); + ret = dns_rdata_tostruct(&rdata, &querytsig, NULL); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + } + + /* + * Do the key name and algorithm match that of the query? + */ + if (response && + (!dns_name_equal(keyname, &tsigkey->name) || + !dns_name_equal(&tsig.algorithm, &querytsig.algorithm))) + { + msg->tsigstatus = dns_tsigerror_badkey; + tsig_log(msg->tsigkey, 2, + "key name and algorithm do not match"); + return (DNS_R_TSIGVERIFYFAILURE); + } + + /* + * Get the current time. + */ + if (msg->fuzzing) { + now = msg->fuzztime; + } else { + isc_stdtime_get(&now); + } + + /* + * Find dns_tsigkey_t based on keyname. + */ + if (tsigkey == NULL) { + ret = ISC_R_NOTFOUND; + if (ring1 != NULL) { + ret = dns_tsigkey_find(&tsigkey, keyname, + &tsig.algorithm, ring1); + } + if (ret == ISC_R_NOTFOUND && ring2 != NULL) { + ret = dns_tsigkey_find(&tsigkey, keyname, + &tsig.algorithm, ring2); + } + if (ret != ISC_R_SUCCESS) { + msg->tsigstatus = dns_tsigerror_badkey; + ret = dns_tsigkey_create(keyname, &tsig.algorithm, NULL, + 0, false, NULL, now, now, mctx, + NULL, &msg->tsigkey); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + tsig_log(msg->tsigkey, 2, "unknown key"); + return (DNS_R_TSIGVERIFYFAILURE); + } + msg->tsigkey = tsigkey; + } + + key = tsigkey->key; + + /* + * Check digest length. + */ + alg = dst_key_alg(key); + ret = dst_key_sigsize(key, &siglen); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + if (dns__tsig_algvalid(alg)) { + if (tsig.siglen > siglen) { + tsig_log(msg->tsigkey, 2, "signature length too big"); + return (DNS_R_FORMERR); + } + if (tsig.siglen > 0 && + (tsig.siglen < 10 || tsig.siglen < ((siglen + 1) / 2))) + { + tsig_log(msg->tsigkey, 2, + "signature length below minimum"); + return (DNS_R_FORMERR); + } + } + + if (tsig.siglen > 0) { + uint16_t addcount_n; + + sig_r.base = tsig.signature; + sig_r.length = tsig.siglen; + + ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, 0, &ctx); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + if (response) { + isc_buffer_init(&databuf, data, sizeof(data)); + isc_buffer_putuint16(&databuf, querytsig.siglen); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + if (querytsig.siglen > 0) { + r.length = querytsig.siglen; + r.base = querytsig.signature; + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + } + } + + /* + * Extract the header. + */ + isc_buffer_usedregion(source, &r); + memmove(header, r.base, DNS_MESSAGE_HEADERLEN); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + + /* + * Decrement the additional field counter. + */ + memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2); + addcount_n = ntohs(addcount); + addcount = htons((uint16_t)(addcount_n - 1)); + memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2); + + /* + * Put in the original id. + */ + id = htons(tsig.originalid); + memmove(&header[0], &id, 2); + + /* + * Digest the modified header. + */ + header_r.base = (unsigned char *)header; + header_r.length = DNS_MESSAGE_HEADERLEN; + ret = dst_context_adddata(ctx, &header_r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest all non-TSIG records. + */ + isc_buffer_usedregion(source, &source_r); + r.base = source_r.base + DNS_MESSAGE_HEADERLEN; + r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN; + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest the key name. + */ + dns_name_toregion(&tsigkey->name, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + isc_buffer_init(&databuf, data, sizeof(data)); + isc_buffer_putuint16(&databuf, tsig.common.rdclass); + isc_buffer_putuint32(&databuf, msg->tsig->ttl); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest the key algorithm. + */ + dns_name_toregion(tsigkey->algorithm, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + isc_buffer_clear(&databuf); + isc_buffer_putuint48(&databuf, tsig.timesigned); + isc_buffer_putuint16(&databuf, tsig.fudge); + isc_buffer_putuint16(&databuf, tsig.error); + isc_buffer_putuint16(&databuf, tsig.otherlen); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + if (tsig.otherlen > 0) { + r.base = tsig.other; + r.length = tsig.otherlen; + ret = dst_context_adddata(ctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + } + + ret = dst_context_verify(ctx, &sig_r); + if (ret == DST_R_VERIFYFAILURE) { + ret = DNS_R_TSIGVERIFYFAILURE; + tsig_log(msg->tsigkey, 2, + "signature failed to verify(1)"); + goto cleanup_context; + } else if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + msg->verified_sig = 1; + } else if (!response || (tsig.error != dns_tsigerror_badsig && + tsig.error != dns_tsigerror_badkey)) + { + tsig_log(msg->tsigkey, 2, "signature was empty"); + return (DNS_R_TSIGVERIFYFAILURE); + } + + /* + * Here at this point, the MAC has been verified. Even if any of + * the following code returns a TSIG error, the reply will be + * signed and WILL always include the request MAC in the digest + * computation. + */ + + /* + * Is the time ok? + */ + if (now + msg->timeadjust > tsig.timesigned + tsig.fudge) { + msg->tsigstatus = dns_tsigerror_badtime; + tsig_log(msg->tsigkey, 2, "signature has expired"); + ret = DNS_R_CLOCKSKEW; + goto cleanup_context; + } else if (now + msg->timeadjust < tsig.timesigned - tsig.fudge) { + msg->tsigstatus = dns_tsigerror_badtime; + tsig_log(msg->tsigkey, 2, "signature is in the future"); + ret = DNS_R_CLOCKSKEW; + goto cleanup_context; + } + + if (dns__tsig_algvalid(alg)) { + uint16_t digestbits = dst_key_getbits(key); + + if (tsig.siglen > 0 && digestbits != 0 && + tsig.siglen < ((digestbits + 7) / 8)) + { + msg->tsigstatus = dns_tsigerror_badtrunc; + tsig_log(msg->tsigkey, 2, + "truncated signature length too small"); + ret = DNS_R_TSIGVERIFYFAILURE; + goto cleanup_context; + } + if (tsig.siglen > 0 && digestbits == 0 && tsig.siglen < siglen) + { + msg->tsigstatus = dns_tsigerror_badtrunc; + tsig_log(msg->tsigkey, 2, "signature length too small"); + ret = DNS_R_TSIGVERIFYFAILURE; + goto cleanup_context; + } + } + + if (response && tsig.error != dns_rcode_noerror) { + msg->tsigstatus = tsig.error; + if (tsig.error == dns_tsigerror_badtime) { + ret = DNS_R_CLOCKSKEW; + } else { + ret = DNS_R_TSIGERRORSET; + } + goto cleanup_context; + } + + msg->tsigstatus = dns_rcode_noerror; + ret = ISC_R_SUCCESS; + +cleanup_context: + if (ctx != NULL) { + dst_context_destroy(&ctx); + } + + return (ret); +} + +static isc_result_t +tsig_verify_tcp(isc_buffer_t *source, dns_message_t *msg) { + dns_rdata_any_tsig_t tsig, querytsig; + isc_region_t r, source_r, header_r, sig_r; + isc_buffer_t databuf; + unsigned char data[32]; + dns_name_t *keyname; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_stdtime_t now; + isc_result_t ret; + dns_tsigkey_t *tsigkey; + dst_key_t *key = NULL; + unsigned char header[DNS_MESSAGE_HEADERLEN]; + uint16_t addcount, id; + bool has_tsig = false; + isc_mem_t *mctx; + unsigned int siglen; + unsigned int alg; + + REQUIRE(source != NULL); + REQUIRE(msg != NULL); + REQUIRE(dns_message_gettsigkey(msg) != NULL); + REQUIRE(msg->tcp_continuation == 1); + REQUIRE(msg->querytsig != NULL); + + msg->verified_sig = 0; + msg->tsigstatus = dns_tsigerror_badsig; + + if (!is_response(msg)) { + return (DNS_R_EXPECTEDRESPONSE); + } + + mctx = msg->mctx; + + tsigkey = dns_message_gettsigkey(msg); + key = tsigkey->key; + + /* + * Extract and parse the previous TSIG + */ + ret = dns_rdataset_first(msg->querytsig); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + dns_rdataset_current(msg->querytsig, &rdata); + ret = dns_rdata_tostruct(&rdata, &querytsig, NULL); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + dns_rdata_reset(&rdata); + + /* + * If there is a TSIG in this message, do some checks. + */ + if (msg->tsig != NULL) { + has_tsig = true; + + keyname = msg->tsigname; + ret = dns_rdataset_first(msg->tsig); + if (ret != ISC_R_SUCCESS) { + goto cleanup_querystruct; + } + dns_rdataset_current(msg->tsig, &rdata); + ret = dns_rdata_tostruct(&rdata, &tsig, NULL); + if (ret != ISC_R_SUCCESS) { + goto cleanup_querystruct; + } + + /* + * Do the key name and algorithm match that of the query? + */ + if (!dns_name_equal(keyname, &tsigkey->name) || + !dns_name_equal(&tsig.algorithm, &querytsig.algorithm)) + { + msg->tsigstatus = dns_tsigerror_badkey; + ret = DNS_R_TSIGVERIFYFAILURE; + tsig_log(msg->tsigkey, 2, + "key name and algorithm do not match"); + goto cleanup_querystruct; + } + + /* + * Check digest length. + */ + alg = dst_key_alg(key); + ret = dst_key_sigsize(key, &siglen); + if (ret != ISC_R_SUCCESS) { + goto cleanup_querystruct; + } + if (dns__tsig_algvalid(alg)) { + if (tsig.siglen > siglen) { + tsig_log(tsigkey, 2, + "signature length too big"); + ret = DNS_R_FORMERR; + goto cleanup_querystruct; + } + if (tsig.siglen > 0 && + (tsig.siglen < 10 || + tsig.siglen < ((siglen + 1) / 2))) + { + tsig_log(tsigkey, 2, + "signature length below minimum"); + ret = DNS_R_FORMERR; + goto cleanup_querystruct; + } + } + } + + if (msg->tsigctx == NULL) { + ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, 0, &msg->tsigctx); + if (ret != ISC_R_SUCCESS) { + goto cleanup_querystruct; + } + + /* + * Digest the length of the query signature + */ + isc_buffer_init(&databuf, data, sizeof(data)); + isc_buffer_putuint16(&databuf, querytsig.siglen); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(msg->tsigctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest the data of the query signature + */ + if (querytsig.siglen > 0) { + r.length = querytsig.siglen; + r.base = querytsig.signature; + ret = dst_context_adddata(msg->tsigctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + } + } + + /* + * Extract the header. + */ + isc_buffer_usedregion(source, &r); + memmove(header, r.base, DNS_MESSAGE_HEADERLEN); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + + /* + * Decrement the additional field counter if necessary. + */ + if (has_tsig) { + uint16_t addcount_n; + + memmove(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2); + addcount_n = ntohs(addcount); + addcount = htons((uint16_t)(addcount_n - 1)); + memmove(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2); + + /* + * Put in the original id. + * + * XXX Can TCP transfers be forwarded? How would that + * work? + */ + id = htons(tsig.originalid); + memmove(&header[0], &id, 2); + } + + /* + * Digest the modified header. + */ + header_r.base = (unsigned char *)header; + header_r.length = DNS_MESSAGE_HEADERLEN; + ret = dst_context_adddata(msg->tsigctx, &header_r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest all non-TSIG records. + */ + isc_buffer_usedregion(source, &source_r); + r.base = source_r.base + DNS_MESSAGE_HEADERLEN; + if (has_tsig) { + r.length = msg->sigstart - DNS_MESSAGE_HEADERLEN; + } else { + r.length = source_r.length - DNS_MESSAGE_HEADERLEN; + } + ret = dst_context_adddata(msg->tsigctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + /* + * Digest the time signed and fudge. + */ + if (has_tsig) { + isc_buffer_init(&databuf, data, sizeof(data)); + isc_buffer_putuint48(&databuf, tsig.timesigned); + isc_buffer_putuint16(&databuf, tsig.fudge); + isc_buffer_usedregion(&databuf, &r); + ret = dst_context_adddata(msg->tsigctx, &r); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + + sig_r.base = tsig.signature; + sig_r.length = tsig.siglen; + if (tsig.siglen == 0) { + if (tsig.error != dns_rcode_noerror) { + msg->tsigstatus = tsig.error; + if (tsig.error == dns_tsigerror_badtime) { + ret = DNS_R_CLOCKSKEW; + } else { + ret = DNS_R_TSIGERRORSET; + } + } else { + tsig_log(msg->tsigkey, 2, "signature is empty"); + ret = DNS_R_TSIGVERIFYFAILURE; + } + goto cleanup_context; + } + + ret = dst_context_verify(msg->tsigctx, &sig_r); + if (ret == DST_R_VERIFYFAILURE) { + tsig_log(msg->tsigkey, 2, + "signature failed to verify(2)"); + ret = DNS_R_TSIGVERIFYFAILURE; + goto cleanup_context; + } else if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + msg->verified_sig = 1; + + /* + * Here at this point, the MAC has been verified. Even + * if any of the following code returns a TSIG error, + * the reply will be signed and WILL always include the + * request MAC in the digest computation. + */ + + /* + * Is the time ok? + */ + if (msg->fuzzing) { + now = msg->fuzztime; + } else { + isc_stdtime_get(&now); + } + + if (now + msg->timeadjust > tsig.timesigned + tsig.fudge) { + msg->tsigstatus = dns_tsigerror_badtime; + tsig_log(msg->tsigkey, 2, "signature has expired"); + ret = DNS_R_CLOCKSKEW; + goto cleanup_context; + } else if (now + msg->timeadjust < tsig.timesigned - tsig.fudge) + { + msg->tsigstatus = dns_tsigerror_badtime; + tsig_log(msg->tsigkey, 2, "signature is in the future"); + ret = DNS_R_CLOCKSKEW; + goto cleanup_context; + } + + alg = dst_key_alg(key); + ret = dst_key_sigsize(key, &siglen); + if (ret != ISC_R_SUCCESS) { + goto cleanup_context; + } + if (dns__tsig_algvalid(alg)) { + uint16_t digestbits = dst_key_getbits(key); + + if (tsig.siglen > 0 && digestbits != 0 && + tsig.siglen < ((digestbits + 7) / 8)) + { + msg->tsigstatus = dns_tsigerror_badtrunc; + tsig_log(msg->tsigkey, 2, + "truncated signature length " + "too small"); + ret = DNS_R_TSIGVERIFYFAILURE; + goto cleanup_context; + } + if (tsig.siglen > 0 && digestbits == 0 && + tsig.siglen < siglen) + { + msg->tsigstatus = dns_tsigerror_badtrunc; + tsig_log(msg->tsigkey, 2, + "signature length too small"); + ret = DNS_R_TSIGVERIFYFAILURE; + goto cleanup_context; + } + } + + if (tsig.error != dns_rcode_noerror) { + msg->tsigstatus = tsig.error; + if (tsig.error == dns_tsigerror_badtime) { + ret = DNS_R_CLOCKSKEW; + } else { + ret = DNS_R_TSIGERRORSET; + } + goto cleanup_context; + } + } + + msg->tsigstatus = dns_rcode_noerror; + ret = ISC_R_SUCCESS; + +cleanup_context: + /* + * Except in error conditions, don't destroy the DST context + * for unsigned messages; it is a running sum till the next + * TSIG signed message. + */ + if ((ret != ISC_R_SUCCESS || has_tsig) && msg->tsigctx != NULL) { + dst_context_destroy(&msg->tsigctx); + } + +cleanup_querystruct: + dns_rdata_freestruct(&querytsig); + + return (ret); +} + +isc_result_t +dns_tsigkey_find(dns_tsigkey_t **tsigkey, const dns_name_t *name, + const dns_name_t *algorithm, dns_tsig_keyring_t *ring) { + dns_tsigkey_t *key; + isc_stdtime_t now; + isc_result_t result; + + REQUIRE(tsigkey != NULL); + REQUIRE(*tsigkey == NULL); + REQUIRE(name != NULL); + REQUIRE(ring != NULL); + + RWLOCK(&ring->lock, isc_rwlocktype_write); + cleanup_ring(ring); + RWUNLOCK(&ring->lock, isc_rwlocktype_write); + + isc_stdtime_get(&now); + RWLOCK(&ring->lock, isc_rwlocktype_read); + key = NULL; + result = dns_rbt_findname(ring->keys, name, 0, NULL, (void *)&key); + if (result == DNS_R_PARTIALMATCH || result == ISC_R_NOTFOUND) { + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + return (ISC_R_NOTFOUND); + } + if (algorithm != NULL && !dns_name_equal(key->algorithm, algorithm)) { + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + return (ISC_R_NOTFOUND); + } + if (key->inception != key->expire && isc_serial_lt(key->expire, now)) { + /* + * The key has expired. + */ + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + RWLOCK(&ring->lock, isc_rwlocktype_write); + remove_fromring(key); + RWUNLOCK(&ring->lock, isc_rwlocktype_write); + return (ISC_R_NOTFOUND); + } +#if 0 + /* + * MPAXXX We really should look at the inception time. + */ + if (key->inception != key->expire && + isc_serial_lt(key->inception, now)) { + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + adjust_lru(key); + return (ISC_R_NOTFOUND); + } +#endif /* if 0 */ + isc_refcount_increment(&key->refs); + RWUNLOCK(&ring->lock, isc_rwlocktype_read); + adjust_lru(key); + *tsigkey = key; + return (ISC_R_SUCCESS); +} + +static void +free_tsignode(void *node, void *_unused) { + dns_tsigkey_t *key; + + REQUIRE(node != NULL); + + UNUSED(_unused); + + key = node; + if (key->generated) { + if (ISC_LINK_LINKED(key, link)) { + ISC_LIST_UNLINK(key->ring->lru, key, link); + } + } + dns_tsigkey_detach(&key); +} + +isc_result_t +dns_tsigkeyring_create(isc_mem_t *mctx, dns_tsig_keyring_t **ringp) { + isc_result_t result; + dns_tsig_keyring_t *ring; + + REQUIRE(mctx != NULL); + REQUIRE(ringp != NULL); + REQUIRE(*ringp == NULL); + + ring = isc_mem_get(mctx, sizeof(dns_tsig_keyring_t)); + + isc_rwlock_init(&ring->lock, 0, 0); + ring->keys = NULL; + result = dns_rbt_create(mctx, free_tsignode, NULL, &ring->keys); + if (result != ISC_R_SUCCESS) { + isc_rwlock_destroy(&ring->lock); + isc_mem_put(mctx, ring, sizeof(dns_tsig_keyring_t)); + return (result); + } + + ring->writecount = 0; + ring->mctx = NULL; + ring->generated = 0; + ring->maxgenerated = DNS_TSIG_MAXGENERATEDKEYS; + ISC_LIST_INIT(ring->lru); + isc_mem_attach(mctx, &ring->mctx); + isc_refcount_init(&ring->references, 1); + + *ringp = ring; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_tsigkeyring_add(dns_tsig_keyring_t *ring, const dns_name_t *name, + dns_tsigkey_t *tkey) { + isc_result_t result; + + REQUIRE(VALID_TSIG_KEY(tkey)); + REQUIRE(tkey->ring == NULL); + REQUIRE(name != NULL); + + result = keyring_add(ring, name, tkey); + if (result == ISC_R_SUCCESS) { + isc_refcount_increment(&tkey->refs); + } + + return (result); +} + +void +dns_tsigkeyring_attach(dns_tsig_keyring_t *source, + dns_tsig_keyring_t **target) { + REQUIRE(source != NULL); + REQUIRE(target != NULL && *target == NULL); + + isc_refcount_increment(&source->references); + + *target = source; +} + +void +dns_tsigkeyring_detach(dns_tsig_keyring_t **ringp) { + dns_tsig_keyring_t *ring; + + REQUIRE(ringp != NULL); + REQUIRE(*ringp != NULL); + + ring = *ringp; + *ringp = NULL; + + if (isc_refcount_decrement(&ring->references) == 1) { + destroyring(ring); + } +} + +void +dns_keyring_restore(dns_tsig_keyring_t *ring, FILE *fp) { + isc_stdtime_t now; + isc_result_t result; + + isc_stdtime_get(&now); + do { + result = restore_key(ring, now, fp); + if (result == ISC_R_NOMORE) { + return; + } + if (result == DNS_R_BADALG || result == DNS_R_EXPIRED) { + result = ISC_R_SUCCESS; + } + } while (result == ISC_R_SUCCESS); +} diff --git a/lib/dns/tsig_p.h b/lib/dns/tsig_p.h new file mode 100644 index 0000000..a19688c --- /dev/null +++ b/lib/dns/tsig_p.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/*! \file */ + +#include + +#include + +#include + +/*% + * These functions must not be used outside this module and + * its associated unit tests. + */ + +ISC_LANG_BEGINDECLS + +bool +dns__tsig_algvalid(unsigned int alg); +unsigned int +dns__tsig_algfromname(const dns_name_t *algorithm); +const dns_name_t * +dns__tsig_algnamefromname(const dns_name_t *algorithm); +bool +dns__tsig_algallocated(const dns_name_t *algorithm); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/ttl.c b/lib/dns/ttl.c new file mode 100644 index 0000000..9c6a8ae --- /dev/null +++ b/lib/dns/ttl.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +static isc_result_t +bind_ttl(isc_textregion_t *source, uint32_t *ttl); + +/* + * Helper for dns_ttl_totext(). + */ +static isc_result_t +ttlfmt(unsigned int t, const char *s, bool verbose, bool space, + isc_buffer_t *target) { + char tmp[60]; + unsigned int len; + isc_region_t region; + + if (verbose) { + len = snprintf(tmp, sizeof(tmp), "%s%u %s%s", space ? " " : "", + t, s, t == 1 ? "" : "s"); + } else { + len = snprintf(tmp, sizeof(tmp), "%u%c", t, s[0]); + } + + INSIST(len + 1 <= sizeof(tmp)); + isc_buffer_availableregion(target, ®ion); + if (len > region.length) { + return (ISC_R_NOSPACE); + } + memmove(region.base, tmp, len); + isc_buffer_add(target, len); + + return (ISC_R_SUCCESS); +} + +/* + * Derived from bind8 ns_format_ttl(). + */ +isc_result_t +dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target) { + unsigned secs, mins, hours, days, weeks, x; + + secs = src % 60; + src /= 60; + mins = src % 60; + src /= 60; + hours = src % 24; + src /= 24; + days = src % 7; + src /= 7; + weeks = src; + src = 0; + POST(src); + + x = 0; + if (weeks != 0) { + RETERR(ttlfmt(weeks, "week", verbose, (x > 0), target)); + x++; + } + if (days != 0) { + RETERR(ttlfmt(days, "day", verbose, (x > 0), target)); + x++; + } + if (hours != 0) { + RETERR(ttlfmt(hours, "hour", verbose, (x > 0), target)); + x++; + } + if (mins != 0) { + RETERR(ttlfmt(mins, "minute", verbose, (x > 0), target)); + x++; + } + if (secs != 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) { + RETERR(ttlfmt(secs, "second", verbose, (x > 0), target)); + x++; + } + INSIST(x > 0); + /* + * If only a single unit letter is printed, print it + * in upper case. (Why? Because BIND 8 does that. + * Presumably it has a reason.) + */ + if (x == 1 && upcase && !verbose) { + isc_region_t region; + /* + * The unit letter is the last character in the + * used region of the buffer. + * + * toupper() does not need its argument to be masked of cast + * here because region.base is type unsigned char *. + */ + isc_buffer_usedregion(target, ®ion); + region.base[region.length - 1] = + toupper(region.base[region.length - 1]); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl) { + return (bind_ttl(source, ttl)); +} + +isc_result_t +dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl) { + isc_result_t result; + + result = bind_ttl(source, ttl); + if (result != ISC_R_SUCCESS && result != ISC_R_RANGE) { + result = DNS_R_BADTTL; + } + return (result); +} + +static isc_result_t +bind_ttl(isc_textregion_t *source, uint32_t *ttl) { + uint64_t tmp = 0ULL; + uint32_t n; + char *s; + char buf[64]; + char nbuf[64]; /* Number buffer */ + + /* + * Copy the buffer as it may not be NULL terminated. + * No legal counter / ttl is longer that 63 characters. + */ + if (source->length > sizeof(buf) - 1) { + return (DNS_R_SYNTAX); + } + /* Copy source->length bytes and NUL terminate. */ + snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base); + s = buf; + + do { + isc_result_t result; + + char *np = nbuf; + while (*s != '\0' && isdigit((unsigned char)*s)) { + *np++ = *s++; + } + *np++ = '\0'; + INSIST(np - nbuf <= (int)sizeof(nbuf)); + result = isc_parse_uint32(&n, nbuf, 10); + if (result != ISC_R_SUCCESS) { + return (DNS_R_SYNTAX); + } + switch (*s) { + case 'w': + case 'W': + tmp += (uint64_t)n * 7 * 24 * 3600; + s++; + break; + case 'd': + case 'D': + tmp += (uint64_t)n * 24 * 3600; + s++; + break; + case 'h': + case 'H': + tmp += (uint64_t)n * 3600; + s++; + break; + case 'm': + case 'M': + tmp += (uint64_t)n * 60; + s++; + break; + case 's': + case 'S': + tmp += (uint64_t)n; + s++; + break; + case '\0': + /* Plain number? */ + if (tmp != 0ULL) { + return (DNS_R_SYNTAX); + } + tmp = n; + break; + default: + return (DNS_R_SYNTAX); + } + } while (*s != '\0'); + + if (tmp > 0xffffffffULL) { + return (ISC_R_RANGE); + } + + *ttl = (uint32_t)(tmp & 0xffffffffUL); + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/update.c b/lib/dns/update.c new file mode 100644 index 0000000..13fd05b --- /dev/null +++ b/lib/dns/update.c @@ -0,0 +1,2279 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************/ + +#define STATE_MAGIC ISC_MAGIC('S', 'T', 'T', 'E') +#define DNS_STATE_VALID(state) ISC_MAGIC_VALID(state, STATE_MAGIC) + +/*% + * Log level for tracing dynamic update protocol requests. + */ +#define LOGLEVEL_PROTOCOL ISC_LOG_INFO + +/*% + * Log level for low-level debug tracing. + */ +#define LOGLEVEL_DEBUG ISC_LOG_DEBUG(8) + +/*% + * Check an operation for failure. These macros all assume that + * the function using them has a 'result' variable and a 'failure' + * label. + */ +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/*% + * Fail unconditionally with result 'code', which must not + * be ISC_R_SUCCESS. The reason for failure presumably has + * been logged already. + * + * The test against ISC_R_SUCCESS is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ + +#define FAIL(code) \ + do { \ + result = (code); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/*% + * Fail unconditionally and log as a client error. + * The test against ISC_R_SUCCESS is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ +#define FAILC(code, msg) \ + do { \ + const char *_what = "failed"; \ + result = (code); \ + switch (result) { \ + case DNS_R_NXDOMAIN: \ + case DNS_R_YXDOMAIN: \ + case DNS_R_YXRRSET: \ + case DNS_R_NXRRSET: \ + _what = "unsuccessful"; \ + } \ + update_log(log, zone, LOGLEVEL_PROTOCOL, "update %s: %s (%s)", \ + _what, msg, isc_result_totext(result)); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define FAILN(code, name, msg) \ + do { \ + const char *_what = "failed"; \ + result = (code); \ + switch (result) { \ + case DNS_R_NXDOMAIN: \ + case DNS_R_YXDOMAIN: \ + case DNS_R_YXRRSET: \ + case DNS_R_NXRRSET: \ + _what = "unsuccessful"; \ + } \ + if (isc_log_wouldlog(dns_lctx, LOGLEVEL_PROTOCOL)) { \ + char _nbuf[DNS_NAME_FORMATSIZE]; \ + dns_name_format(name, _nbuf, sizeof(_nbuf)); \ + update_log(log, zone, LOGLEVEL_PROTOCOL, \ + "update %s: %s: %s (%s)", _what, _nbuf, \ + msg, isc_result_totext(result)); \ + } \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define FAILNT(code, name, type, msg) \ + do { \ + const char *_what = "failed"; \ + result = (code); \ + switch (result) { \ + case DNS_R_NXDOMAIN: \ + case DNS_R_YXDOMAIN: \ + case DNS_R_YXRRSET: \ + case DNS_R_NXRRSET: \ + _what = "unsuccessful"; \ + } \ + if (isc_log_wouldlog(dns_lctx, LOGLEVEL_PROTOCOL)) { \ + char _nbuf[DNS_NAME_FORMATSIZE]; \ + char _tbuf[DNS_RDATATYPE_FORMATSIZE]; \ + dns_name_format(name, _nbuf, sizeof(_nbuf)); \ + dns_rdatatype_format(type, _tbuf, sizeof(_tbuf)); \ + update_log(log, zone, LOGLEVEL_PROTOCOL, \ + "update %s: %s/%s: %s (%s)", _what, _nbuf, \ + _tbuf, msg, isc_result_totext(result)); \ + } \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/*% + * Fail unconditionally and log as a server error. + * The test against ISC_R_SUCCESS is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ +#define FAILS(code, msg) \ + do { \ + result = (code); \ + update_log(log, zone, LOGLEVEL_PROTOCOL, "error: %s: %s", msg, \ + isc_result_totext(result)); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/**************************************************************************/ + +typedef struct rr rr_t; + +struct rr { + /* dns_name_t name; */ + uint32_t ttl; + dns_rdata_t rdata; +}; + +typedef struct update_event update_event_t; + +/**************************************************************************/ + +static void +update_log(dns_update_log_t *callback, dns_zone_t *zone, int level, + const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5); + +static void +update_log(dns_update_log_t *callback, dns_zone_t *zone, int level, + const char *fmt, ...) { + va_list ap; + char message[4096]; + + if (callback == NULL) { + return; + } + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(message, sizeof(message), fmt, ap); + va_end(ap); + + (callback->func)(callback->arg, zone, level, message); +} + +/*% + * Update a single RR in version 'ver' of 'db' and log the + * update in 'diff'. + * + * Ensures: + * \li '*tuple' == NULL. Either the tuple is freed, or its + * ownership has been transferred to the diff. + */ +static isc_result_t +do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff) { + dns_diff_t temp_diff; + isc_result_t result; + + /* + * Create a singleton diff. + */ + dns_diff_init(diff->mctx, &temp_diff); + ISC_LIST_APPEND(temp_diff.tuples, *tuple, link); + + /* + * Apply it to the database. + */ + result = dns_diff_apply(&temp_diff, db, ver); + ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link); + if (result != ISC_R_SUCCESS) { + dns_difftuple_free(tuple); + return (result); + } + + /* + * Merge it into the current pending journal entry. + */ + dns_diff_appendminimal(diff, tuple); + + /* + * Do not clear temp_diff. + */ + return (ISC_R_SUCCESS); +} + +static isc_result_t +update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, + dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, + dns_rdata_t *rdata) { + dns_difftuple_t *tuple = NULL; + isc_result_t result; + result = dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (do_one_tuple(&tuple, db, ver, diff)); +} + +/**************************************************************************/ +/* + * Callback-style iteration over rdatasets and rdatas. + * + * foreach_rrset() can be used to iterate over the RRsets + * of a name and call a callback function with each + * one. Similarly, foreach_rr() can be used to iterate + * over the individual RRs at name, optionally restricted + * to RRs of a given type. + * + * The callback functions are called "actions" and take + * two arguments: a void pointer for passing arbitrary + * context information, and a pointer to the current RRset + * or RR. By convention, their names end in "_action". + */ + +/* + * XXXRTH We might want to make this public somewhere in libdns. + */ + +/*% + * Function type for foreach_rrset() iterator actions. + */ +typedef isc_result_t +rrset_func(void *data, dns_rdataset_t *rrset); + +/*% + * Function type for foreach_rr() iterator actions. + */ +typedef isc_result_t +rr_func(void *data, rr_t *rr); + +/*% + * Internal context struct for foreach_node_rr(). + */ +typedef struct { + rr_func *rr_action; + void *rr_action_data; +} foreach_node_rr_ctx_t; + +/*% + * Internal helper function for foreach_node_rr(). + */ +static isc_result_t +foreach_node_rr_action(void *data, dns_rdataset_t *rdataset) { + isc_result_t result; + foreach_node_rr_ctx_t *ctx = data; + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + rr_t rr = { 0, DNS_RDATA_INIT }; + + dns_rdataset_current(rdataset, &rr.rdata); + rr.ttl = rdataset->ttl; + result = (*ctx->rr_action)(ctx->rr_action_data, &rr); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + if (result != ISC_R_NOMORE) { + return (result); + } + return (ISC_R_SUCCESS); +} + +/*% + * For each rdataset of 'name' in 'ver' of 'db', call 'action' + * with the rdataset and 'action_data' as arguments. If the name + * does not exist, do nothing. + * + * If 'action' returns an error, abort iteration and return the error. + */ +static isc_result_t +foreach_rrset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + rrset_func *action, void *action_data) { + isc_result_t result; + dns_dbnode_t *node; + dns_rdatasetiter_t *iter; + + node = NULL; + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + iter = NULL; + result = dns_db_allrdatasets(db, node, ver, 0, (isc_stdtime_t)0, &iter); + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iter)) + { + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(iter, &rdataset); + + result = (*action)(action_data, &rdataset); + + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup_iterator; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup_iterator: + dns_rdatasetiter_destroy(&iter); + +cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/*% + * For each RR of 'name' in 'ver' of 'db', call 'action' + * with the RR and 'action_data' as arguments. If the name + * does not exist, do nothing. + * + * If 'action' returns an error, abort iteration + * and return the error. + */ +static isc_result_t +foreach_node_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + rr_func *rr_action, void *rr_action_data) { + foreach_node_rr_ctx_t ctx; + ctx.rr_action = rr_action; + ctx.rr_action_data = rr_action_data; + return (foreach_rrset(db, ver, name, foreach_node_rr_action, &ctx)); +} + +/*% + * For each of the RRs specified by 'db', 'ver', 'name', 'type', + * (which can be dns_rdatatype_any to match any type), and 'covers', call + * 'action' with the RR and 'action_data' as arguments. If the name + * does not exist, or if no RRset of the given type exists at the name, + * do nothing. + * + * If 'action' returns an error, abort iteration and return the error. + */ +static isc_result_t +foreach_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdatatype_t type, dns_rdatatype_t covers, rr_func *rr_action, + void *rr_action_data) { + isc_result_t result; + dns_dbnode_t *node; + dns_rdataset_t rdataset; + + if (type == dns_rdatatype_any) { + return (foreach_node_rr(db, ver, name, rr_action, + rr_action_data)); + } + + node = NULL; + if (type == dns_rdatatype_nsec3 || + (type == dns_rdatatype_rrsig && covers == dns_rdatatype_nsec3)) + { + result = dns_db_findnsec3node(db, name, false, &node); + } else { + result = dns_db_findnode(db, name, false, &node); + } + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, ver, type, covers, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + goto cleanup_node; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + rr_t rr = { 0, DNS_RDATA_INIT }; + dns_rdataset_current(&rdataset, &rr.rdata); + rr.ttl = rdataset.ttl; + result = (*rr_action)(rr_action_data, &rr); + if (result != ISC_R_SUCCESS) { + goto cleanup_rdataset; + } + } + if (result != ISC_R_NOMORE) { + goto cleanup_rdataset; + } + result = ISC_R_SUCCESS; + +cleanup_rdataset: + dns_rdataset_disassociate(&rdataset); +cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/**************************************************************************/ +/* + * Various tests on the database contents (for prerequisites, etc). + */ + +/*% + * Function type for predicate functions that compare a database RR 'db_rr' + * against an update RR 'update_rr'. + */ +typedef bool +rr_predicate(dns_rdata_t *update_rr, dns_rdata_t *db_rr); + +/*% + * Helper function for rrset_exists(). + */ +static isc_result_t +rrset_exists_action(void *data, rr_t *rr) { + UNUSED(data); + UNUSED(rr); + return (ISC_R_EXISTS); +} + +/*% + * Utility macro for RR existence checking functions. + * + * If the variable 'result' has the value ISC_R_EXISTS or + * ISC_R_SUCCESS, set *exists to true or false, + * respectively, and return success. + * + * If 'result' has any other value, there was a failure. + * Return the failure result code and do not set *exists. + * + * This would be more readable as "do { if ... } while(0)", + * but that form generates tons of warnings on Solaris 2.6. + */ +#define RETURN_EXISTENCE_FLAG \ + return ((result == ISC_R_EXISTS) \ + ? (*exists = true, ISC_R_SUCCESS) \ + : ((result == ISC_R_SUCCESS) \ + ? (*exists = false, ISC_R_SUCCESS) \ + : result)) + +/*% + * Set '*exists' to true iff an rrset of the given type exists, + * to false otherwise. + */ +static isc_result_t +rrset_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdatatype_t type, dns_rdatatype_t covers, bool *exists) { + isc_result_t result; + result = foreach_rr(db, ver, name, type, covers, rrset_exists_action, + NULL); + RETURN_EXISTENCE_FLAG; +} + +/*% + * Set '*visible' to true if the RRset exists and is part of the + * visible zone. Otherwise '*visible' is set to false unless a + * error occurs. + */ +static isc_result_t +rrset_visible(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdatatype_t type, bool *visible) { + isc_result_t result; + dns_fixedname_t fixed; + + dns_fixedname_init(&fixed); + result = dns_db_find(db, name, ver, type, DNS_DBFIND_NOWILD, + (isc_stdtime_t)0, NULL, dns_fixedname_name(&fixed), + NULL, NULL); + switch (result) { + case ISC_R_SUCCESS: + *visible = true; + break; + /* + * Glue, obscured, deleted or replaced records. + */ + case DNS_R_DELEGATION: + case DNS_R_DNAME: + case DNS_R_CNAME: + case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + case DNS_R_EMPTYNAME: + case DNS_R_COVERINGNSEC: + *visible = false; + result = ISC_R_SUCCESS; + break; + default: + *visible = false; /* silence false compiler warning */ + break; + } + return (result); +} + +/*% + * Context struct and helper function for name_exists(). + */ + +static isc_result_t +name_exists_action(void *data, dns_rdataset_t *rrset) { + UNUSED(data); + UNUSED(rrset); + return (ISC_R_EXISTS); +} + +/*% + * Set '*exists' to true iff the given name exists, to false otherwise. + */ +static isc_result_t +name_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + bool *exists) { + isc_result_t result; + result = foreach_rrset(db, ver, name, name_exists_action, NULL); + RETURN_EXISTENCE_FLAG; +} + +/**************************************************************************/ +/* + * Checking of "RRset exists (value dependent)" prerequisites. + * + * In the RFC2136 section 3.2.5, this is the pseudocode involving + * a variable called "temp", a mapping of tuples to rrsets. + * + * Here, we represent the "temp" data structure as (non-minimal) "dns_diff_t" + * where each tuple has op==DNS_DIFFOP_EXISTS. + */ + +/*% + * A comparison function defining the sorting order for the entries + * in the "temp" data structure. The major sort key is the owner name, + * followed by the type and rdata. + */ +static int +temp_order(const void *av, const void *bv) { + dns_difftuple_t const *const *ap = av; + dns_difftuple_t const *const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + int r; + r = dns_name_compare(&a->name, &b->name); + if (r != 0) { + return (r); + } + r = (b->rdata.type - a->rdata.type); + if (r != 0) { + return (r); + } + r = dns_rdata_casecompare(&a->rdata, &b->rdata); + return (r); +} + +/**************************************************************************/ +/* + * Conditional deletion of RRs. + */ + +/*% + * Context structure for delete_if(). + */ + +typedef struct { + rr_predicate *predicate; + dns_db_t *db; + dns_dbversion_t *ver; + dns_diff_t *diff; + dns_name_t *name; + dns_rdata_t *update_rr; +} conditional_delete_ctx_t; + +/*% + * Predicate functions for delete_if(). + */ + +/*% + * Return true always. + */ +static bool +true_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) { + UNUSED(update_rr); + UNUSED(db_rr); + return (true); +} + +/*% + * Return true if the record is a RRSIG. + */ +static bool +rrsig_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) { + UNUSED(update_rr); + return ((db_rr->type == dns_rdatatype_rrsig) ? true : false); +} + +/*% + * Internal helper function for delete_if(). + */ +static isc_result_t +delete_if_action(void *data, rr_t *rr) { + conditional_delete_ctx_t *ctx = data; + if ((*ctx->predicate)(ctx->update_rr, &rr->rdata)) { + isc_result_t result; + result = update_one_rr(ctx->db, ctx->ver, ctx->diff, + DNS_DIFFOP_DEL, ctx->name, rr->ttl, + &rr->rdata); + return (result); + } else { + return (ISC_R_SUCCESS); + } +} + +/*% + * Conditionally delete RRs. Apply 'predicate' to the RRs + * specified by 'db', 'ver', 'name', and 'type' (which can + * be dns_rdatatype_any to match any type). Delete those + * RRs for which the predicate returns true, and log the + * deletions in 'diff'. + */ +static isc_result_t +delete_if(rr_predicate *predicate, dns_db_t *db, dns_dbversion_t *ver, + dns_name_t *name, dns_rdatatype_t type, dns_rdatatype_t covers, + dns_rdata_t *update_rr, dns_diff_t *diff) { + conditional_delete_ctx_t ctx; + ctx.predicate = predicate; + ctx.db = db; + ctx.ver = ver; + ctx.diff = diff; + ctx.name = name; + ctx.update_rr = update_rr; + return (foreach_rr(db, ver, name, type, covers, delete_if_action, + &ctx)); +} + +/**************************************************************************/ +/* + * Incremental updating of NSECs and RRSIGs. + */ + +/*% + * We abuse the dns_diff_t type to represent a set of domain names + * affected by the update. + */ +static isc_result_t +namelist_append_name(dns_diff_t *list, dns_name_t *name) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + static dns_rdata_t dummy_rdata = DNS_RDATA_INIT; + + CHECK(dns_difftuple_create(list->mctx, DNS_DIFFOP_EXISTS, name, 0, + &dummy_rdata, &tuple)); + dns_diff_append(list, &tuple); +failure: + return (result); +} + +static isc_result_t +namelist_append_subdomain(dns_db_t *db, dns_name_t *name, + dns_diff_t *affected) { + isc_result_t result; + dns_fixedname_t fixedname; + dns_name_t *child; + dns_dbiterator_t *dbit = NULL; + + child = dns_fixedname_initname(&fixedname); + + CHECK(dns_db_createiterator(db, DNS_DB_NONSEC3, &dbit)); + + for (result = dns_dbiterator_seek(dbit, name); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbit)) + { + dns_dbnode_t *node = NULL; + CHECK(dns_dbiterator_current(dbit, &node, child)); + dns_db_detachnode(db, &node); + if (!dns_name_issubdomain(child, name)) { + break; + } + CHECK(namelist_append_name(affected, child)); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +failure: + if (dbit != NULL) { + dns_dbiterator_destroy(&dbit); + } + return (result); +} + +/*% + * Helper function for non_nsec_rrset_exists(). + */ +static isc_result_t +is_non_nsec_action(void *data, dns_rdataset_t *rrset) { + UNUSED(data); + if (!(rrset->type == dns_rdatatype_nsec || + rrset->type == dns_rdatatype_nsec3 || + (rrset->type == dns_rdatatype_rrsig && + (rrset->covers == dns_rdatatype_nsec || + rrset->covers == dns_rdatatype_nsec3)))) + { + return (ISC_R_EXISTS); + } + return (ISC_R_SUCCESS); +} + +/*% + * Check whether there is an rrset other than a NSEC or RRSIG NSEC, + * i.e., anything that justifies the continued existence of a name + * after a secure update. + * + * If such an rrset exists, set '*exists' to true. + * Otherwise, set it to false. + */ +static isc_result_t +non_nsec_rrset_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + bool *exists) { + isc_result_t result; + result = foreach_rrset(db, ver, name, is_non_nsec_action, NULL); + RETURN_EXISTENCE_FLAG; +} + +/*% + * A comparison function for sorting dns_diff_t:s by name. + */ +static int +name_order(const void *av, const void *bv) { + dns_difftuple_t const *const *ap = av; + dns_difftuple_t const *const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + return (dns_name_compare(&a->name, &b->name)); +} + +static isc_result_t +uniqify_name_list(dns_diff_t *list) { + isc_result_t result; + dns_difftuple_t *p, *q; + + CHECK(dns_diff_sort(list, name_order)); + + p = ISC_LIST_HEAD(list->tuples); + while (p != NULL) { + do { + q = ISC_LIST_NEXT(p, link); + if (q == NULL || !dns_name_equal(&p->name, &q->name)) { + break; + } + ISC_LIST_UNLINK(list->tuples, q, link); + dns_difftuple_free(&q); + } while (1); + p = ISC_LIST_NEXT(p, link); + } +failure: + return (result); +} + +static isc_result_t +is_active(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, bool *flag, + bool *cut, bool *unsecure) { + isc_result_t result; + dns_fixedname_t foundname; + dns_fixedname_init(&foundname); + result = dns_db_find(db, name, ver, dns_rdatatype_any, + DNS_DBFIND_GLUEOK | DNS_DBFIND_NOWILD, + (isc_stdtime_t)0, NULL, + dns_fixedname_name(&foundname), NULL, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_EMPTYNAME) { + *flag = true; + *cut = false; + if (unsecure != NULL) { + *unsecure = false; + } + return (ISC_R_SUCCESS); + } else if (result == DNS_R_ZONECUT) { + *flag = true; + *cut = true; + if (unsecure != NULL) { + /* + * We are at the zonecut. Check to see if there + * is a DS RRset. + */ + if (dns_db_find(db, name, ver, dns_rdatatype_ds, 0, + (isc_stdtime_t)0, NULL, + dns_fixedname_name(&foundname), NULL, + NULL) == DNS_R_NXRRSET) + { + *unsecure = true; + } else { + *unsecure = false; + } + } + return (ISC_R_SUCCESS); + } else if (result == DNS_R_GLUE || result == DNS_R_DNAME || + result == DNS_R_DELEGATION || result == DNS_R_NXDOMAIN) + { + *flag = false; + *cut = false; + if (unsecure != NULL) { + *unsecure = false; + } + return (ISC_R_SUCCESS); + } else { + /* + * Silence compiler. + */ + *flag = false; + *cut = false; + if (unsecure != NULL) { + *unsecure = false; + } + return (result); + } +} + +/*% + * Find the next/previous name that has a NSEC record. + * In other words, skip empty database nodes and names that + * have had their NSECs removed because they are obscured by + * a zone cut. + */ +static isc_result_t +next_active(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_name_t *oldname, dns_name_t *newname, + bool forward) { + isc_result_t result; + dns_dbiterator_t *dbit = NULL; + bool has_nsec = false; + unsigned int wraps = 0; + bool secure = dns_db_issecure(db); + + CHECK(dns_db_createiterator(db, 0, &dbit)); + + CHECK(dns_dbiterator_seek(dbit, oldname)); + do { + dns_dbnode_t *node = NULL; + + if (forward) { + result = dns_dbiterator_next(dbit); + } else { + result = dns_dbiterator_prev(dbit); + } + if (result == ISC_R_NOMORE) { + /* + * Wrap around. + */ + if (forward) { + CHECK(dns_dbiterator_first(dbit)); + } else { + CHECK(dns_dbiterator_last(dbit)); + } + wraps++; + if (wraps == 2) { + update_log(log, zone, ISC_LOG_ERROR, + "secure zone with no NSECs"); + result = DNS_R_BADZONE; + goto failure; + } + } + CHECK(dns_dbiterator_current(dbit, &node, newname)); + dns_db_detachnode(db, &node); + + /* + * The iterator may hold the tree lock, and + * rrset_exists() calls dns_db_findnode() which + * may try to reacquire it. To avoid deadlock + * we must pause the iterator first. + */ + CHECK(dns_dbiterator_pause(dbit)); + if (secure) { + CHECK(rrset_exists(db, ver, newname, dns_rdatatype_nsec, + 0, &has_nsec)); + } else { + dns_fixedname_t ffound; + dns_name_t *found; + found = dns_fixedname_initname(&ffound); + result = dns_db_find( + db, newname, ver, dns_rdatatype_soa, + DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); + if (result == ISC_R_SUCCESS || + result == DNS_R_EMPTYNAME || + result == DNS_R_NXRRSET || result == DNS_R_CNAME || + (result == DNS_R_DELEGATION && + dns_name_equal(newname, found))) + { + has_nsec = true; + result = ISC_R_SUCCESS; + } else if (result != DNS_R_NXDOMAIN) { + break; + } + } + } while (!has_nsec); +failure: + if (dbit != NULL) { + dns_dbiterator_destroy(&dbit); + } + + return (result); +} + +/*% + * Add a NSEC record for "name", recording the change in "diff". + * The existing NSEC is removed. + */ +static isc_result_t +add_nsec(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_name_t *name, dns_ttl_t nsecttl, + dns_diff_t *diff) { + isc_result_t result; + dns_dbnode_t *node = NULL; + unsigned char buffer[DNS_NSEC_BUFFERSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_difftuple_t *tuple = NULL; + dns_fixedname_t fixedname; + dns_name_t *target; + + target = dns_fixedname_initname(&fixedname); + + /* + * Find the successor name, aka NSEC target. + */ + CHECK(next_active(log, zone, db, ver, name, target, true)); + + /* + * Create the NSEC RDATA. + */ + CHECK(dns_db_findnode(db, name, false, &node)); + dns_rdata_init(&rdata); + CHECK(dns_nsec_buildrdata(db, ver, node, target, buffer, &rdata)); + dns_db_detachnode(db, &node); + + /* + * Delete the old NSEC and record the change. + */ + CHECK(delete_if(true_p, db, ver, name, dns_rdatatype_nsec, 0, NULL, + diff)); + /* + * Add the new NSEC and record the change. + */ + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name, nsecttl, + &rdata, &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); + INSIST(tuple == NULL); + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/*% + * Add a placeholder NSEC record for "name", recording the change in "diff". + */ +static isc_result_t +add_placeholder_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_diff_t *diff) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + isc_region_t r; + unsigned char data[1] = { 0 }; /* The root domain, no bits. */ + dns_rdata_t rdata = DNS_RDATA_INIT; + + r.base = data; + r.length = sizeof(data); + dns_rdata_fromregion(&rdata, dns_db_class(db), dns_rdatatype_nsec, &r); + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name, 0, &rdata, + &tuple)); + CHECK(do_one_tuple(&tuple, db, ver, diff)); +failure: + return (result); +} + +static isc_result_t +find_zone_keys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_mem_t *mctx, unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys) { + isc_result_t result; + isc_stdtime_t now; + dns_dbnode_t *node = NULL; + const char *directory = dns_zone_getkeydirectory(zone); + + CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); + isc_stdtime_get(&now); + + dns_zone_lock_keyfiles(zone); + result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db), + directory, now, mctx, maxkeys, keys, + nkeys); + dns_zone_unlock_keyfiles(zone); + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/*% + * Add RRSIG records for an RRset, recording the change in "diff". + */ +static isc_result_t +add_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_name_t *name, dns_rdatatype_t type, + dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys, + isc_stdtime_t inception, isc_stdtime_t expire, bool check_ksk, + bool keyset_kskonly) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_kasp_t *kasp = dns_zone_getkasp(zone); + dns_rdataset_t rdataset; + dns_rdata_t sig_rdata = DNS_RDATA_INIT; + dns_stats_t *dnssecsignstats = dns_zone_getdnssecsignstats(zone); + isc_buffer_t buffer; + unsigned char data[1024]; /* XXX */ + unsigned int i, j; + bool added_sig = false; + bool use_kasp = false; + isc_mem_t *mctx = diff->mctx; + + if (kasp != NULL) { + check_ksk = false; + keyset_kskonly = true; + use_kasp = true; + } + + dns_rdataset_init(&rdataset); + isc_buffer_init(&buffer, data, sizeof(data)); + + /* Get the rdataset to sign. */ + if (type == dns_rdatatype_nsec3) { + CHECK(dns_db_findnsec3node(db, name, false, &node)); + } else { + CHECK(dns_db_findnode(db, name, false, &node)); + } + CHECK(dns_db_findrdataset(db, node, ver, type, 0, (isc_stdtime_t)0, + &rdataset, NULL)); + dns_db_detachnode(db, &node); + +#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0) +#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0) +#define ID(x) dst_key_id(x) +#define ALG(x) dst_key_alg(x) + + /* + * If we are honoring KSK flags then we need to check that we + * have both KSK and non-KSK keys that are not revoked per + * algorithm. + */ + for (i = 0; i < nkeys; i++) { + bool both = false; + + /* Don't add signatures for offline or inactive keys */ + if (!dst_key_isprivate(keys[i])) { + continue; + } + if (dst_key_inactive(keys[i])) { + continue; + } + + if (check_ksk && !REVOKE(keys[i])) { + bool have_ksk, have_nonksk; + if (KSK(keys[i])) { + have_ksk = true; + have_nonksk = false; + } else { + have_ksk = false; + have_nonksk = true; + } + for (j = 0; j < nkeys; j++) { + if (j == i || ALG(keys[i]) != ALG(keys[j])) { + continue; + } + + /* Don't consider inactive keys, however + * the KSK may be temporary offline, so do + * consider KSKs which private key files are + * unavailable. + */ + if (dst_key_inactive(keys[j])) { + continue; + } + + if (REVOKE(keys[j])) { + continue; + } + if (KSK(keys[j])) { + have_ksk = true; + } else if (dst_key_isprivate(keys[j])) { + have_nonksk = true; + } + both = have_ksk && have_nonksk; + if (both) { + break; + } + } + } + + if (use_kasp) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_stdtime_t when; + isc_result_t kresult; + bool ksk = false; + bool zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + continue; + } else if (zsk && + !dst_key_is_signing(keys[i], DST_BOOL_ZSK, + inception, &when)) + { + /* + * This key is not active for zone-signing. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { + /* + * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). + */ + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + if (!KSK(keys[i]) && keyset_kskonly) { + continue; + } + } else if (KSK(keys[i])) { + continue; + } + } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + + /* Calculate the signature, creating a RRSIG RDATA. */ + CHECK(dns_dnssec_sign(name, &rdataset, keys[i], &inception, + &expire, mctx, &buffer, &sig_rdata)); + + /* Update the database and journal with the RRSIG. */ + /* XXX inefficient - will cause dataset merging */ + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name, + rdataset.ttl, &sig_rdata)); + dns_rdata_reset(&sig_rdata); + isc_buffer_init(&buffer, data, sizeof(data)); + added_sig = true; + /* Update DNSSEC sign statistics. */ + if (dnssecsignstats != NULL) { + dns_dnssecsignstats_increment(dnssecsignstats, + ID(keys[i]), + (uint8_t)ALG(keys[i]), + dns_dnssecsignstats_sign); + } + } + if (!added_sig) { + update_log(log, zone, ISC_LOG_ERROR, + "found no active private keys, " + "unable to generate any signatures"); + result = ISC_R_NOTFOUND; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/* + * Delete expired RRsigs and any RRsigs we are about to re-sign. + * See also zone.c:del_sigs(). + */ +static isc_result_t +del_keysigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int i; + dns_rdata_rrsig_t rrsig; + bool found; + + dns_rdataset_init(&rdataset); + + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig, + dns_rdatatype_dnskey, (isc_stdtime_t)0, + &rdataset, NULL); + dns_db_detachnode(db, &node); + + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + found = false; + for (i = 0; i < nkeys; i++) { + if (rrsig.keyid == dst_key_id(keys[i])) { + found = true; + if (!dst_key_isprivate(keys[i]) && + !dst_key_inactive(keys[i])) + { + /* + * The re-signing code in zone.c + * will mark this as offline. + * Just skip the record for now. + */ + break; + } + result = update_one_rr(db, ver, diff, + DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata); + break; + } + } + /* + * If there is not a matching DNSKEY then delete the RRSIG. + */ + if (!found) { + result = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, + name, rdataset.ttl, &rdata); + } + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) { + break; + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static isc_result_t +add_exposed_sigs(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_name_t *name, bool cut, + dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys, + isc_stdtime_t inception, isc_stdtime_t expire, bool check_ksk, + bool keyset_kskonly, unsigned int *sigs) { + isc_result_t result; + dns_dbnode_t *node; + dns_rdatasetiter_t *iter; + + node = NULL; + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + iter = NULL; + result = dns_db_allrdatasets(db, node, ver, 0, (isc_stdtime_t)0, &iter); + if (result != ISC_R_SUCCESS) { + goto cleanup_node; + } + + for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iter)) + { + dns_rdataset_t rdataset; + dns_rdatatype_t type; + bool flag; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(iter, &rdataset); + type = rdataset.type; + dns_rdataset_disassociate(&rdataset); + + /* + * We don't need to sign unsigned NSEC records at the cut + * as they are handled elsewhere. + */ + if ((type == dns_rdatatype_rrsig) || + (cut && type != dns_rdatatype_ds)) + { + continue; + } + result = rrset_exists(db, ver, name, dns_rdatatype_rrsig, type, + &flag); + if (result != ISC_R_SUCCESS) { + goto cleanup_iterator; + } + if (flag) { + continue; + } + result = add_sigs(log, zone, db, ver, name, type, diff, keys, + nkeys, inception, expire, check_ksk, + keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto cleanup_iterator; + } + (*sigs)++; + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup_iterator: + dns_rdatasetiter_destroy(&iter); + +cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/*% + * Update RRSIG, NSEC and NSEC3 records affected by an update. The original + * update, including the SOA serial update but excluding the RRSIG & NSEC + * changes, is in "diff" and has already been applied to "newver" of "db". + * The database version prior to the update is "oldver". + * + * The necessary RRSIG, NSEC and NSEC3 changes will be applied to "newver" + * and added (as a minimal diff) to "diff". + * + * The RRSIGs generated will be valid for 'sigvalidityinterval' seconds. + */ +isc_result_t +dns_update_signatures(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *oldver, dns_dbversion_t *newver, + dns_diff_t *diff, uint32_t sigvalidityinterval) { + return (dns_update_signaturesinc(log, zone, db, oldver, newver, diff, + sigvalidityinterval, NULL)); +} + +struct dns_update_state { + unsigned int magic; + dns_diff_t diffnames; + dns_diff_t affected; + dns_diff_t sig_diff; + dns_diff_t nsec_diff; + dns_diff_t nsec_mindiff; + dns_diff_t work; + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + unsigned int nkeys; + isc_stdtime_t inception, expire, soaexpire, keyexpire; + dns_ttl_t nsecttl; + bool check_ksk, keyset_kskonly, build_nsec3; + enum { + sign_updates, + remove_orphaned, + build_chain, + process_nsec, + sign_nsec, + update_nsec3, + process_nsec3, + sign_nsec3 + } state; +}; + +static uint32_t +dns__jitter_expire(dns_zone_t *zone, uint32_t sigvalidityinterval) { + /* Spread out signatures over time */ + if (sigvalidityinterval >= 3600U) { + uint32_t expiryinterval = + dns_zone_getsigresigninginterval(zone); + + if (sigvalidityinterval < 7200U) { + expiryinterval = 1200; + } else if (expiryinterval > sigvalidityinterval) { + expiryinterval = sigvalidityinterval; + } else { + expiryinterval = sigvalidityinterval - expiryinterval; + } + uint32_t jitter = isc_random_uniform(expiryinterval); + sigvalidityinterval -= jitter; + } + return (sigvalidityinterval); +} + +isc_result_t +dns_update_signaturesinc(dns_update_log_t *log, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *oldver, dns_dbversion_t *newver, + dns_diff_t *diff, uint32_t sigvalidityinterval, + dns_update_state_t **statep) { + isc_result_t result = ISC_R_SUCCESS; + dns_update_state_t mystate, *state; + + dns_difftuple_t *t, *next; + bool flag, build_nsec; + unsigned int i; + isc_stdtime_t now; + dns_rdata_soa_t soa; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + bool unsecure; + bool cut; + dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone); + unsigned int sigs = 0; + unsigned int maxsigs = dns_zone_getsignatures(zone); + + if (statep == NULL || *statep == NULL) { + if (statep == NULL) { + state = &mystate; + } else { + state = isc_mem_get(diff->mctx, sizeof(*state)); + } + + dns_diff_init(diff->mctx, &state->diffnames); + dns_diff_init(diff->mctx, &state->affected); + dns_diff_init(diff->mctx, &state->sig_diff); + dns_diff_init(diff->mctx, &state->nsec_diff); + dns_diff_init(diff->mctx, &state->nsec_mindiff); + dns_diff_init(diff->mctx, &state->work); + state->nkeys = 0; + state->build_nsec3 = false; + + result = find_zone_keys(zone, db, newver, diff->mctx, + DNS_MAXZONEKEYS, state->zone_keys, + &state->nkeys); + if (result != ISC_R_SUCCESS) { + update_log(log, zone, ISC_LOG_ERROR, + "could not get zone keys for secure " + "dynamic update"); + goto failure; + } + + isc_stdtime_get(&now); + state->inception = now - 3600; /* Allow for some clock skew. */ + state->expire = now + + dns__jitter_expire(zone, sigvalidityinterval); + state->soaexpire = now + sigvalidityinterval; + state->keyexpire = dns_zone_getkeyvalidityinterval(zone); + if (state->keyexpire == 0) { + state->keyexpire = state->expire; + } else { + state->keyexpire += now; + } + + /* + * Do we look at the KSK flag on the DNSKEY to determining which + * keys sign which RRsets? First check the zone option then + * check the keys flags to make sure at least one has a ksk set + * and one doesn't. + */ + state->check_ksk = ((dns_zone_getoptions(zone) & + DNS_ZONEOPT_UPDATECHECKKSK) != 0); + state->keyset_kskonly = ((dns_zone_getoptions(zone) & + DNS_ZONEOPT_DNSKEYKSKONLY) != 0); + + /* + * Calculate the NSEC/NSEC3 TTL as a minimum of the SOA TTL and + * MINIMUM field. + */ + CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); + dns_rdataset_init(&rdataset); + CHECK(dns_db_findrdataset(db, node, newver, dns_rdatatype_soa, + 0, (isc_stdtime_t)0, &rdataset, + NULL)); + CHECK(dns_rdataset_first(&rdataset)); + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &soa, NULL)); + state->nsecttl = ISC_MIN(rdataset.ttl, soa.minimum); + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + + /* + * Find all RRsets directly affected by the update, and + * update their RRSIGs. Also build a list of names affected + * by the update in "diffnames". + */ + CHECK(dns_diff_sort(diff, temp_order)); + state->state = sign_updates; + state->magic = STATE_MAGIC; + if (statep != NULL) { + *statep = state; + } + } else { + REQUIRE(DNS_STATE_VALID(*statep)); + state = *statep; + } + +next_state: + switch (state->state) { + case sign_updates: + t = ISC_LIST_HEAD(diff->tuples); + while (t != NULL) { + dns_name_t *name = &t->name; + /* + * Now "name" is a new, unique name affected by the + * update. + */ + + CHECK(namelist_append_name(&state->diffnames, name)); + + while (t != NULL && dns_name_equal(&t->name, name)) { + dns_rdatatype_t type; + type = t->rdata.type; + + /* + * Now "name" and "type" denote a new unique + * RRset affected by the update. + */ + + /* Don't sign RRSIGs. */ + if (type == dns_rdatatype_rrsig) { + goto skip; + } + + /* + * Delete all old RRSIGs covering this type, + * since they are all invalid when the signed + * RRset has changed. We may not be able to + * recreate all of them - tough. + * Special case changes to the zone's DNSKEY + * records to support offline KSKs. + */ + if (type == dns_rdatatype_dnskey) { + del_keysigs(db, newver, name, + &state->sig_diff, + state->zone_keys, + state->nkeys); + } else { + CHECK(delete_if( + true_p, db, newver, name, + dns_rdatatype_rrsig, type, NULL, + &state->sig_diff)); + } + + /* + * If this RRset is still visible after the + * update, add a new signature for it. + */ + CHECK(rrset_visible(db, newver, name, type, + &flag)); + if (flag) { + isc_stdtime_t exp; + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + exp = state->keyexpire; + } else if (type == dns_rdatatype_soa) { + exp = state->soaexpire; + } else { + exp = state->expire; + } + + CHECK(add_sigs( + log, zone, db, newver, name, + type, &state->sig_diff, + state->zone_keys, state->nkeys, + state->inception, exp, + state->check_ksk, + state->keyset_kskonly)); + sigs++; + } + skip: + /* Skip any other updates to the same RRset. */ + while (t != NULL && + dns_name_equal(&t->name, name) && + t->rdata.type == type) + { + next = ISC_LIST_NEXT(t, link); + ISC_LIST_UNLINK(diff->tuples, t, link); + ISC_LIST_APPEND(state->work.tuples, t, + link); + t = next; + } + } + if (state != &mystate && sigs > maxsigs) { + return (DNS_R_CONTINUE); + } + } + ISC_LIST_APPENDLIST(diff->tuples, state->work.tuples, link); + + update_log(log, zone, ISC_LOG_DEBUG(3), + "updated data signatures"); + FALLTHROUGH; + case remove_orphaned: + state->state = remove_orphaned; + + /* Remove orphaned NSECs and RRSIG NSECs. */ + for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + CHECK(non_nsec_rrset_exists(db, newver, &t->name, + &flag)); + if (!flag) { + CHECK(delete_if(true_p, db, newver, &t->name, + dns_rdatatype_any, 0, NULL, + &state->sig_diff)); + } + } + update_log(log, zone, ISC_LOG_DEBUG(3), + "removed any orphaned NSEC records"); + + /* + * See if we need to build NSEC or NSEC3 chains. + */ + CHECK(dns_private_chains(db, newver, privatetype, &build_nsec, + &state->build_nsec3)); + if (!build_nsec) { + state->state = update_nsec3; + goto next_state; + } + + update_log(log, zone, ISC_LOG_DEBUG(3), + "rebuilding NSEC chain"); + + FALLTHROUGH; + case build_chain: + state->state = build_chain; + /* + * When a name is created or deleted, its predecessor needs to + * have its NSEC updated. + */ + for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + bool existed, exists; + dns_fixedname_t fixedname; + dns_name_t *prevname; + + prevname = dns_fixedname_initname(&fixedname); + + if (oldver != NULL) { + CHECK(name_exists(db, oldver, &t->name, + &existed)); + } else { + existed = false; + } + CHECK(name_exists(db, newver, &t->name, &exists)); + if (exists == existed) { + continue; + } + + /* + * Find the predecessor. + * When names become obscured or unobscured in this + * update transaction, we may find the wrong + * predecessor because the NSECs have not yet been + * updated to reflect the delegation change. This + * should not matter because in this case, the correct + * predecessor is either the delegation node or a + * newly unobscured node, and those nodes are on the + * "affected" list in any case. + */ + CHECK(next_active(log, zone, db, newver, &t->name, + prevname, false)); + CHECK(namelist_append_name(&state->affected, prevname)); + } + + /* + * Find names potentially affected by delegation changes + * (obscured by adding an NS or DNAME, or unobscured by + * removing one). + */ + for (t = ISC_LIST_HEAD(state->diffnames.tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + bool ns_existed, dname_existed; + bool ns_exists, dname_exists; + + if (oldver != NULL) { + CHECK(rrset_exists(db, oldver, &t->name, + dns_rdatatype_ns, 0, + &ns_existed)); + } else { + ns_existed = false; + } + if (oldver != NULL) { + CHECK(rrset_exists(db, oldver, &t->name, + dns_rdatatype_dname, 0, + &dname_existed)); + } else { + dname_existed = false; + } + CHECK(rrset_exists(db, newver, &t->name, + dns_rdatatype_ns, 0, &ns_exists)); + CHECK(rrset_exists(db, newver, &t->name, + dns_rdatatype_dname, 0, + &dname_exists)); + if ((ns_exists || dname_exists) == + (ns_existed || dname_existed)) + { + continue; + } + /* + * There was a delegation change. Mark all subdomains + * of t->name as potentially needing a NSEC update. + */ + CHECK(namelist_append_subdomain(db, &t->name, + &state->affected)); + } + ISC_LIST_APPENDLIST(state->affected.tuples, + state->diffnames.tuples, link); + INSIST(ISC_LIST_EMPTY(state->diffnames.tuples)); + + CHECK(uniqify_name_list(&state->affected)); + + FALLTHROUGH; + case process_nsec: + state->state = process_nsec; + + /* + * Determine which names should have NSECs, and delete/create + * NSECs to make it so. We don't know the final NSEC targets + * yet, so we just create placeholder NSECs with arbitrary + * contents to indicate that their respective owner names + * should be part of the NSEC chain. + */ + while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) { + bool exists; + dns_name_t *name = &t->name; + + CHECK(name_exists(db, newver, name, &exists)); + if (!exists) { + goto unlink; + } + CHECK(is_active(db, newver, name, &flag, &cut, NULL)); + if (!flag) { + /* + * This name is obscured. Delete any + * existing NSEC record. + */ + CHECK(delete_if(true_p, db, newver, name, + dns_rdatatype_nsec, 0, NULL, + &state->nsec_diff)); + CHECK(delete_if(rrsig_p, db, newver, name, + dns_rdatatype_any, 0, NULL, + diff)); + } else { + /* + * This name is not obscured. It needs to have + * a NSEC unless it is the at the origin, in + * which case it should already exist if there + * is a complete NSEC chain and if there isn't + * a complete NSEC chain we don't want to add + * one as that would signal that there is a + * complete NSEC chain. + */ + if (!dns_name_equal(name, dns_db_origin(db))) { + CHECK(rrset_exists(db, newver, name, + dns_rdatatype_nsec, + 0, &flag)); + if (!flag) { + CHECK(add_placeholder_nsec( + db, newver, name, + diff)); + } + } + CHECK(add_exposed_sigs( + log, zone, db, newver, name, cut, + &state->sig_diff, state->zone_keys, + state->nkeys, state->inception, + state->expire, state->check_ksk, + state->keyset_kskonly, &sigs)); + } + unlink: + ISC_LIST_UNLINK(state->affected.tuples, t, link); + ISC_LIST_APPEND(state->work.tuples, t, link); + if (state != &mystate && sigs > maxsigs) { + return (DNS_R_CONTINUE); + } + } + ISC_LIST_APPENDLIST(state->affected.tuples, state->work.tuples, + link); + + /* + * Now we know which names are part of the NSEC chain. + * Make them all point at their correct targets. + */ + for (t = ISC_LIST_HEAD(state->affected.tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + CHECK(rrset_exists(db, newver, &t->name, + dns_rdatatype_nsec, 0, &flag)); + if (flag) { + /* + * There is a NSEC, but we don't know if it + * is correct. Delete it and create a correct + * one to be sure. If the update was + * unnecessary, the diff minimization + * will take care of eliminating it from the + * journal, IXFRs, etc. + * + * The RRSIG bit should always be set in the + * NSECs we generate, because they will all + * get RRSIG NSECs. + * (XXX what if the zone keys are missing?). + * Because the RRSIG NSECs have not necessarily + * been created yet, the correctness of the + * bit mask relies on the assumption that NSECs + * are only created if there is other data, and + * if there is other data, there are other + * RRSIGs. + */ + CHECK(add_nsec(log, zone, db, newver, &t->name, + state->nsecttl, + &state->nsec_diff)); + } + } + + /* + * Minimize the set of NSEC updates so that we don't + * have to regenerate the RRSIG NSECs for NSECs that were + * replaced with identical ones. + */ + while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) { + ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link); + dns_diff_appendminimal(&state->nsec_mindiff, &t); + } + + update_log(log, zone, ISC_LOG_DEBUG(3), + "signing rebuilt NSEC chain"); + + FALLTHROUGH; + case sign_nsec: + state->state = sign_nsec; + /* Update RRSIG NSECs. */ + while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL) + { + if (t->op == DNS_DIFFOP_DEL) { + CHECK(delete_if(true_p, db, newver, &t->name, + dns_rdatatype_rrsig, + dns_rdatatype_nsec, NULL, + &state->sig_diff)); + } else if (t->op == DNS_DIFFOP_ADD) { + CHECK(add_sigs(log, zone, db, newver, &t->name, + dns_rdatatype_nsec, + &state->sig_diff, + state->zone_keys, state->nkeys, + state->inception, state->expire, + state->check_ksk, + state->keyset_kskonly)); + sigs++; + } else { + UNREACHABLE(); + } + ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link); + ISC_LIST_APPEND(state->work.tuples, t, link); + if (state != &mystate && sigs > maxsigs) { + return (DNS_R_CONTINUE); + } + } + ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples, + state->work.tuples, link); + FALLTHROUGH; + case update_nsec3: + state->state = update_nsec3; + + /* Record our changes for the journal. */ + while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) { + ISC_LIST_UNLINK(state->sig_diff.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } + while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL) + { + ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } + + INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples)); + INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples)); + INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples)); + + if (!state->build_nsec3) { + update_log(log, zone, ISC_LOG_DEBUG(3), + "no NSEC3 chains to rebuild"); + goto failure; + } + + update_log(log, zone, ISC_LOG_DEBUG(3), + "rebuilding NSEC3 chains"); + + dns_diff_clear(&state->diffnames); + dns_diff_clear(&state->affected); + + CHECK(dns_diff_sort(diff, temp_order)); + + /* + * Find names potentially affected by delegation changes + * (obscured by adding an NS or DNAME, or unobscured by + * removing one). + */ + t = ISC_LIST_HEAD(diff->tuples); + while (t != NULL) { + dns_name_t *name = &t->name; + + bool ns_existed, dname_existed; + bool ns_exists, dname_exists; + bool exists, existed; + + if (t->rdata.type == dns_rdatatype_nsec || + t->rdata.type == dns_rdatatype_rrsig) + { + t = ISC_LIST_NEXT(t, link); + continue; + } + + CHECK(namelist_append_name(&state->affected, name)); + + if (oldver != NULL) { + CHECK(rrset_exists(db, oldver, name, + dns_rdatatype_ns, 0, + &ns_existed)); + } else { + ns_existed = false; + } + if (oldver != NULL) { + CHECK(rrset_exists(db, oldver, name, + dns_rdatatype_dname, 0, + &dname_existed)); + } else { + dname_existed = false; + } + CHECK(rrset_exists(db, newver, name, dns_rdatatype_ns, + 0, &ns_exists)); + CHECK(rrset_exists(db, newver, name, + dns_rdatatype_dname, 0, + &dname_exists)); + + exists = ns_exists || dname_exists; + existed = ns_existed || dname_existed; + if (exists == existed) { + goto nextname; + } + /* + * There was a delegation change. Mark all subdomains + * of t->name as potentially needing a NSEC3 update. + */ + CHECK(namelist_append_subdomain(db, name, + &state->affected)); + + nextname: + while (t != NULL && dns_name_equal(&t->name, name)) { + t = ISC_LIST_NEXT(t, link); + } + } + + FALLTHROUGH; + case process_nsec3: + state->state = process_nsec3; + while ((t = ISC_LIST_HEAD(state->affected.tuples)) != NULL) { + dns_name_t *name = &t->name; + + unsecure = false; /* Silence compiler warning. */ + CHECK(is_active(db, newver, name, &flag, &cut, + &unsecure)); + + if (!flag) { + CHECK(delete_if(rrsig_p, db, newver, name, + dns_rdatatype_any, 0, NULL, + diff)); + CHECK(dns_nsec3_delnsec3sx(db, newver, name, + privatetype, + &state->nsec_diff)); + } else { + CHECK(add_exposed_sigs( + log, zone, db, newver, name, cut, + &state->sig_diff, state->zone_keys, + state->nkeys, state->inception, + state->expire, state->check_ksk, + state->keyset_kskonly, &sigs)); + CHECK(dns_nsec3_addnsec3sx( + db, newver, name, state->nsecttl, + unsecure, privatetype, + &state->nsec_diff)); + } + ISC_LIST_UNLINK(state->affected.tuples, t, link); + ISC_LIST_APPEND(state->work.tuples, t, link); + if (state != &mystate && sigs > maxsigs) { + return (DNS_R_CONTINUE); + } + } + ISC_LIST_APPENDLIST(state->affected.tuples, state->work.tuples, + link); + + /* + * Minimize the set of NSEC3 updates so that we don't + * have to regenerate the RRSIG NSEC3s for NSEC3s that were + * replaced with identical ones. + */ + while ((t = ISC_LIST_HEAD(state->nsec_diff.tuples)) != NULL) { + ISC_LIST_UNLINK(state->nsec_diff.tuples, t, link); + dns_diff_appendminimal(&state->nsec_mindiff, &t); + } + + update_log(log, zone, ISC_LOG_DEBUG(3), + "signing rebuilt NSEC3 chain"); + + FALLTHROUGH; + case sign_nsec3: + state->state = sign_nsec3; + /* Update RRSIG NSEC3s. */ + while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL) + { + if (t->op == DNS_DIFFOP_DEL) { + CHECK(delete_if(true_p, db, newver, &t->name, + dns_rdatatype_rrsig, + dns_rdatatype_nsec3, NULL, + &state->sig_diff)); + } else if (t->op == DNS_DIFFOP_ADD) { + CHECK(add_sigs(log, zone, db, newver, &t->name, + dns_rdatatype_nsec3, + &state->sig_diff, + state->zone_keys, state->nkeys, + state->inception, state->expire, + state->check_ksk, + state->keyset_kskonly)); + sigs++; + } else { + UNREACHABLE(); + } + ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link); + ISC_LIST_APPEND(state->work.tuples, t, link); + if (state != &mystate && sigs > maxsigs) { + return (DNS_R_CONTINUE); + } + } + ISC_LIST_APPENDLIST(state->nsec_mindiff.tuples, + state->work.tuples, link); + + /* Record our changes for the journal. */ + while ((t = ISC_LIST_HEAD(state->sig_diff.tuples)) != NULL) { + ISC_LIST_UNLINK(state->sig_diff.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } + while ((t = ISC_LIST_HEAD(state->nsec_mindiff.tuples)) != NULL) + { + ISC_LIST_UNLINK(state->nsec_mindiff.tuples, t, link); + dns_diff_appendminimal(diff, &t); + } + + INSIST(ISC_LIST_EMPTY(state->sig_diff.tuples)); + INSIST(ISC_LIST_EMPTY(state->nsec_diff.tuples)); + INSIST(ISC_LIST_EMPTY(state->nsec_mindiff.tuples)); + break; + default: + UNREACHABLE(); + } + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + dns_diff_clear(&state->sig_diff); + dns_diff_clear(&state->nsec_diff); + dns_diff_clear(&state->nsec_mindiff); + + dns_diff_clear(&state->affected); + dns_diff_clear(&state->diffnames); + dns_diff_clear(&state->work); + + for (i = 0; i < state->nkeys; i++) { + dst_key_free(&state->zone_keys[i]); + } + + if (state != &mystate) { + *statep = NULL; + state->magic = 0; + isc_mem_put(diff->mctx, state, sizeof(*state)); + } + + return (result); +} + +static isc_stdtime_t +epoch_to_yyyymmdd(time_t when) { + struct tm t, *tm = localtime_r(&when, &t); + if (tm == NULL) { + return (0); + } + return (((tm->tm_year + 1900) * 10000) + ((tm->tm_mon + 1) * 100) + + tm->tm_mday); +} + +static uint32_t +dns__update_soaserial(uint32_t serial, dns_updatemethod_t method) { + isc_stdtime_t now; + + switch (method) { + case dns_updatemethod_none: + return (serial); + case dns_updatemethod_unixtime: + isc_stdtime_get(&now); + return (now); + case dns_updatemethod_date: + isc_stdtime_get(&now); + return (epoch_to_yyyymmdd((time_t)now) * 100); + case dns_updatemethod_increment: + /* RFC1982 */ + serial = (serial + 1) & 0xFFFFFFFF; + if (serial == 0) { + return (1); + } + return (serial); + default: + UNREACHABLE(); + } +} + +uint32_t +dns_update_soaserial(uint32_t serial, dns_updatemethod_t method, + dns_updatemethod_t *used) { + uint32_t new_serial = dns__update_soaserial(serial, method); + switch (method) { + case dns_updatemethod_none: + case dns_updatemethod_increment: + break; + case dns_updatemethod_unixtime: + case dns_updatemethod_date: + if (!(new_serial != 0 && isc_serial_gt(new_serial, serial))) { + /* + * If the new date serial following YYYYMMDD00 is equal + * to or smaller than the current serial, but YYYYMMDD99 + * would be larger, pretend we have used the + * "dns_updatemethod_date" method. + */ + if (method == dns_updatemethod_unixtime || + !isc_serial_gt(new_serial + 99, serial)) + { + method = dns_updatemethod_increment; + } + new_serial = dns__update_soaserial( + serial, dns_updatemethod_increment); + } + break; + default: + UNREACHABLE(); + } + + if (used != NULL) { + *used = method; + } + + return (new_serial); +} diff --git a/lib/dns/validator.c b/lib/dns/validator.c new file mode 100644 index 0000000..56a0ced --- /dev/null +++ b/lib/dns/validator.c @@ -0,0 +1,3394 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! \file + * \brief + * Basic processing sequences: + * + * \li When called with rdataset and sigrdataset: + * validator_start -> validate_answer -> proveunsecure + * validator_start -> validate_answer -> validate_nx (if secure wildcard) + * + * \li When called with rdataset but no sigrdataset: + * validator_start -> proveunsecure + * + * \li When called with no rdataset or sigrdataset: + * validator_start -> validate_nx-> proveunsecure + * + * validator_start: determine what type of validation to do. + * validate_answer: attempt to perform a positive validation. + * proveunsecure: attempt to prove the answer comes from an unsecure zone. + * validate_nx: attempt to prove a negative response. + */ + +#define VALIDATOR_MAGIC ISC_MAGIC('V', 'a', 'l', '?') +#define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC) + +#define VALATTR_SHUTDOWN 0x0001 /*%< Shutting down. */ +#define VALATTR_CANCELED 0x0002 /*%< Canceled. */ +#define VALATTR_TRIEDVERIFY \ + 0x0004 /*%< We have found a key and \ + * have attempted a verify. */ +#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */ + +/*! + * NSEC proofs to be looked for. + */ +#define VALATTR_NEEDNOQNAME 0x00000100 +#define VALATTR_NEEDNOWILDCARD 0x00000200 +#define VALATTR_NEEDNODATA 0x00000400 + +/*! + * NSEC proofs that have been found. + */ +#define VALATTR_FOUNDNOQNAME 0x00001000 +#define VALATTR_FOUNDNOWILDCARD 0x00002000 +#define VALATTR_FOUNDNODATA 0x00004000 +#define VALATTR_FOUNDCLOSEST 0x00008000 +#define VALATTR_FOUNDOPTOUT 0x00010000 +#define VALATTR_FOUNDUNKNOWN 0x00020000 + +#define NEEDNODATA(val) ((val->attributes & VALATTR_NEEDNODATA) != 0) +#define NEEDNOQNAME(val) ((val->attributes & VALATTR_NEEDNOQNAME) != 0) +#define NEEDNOWILDCARD(val) ((val->attributes & VALATTR_NEEDNOWILDCARD) != 0) +#define FOUNDNODATA(val) ((val->attributes & VALATTR_FOUNDNODATA) != 0) +#define FOUNDNOQNAME(val) ((val->attributes & VALATTR_FOUNDNOQNAME) != 0) +#define FOUNDNOWILDCARD(val) ((val->attributes & VALATTR_FOUNDNOWILDCARD) != 0) +#define FOUNDCLOSEST(val) ((val->attributes & VALATTR_FOUNDCLOSEST) != 0) +#define FOUNDOPTOUT(val) ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) + +#define SHUTDOWN(v) (((v)->attributes & VALATTR_SHUTDOWN) != 0) +#define CANCELED(v) (((v)->attributes & VALATTR_CANCELED) != 0) + +#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) +#define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) + +static void +destroy(dns_validator_t *val); + +static isc_result_t +select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset); + +static isc_result_t +validate_answer(dns_validator_t *val, bool resume); + +static isc_result_t +validate_dnskey(dns_validator_t *val); + +static isc_result_t +validate_nx(dns_validator_t *val, bool resume); + +static isc_result_t +proveunsecure(dns_validator_t *val, bool have_ds, bool resume); + +static void +validator_logv(dns_validator_t *val, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, va_list ap) + ISC_FORMAT_PRINTF(5, 0); + +static void +validator_log(void *val, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +static void +validator_logcreate(dns_validator_t *val, dns_name_t *name, + dns_rdatatype_t type, const char *caller, + const char *operation); + +/*% + * Ensure the validator's rdatasets are marked as expired. + */ +static void +expire_rdatasets(dns_validator_t *val) { + if (dns_rdataset_isassociated(&val->frdataset)) { + dns_rdataset_expire(&val->frdataset); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_expire(&val->fsigrdataset); + } +} + +/*% + * Ensure the validator's rdatasets are disassociated. + */ +static void +disassociate_rdatasets(dns_validator_t *val) { + if (dns_rdataset_isassociated(&val->fdsset)) { + dns_rdataset_disassociate(&val->fdsset); + } + if (dns_rdataset_isassociated(&val->frdataset)) { + dns_rdataset_disassociate(&val->frdataset); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_disassociate(&val->fsigrdataset); + } +} + +/*% + * Mark the rdatasets in val->event with trust level "answer", + * indicating that they did not validate, but could be cached as insecure. + * + * If we are validating a name that is marked as "must be secure", log a + * warning and return DNS_R_MUSTBESECURE instead. + */ +static isc_result_t +markanswer(dns_validator_t *val, const char *where, const char *mbstext) { + if (val->mustbesecure && mbstext != NULL) { + validator_log(val, ISC_LOG_WARNING, + "must be secure failure, %s", mbstext); + return (DNS_R_MUSTBESECURE); + } + + validator_log(val, ISC_LOG_DEBUG(3), "marking as answer (%s)", where); + if (val->event->rdataset != NULL) { + dns_rdataset_settrust(val->event->rdataset, dns_trust_answer); + } + if (val->event->sigrdataset != NULL) { + dns_rdataset_settrust(val->event->sigrdataset, + dns_trust_answer); + } + + return (ISC_R_SUCCESS); +} + +/*% + * Mark the RRsets in val->event with trust level secure. + */ +static void +marksecure(dns_validatorevent_t *event) { + dns_rdataset_settrust(event->rdataset, dns_trust_secure); + if (event->sigrdataset != NULL) { + dns_rdataset_settrust(event->sigrdataset, dns_trust_secure); + } + event->secure = true; +} + +/* + * Validator 'val' is finished; send the completion event to the task + * that called dns_validator_create(), with result `result`. + */ +static void +validator_done(dns_validator_t *val, isc_result_t result) { + isc_task_t *task; + + if (val->event == NULL) { + return; + } + + /* + * Caller must be holding the lock. + */ + + val->event->result = result; + task = val->event->ev_sender; + val->event->ev_sender = val; + val->event->ev_type = DNS_EVENT_VALIDATORDONE; + val->event->ev_action = val->action; + val->event->ev_arg = val->arg; + isc_task_sendanddetach(&task, (isc_event_t **)&val->event); +} + +/* + * Called when deciding whether to destroy validator 'val'. + */ +static bool +exit_check(dns_validator_t *val) { + /* + * Caller must be holding the lock. + */ + if (!SHUTDOWN(val)) { + return (false); + } + + INSIST(val->event == NULL); + + if (val->fetch != NULL || val->subvalidator != NULL) { + return (false); + } + + return (true); +} + +/*% + * Look in the NSEC record returned from a DS query to see if there is + * a NS RRset at this name. If it is found we are at a delegation point. + */ +static bool +isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, + isc_result_t dbresult) { + dns_fixedname_t fixed; + dns_label_t hashlabel; + dns_name_t nsec3name; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t set; + int order; + int scope; + bool found; + isc_buffer_t buffer; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + unsigned int length; + + REQUIRE(dbresult == DNS_R_NXRRSET || dbresult == DNS_R_NCACHENXRRSET); + + dns_rdataset_init(&set); + if (dbresult == DNS_R_NXRRSET) { + dns_rdataset_clone(rdataset, &set); + } else { + result = dns_ncache_getrdataset(rdataset, name, + dns_rdatatype_nsec, &set); + if (result == ISC_R_NOTFOUND) { + goto trynsec3; + } + if (result != ISC_R_SUCCESS) { + return (false); + } + } + + INSIST(set.type == dns_rdatatype_nsec); + + found = false; + result = dns_rdataset_first(&set); + if (result == ISC_R_SUCCESS) { + dns_rdataset_current(&set, &rdata); + found = dns_nsec_typepresent(&rdata, dns_rdatatype_ns); + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&set); + return (found); + +trynsec3: + /* + * Iterate over the ncache entry. + */ + found = false; + dns_name_init(&nsec3name, NULL); + dns_fixedname_init(&fixed); + dns_name_downcase(name, dns_fixedname_name(&fixed), NULL); + name = dns_fixedname_name(&fixed); + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_ncache_current(rdataset, &nsec3name, &set); + if (set.type != dns_rdatatype_nsec3) { + dns_rdataset_disassociate(&set); + continue; + } + dns_name_getlabel(&nsec3name, 0, &hashlabel); + isc_region_consume(&hashlabel, 1); + isc_buffer_init(&buffer, owner, sizeof(owner)); + result = isc_base32hexnp_decoderegion(&hashlabel, &buffer); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&set); + continue; + } + for (result = dns_rdataset_first(&set); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&set)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(&set, &rdata); + (void)dns_rdata_tostruct(&rdata, &nsec3, NULL); + if (nsec3.hash != 1) { + continue; + } + length = isc_iterated_hash( + hash, nsec3.hash, nsec3.iterations, nsec3.salt, + nsec3.salt_length, name->ndata, name->length); + if (length != isc_buffer_usedlength(&buffer)) { + continue; + } + order = memcmp(hash, owner, length); + if (order == 0) { + found = dns_nsec3_typepresent(&rdata, + dns_rdatatype_ns); + dns_rdataset_disassociate(&set); + return (found); + } + if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) { + continue; + } + /* + * Does this optout span cover the name? + */ + scope = memcmp(owner, nsec3.next, nsec3.next_length); + if ((scope < 0 && order > 0 && + memcmp(hash, nsec3.next, length) < 0) || + (scope >= 0 && + (order > 0 || + memcmp(hash, nsec3.next, length) < 0))) + { + dns_rdataset_disassociate(&set); + return (true); + } + } + dns_rdataset_disassociate(&set); + } + return (found); +} + +/*% + * We have been asked to look for a key. + * If found, resume the validation process. + * If not found, fail the validation process. + */ +static void +fetch_callback_dnskey(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent; + dns_validator_t *val; + dns_rdataset_t *rdataset; + bool want_destroy; + isc_result_t result; + isc_result_t eresult; + isc_result_t saved_result; + dns_fetch_t *fetch; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_FETCHDONE); + devent = (dns_fetchevent_t *)event; + val = devent->ev_arg; + rdataset = &val->frdataset; + eresult = devent->result; + + /* Free resources which are not of interest. */ + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_disassociate(&val->fsigrdataset); + } + isc_event_free(&event); + + INSIST(val->event != NULL); + + validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey"); + LOCK(&val->lock); + fetch = val->fetch; + val->fetch = NULL; + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + } else if (eresult == ISC_R_SUCCESS || eresult == DNS_R_NCACHENXRRSET) { + /* + * We have an answer to our DNSKEY query. Either the DNSKEY + * RRset or a NODATA response. + */ + validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", + eresult == ISC_R_SUCCESS ? "keyset" + : "NCACHENXRRSET", + dns_trust_totext(rdataset->trust)); + /* + * Only extract the dst key if the keyset exists and is secure. + */ + if (eresult == ISC_R_SUCCESS && + rdataset->trust >= dns_trust_secure) + { + result = select_signing_key(val, rdataset); + if (result == ISC_R_SUCCESS) { + val->keyset = &val->frdataset; + } + } + result = validate_answer(val, true); + if (result == DNS_R_NOVALIDSIG && + (val->attributes & VALATTR_TRIEDVERIFY) == 0) + { + saved_result = result; + validator_log(val, ISC_LOG_DEBUG(3), + "falling back to insecurity proof"); + result = proveunsecure(val, false, false); + if (result == DNS_R_NOTINSECURE) { + result = saved_result; + } + } + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_dnskey: got %s", + isc_result_totext(eresult)); + if (eresult == ISC_R_CANCELED) { + validator_done(val, eresult); + } else { + validator_done(val, DNS_R_BROKENCHAIN); + } + } + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + + if (fetch != NULL) { + dns_resolver_destroyfetch(&fetch); + } + + if (want_destroy) { + destroy(val); + } +} + +/*% + * We have been asked to look for a DS. This may be part of + * walking a trust chain, or an insecurity proof. + */ +static void +fetch_callback_ds(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent; + dns_validator_t *val; + dns_rdataset_t *rdataset; + bool want_destroy, trustchain; + isc_result_t result; + isc_result_t eresult; + dns_fetch_t *fetch; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_FETCHDONE); + devent = (dns_fetchevent_t *)event; + val = devent->ev_arg; + rdataset = &val->frdataset; + eresult = devent->result; + + /* + * Set 'trustchain' to true if we're walking a chain of + * trust; false if we're attempting to prove insecurity. + */ + trustchain = ((val->attributes & VALATTR_INSECURITY) == 0); + + /* Free resources which are not of interest. */ + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_disassociate(&val->fsigrdataset); + } + + INSIST(val->event != NULL); + + validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds"); + LOCK(&val->lock); + fetch = val->fetch; + val->fetch = NULL; + + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + goto done; + } + + switch (eresult) { + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + /* + * These results only make sense if we're attempting + * an insecurity proof, not when walking a chain of trust. + */ + if (trustchain) { + goto unexpected; + } + + FALLTHROUGH; + case ISC_R_SUCCESS: + if (trustchain) { + /* + * We looked for a DS record as part of + * following a key chain upwards; resume following + * the chain. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "dsset with trust %s", + dns_trust_totext(rdataset->trust)); + val->dsset = &val->frdataset; + result = validate_dnskey(val); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else { + /* + * There is a DS which may or may not be a zone cut. + * In either case we are still in a secure zone, + * so keep looking for the break in the chain + * of trust. + */ + result = proveunsecure(val, (eresult == ISC_R_SUCCESS), + true); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } + break; + case DNS_R_CNAME: + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + case DNS_R_SERVFAIL: /* RFC 1034 parent? */ + if (trustchain) { + /* + * Failed to find a DS while following the + * chain of trust; now we need to prove insecurity. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "falling back to insecurity proof (%s)", + isc_result_totext(eresult)); + result = proveunsecure(val, false, false); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else if (eresult == DNS_R_SERVFAIL) { + goto unexpected; + } else if (eresult != DNS_R_CNAME && + isdelegation(devent->foundname, &val->frdataset, + eresult)) + { + /* + * Failed to find a DS while trying to prove + * insecurity. If this is a zone cut, that + * means we're insecure. + */ + result = markanswer(val, "fetch_callback_ds", + "no DS and this is a delegation"); + validator_done(val, result); + } else { + /* + * Not a zone cut, so we have to keep looking for + * the break point in the chain of trust. + */ + result = proveunsecure(val, false, true); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } + break; + + default: + unexpected: + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_ds: got %s", + isc_result_totext(eresult)); + if (eresult == ISC_R_CANCELED) { + validator_done(val, eresult); + } else { + validator_done(val, DNS_R_BROKENCHAIN); + } + } +done: + + isc_event_free(&event); + want_destroy = exit_check(val); + UNLOCK(&val->lock); + + if (fetch != NULL) { + dns_resolver_destroyfetch(&fetch); + } + + if (want_destroy) { + destroy(val); + } +} + +/*% + * Callback from when a DNSKEY RRset has been validated. + * + * Resumes the stalled validation process. + */ +static void +validator_callback_dnskey(isc_task_t *task, isc_event_t *event) { + dns_validatorevent_t *devent; + dns_validator_t *val; + bool want_destroy; + isc_result_t result; + isc_result_t eresult; + isc_result_t saved_result; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE); + + devent = (dns_validatorevent_t *)event; + val = devent->ev_arg; + eresult = devent->result; + + isc_event_free(&event); + dns_validator_destroy(&val->subvalidator); + + INSIST(val->event != NULL); + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey"); + LOCK(&val->lock); + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + } else if (eresult == ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s", + dns_trust_totext(val->frdataset.trust)); + /* + * Only extract the dst key if the keyset is secure. + */ + if (val->frdataset.trust >= dns_trust_secure) { + (void)select_signing_key(val, &val->frdataset); + } + result = validate_answer(val, true); + if (result == DNS_R_NOVALIDSIG && + (val->attributes & VALATTR_TRIEDVERIFY) == 0) + { + saved_result = result; + validator_log(val, ISC_LOG_DEBUG(3), + "falling back to insecurity proof"); + result = proveunsecure(val, false, false); + if (result == DNS_R_NOTINSECURE) { + result = saved_result; + } + } + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else { + if (eresult != DNS_R_BROKENCHAIN) { + expire_rdatasets(val); + } + validator_log(val, ISC_LOG_DEBUG(3), + "validator_callback_dnskey: got %s", + isc_result_totext(eresult)); + validator_done(val, DNS_R_BROKENCHAIN); + } + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } +} + +/*% + * Callback when the DS record has been validated. + * + * Resumes validation of the zone key or the unsecure zone proof. + */ +static void +validator_callback_ds(isc_task_t *task, isc_event_t *event) { + dns_validatorevent_t *devent; + dns_validator_t *val; + bool want_destroy; + isc_result_t result; + isc_result_t eresult; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE); + + devent = (dns_validatorevent_t *)event; + val = devent->ev_arg; + eresult = devent->result; + + isc_event_free(&event); + dns_validator_destroy(&val->subvalidator); + + INSIST(val->event != NULL); + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds"); + LOCK(&val->lock); + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + } else if (eresult == ISC_R_SUCCESS) { + bool have_dsset; + dns_name_t *name; + validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", + val->frdataset.type == dns_rdatatype_ds ? "dsset" + : "ds " + "non-" + "existe" + "nce", + dns_trust_totext(val->frdataset.trust)); + have_dsset = (val->frdataset.type == dns_rdatatype_ds); + name = dns_fixedname_name(&val->fname); + if ((val->attributes & VALATTR_INSECURITY) != 0 && + val->frdataset.covers == dns_rdatatype_ds && + NEGATIVE(&val->frdataset) && + isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET)) + { + result = markanswer(val, "validator_callback_ds", + "no DS and this is a delegation"); + } else if ((val->attributes & VALATTR_INSECURITY) != 0) { + result = proveunsecure(val, have_dsset, true); + } else { + result = validate_dnskey(val); + } + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else { + if (eresult != DNS_R_BROKENCHAIN) { + expire_rdatasets(val); + } + validator_log(val, ISC_LOG_DEBUG(3), + "validator_callback_ds: got %s", + isc_result_totext(eresult)); + validator_done(val, DNS_R_BROKENCHAIN); + } + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } +} + +/*% + * Callback when the CNAME record has been validated. + * + * Resumes validation of the unsecure zone proof. + */ +static void +validator_callback_cname(isc_task_t *task, isc_event_t *event) { + dns_validatorevent_t *devent; + dns_validator_t *val; + bool want_destroy; + isc_result_t result; + isc_result_t eresult; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE); + + devent = (dns_validatorevent_t *)event; + val = devent->ev_arg; + eresult = devent->result; + + isc_event_free(&event); + dns_validator_destroy(&val->subvalidator); + + INSIST(val->event != NULL); + INSIST((val->attributes & VALATTR_INSECURITY) != 0); + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname"); + LOCK(&val->lock); + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + } else if (eresult == ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s", + dns_trust_totext(val->frdataset.trust)); + result = proveunsecure(val, false, true); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } else { + if (eresult != DNS_R_BROKENCHAIN) { + expire_rdatasets(val); + } + validator_log(val, ISC_LOG_DEBUG(3), + "validator_callback_cname: got %s", + isc_result_totext(eresult)); + validator_done(val, DNS_R_BROKENCHAIN); + } + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } +} + +/*% + * Callback for when NSEC records have been validated. + * + * Looks for NOQNAME, NODATA and OPTOUT proofs. + * + * Resumes the negative response validation by calling validate_nx(). + */ +static void +validator_callback_nsec(isc_task_t *task, isc_event_t *event) { + dns_validatorevent_t *devent; + dns_validator_t *val; + dns_rdataset_t *rdataset; + bool want_destroy; + isc_result_t result; + bool exists, data; + + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_VALIDATORDONE); + + devent = (dns_validatorevent_t *)event; + rdataset = devent->rdataset; + val = devent->ev_arg; + result = devent->result; + dns_validator_destroy(&val->subvalidator); + + INSIST(val->event != NULL); + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec"); + LOCK(&val->lock); + if (CANCELED(val)) { + validator_done(val, ISC_R_CANCELED); + } else if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "validator_callback_nsec: got %s", + isc_result_totext(result)); + if (result == DNS_R_BROKENCHAIN) { + val->authfail++; + } + if (result == ISC_R_CANCELED) { + validator_done(val, result); + } else { + result = validate_nx(val, true); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } + } else { + dns_name_t **proofs = val->event->proofs; + dns_name_t *wild = dns_fixedname_name(&val->wild); + + if (rdataset->type == dns_rdatatype_nsec && + rdataset->trust == dns_trust_secure && + (NEEDNODATA(val) || NEEDNOQNAME(val)) && + !FOUNDNODATA(val) && !FOUNDNOQNAME(val) && + dns_nsec_noexistnodata(val->event->type, val->event->name, + devent->name, rdataset, &exists, + &data, wild, validator_log, + val) == ISC_R_SUCCESS) + { + if (exists && !data) { + val->attributes |= VALATTR_FOUNDNODATA; + if (NEEDNODATA(val)) { + proofs[DNS_VALIDATOR_NODATAPROOF] = + devent->name; + } + } + if (!exists) { + dns_name_t *closest; + unsigned int clabels; + + val->attributes |= VALATTR_FOUNDNOQNAME; + + closest = dns_fixedname_name(&val->closest); + clabels = dns_name_countlabels(closest); + /* + * If we are validating a wildcard response + * clabels will not be zero. We then need + * to check if the generated wildcard from + * dns_nsec_noexistnodata is consistent with + * the wildcard used to generate the response. + */ + if (clabels == 0 || + dns_name_countlabels(wild) == clabels + 1) + { + val->attributes |= VALATTR_FOUNDCLOSEST; + } + /* + * The NSEC noqname proof also contains + * the closest encloser. + */ + if (NEEDNOQNAME(val)) { + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = + devent->name; + } + } + } + + result = validate_nx(val, true); + if (result != DNS_R_WAIT) { + validator_done(val, result); + } + } + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } + + /* + * Free stuff from the event. + */ + isc_event_free(&event); +} + +/*% + * Looks for the requested name and type in the view (zones and cache). + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOTFOUND + * \li DNS_R_NCACHENXDOMAIN + * \li DNS_R_NCACHENXRRSET + * \li DNS_R_NXRRSET + * \li DNS_R_NXDOMAIN + * \li DNS_R_BROKENCHAIN + */ +static isc_result_t +view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) { + dns_fixedname_t fixedname; + dns_name_t *foundname; + isc_result_t result; + unsigned int options; + isc_time_t now; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + disassociate_rdatasets(val); + + if (isc_time_now(&now) == ISC_R_SUCCESS && + dns_resolver_getbadcache(val->view->resolver, name, type, &now)) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + validator_log(val, ISC_LOG_INFO, "bad cache hit (%s/%s)", + namebuf, typebuf); + return (DNS_R_BROKENCHAIN); + } + + options = DNS_DBFIND_PENDINGOK; + foundname = dns_fixedname_initname(&fixedname); + result = dns_view_find(val->view, name, type, 0, options, false, false, + NULL, NULL, foundname, &val->frdataset, + &val->fsigrdataset); + + if (result == DNS_R_NXDOMAIN) { + goto notfound; + } else if (result != ISC_R_SUCCESS && result != DNS_R_NCACHENXDOMAIN && + result != DNS_R_NCACHENXRRSET && result != DNS_R_EMPTYNAME && + result != DNS_R_NXRRSET && result != ISC_R_NOTFOUND) + { + result = ISC_R_NOTFOUND; + goto notfound; + } + + return (result); + +notfound: + disassociate_rdatasets(val); + + return (result); +} + +/*% + * Checks to make sure we are not going to loop. As we use a SHARED fetch + * the validation process will stall if looping was to occur. + */ +static bool +check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_validator_t *parent; + + for (parent = val; parent != NULL; parent = parent->parent) { + if (parent->event != NULL && parent->event->type == type && + dns_name_equal(parent->event->name, name) && + /* + * As NSEC3 records are meta data you sometimes + * need to prove a NSEC3 record which says that + * itself doesn't exist. + */ + (parent->event->type != dns_rdatatype_nsec3 || + rdataset == NULL || sigrdataset == NULL || + parent->event->message == NULL || + parent->event->rdataset != NULL || + parent->event->sigrdataset != NULL)) + { + validator_log(val, ISC_LOG_DEBUG(3), + "continuing validation would lead to " + "deadlock: aborting validation"); + return (true); + } + } + return (false); +} + +/*% + * Start a fetch for the requested name and type. + */ +static isc_result_t +create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + isc_taskaction_t callback, const char *caller) { + unsigned int fopts = 0; + + disassociate_rdatasets(val); + + if (check_deadlock(val, name, type, NULL, NULL)) { + validator_log(val, ISC_LOG_DEBUG(3), + "deadlock found (create_fetch)"); + return (DNS_R_NOVALIDSIG); + } + + if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) { + fopts |= DNS_FETCHOPT_NOCDFLAG; + } + + if ((val->options & DNS_VALIDATOR_NONTA) != 0) { + fopts |= DNS_FETCHOPT_NONTA; + } + + validator_logcreate(val, name, type, caller, "fetch"); + return (dns_resolver_createfetch( + val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0, + fopts, 0, NULL, val->event->ev_sender, callback, val, + &val->frdataset, &val->fsigrdataset, &val->fetch)); +} + +/*% + * Start a subvalidation process. + */ +static isc_result_t +create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + isc_taskaction_t action, const char *caller) { + isc_result_t result; + unsigned int vopts = 0; + dns_rdataset_t *sig = NULL; + + if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) { + sig = sigrdataset; + } + + if (check_deadlock(val, name, type, rdataset, sig)) { + validator_log(val, ISC_LOG_DEBUG(3), + "deadlock found (create_validator)"); + return (DNS_R_NOVALIDSIG); + } + + /* OK to clear other options, but preserve NOCDFLAG and NONTA. */ + vopts |= (val->options & + (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA)); + + validator_logcreate(val, name, type, caller, "validator"); + result = dns_validator_create(val->view, name, type, rdataset, sig, + NULL, vopts, val->task, action, val, + &val->subvalidator); + if (result == ISC_R_SUCCESS) { + val->subvalidator->parent = val; + val->subvalidator->depth = val->depth + 1; + } + return (result); +} + +/*% + * Try to find a key that could have signed val->siginfo among those in + * 'rdataset'. If found, build a dst_key_t for it and point val->key at + * it. + * + * If val->key is already non-NULL, locate it in the rdataset and then + * search past it for the *next* key that could have signed 'siginfo', then + * set val->key to that. + * + * Returns ISC_R_SUCCESS if a possible matching key has been found, + * ISC_R_NOTFOUND if not. Any other value indicates error. + */ +static isc_result_t +select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_rdata_rrsig_t *siginfo = val->siginfo; + isc_buffer_t b; + dns_rdata_t rdata = DNS_RDATA_INIT; + dst_key_t *oldkey = val->key; + bool foundold; + + if (oldkey == NULL) { + foundold = true; + } else { + foundold = false; + val->key = NULL; + } + + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + goto failure; + } + do { + dns_rdataset_current(rdataset, &rdata); + + isc_buffer_init(&b, rdata.data, rdata.length); + isc_buffer_add(&b, rdata.length); + INSIST(val->key == NULL); + result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b, + val->view->mctx, &val->key); + if (result == ISC_R_SUCCESS) { + if (siginfo->algorithm == + (dns_secalg_t)dst_key_alg(val->key) && + siginfo->keyid == + (dns_keytag_t)dst_key_id(val->key) && + dst_key_iszonekey(val->key)) + { + if (foundold) { + /* + * This is the key we're looking for. + */ + return (ISC_R_SUCCESS); + } else if (dst_key_compare(oldkey, val->key)) { + foundold = true; + dst_key_free(&oldkey); + } + } + dst_key_free(&val->key); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); + } while (result == ISC_R_SUCCESS); + + if (result == ISC_R_NOMORE) { + result = ISC_R_NOTFOUND; + } + +failure: + if (oldkey != NULL) { + dst_key_free(&oldkey); + } + + return (result); +} + +/*% + * Get the key that generated the signature in val->siginfo. + */ +static isc_result_t +seek_dnskey(dns_validator_t *val) { + isc_result_t result; + dns_rdata_rrsig_t *siginfo = val->siginfo; + unsigned int nlabels; + int order; + dns_namereln_t namereln; + + /* + * Is the signer name appropriate for this signature? + * + * The signer name must be at the same level as the owner name + * or closer to the DNS root. + */ + namereln = dns_name_fullcompare(val->event->name, &siginfo->signer, + &order, &nlabels); + if (namereln != dns_namereln_subdomain && + namereln != dns_namereln_equal) + { + return (DNS_R_CONTINUE); + } + + if (namereln == dns_namereln_equal) { + /* + * If this is a self-signed keyset, it must not be a zone key + * (since seek_dnskey is not called from validate_dnskey). + */ + if (val->event->rdataset->type == dns_rdatatype_dnskey) { + return (DNS_R_CONTINUE); + } + + /* + * Records appearing in the parent zone at delegation + * points cannot be self-signed. + */ + if (dns_rdatatype_atparent(val->event->rdataset->type)) { + return (DNS_R_CONTINUE); + } + } else { + /* + * SOA and NS RRsets can only be signed by a key with + * the same name. + */ + if (val->event->rdataset->type == dns_rdatatype_soa || + val->event->rdataset->type == dns_rdatatype_ns) + { + const char *type; + + if (val->event->rdataset->type == dns_rdatatype_soa) { + type = "SOA"; + } else { + type = "NS"; + } + validator_log(val, ISC_LOG_DEBUG(3), + "%s signer mismatch", type); + return (DNS_R_CONTINUE); + } + } + + /* + * Do we know about this key? + */ + result = view_find(val, &siginfo->signer, dns_rdatatype_dnskey); + switch (result) { + case ISC_R_SUCCESS: + /* + * We have an rrset for the given keyname. + */ + val->keyset = &val->frdataset; + if ((DNS_TRUST_PENDING(val->frdataset.trust) || + DNS_TRUST_ANSWER(val->frdataset.trust)) && + dns_rdataset_isassociated(&val->fsigrdataset)) + { + /* + * We know the key but haven't validated it yet or + * we have a key of trust answer but a DS + * record for the zone may have been added. + */ + result = create_validator( + val, &siginfo->signer, dns_rdatatype_dnskey, + &val->frdataset, &val->fsigrdataset, + validator_callback_dnskey, "seek_dnskey"); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (DNS_R_WAIT); + } else if (DNS_TRUST_PENDING(val->frdataset.trust)) { + /* + * Having a pending key with no signature means that + * something is broken. + */ + result = DNS_R_CONTINUE; + } else if (val->frdataset.trust < dns_trust_secure) { + /* + * The key is legitimately insecure. There's no + * point in even attempting verification. + */ + val->key = NULL; + result = ISC_R_SUCCESS; + } else { + /* + * See if we've got the key used in the signature. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "keyset with trust %s", + dns_trust_totext(val->frdataset.trust)); + result = select_signing_key(val, val->keyset); + if (result != ISC_R_SUCCESS) { + /* + * Either the key we're looking for is not + * in the rrset, or something bad happened. + * Give up. + */ + result = DNS_R_CONTINUE; + } + } + break; + + case ISC_R_NOTFOUND: + /* + * We don't know anything about this key. + */ + result = create_fetch(val, &siginfo->signer, + dns_rdatatype_dnskey, + fetch_callback_dnskey, "seek_dnskey"); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (DNS_R_WAIT); + + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_EMPTYNAME: + case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + /* + * This key doesn't exist. + */ + result = DNS_R_CONTINUE; + break; + + case DNS_R_BROKENCHAIN: + return (result); + + default: + break; + } + + if (dns_rdataset_isassociated(&val->frdataset) && + val->keyset != &val->frdataset) + { + dns_rdataset_disassociate(&val->frdataset); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_disassociate(&val->fsigrdataset); + } + + return (result); +} + +/* + * Compute the tag for a key represented in a DNSKEY rdata. + */ +static dns_keytag_t +compute_keytag(dns_rdata_t *rdata) { + isc_region_t r; + + dns_rdata_toregion(rdata, &r); + return (dst_region_computeid(&r)); +} + +/*% + * Is the DNSKEY rrset in val->event->rdataset self-signed? + */ +static bool +selfsigned_dnskey(dns_validator_t *val) { + dns_rdataset_t *rdataset = val->event->rdataset; + dns_rdataset_t *sigrdataset = val->event->sigrdataset; + dns_name_t *name = val->event->name; + isc_result_t result; + isc_mem_t *mctx = val->view->mctx; + bool answer = false; + + if (rdataset->type != dns_rdatatype_dnskey) { + return (false); + } + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t keyrdata = DNS_RDATA_INIT; + dns_rdata_t sigrdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t key; + dns_rdata_rrsig_t sig; + dns_keytag_t keytag; + + dns_rdata_reset(&keyrdata); + dns_rdataset_current(rdataset, &keyrdata); + result = dns_rdata_tostruct(&keyrdata, &key, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + keytag = compute_keytag(&keyrdata); + + for (result = dns_rdataset_first(sigrdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigrdataset)) + { + dst_key_t *dstkey = NULL; + + dns_rdata_reset(&sigrdata); + dns_rdataset_current(sigrdataset, &sigrdata); + result = dns_rdata_tostruct(&sigrdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (sig.algorithm != key.algorithm || + sig.keyid != keytag || + !dns_name_equal(name, &sig.signer)) + { + continue; + } + + /* + * If the REVOKE bit is not set we have a + * theoretically self signed DNSKEY RRset. + * This will be verified later. + */ + if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) { + answer = true; + continue; + } + + result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx, + &dstkey); + if (result != ISC_R_SUCCESS) { + continue; + } + + /* + * If this RRset is pending and it is trusted, + * see if it was self signed by this DNSKEY. + */ + if (DNS_TRUST_PENDING(rdataset->trust) && + dns_view_istrusted(val->view, name, &key)) + { + result = dns_dnssec_verify( + name, rdataset, dstkey, true, + val->view->maxbits, mctx, &sigrdata, + NULL); + if (result == ISC_R_SUCCESS) { + /* + * The key with the REVOKE flag has + * self signed the RRset so it is no + * good. + */ + dns_view_untrust(val->view, name, &key); + } + } else if (rdataset->trust >= dns_trust_secure) { + /* + * We trust this RRset so if the key is + * marked revoked remove it. + */ + dns_view_untrust(val->view, name, &key); + } + + dst_key_free(&dstkey); + } + } + + return (answer); +} + +/*% + * Attempt to verify the rdataset using the given key and rdata (RRSIG). + * The signature was good and from a wildcard record and the QNAME does + * not match the wildcard we need to look for a NOQNAME proof. + * + * Returns: + * \li ISC_R_SUCCESS if the verification succeeds. + * \li Others if the verification fails. + */ +static isc_result_t +verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, + uint16_t keyid) { + isc_result_t result; + dns_fixedname_t fixed; + bool ignore = false; + dns_name_t *wild; + + val->attributes |= VALATTR_TRIEDVERIFY; + wild = dns_fixedname_initname(&fixed); +again: + result = dns_dnssec_verify(val->event->name, val->event->rdataset, key, + ignore, val->view->maxbits, val->view->mctx, + rdata, wild); + if ((result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) && + val->view->acceptexpired) + { + ignore = true; + goto again; + } + + if (ignore && (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD)) + { + validator_log(val, ISC_LOG_INFO, + "accepted expired %sRRSIG (keyid=%u)", + (result == DNS_R_FROMWILDCARD) ? "wildcard " : "", + keyid); + } else if (result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) { + validator_log(val, ISC_LOG_INFO, + "verify failed due to bad signature (keyid=%u): " + "%s", + keyid, isc_result_totext(result)); + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "verify rdataset (keyid=%u): %s", keyid, + isc_result_totext(result)); + } + if (result == DNS_R_FROMWILDCARD) { + if (!dns_name_equal(val->event->name, wild)) { + dns_name_t *closest; + unsigned int labels; + + /* + * Compute the closest encloser in case we need it + * for the NSEC3 NOQNAME proof. + */ + closest = dns_fixedname_name(&val->closest); + dns_name_copy(wild, closest); + labels = dns_name_countlabels(closest) - 1; + dns_name_getlabelsequence(closest, 1, labels, closest); + val->attributes |= VALATTR_NEEDNOQNAME; + } + result = ISC_R_SUCCESS; + } + return (result); +} + +/*% + * Attempts positive response validation of a normal RRset. + * + * Returns: + * \li ISC_R_SUCCESS Validation completed successfully + * \li DNS_R_WAIT Validation has started but is waiting + * for an event. + * \li Other return codes are possible and all indicate failure. + */ +static isc_result_t +validate_answer(dns_validator_t *val, bool resume) { + isc_result_t result, vresult = DNS_R_NOVALIDSIG; + dns_validatorevent_t *event; + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* + * Caller must be holding the validator lock. + */ + + event = val->event; + + if (resume) { + /* + * We already have a sigrdataset. + */ + result = ISC_R_SUCCESS; + validator_log(val, ISC_LOG_DEBUG(3), "resuming validate"); + } else { + result = dns_rdataset_first(event->sigrdataset); + } + + for (; result == ISC_R_SUCCESS; + result = dns_rdataset_next(event->sigrdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(event->sigrdataset, &rdata); + if (val->siginfo == NULL) { + val->siginfo = isc_mem_get(val->view->mctx, + sizeof(*val->siginfo)); + } + result = dns_rdata_tostruct(&rdata, val->siginfo, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * At this point we could check that the signature algorithm + * was known and "sufficiently good". + */ + if (!dns_resolver_algorithm_supported(val->view->resolver, + event->name, + val->siginfo->algorithm)) + { + resume = false; + continue; + } + + if (!resume) { + result = seek_dnskey(val); + if (result == DNS_R_CONTINUE) { + continue; /* Try the next SIG RR. */ + } + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* + * There isn't a secure DNSKEY for this signature so move + * onto the next RRSIG. + */ + if (val->key == NULL) { + resume = false; + continue; + } + + do { + isc_result_t tresult; + vresult = verify(val, val->key, &rdata, + val->siginfo->keyid); + if (vresult == ISC_R_SUCCESS) { + break; + } + + tresult = select_signing_key(val, val->keyset); + if (tresult != ISC_R_SUCCESS) { + break; + } + } while (1); + if (vresult != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "failed to verify rdataset"); + } else { + dns_rdataset_trimttl(event->rdataset, + event->sigrdataset, val->siginfo, + val->start, + val->view->acceptexpired); + } + + if (val->key != NULL) { + dst_key_free(&val->key); + } + if (val->keyset != NULL) { + dns_rdataset_disassociate(val->keyset); + val->keyset = NULL; + } + val->key = NULL; + if (NEEDNOQNAME(val)) { + if (val->event->message == NULL) { + validator_log(val, ISC_LOG_DEBUG(3), + "no message available " + "for noqname proof"); + return (DNS_R_NOVALIDSIG); + } + validator_log(val, ISC_LOG_DEBUG(3), + "looking for noqname proof"); + return (validate_nx(val, false)); + } else if (vresult == ISC_R_SUCCESS) { + marksecure(event); + validator_log(val, ISC_LOG_DEBUG(3), + "marking as secure, " + "noqname proof not needed"); + return (ISC_R_SUCCESS); + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "verify failure: %s", + isc_result_totext(result)); + resume = false; + } + } + if (result != ISC_R_NOMORE) { + validator_log(val, ISC_LOG_DEBUG(3), + "failed to iterate signatures: %s", + isc_result_totext(result)); + return (result); + } + + validator_log(val, ISC_LOG_INFO, "no valid signature found"); + return (vresult); +} + +/*% + * Check whether this DNSKEY (keyrdata) signed the DNSKEY RRset + * (val->event->rdataset). + */ +static isc_result_t +check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid, + dns_secalg_t algorithm) { + dns_rdata_rrsig_t sig; + dst_key_t *dstkey = NULL; + isc_result_t result; + + for (result = dns_rdataset_first(val->event->sigrdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(val->event->sigrdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(val->event->sigrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (keyid != sig.keyid || algorithm != sig.algorithm) { + continue; + } + if (dstkey == NULL) { + result = dns_dnssec_keyfromrdata( + val->event->name, keyrdata, val->view->mctx, + &dstkey); + if (result != ISC_R_SUCCESS) { + /* + * This really shouldn't happen, but... + */ + continue; + } + } + result = verify(val, dstkey, &rdata, sig.keyid); + if (result == ISC_R_SUCCESS) { + break; + } + } + + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + + return (result); +} + +/* + * get_dsset() is called to look up a DS RRset corresponding to the name + * of a DNSKEY record, either in the cache or, if necessary, by starting a + * fetch. This is done in the context of validating a zone key to build a + * trust chain. + * + * Returns: + * \li ISC_R_COMPLETE a DS has not been found; the caller should + * stop trying to validate the zone key and + * return the result code in '*resp'. + * \li DNS_R_CONTINUE a DS has been found and the caller may + * continue the zone key validation. + */ +static isc_result_t +get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) { + isc_result_t result; + + result = view_find(val, tname, dns_rdatatype_ds); + switch (result) { + case ISC_R_SUCCESS: + /* + * We have a DS RRset. + */ + val->dsset = &val->frdataset; + if ((DNS_TRUST_PENDING(val->frdataset.trust) || + DNS_TRUST_ANSWER(val->frdataset.trust)) && + dns_rdataset_isassociated(&val->fsigrdataset)) + { + /* + * ... which is signed but not yet validated. + */ + result = create_validator( + val, tname, dns_rdatatype_ds, &val->frdataset, + &val->fsigrdataset, validator_callback_ds, + "validate_dnskey"); + *resp = DNS_R_WAIT; + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + } else if (DNS_TRUST_PENDING(val->frdataset.trust)) { + /* + * There should never be an unsigned DS. + */ + disassociate_rdatasets(val); + validator_log(val, ISC_LOG_DEBUG(2), + "unsigned DS record"); + *resp = DNS_R_NOVALIDSIG; + return (ISC_R_COMPLETE); + } + break; + + case ISC_R_NOTFOUND: + /* + * We don't have the DS. Find it. + */ + result = create_fetch(val, tname, dns_rdatatype_ds, + fetch_callback_ds, "validate_dnskey"); + *resp = DNS_R_WAIT; + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_EMPTYNAME: + case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + case DNS_R_CNAME: + /* + * The DS does not exist. + */ + disassociate_rdatasets(val); + validator_log(val, ISC_LOG_DEBUG(2), "no DS record"); + *resp = DNS_R_NOVALIDSIG; + return (ISC_R_COMPLETE); + + case DNS_R_BROKENCHAIN: + *resp = result; + return (ISC_R_COMPLETE); + + default: + break; + } + + return (DNS_R_CONTINUE); +} + +/*% + * Attempts positive response validation of an RRset containing zone keys + * (i.e. a DNSKEY rrset). + * + * Caller must be holding the validator lock. + * + * Returns: + * \li ISC_R_SUCCESS Validation completed successfully + * \li DNS_R_WAIT Validation has started but is waiting + * for an event. + * \li Other return codes are possible and all indicate failure. + */ +static isc_result_t +validate_dnskey(dns_validator_t *val) { + isc_result_t result; + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t keyrdata = DNS_RDATA_INIT; + dns_keynode_t *keynode = NULL; + dns_rdata_ds_t ds; + bool supported_algorithm; + char digest_types[256]; + + /* + * If we don't already have a DS RRset, check to see if there's + * a DS style trust anchor configured for this key. + */ + if (val->dsset == NULL) { + result = dns_keytable_find(val->keytable, val->event->name, + &keynode); + if (result == ISC_R_SUCCESS) { + if (dns_keynode_dsset(keynode, &val->fdsset)) { + val->dsset = &val->fdsset; + } + dns_keytable_detachkeynode(val->keytable, &keynode); + } + } + + /* + * No trust anchor for this name, so we look up the DS at the parent. + */ + if (val->dsset == NULL) { + isc_result_t tresult = ISC_R_SUCCESS; + + /* + * If this is the root name and there was no trust anchor, + * we can give up now, since there's no DS at the root. + */ + if (dns_name_equal(val->event->name, dns_rootname)) { + if ((val->attributes & VALATTR_TRIEDVERIFY) != 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "root key failed to validate"); + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "no trusted root key"); + } + result = DNS_R_NOVALIDSIG; + goto cleanup; + } + + /* + * Look up the DS RRset for this name. + */ + result = get_dsset(val, val->event->name, &tresult); + if (result == ISC_R_COMPLETE) { + result = tresult; + goto cleanup; + } + } + + /* + * We have a DS set. + */ + INSIST(val->dsset != NULL); + + if (val->dsset->trust < dns_trust_secure) { + result = markanswer(val, "validate_dnskey (2)", "insecure DS"); + goto cleanup; + } + + /* + * Look through the DS record and find the keys that can sign the + * key set and the matching signature. For each such key, attempt + * verification. + */ + + supported_algorithm = false; + + /* + * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we + * are required to prefer it over DNS_DSDIGEST_SHA1. This in + * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a + * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present. + */ + memset(digest_types, 1, sizeof(digest_types)); + for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(val->dsset)) + { + dns_rdata_reset(&dsrdata); + dns_rdataset_current(val->dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (!dns_resolver_ds_digest_supported(val->view->resolver, + val->event->name, + ds.digest_type)) + { + continue; + } + + if (!dns_resolver_algorithm_supported(val->view->resolver, + val->event->name, + ds.algorithm)) + { + continue; + } + + if ((ds.digest_type == DNS_DSDIGEST_SHA256 && + ds.length == ISC_SHA256_DIGESTLENGTH) || + (ds.digest_type == DNS_DSDIGEST_SHA384 && + ds.length == ISC_SHA384_DIGESTLENGTH)) + { + digest_types[DNS_DSDIGEST_SHA1] = 0; + break; + } + } + + for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(val->dsset)) + { + dns_rdata_reset(&dsrdata); + dns_rdataset_current(val->dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (digest_types[ds.digest_type] == 0) { + continue; + } + + if (!dns_resolver_ds_digest_supported(val->view->resolver, + val->event->name, + ds.digest_type)) + { + continue; + } + + if (!dns_resolver_algorithm_supported(val->view->resolver, + val->event->name, + ds.algorithm)) + { + continue; + } + + supported_algorithm = true; + + /* + * Find the DNSKEY matching the DS... + */ + result = dns_dnssec_matchdskey(val->event->name, &dsrdata, + val->event->rdataset, &keyrdata); + if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "no DNSKEY matching DS"); + continue; + } + + /* + * ... and check that it signed the DNSKEY RRset. + */ + result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm); + if (result == ISC_R_SUCCESS) { + break; + } + validator_log(val, ISC_LOG_DEBUG(3), + "no RRSIG matching DS key"); + } + + if (result == ISC_R_SUCCESS) { + marksecure(val->event); + validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)"); + } else if (result == ISC_R_NOMORE && !supported_algorithm) { + validator_log(val, ISC_LOG_DEBUG(3), + "no supported algorithm/digest (DS)"); + result = markanswer(val, "validate_dnskey (3)", + "no supported algorithm/digest (DS)"); + } else { + validator_log(val, ISC_LOG_INFO, + "no valid signature found (DS)"); + result = DNS_R_NOVALIDSIG; + } + +cleanup: + if (val->dsset == &val->fdsset) { + val->dsset = NULL; + dns_rdataset_disassociate(&val->fdsset); + } + + return (result); +} + +/*% + * val_rdataset_first and val_rdataset_next provide iteration methods + * that hide whether we are iterating across the AUTHORITY section of + * a message, or a negative cache rdataset. + */ +static isc_result_t +val_rdataset_first(dns_validator_t *val, dns_name_t **namep, + dns_rdataset_t **rdatasetp) { + dns_message_t *message = val->event->message; + isc_result_t result; + + REQUIRE(rdatasetp != NULL); + REQUIRE(namep != NULL); + if (message == NULL) { + REQUIRE(*rdatasetp != NULL); + REQUIRE(*namep != NULL); + } else { + REQUIRE(*rdatasetp == NULL); + REQUIRE(*namep == NULL); + } + + if (message != NULL) { + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_message_currentname(message, DNS_SECTION_AUTHORITY, namep); + *rdatasetp = ISC_LIST_HEAD((*namep)->list); + INSIST(*rdatasetp != NULL); + } else { + result = dns_rdataset_first(val->event->rdataset); + if (result == ISC_R_SUCCESS) { + dns_ncache_current(val->event->rdataset, *namep, + *rdatasetp); + } + } + return (result); +} + +static isc_result_t +val_rdataset_next(dns_validator_t *val, dns_name_t **namep, + dns_rdataset_t **rdatasetp) { + dns_message_t *message = val->event->message; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rdatasetp != NULL && *rdatasetp != NULL); + REQUIRE(namep != NULL && *namep != NULL); + + if (message != NULL) { + dns_rdataset_t *rdataset = *rdatasetp; + rdataset = ISC_LIST_NEXT(rdataset, link); + if (rdataset == NULL) { + *namep = NULL; + result = dns_message_nextname(message, + DNS_SECTION_AUTHORITY); + if (result == ISC_R_SUCCESS) { + dns_message_currentname( + message, DNS_SECTION_AUTHORITY, namep); + rdataset = ISC_LIST_HEAD((*namep)->list); + INSIST(rdataset != NULL); + } + } + *rdatasetp = rdataset; + } else { + dns_rdataset_disassociate(*rdatasetp); + result = dns_rdataset_next(val->event->rdataset); + if (result == ISC_R_SUCCESS) { + dns_ncache_current(val->event->rdataset, *namep, + *rdatasetp); + } + } + return (result); +} + +/*% + * Look for NODATA at the wildcard and NOWILDCARD proofs in the + * previously validated NSEC records. As these proofs are mutually + * exclusive we stop when one is found. + * + * Returns + * \li ISC_R_SUCCESS + */ +static isc_result_t +checkwildcard(dns_validator_t *val, dns_rdatatype_t type, + dns_name_t *zonename) { + dns_name_t *name, *wild, tname; + isc_result_t result; + bool exists, data; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rdataset_t *rdataset, trdataset; + + dns_name_init(&tname, NULL); + dns_rdataset_init(&trdataset); + wild = dns_fixedname_name(&val->wild); + + if (dns_name_countlabels(wild) == 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "in checkwildcard: no wildcard to check"); + return (ISC_R_SUCCESS); + } + + dns_name_format(wild, namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), "in checkwildcard: %s", namebuf); + + if (val->event->message == NULL) { + name = &tname; + rdataset = &trdataset; + } else { + name = NULL; + rdataset = NULL; + } + + for (result = val_rdataset_first(val, &name, &rdataset); + result == ISC_R_SUCCESS; + result = val_rdataset_next(val, &name, &rdataset)) + { + if (rdataset->type != type || + rdataset->trust != dns_trust_secure) + { + continue; + } + + if (rdataset->type == dns_rdatatype_nsec && + (NEEDNODATA(val) || NEEDNOWILDCARD(val)) && + !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) && + dns_nsec_noexistnodata(val->event->type, wild, name, + rdataset, &exists, &data, NULL, + validator_log, val) == ISC_R_SUCCESS) + { + dns_name_t **proofs = val->event->proofs; + if (exists && !data) { + val->attributes |= VALATTR_FOUNDNODATA; + } + if (exists && !data && NEEDNODATA(val)) { + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } + if (!exists) { + val->attributes |= VALATTR_FOUNDNOWILDCARD; + } + if (!exists && NEEDNOQNAME(val)) { + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name; + } + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_disassociate(&trdataset); + } + return (ISC_R_SUCCESS); + } + + if (rdataset->type == dns_rdatatype_nsec3 && + (NEEDNODATA(val) || NEEDNOWILDCARD(val)) && + !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) && + dns_nsec3_noexistnodata( + val->event->type, wild, name, rdataset, zonename, + &exists, &data, NULL, NULL, NULL, NULL, NULL, NULL, + validator_log, val) == ISC_R_SUCCESS) + { + dns_name_t **proofs = val->event->proofs; + if (exists && !data) { + val->attributes |= VALATTR_FOUNDNODATA; + } + if (exists && !data && NEEDNODATA(val)) { + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } + if (!exists) { + val->attributes |= VALATTR_FOUNDNOWILDCARD; + } + if (!exists && NEEDNOQNAME(val)) { + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name; + } + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_disassociate(&trdataset); + } + return (ISC_R_SUCCESS); + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_disassociate(&trdataset); + } + return (result); +} + +/* + * Look for the needed proofs for a negative or wildcard response + * from a zone using NSEC3, and set flags in the validator as they + * are found. + */ +static isc_result_t +findnsec3proofs(dns_validator_t *val) { + dns_name_t *name, tname; + isc_result_t result; + bool exists, data, optout, unknown; + bool setclosest, setnearest, *setclosestp; + dns_fixedname_t fclosest, fnearest, fzonename; + dns_name_t *closest, *nearest, *zonename, *closestp; + dns_name_t **proofs = val->event->proofs; + dns_rdataset_t *rdataset, trdataset; + + dns_name_init(&tname, NULL); + dns_rdataset_init(&trdataset); + closest = dns_fixedname_initname(&fclosest); + nearest = dns_fixedname_initname(&fnearest); + zonename = dns_fixedname_initname(&fzonename); + + if (val->event->message == NULL) { + name = &tname; + rdataset = &trdataset; + } else { + name = NULL; + rdataset = NULL; + } + + for (result = val_rdataset_first(val, &name, &rdataset); + result == ISC_R_SUCCESS; + result = val_rdataset_next(val, &name, &rdataset)) + { + if (rdataset->type != dns_rdatatype_nsec3 || + rdataset->trust != dns_trust_secure) + { + continue; + } + + result = dns_nsec3_noexistnodata( + val->event->type, val->event->name, name, rdataset, + zonename, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, validator_log, val); + if (result != ISC_R_IGNORE && result != ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&trdataset)) { + dns_rdataset_disassociate(&trdataset); + } + return (result); + } + } + if (result != ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + POST(result); + + if (dns_name_countlabels(zonename) == 0) { + return (ISC_R_SUCCESS); + } + + /* + * If the val->closest is set then we want to use it otherwise + * we need to discover it. + */ + if (dns_name_countlabels(dns_fixedname_name(&val->closest)) != 0) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(dns_fixedname_name(&val->closest), namebuf, + sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), + "closest encloser from wildcard signature '%s'", + namebuf); + dns_name_copy(dns_fixedname_name(&val->closest), closest); + closestp = NULL; + setclosestp = NULL; + } else { + closestp = closest; + setclosestp = &setclosest; + } + + for (result = val_rdataset_first(val, &name, &rdataset); + result == ISC_R_SUCCESS; + result = val_rdataset_next(val, &name, &rdataset)) + { + if (rdataset->type != dns_rdatatype_nsec3 || + rdataset->trust != dns_trust_secure) + { + continue; + } + + /* + * We process all NSEC3 records to find the closest + * encloser and nearest name to the closest encloser. + */ + setclosest = setnearest = false; + optout = false; + unknown = false; + result = dns_nsec3_noexistnodata( + val->event->type, val->event->name, name, rdataset, + zonename, &exists, &data, &optout, &unknown, + setclosestp, &setnearest, closestp, nearest, + validator_log, val); + if (unknown) { + val->attributes |= VALATTR_FOUNDUNKNOWN; + } + if (result == DNS_R_NSEC3ITERRANGE) { + /* + * We don't really know which NSEC3 record provides + * which proof. Just populate them. + */ + if (NEEDNOQNAME(val) && + proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL) + { + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name; + } else if (setclosest) { + proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name; + } else if (NEEDNODATA(val) && + proofs[DNS_VALIDATOR_NODATAPROOF] == NULL) + { + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } else if (NEEDNOWILDCARD(val) && + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] == + NULL) + { + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name; + } + return (result); + } + if (result != ISC_R_SUCCESS) { + continue; + } + if (setclosest) { + proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name; + } + if (exists && !data && NEEDNODATA(val)) { + val->attributes |= VALATTR_FOUNDNODATA; + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } + if (!exists && setnearest) { + val->attributes |= VALATTR_FOUNDNOQNAME; + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name; + if (optout) { + val->attributes |= VALATTR_FOUNDOPTOUT; + } + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + /* + * To know we have a valid noqname and optout proofs we need to also + * have a valid closest encloser. Otherwise we could still be looking + * at proofs from the parent zone. + */ + if (dns_name_countlabels(closest) > 0 && + dns_name_countlabels(nearest) == + dns_name_countlabels(closest) + 1 && + dns_name_issubdomain(nearest, closest)) + { + val->attributes |= VALATTR_FOUNDCLOSEST; + result = dns_name_concatenate(dns_wildcardname, closest, + dns_fixedname_name(&val->wild), + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } else { + val->attributes &= ~VALATTR_FOUNDNOQNAME; + val->attributes &= ~VALATTR_FOUNDOPTOUT; + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = NULL; + } + + /* + * Do we need to check for the wildcard? + */ + if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && + ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val))) + { + result = checkwildcard(val, dns_rdatatype_nsec3, zonename); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + return (result); +} + +/* + * Start a validator for negative response data. + * + * Returns: + * \li DNS_R_CONTINUE Validation skipped, continue + * \li DNS_R_WAIT Validation is in progress + * + * \li Other return codes indicate failure. + */ +static isc_result_t +validate_neg_rrset(dns_validator_t *val, dns_name_t *name, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + isc_result_t result; + + /* + * If a signed zone is missing the zone key, bad + * things could happen. A query for data in the zone + * would lead to a query for the zone key, which + * would return a negative answer, which would contain + * an SOA and an NSEC signed by the missing key, which + * would trigger another query for the DNSKEY (since + * the first one is still in progress), and go into an + * infinite loop. Avoid that. + */ + if (val->event->type == dns_rdatatype_dnskey && + rdataset->type == dns_rdatatype_nsec && + dns_name_equal(name, val->event->name)) + { + dns_rdata_t nsec = DNS_RDATA_INIT; + + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_rdataset_current(rdataset, &nsec); + if (dns_nsec_typepresent(&nsec, dns_rdatatype_soa)) { + return (DNS_R_CONTINUE); + } + } + + val->currentset = rdataset; + result = create_validator(val, name, rdataset->type, rdataset, + sigrdataset, validator_callback_nsec, + "validate_neg_rrset"); + if (result != ISC_R_SUCCESS) { + return (result); + } + + val->authcount++; + return (DNS_R_WAIT); +} + +/*% + * Validate the authority section records. + */ +static isc_result_t +validate_authority(dns_validator_t *val, bool resume) { + dns_name_t *name; + dns_message_t *message = val->event->message; + isc_result_t result; + + if (!resume) { + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + } else { + result = ISC_R_SUCCESS; + } + + for (; result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) + { + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + if (resume) { + rdataset = ISC_LIST_NEXT(val->currentset, link); + val->currentset = NULL; + resume = false; + } else { + rdataset = ISC_LIST_HEAD(name->list); + } + + for (; rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == dns_rdatatype_rrsig) { + continue; + } + + for (sigrdataset = ISC_LIST_HEAD(name->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (sigrdataset->type == dns_rdatatype_rrsig && + sigrdataset->covers == rdataset->type) + { + break; + } + } + + result = validate_neg_rrset(val, name, rdataset, + sigrdataset); + if (result != DNS_R_CONTINUE) { + return (result); + } + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + return (result); +} + +/*% + * Validate negative cache elements. + */ +static isc_result_t +validate_ncache(dns_validator_t *val, bool resume) { + dns_name_t *name; + isc_result_t result; + + if (!resume) { + result = dns_rdataset_first(val->event->rdataset); + } else { + result = dns_rdataset_next(val->event->rdataset); + } + + for (; result == ISC_R_SUCCESS; + result = dns_rdataset_next(val->event->rdataset)) + { + dns_rdataset_t *rdataset, *sigrdataset = NULL; + + disassociate_rdatasets(val); + + name = dns_fixedname_initname(&val->fname); + rdataset = &val->frdataset; + dns_ncache_current(val->event->rdataset, name, rdataset); + + if (val->frdataset.type == dns_rdatatype_rrsig) { + continue; + } + + result = dns_ncache_getsigrdataset(val->event->rdataset, name, + rdataset->type, + &val->fsigrdataset); + if (result == ISC_R_SUCCESS) { + sigrdataset = &val->fsigrdataset; + } + + result = validate_neg_rrset(val, name, rdataset, sigrdataset); + if (result == DNS_R_CONTINUE) { + continue; + } + + return (result); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + return (result); +} + +/*% + * Prove a negative answer is good or that there is a NOQNAME when the + * answer is from a wildcard. + * + * Loop through the authority section looking for NODATA, NOWILDCARD + * and NOQNAME proofs in the NSEC records by calling + * validator_callback_nsec(). + * + * If the required proofs are found we are done. + * + * If the proofs are not found attempt to prove this is an unsecure + * response. + */ +static isc_result_t +validate_nx(dns_validator_t *val, bool resume) { + isc_result_t result; + + if (resume) { + validator_log(val, ISC_LOG_DEBUG(3), "resuming validate_nx"); + } + + if (val->event->message == NULL) { + result = validate_ncache(val, resume); + } else { + result = validate_authority(val, resume); + } + + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Do we only need to check for NOQNAME? To get here we must have + * had a secure wildcard answer. + */ + if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) { + if (!FOUNDNOQNAME(val)) { + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), + "too many iterations"); + markanswer(val, "validate_nx (3)", NULL); + return (ISC_R_SUCCESS); + } + } + + if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val)) + { + validator_log(val, ISC_LOG_DEBUG(3), + "marking as secure, noqname proof found"); + marksecure(val->event); + return (ISC_R_SUCCESS); + } else if (FOUNDOPTOUT(val) && + dns_name_countlabels( + dns_fixedname_name(&val->wild)) != 0) + { + validator_log(val, ISC_LOG_DEBUG(3), + "optout proof found"); + val->event->optout = true; + markanswer(val, "validate_nx (1)", NULL); + return (ISC_R_SUCCESS); + } else if ((val->attributes & VALATTR_FOUNDUNKNOWN) != 0) { + validator_log(val, ISC_LOG_DEBUG(3), + "unknown NSEC3 hash algorithm found"); + markanswer(val, "validate_nx (2)", NULL); + return (ISC_R_SUCCESS); + } + + validator_log(val, ISC_LOG_DEBUG(3), "noqname proof not found"); + return (DNS_R_NOVALIDNSEC); + } + + if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) { + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), + "too many iterations"); + markanswer(val, "validate_nx (4)", NULL); + return (ISC_R_SUCCESS); + } + } + + /* + * Do we need to check for the wildcard? + */ + if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && + ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val))) + { + result = checkwildcard(val, dns_rdatatype_nsec, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if ((NEEDNODATA(val) && (FOUNDNODATA(val) || FOUNDOPTOUT(val))) || + (NEEDNOQNAME(val) && FOUNDNOQNAME(val) && NEEDNOWILDCARD(val) && + FOUNDNOWILDCARD(val) && FOUNDCLOSEST(val))) + { + if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) { + val->event->optout = true; + } + validator_log(val, ISC_LOG_DEBUG(3), + "nonexistence proof(s) found"); + if (val->event->message == NULL) { + marksecure(val->event); + } else { + val->event->secure = true; + } + return (ISC_R_SUCCESS); + } + + if (val->authfail != 0 && val->authcount == val->authfail) { + return (DNS_R_BROKENCHAIN); + } + + validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found"); + return (proveunsecure(val, false, false)); +} + +/*% + * Check that DS rdataset has at least one record with + * a supported algorithm and digest. + */ +static bool +check_ds_algs(dns_validator_t *val, dns_name_t *name, + dns_rdataset_t *rdataset) { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_ds_t ds; + isc_result_t result; + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (dns_resolver_ds_digest_supported(val->view->resolver, name, + ds.digest_type) && + dns_resolver_algorithm_supported(val->view->resolver, name, + ds.algorithm)) + { + dns_rdata_reset(&dsrdata); + return (true); + } + dns_rdata_reset(&dsrdata); + } + return (false); +} + +/*% + * seek_ds is called to look up DS rrsets at the label of val->event->name + * indicated by val->labels. This is done while building an insecurity + * proof, and so it will attempt validation of NXDOMAIN, NXRRSET or CNAME + * responses. + * + * Returns: + * \li ISC_R_COMPLETE a result has been determined and copied + * into `*resp`; ISC_R_SUCCESS indicates that + * the name has been proven insecure and any + * other result indicates failure. + * \li DNS_R_CONTINUE result is indeterminate; caller should + * continue walking down labels. + */ +static isc_result_t +seek_ds(dns_validator_t *val, isc_result_t *resp) { + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixedfound; + dns_name_t *found = dns_fixedname_initname(&fixedfound); + dns_name_t *tname = dns_fixedname_initname(&val->fname); + + if (val->labels == dns_name_countlabels(val->event->name)) { + dns_name_copy(val->event->name, tname); + } else { + dns_name_split(val->event->name, val->labels, NULL, tname); + } + + dns_name_format(tname, namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), "checking existence of DS at '%s'", + namebuf); + + result = view_find(val, tname, dns_rdatatype_ds); + switch (result) { + case ISC_R_SUCCESS: + /* + * There is a DS here. If it's already been + * validated, continue walking down labels. + */ + if (val->frdataset.trust >= dns_trust_secure) { + if (!check_ds_algs(val, tname, &val->frdataset)) { + validator_log(val, ISC_LOG_DEBUG(3), + "no supported algorithm/" + "digest (%s/DS)", + namebuf); + *resp = markanswer(val, "proveunsecure (5)", + "no supported " + "algorithm/digest (DS)"); + return (ISC_R_COMPLETE); + } + + break; + } + + /* + * Otherwise, try to validate it now. + */ + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + result = create_validator( + val, tname, dns_rdatatype_ds, &val->frdataset, + &val->fsigrdataset, validator_callback_ds, + "proveunsecure"); + *resp = DNS_R_WAIT; + if (result != ISC_R_SUCCESS) { + *resp = result; + } + } else { + /* + * There should never be an unsigned DS. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "unsigned DS record"); + *resp = DNS_R_NOVALIDSIG; + } + + return (ISC_R_COMPLETE); + + case ISC_R_NOTFOUND: + /* + * We don't know anything about the DS. Find it. + */ + *resp = DNS_R_WAIT; + result = create_fetch(val, tname, dns_rdatatype_ds, + fetch_callback_ds, "proveunsecure"); + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + /* + * There is no DS. If this is a delegation, + * we may be done. + * + * If we have "trust == answer" then this namespace + * has switched from insecure to should be secure. + */ + if (DNS_TRUST_PENDING(val->frdataset.trust) || + DNS_TRUST_ANSWER(val->frdataset.trust)) + { + result = create_validator( + val, tname, dns_rdatatype_ds, &val->frdataset, + &val->fsigrdataset, validator_callback_ds, + "proveunsecure"); + *resp = DNS_R_WAIT; + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + } + + /* + * Zones using NSEC3 don't return a NSEC RRset so + * we need to use dns_view_findzonecut2 to find + * the zone cut. + */ + if (result == DNS_R_NXRRSET && + !dns_rdataset_isassociated(&val->frdataset) && + dns_view_findzonecut(val->view, tname, found, NULL, 0, 0, + false, false, NULL, + NULL) == ISC_R_SUCCESS && + dns_name_equal(tname, found)) + { + *resp = markanswer(val, "proveunsecure (3)", + "no DS at zone cut"); + return (ISC_R_COMPLETE); + } + + if (val->frdataset.trust < dns_trust_secure) { + /* + * This shouldn't happen, since the negative + * response should have been validated. Since + * there's no way of validating existing + * negative response blobs, give up. + */ + validator_log(val, ISC_LOG_WARNING, + "can't validate existing " + "negative responses (no DS)"); + *resp = DNS_R_MUSTBESECURE; + return (ISC_R_COMPLETE); + } + + if (isdelegation(tname, &val->frdataset, result)) { + *resp = markanswer(val, "proveunsecure (4)", + "this is a delegation"); + return (ISC_R_COMPLETE); + } + + break; + + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: + /* + * This is not a zone cut. Assuming things are + * as expected, continue. + */ + if (!dns_rdataset_isassociated(&val->frdataset)) { + /* + * There should be an NSEC here, since we + * are still in a secure zone. + */ + *resp = DNS_R_NOVALIDNSEC; + return (ISC_R_COMPLETE); + } else if (DNS_TRUST_PENDING(val->frdataset.trust) || + DNS_TRUST_ANSWER(val->frdataset.trust)) + { + /* + * If we have "trust == answer" then this + * namespace has switched from insecure to + * should be secure. + */ + *resp = DNS_R_WAIT; + result = create_validator( + val, tname, dns_rdatatype_ds, &val->frdataset, + &val->fsigrdataset, validator_callback_ds, + "proveunsecure"); + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + } else if (val->frdataset.trust < dns_trust_secure) { + /* + * This shouldn't happen, since the negative + * response should have been validated. Since + * there's no way of validating existing + * negative response blobs, give up. + */ + validator_log(val, ISC_LOG_WARNING, + "can't validate existing " + "negative responses " + "(not a zone cut)"); + *resp = DNS_R_NOVALIDSIG; + return (ISC_R_COMPLETE); + } + + break; + + case DNS_R_CNAME: + if (DNS_TRUST_PENDING(val->frdataset.trust) || + DNS_TRUST_ANSWER(val->frdataset.trust)) + { + result = create_validator( + val, tname, dns_rdatatype_cname, + &val->frdataset, &val->fsigrdataset, + validator_callback_cname, + "proveunsecure " + "(cname)"); + *resp = DNS_R_WAIT; + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return (ISC_R_COMPLETE); + } + + break; + + default: + *resp = result; + return (ISC_R_COMPLETE); + } + + /* + * No definite answer yet; continue walking down labels. + */ + return (DNS_R_CONTINUE); +} + +/*% + * proveunsecure walks down, label by label, from the closest enclosing + * trust anchor to the name that is being validated, looking for an + * endpoint in the chain of trust. That occurs when we can prove that + * a DS record does not exist at a delegation point, or that a DS exists + * at a delegation point but we don't support its algorithm/digest. If + * no such endpoint is found, then the response should have been secure. + * + * Returns: + * \li ISC_R_SUCCESS val->event->name is in an unsecure zone + * \li DNS_R_WAIT validation is in progress. + * \li DNS_R_MUSTBESECURE val->event->name is supposed to be secure + * (policy) but we proved that it is unsecure. + * \li DNS_R_NOVALIDSIG + * \li DNS_R_NOVALIDNSEC + * \li DNS_R_NOTINSECURE + * \li DNS_R_BROKENCHAIN + */ +static isc_result_t +proveunsecure(dns_validator_t *val, bool have_ds, bool resume) { + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixedsecroot; + dns_name_t *secroot = dns_fixedname_initname(&fixedsecroot); + unsigned int labels; + + /* + * We're attempting to prove insecurity. + */ + val->attributes |= VALATTR_INSECURITY; + + dns_name_copy(val->event->name, secroot); + + /* + * If this is a response to a DS query, we need to look in + * the parent zone for the trust anchor. + */ + labels = dns_name_countlabels(secroot); + if (val->event->type == dns_rdatatype_ds && labels > 1U) { + dns_name_getlabelsequence(secroot, 1, labels - 1, secroot); + } + + result = dns_keytable_finddeepestmatch(val->keytable, secroot, secroot); + if (result == ISC_R_NOTFOUND) { + validator_log(val, ISC_LOG_DEBUG(3), "not beneath secure root"); + return (markanswer(val, "proveunsecure (1)", + "not beneath secure root")); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + if (!resume) { + /* + * We are looking for interruptions in the chain of trust. + * That can only happen *below* the trust anchor, so we + * start looking at the next label down. + */ + val->labels = dns_name_countlabels(secroot) + 1; + } else { + validator_log(val, ISC_LOG_DEBUG(3), "resuming proveunsecure"); + + /* + * If we have a DS rdataset and it is secure, check whether + * it has a supported algorithm combination. If not, this is + * an insecure delegation as far as this resolver is concerned. + */ + if (have_ds && val->frdataset.trust >= dns_trust_secure && + !check_ds_algs(val, dns_fixedname_name(&val->fname), + &val->frdataset)) + { + dns_name_format(dns_fixedname_name(&val->fname), + namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_DEBUG(3), + "no supported algorithm/digest (%s/DS)", + namebuf); + result = markanswer(val, "proveunsecure (2)", namebuf); + goto out; + } + val->labels++; + } + + /* + * Walk down through each of the remaining labels in the name, + * looking for DS records. + */ + while (val->labels <= dns_name_countlabels(val->event->name)) { + isc_result_t tresult; + + result = seek_ds(val, &tresult); + if (result == ISC_R_COMPLETE) { + result = tresult; + goto out; + } + + INSIST(result == DNS_R_CONTINUE); + val->labels++; + } + + /* Couldn't complete insecurity proof. */ + validator_log(val, ISC_LOG_DEBUG(3), "insecurity proof failed: %s", + isc_result_totext(result)); + return (DNS_R_NOTINSECURE); + +out: + if (result != DNS_R_WAIT) { + disassociate_rdatasets(val); + } + return (result); +} + +/*% + * Start the validation process. + * + * Attempt to validate the answer based on the category it appears to + * fall in. + * \li 1. secure positive answer. + * \li 2. unsecure positive answer. + * \li 3. a negative answer (secure or unsecure). + * + * Note an answer that appears to be a secure positive answer may actually + * be an unsecure positive answer. + */ +static void +validator_start(isc_task_t *task, isc_event_t *event) { + dns_validator_t *val; + dns_validatorevent_t *vevent; + bool want_destroy = false; + isc_result_t result = ISC_R_FAILURE; + + UNUSED(task); + REQUIRE(event->ev_type == DNS_EVENT_VALIDATORSTART); + vevent = (dns_validatorevent_t *)event; + val = vevent->validator; + + /* If the validator has been canceled, val->event == NULL */ + if (val->event == NULL) { + return; + } + + validator_log(val, ISC_LOG_DEBUG(3), "starting"); + + LOCK(&val->lock); + + if (val->event->rdataset != NULL && val->event->sigrdataset != NULL) { + isc_result_t saved_result; + + /* + * This looks like a simple validation. We say "looks like" + * because it might end up requiring an insecurity proof. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "attempting positive response validation"); + + INSIST(dns_rdataset_isassociated(val->event->rdataset)); + INSIST(dns_rdataset_isassociated(val->event->sigrdataset)); + if (selfsigned_dnskey(val)) { + result = validate_dnskey(val); + } else { + result = validate_answer(val, false); + } + if (result == DNS_R_NOVALIDSIG && + (val->attributes & VALATTR_TRIEDVERIFY) == 0) + { + saved_result = result; + validator_log(val, ISC_LOG_DEBUG(3), + "falling back to insecurity proof"); + result = proveunsecure(val, false, false); + if (result == DNS_R_NOTINSECURE) { + result = saved_result; + } + } + } else if (val->event->rdataset != NULL && + val->event->rdataset->type != 0) + { + /* + * This is either an unsecure subdomain or a response + * from a broken server. + */ + INSIST(dns_rdataset_isassociated(val->event->rdataset)); + validator_log(val, ISC_LOG_DEBUG(3), + "attempting insecurity proof"); + + result = proveunsecure(val, false, false); + if (result == DNS_R_NOTINSECURE) { + validator_log(val, ISC_LOG_INFO, + "got insecure response; " + "parent indicates it should be secure"); + } + } else if ((val->event->rdataset == NULL && + val->event->sigrdataset == NULL)) + { + /* + * This is a validation of a negative response. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "attempting negative response validation " + "from message"); + + if (val->event->message->rcode == dns_rcode_nxdomain) { + val->attributes |= VALATTR_NEEDNOQNAME; + val->attributes |= VALATTR_NEEDNOWILDCARD; + } else { + val->attributes |= VALATTR_NEEDNODATA; + } + + result = validate_nx(val, false); + } else if ((val->event->rdataset != NULL && + NEGATIVE(val->event->rdataset))) + { + /* + * This is a delayed validation of a negative cache entry. + */ + validator_log(val, ISC_LOG_DEBUG(3), + "attempting negative response validation " + "from cache"); + + if (NXDOMAIN(val->event->rdataset)) { + val->attributes |= VALATTR_NEEDNOQNAME; + val->attributes |= VALATTR_NEEDNOWILDCARD; + } else { + val->attributes |= VALATTR_NEEDNODATA; + } + + result = validate_nx(val, false); + } else { + UNREACHABLE(); + } + + if (result != DNS_R_WAIT) { + want_destroy = exit_check(val); + validator_done(val, result); + } + + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } +} + +isc_result_t +dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, + dns_message_t *message, unsigned int options, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_validator_t **validatorp) { + isc_result_t result = ISC_R_FAILURE; + dns_validator_t *val; + isc_task_t *tclone = NULL; + dns_validatorevent_t *event; + + REQUIRE(name != NULL); + REQUIRE(rdataset != NULL || + (rdataset == NULL && sigrdataset == NULL && message != NULL)); + REQUIRE(validatorp != NULL && *validatorp == NULL); + + event = (dns_validatorevent_t *)isc_event_allocate( + view->mctx, task, DNS_EVENT_VALIDATORSTART, validator_start, + NULL, sizeof(dns_validatorevent_t)); + + isc_task_attach(task, &tclone); + event->result = ISC_R_FAILURE; + event->name = name; + event->type = type; + event->rdataset = rdataset; + event->sigrdataset = sigrdataset; + event->message = message; + memset(event->proofs, 0, sizeof(event->proofs)); + event->optout = false; + event->secure = false; + + val = isc_mem_get(view->mctx, sizeof(*val)); + *val = (dns_validator_t){ .event = event, + .options = options, + .task = task, + .action = action, + .arg = arg }; + + dns_view_weakattach(view, &val->view); + isc_mutex_init(&val->lock); + + result = dns_view_getsecroots(val->view, &val->keytable); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + val->mustbesecure = dns_resolver_getmustbesecure(view->resolver, name); + dns_rdataset_init(&val->fdsset); + dns_rdataset_init(&val->frdataset); + dns_rdataset_init(&val->fsigrdataset); + dns_fixedname_init(&val->wild); + dns_fixedname_init(&val->closest); + isc_stdtime_get(&val->start); + ISC_LINK_INIT(val, link); + val->magic = VALIDATOR_MAGIC; + + event->validator = val; + + if ((options & DNS_VALIDATOR_DEFER) == 0) { + isc_task_send(task, ISC_EVENT_PTR(&event)); + } + + *validatorp = val; + + return (ISC_R_SUCCESS); + +cleanup: + isc_mutex_destroy(&val->lock); + + isc_task_detach(&tclone); + isc_event_free(ISC_EVENT_PTR(&event)); + + dns_view_weakdetach(&val->view); + isc_mem_put(view->mctx, val, sizeof(*val)); + + return (result); +} + +void +dns_validator_send(dns_validator_t *validator) { + isc_event_t *event; + REQUIRE(VALID_VALIDATOR(validator)); + + LOCK(&validator->lock); + + INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0); + event = (isc_event_t *)validator->event; + validator->options &= ~DNS_VALIDATOR_DEFER; + UNLOCK(&validator->lock); + + isc_task_send(validator->task, ISC_EVENT_PTR(&event)); +} + +void +dns_validator_cancel(dns_validator_t *validator) { + dns_fetch_t *fetch = NULL; + + REQUIRE(VALID_VALIDATOR(validator)); + + LOCK(&validator->lock); + + validator_log(validator, ISC_LOG_DEBUG(3), "dns_validator_cancel"); + + if ((validator->attributes & VALATTR_CANCELED) == 0) { + validator->attributes |= VALATTR_CANCELED; + if (validator->event != NULL) { + fetch = validator->fetch; + validator->fetch = NULL; + + if (validator->subvalidator != NULL) { + dns_validator_cancel(validator->subvalidator); + } + if ((validator->options & DNS_VALIDATOR_DEFER) != 0) { + validator->options &= ~DNS_VALIDATOR_DEFER; + validator_done(validator, ISC_R_CANCELED); + } + } + } + UNLOCK(&validator->lock); + + /* Need to cancel and destroy the fetch outside validator lock */ + if (fetch != NULL) { + dns_resolver_cancelfetch(fetch); + dns_resolver_destroyfetch(&fetch); + } +} + +static void +destroy(dns_validator_t *val) { + isc_mem_t *mctx; + + REQUIRE(SHUTDOWN(val)); + REQUIRE(val->event == NULL); + REQUIRE(val->fetch == NULL); + + val->magic = 0; + if (val->key != NULL) { + dst_key_free(&val->key); + } + if (val->keytable != NULL) { + dns_keytable_detach(&val->keytable); + } + if (val->subvalidator != NULL) { + dns_validator_destroy(&val->subvalidator); + } + disassociate_rdatasets(val); + mctx = val->view->mctx; + if (val->siginfo != NULL) { + isc_mem_put(mctx, val->siginfo, sizeof(*val->siginfo)); + } + isc_mutex_destroy(&val->lock); + dns_view_weakdetach(&val->view); + isc_mem_put(mctx, val, sizeof(*val)); +} + +void +dns_validator_destroy(dns_validator_t **validatorp) { + dns_validator_t *val; + bool want_destroy = false; + + REQUIRE(validatorp != NULL); + val = *validatorp; + *validatorp = NULL; + REQUIRE(VALID_VALIDATOR(val)); + + LOCK(&val->lock); + + val->attributes |= VALATTR_SHUTDOWN; + validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_destroy"); + + want_destroy = exit_check(val); + UNLOCK(&val->lock); + if (want_destroy) { + destroy(val); + } +} + +static void +validator_logv(dns_validator_t *val, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, + va_list ap) { + char msgbuf[2048]; + static const char spaces[] = " *"; + int depth = val->depth * 2; + const char *viewname, *sep1, *sep2; + + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + + if ((unsigned int)depth >= sizeof spaces) { + depth = sizeof spaces - 1; + } + + /* + * Log the view name unless it's: + * * "_default/IN" (which means there's only one view + * configured in the server), or + * * "_dnsclient/IN" (which means this is being called + * from an application using dns/client.c). + */ + if (val->view->rdclass == dns_rdataclass_in && + (strcmp(val->view->name, "_default") == 0 || + strcmp(val->view->name, DNS_CLIENTVIEW_NAME) == 0)) + { + sep1 = viewname = sep2 = ""; + } else { + sep1 = "view "; + viewname = val->view->name; + sep2 = ": "; + } + + if (val->event != NULL && val->event->name != NULL) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(val->event->name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(val->event->type, typebuf, + sizeof(typebuf)); + isc_log_write(dns_lctx, category, module, level, + "%s%s%s%.*svalidating %s/%s: %s", sep1, viewname, + sep2, depth, spaces, namebuf, typebuf, msgbuf); + } else { + isc_log_write(dns_lctx, category, module, level, + "%s%s%s%.*svalidator @%p: %s", sep1, viewname, + sep2, depth, spaces, val, msgbuf); + } +} + +static void +validator_log(void *val, int level, const char *fmt, ...) { + va_list ap; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + + validator_logv(val, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_VALIDATOR, + level, fmt, ap); + va_end(ap); +} + +static void +validator_logcreate(dns_validator_t *val, dns_name_t *name, + dns_rdatatype_t type, const char *caller, + const char *operation) { + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_format(type, typestr, sizeof(typestr)); + validator_log(val, ISC_LOG_DEBUG(9), "%s: creating %s for %s %s", + caller, operation, namestr, typestr); +} diff --git a/lib/dns/view.c b/lib/dns/view.c new file mode 100644 index 0000000..49c9aee --- /dev/null +++ b/lib/dns/view.c @@ -0,0 +1,2761 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#ifdef HAVE_LMDB +#include +#endif /* ifdef HAVE_LMDB */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +#define RESSHUTDOWN(v) \ + ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_RESSHUTDOWN) != 0) +#define ADBSHUTDOWN(v) \ + ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_ADBSHUTDOWN) != 0) +#define REQSHUTDOWN(v) \ + ((atomic_load(&(v)->attributes) & DNS_VIEWATTR_REQSHUTDOWN) != 0) + +#define DNS_VIEW_DELONLYHASH 111 +#define DNS_VIEW_FAILCACHESIZE 1021 + +static void +resolver_shutdown(isc_task_t *task, isc_event_t *event); +static void +adb_shutdown(isc_task_t *task, isc_event_t *event); +static void +req_shutdown(isc_task_t *task, isc_event_t *event); + +isc_result_t +dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name, + dns_view_t **viewp) { + dns_view_t *view; + isc_result_t result; + char buffer[1024]; + + /* + * Create a view. + */ + + REQUIRE(name != NULL); + REQUIRE(viewp != NULL && *viewp == NULL); + + view = isc_mem_get(mctx, sizeof(*view)); + + view->nta_file = NULL; + view->mctx = NULL; + isc_mem_attach(mctx, &view->mctx); + view->name = isc_mem_strdup(mctx, name); + + result = isc_file_sanitize(NULL, view->name, "nta", buffer, + sizeof(buffer)); + if (result != ISC_R_SUCCESS) { + goto cleanup_name; + } + view->nta_file = isc_mem_strdup(mctx, buffer); + + isc_mutex_init(&view->lock); + + isc_rwlock_init(&view->sfd_lock, 0, 0); + + view->zonetable = NULL; + result = dns_zt_create(mctx, rdclass, &view->zonetable); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("dns_zt_create() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup_mutex; + } + + view->secroots_priv = NULL; + view->ntatable_priv = NULL; + view->fwdtable = NULL; + result = dns_fwdtable_create(mctx, &view->fwdtable); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("dns_fwdtable_create() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup_zt; + } + + view->cache = NULL; + view->cachedb = NULL; + ISC_LIST_INIT(view->dlz_searched); + ISC_LIST_INIT(view->dlz_unsearched); + view->hints = NULL; + view->resolver = NULL; + view->adb = NULL; + view->requestmgr = NULL; + view->rdclass = rdclass; + view->frozen = false; + view->task = NULL; + isc_refcount_init(&view->references, 1); + isc_refcount_init(&view->weakrefs, 1); + atomic_init(&view->attributes, + (DNS_VIEWATTR_RESSHUTDOWN | DNS_VIEWATTR_ADBSHUTDOWN | + DNS_VIEWATTR_REQSHUTDOWN)); + view->transports = NULL; + view->statickeys = NULL; + view->dynamickeys = NULL; + view->matchclients = NULL; + view->matchdestinations = NULL; + view->matchrecursiveonly = false; + result = dns_tsigkeyring_create(view->mctx, &view->dynamickeys); + if (result != ISC_R_SUCCESS) { + goto cleanup_weakrefs; + } + view->peers = NULL; + view->order = NULL; + view->delonly = NULL; + view->rootdelonly = false; + view->rootexclude = NULL; + view->adbstats = NULL; + view->resstats = NULL; + view->resquerystats = NULL; + view->cacheshared = false; + ISC_LIST_INIT(view->dns64); + view->dns64cnt = 0; + + /* + * Initialize configuration data with default values. + */ + view->recursion = true; + view->qminimization = false; + view->qmin_strict = false; + view->auth_nxdomain = false; /* Was true in BIND 8 */ + view->enablevalidation = true; + view->acceptexpired = false; + view->use_glue_cache = false; + view->minimal_any = false; + view->minimalresponses = dns_minimal_no; + view->transfer_format = dns_one_answer; + view->cacheacl = NULL; + view->cacheonacl = NULL; + view->checknames = false; + view->queryacl = NULL; + view->queryonacl = NULL; + view->recursionacl = NULL; + view->recursiononacl = NULL; + view->sortlist = NULL; + view->transferacl = NULL; + view->notifyacl = NULL; + view->updateacl = NULL; + view->upfwdacl = NULL; + view->denyansweracl = NULL; + view->nocasecompress = NULL; + view->msgcompression = true; + view->answeracl_exclude = NULL; + view->denyanswernames = NULL; + view->answernames_exclude = NULL; + view->rrl = NULL; + view->sfd = NULL; + view->provideixfr = true; + view->maxcachettl = 7 * 24 * 3600; + view->maxncachettl = 3 * 3600; + view->mincachettl = 0; + view->minncachettl = 0; + view->nta_lifetime = 0; + view->nta_recheck = 0; + view->prefetch_eligible = 0; + view->prefetch_trigger = 0; + view->dstport = 53; + view->preferred_glue = 0; + view->flush = false; + view->maxudp = 0; + view->staleanswerttl = 1; + view->staleanswersok = dns_stale_answer_conf; + view->staleanswersenable = false; + view->nocookieudp = 0; + view->padding = 0; + view->pad_acl = NULL; + view->maxbits = 0; + view->rpzs = NULL; + view->catzs = NULL; + view->managed_keys = NULL; + view->redirect = NULL; + view->redirectzone = NULL; + dns_fixedname_init(&view->redirectfixed); + view->requestnsid = false; + view->sendcookie = true; + view->requireservercookie = false; + view->synthfromdnssec = true; + view->trust_anchor_telemetry = true; + view->root_key_sentinel = true; + view->new_zone_dir = NULL; + view->new_zone_file = NULL; + view->new_zone_db = NULL; + view->new_zone_dbenv = NULL; + view->new_zone_mapsize = 0ULL; + view->new_zone_config = NULL; + view->cfg_destroy = NULL; + view->fail_ttl = 0; + view->failcache = NULL; + result = dns_badcache_init(view->mctx, DNS_VIEW_FAILCACHESIZE, + &view->failcache); + if (result != ISC_R_SUCCESS) { + goto cleanup_dynkeys; + } + view->v6bias = 0; + view->dtenv = NULL; + view->dttypes = 0; + + view->plugins = NULL; + view->plugins_free = NULL; + view->hooktable = NULL; + view->hooktable_free = NULL; + + isc_mutex_init(&view->new_zone_lock); + + result = dns_order_create(view->mctx, &view->order); + if (result != ISC_R_SUCCESS) { + goto cleanup_new_zone_lock; + } + + result = dns_peerlist_new(view->mctx, &view->peers); + if (result != ISC_R_SUCCESS) { + goto cleanup_order; + } + + result = dns_aclenv_create(view->mctx, &view->aclenv); + if (result != ISC_R_SUCCESS) { + goto cleanup_peerlist; + } + + ISC_LINK_INIT(view, link); + ISC_EVENT_INIT(&view->resevent, sizeof(view->resevent), 0, NULL, + DNS_EVENT_VIEWRESSHUTDOWN, resolver_shutdown, view, NULL, + NULL, NULL); + ISC_EVENT_INIT(&view->adbevent, sizeof(view->adbevent), 0, NULL, + DNS_EVENT_VIEWADBSHUTDOWN, adb_shutdown, view, NULL, + NULL, NULL); + ISC_EVENT_INIT(&view->reqevent, sizeof(view->reqevent), 0, NULL, + DNS_EVENT_VIEWREQSHUTDOWN, req_shutdown, view, NULL, + NULL, NULL); + view->viewlist = NULL; + view->magic = DNS_VIEW_MAGIC; + + *viewp = view; + + return (ISC_R_SUCCESS); + +cleanup_peerlist: + if (view->peers != NULL) { + dns_peerlist_detach(&view->peers); + } + +cleanup_order: + if (view->order != NULL) { + dns_order_detach(&view->order); + } + +cleanup_new_zone_lock: + isc_mutex_destroy(&view->new_zone_lock); + + dns_badcache_destroy(&view->failcache); + +cleanup_dynkeys: + if (view->dynamickeys != NULL) { + dns_tsigkeyring_detach(&view->dynamickeys); + } + +cleanup_weakrefs: + isc_refcount_decrementz(&view->weakrefs); + isc_refcount_destroy(&view->weakrefs); + + isc_refcount_decrementz(&view->references); + isc_refcount_destroy(&view->references); + + if (view->fwdtable != NULL) { + dns_fwdtable_destroy(&view->fwdtable); + } + +cleanup_zt: + if (view->zonetable != NULL) { + dns_zt_detach(&view->zonetable); + } + +cleanup_mutex: + isc_rwlock_destroy(&view->sfd_lock); + isc_mutex_destroy(&view->lock); + + if (view->nta_file != NULL) { + isc_mem_free(mctx, view->nta_file); + } + +cleanup_name: + isc_mem_free(mctx, view->name); + isc_mem_putanddetach(&view->mctx, view, sizeof(*view)); + + return (result); +} + +static void +destroy(dns_view_t *view) { + dns_dns64_t *dns64; + dns_dlzdb_t *dlzdb; + + REQUIRE(!ISC_LINK_LINKED(view, link)); + REQUIRE(RESSHUTDOWN(view)); + REQUIRE(ADBSHUTDOWN(view)); + REQUIRE(REQSHUTDOWN(view)); + + isc_refcount_destroy(&view->references); + isc_refcount_destroy(&view->weakrefs); + + if (view->order != NULL) { + dns_order_detach(&view->order); + } + if (view->peers != NULL) { + dns_peerlist_detach(&view->peers); + } + + if (view->dynamickeys != NULL) { + isc_result_t result; + char template[PATH_MAX]; + char keyfile[PATH_MAX]; + FILE *fp = NULL; + + result = isc_file_mktemplate(NULL, template, sizeof(template)); + if (result == ISC_R_SUCCESS) { + (void)isc_file_openuniqueprivate(template, &fp); + } + if (fp == NULL) { + dns_tsigkeyring_detach(&view->dynamickeys); + } else { + result = dns_tsigkeyring_dumpanddetach( + &view->dynamickeys, fp); + if (result == ISC_R_SUCCESS) { + if (fclose(fp) == 0) { + result = isc_file_sanitize( + NULL, view->name, "tsigkeys", + keyfile, sizeof(keyfile)); + if (result == ISC_R_SUCCESS) { + result = isc_file_rename( + template, keyfile); + } + } + if (result != ISC_R_SUCCESS) { + (void)remove(template); + } + } else { + (void)fclose(fp); + (void)remove(template); + } + } + } + if (view->transports != NULL) { + dns_transport_list_detach(&view->transports); + } + if (view->statickeys != NULL) { + dns_tsigkeyring_detach(&view->statickeys); + } + if (view->adb != NULL) { + dns_adb_detach(&view->adb); + } + if (view->resolver != NULL) { + dns_resolver_detach(&view->resolver); + } + dns_rrl_view_destroy(view); + if (view->rpzs != NULL) { + dns_rpz_shutdown_rpzs(view->rpzs); + dns_rpz_detach_rpzs(&view->rpzs); + } + if (view->catzs != NULL) { + dns_catz_shutdown_catzs(view->catzs); + dns_catz_detach_catzs(&view->catzs); + } + for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL; + dlzdb = ISC_LIST_HEAD(view->dlz_searched)) + { + ISC_LIST_UNLINK(view->dlz_searched, dlzdb, link); + dns_dlzdestroy(&dlzdb); + } + for (dlzdb = ISC_LIST_HEAD(view->dlz_unsearched); dlzdb != NULL; + dlzdb = ISC_LIST_HEAD(view->dlz_unsearched)) + { + ISC_LIST_UNLINK(view->dlz_unsearched, dlzdb, link); + dns_dlzdestroy(&dlzdb); + } + if (view->requestmgr != NULL) { + dns_requestmgr_detach(&view->requestmgr); + } + if (view->task != NULL) { + isc_task_detach(&view->task); + } + if (view->hints != NULL) { + dns_db_detach(&view->hints); + } + if (view->cachedb != NULL) { + dns_db_detach(&view->cachedb); + } + if (view->cache != NULL) { + dns_cache_detach(&view->cache); + } + if (view->nocasecompress != NULL) { + dns_acl_detach(&view->nocasecompress); + } + if (view->matchclients != NULL) { + dns_acl_detach(&view->matchclients); + } + if (view->matchdestinations != NULL) { + dns_acl_detach(&view->matchdestinations); + } + if (view->cacheacl != NULL) { + dns_acl_detach(&view->cacheacl); + } + if (view->cacheonacl != NULL) { + dns_acl_detach(&view->cacheonacl); + } + if (view->queryacl != NULL) { + dns_acl_detach(&view->queryacl); + } + if (view->queryonacl != NULL) { + dns_acl_detach(&view->queryonacl); + } + if (view->recursionacl != NULL) { + dns_acl_detach(&view->recursionacl); + } + if (view->recursiononacl != NULL) { + dns_acl_detach(&view->recursiononacl); + } + if (view->sortlist != NULL) { + dns_acl_detach(&view->sortlist); + } + if (view->transferacl != NULL) { + dns_acl_detach(&view->transferacl); + } + if (view->notifyacl != NULL) { + dns_acl_detach(&view->notifyacl); + } + if (view->updateacl != NULL) { + dns_acl_detach(&view->updateacl); + } + if (view->upfwdacl != NULL) { + dns_acl_detach(&view->upfwdacl); + } + if (view->denyansweracl != NULL) { + dns_acl_detach(&view->denyansweracl); + } + if (view->pad_acl != NULL) { + dns_acl_detach(&view->pad_acl); + } + if (view->answeracl_exclude != NULL) { + dns_rbt_destroy(&view->answeracl_exclude); + } + if (view->denyanswernames != NULL) { + dns_rbt_destroy(&view->denyanswernames); + } + if (view->answernames_exclude != NULL) { + dns_rbt_destroy(&view->answernames_exclude); + } + if (view->sfd != NULL) { + dns_rbt_destroy(&view->sfd); + } + if (view->delonly != NULL) { + dns_name_t *name; + int i; + + for (i = 0; i < DNS_VIEW_DELONLYHASH; i++) { + name = ISC_LIST_HEAD(view->delonly[i]); + while (name != NULL) { + ISC_LIST_UNLINK(view->delonly[i], name, link); + dns_name_free(name, view->mctx); + isc_mem_put(view->mctx, name, sizeof(*name)); + name = ISC_LIST_HEAD(view->delonly[i]); + } + } + isc_mem_put(view->mctx, view->delonly, + sizeof(dns_namelist_t) * DNS_VIEW_DELONLYHASH); + view->delonly = NULL; + } + if (view->rootexclude != NULL) { + dns_name_t *name; + int i; + + for (i = 0; i < DNS_VIEW_DELONLYHASH; i++) { + name = ISC_LIST_HEAD(view->rootexclude[i]); + while (name != NULL) { + ISC_LIST_UNLINK(view->rootexclude[i], name, + link); + dns_name_free(name, view->mctx); + isc_mem_put(view->mctx, name, sizeof(*name)); + name = ISC_LIST_HEAD(view->rootexclude[i]); + } + } + isc_mem_put(view->mctx, view->rootexclude, + sizeof(dns_namelist_t) * DNS_VIEW_DELONLYHASH); + view->rootexclude = NULL; + } + if (view->adbstats != NULL) { + isc_stats_detach(&view->adbstats); + } + if (view->resstats != NULL) { + isc_stats_detach(&view->resstats); + } + if (view->resquerystats != NULL) { + dns_stats_detach(&view->resquerystats); + } + if (view->secroots_priv != NULL) { + dns_keytable_detach(&view->secroots_priv); + } + if (view->ntatable_priv != NULL) { + dns_ntatable_detach(&view->ntatable_priv); + } + for (dns64 = ISC_LIST_HEAD(view->dns64); dns64 != NULL; + dns64 = ISC_LIST_HEAD(view->dns64)) + { + dns_dns64_unlink(&view->dns64, dns64); + dns_dns64_destroy(&dns64); + } + if (view->managed_keys != NULL) { + dns_zone_detach(&view->managed_keys); + } + if (view->redirect != NULL) { + dns_zone_detach(&view->redirect); + } +#ifdef HAVE_DNSTAP + if (view->dtenv != NULL) { + dns_dt_detach(&view->dtenv); + } +#endif /* HAVE_DNSTAP */ + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + if (view->new_zone_file != NULL) { + isc_mem_free(view->mctx, view->new_zone_file); + view->new_zone_file = NULL; + } + if (view->new_zone_dir != NULL) { + isc_mem_free(view->mctx, view->new_zone_dir); + view->new_zone_dir = NULL; + } +#ifdef HAVE_LMDB + if (view->new_zone_dbenv != NULL) { + mdb_env_close((MDB_env *)view->new_zone_dbenv); + view->new_zone_dbenv = NULL; + } + if (view->new_zone_db != NULL) { + isc_mem_free(view->mctx, view->new_zone_db); + view->new_zone_db = NULL; + } +#endif /* HAVE_LMDB */ + dns_fwdtable_destroy(&view->fwdtable); + dns_aclenv_detach(&view->aclenv); + if (view->failcache != NULL) { + dns_badcache_destroy(&view->failcache); + } + isc_mutex_destroy(&view->new_zone_lock); + isc_rwlock_destroy(&view->sfd_lock); + isc_mutex_destroy(&view->lock); + isc_refcount_destroy(&view->references); + isc_refcount_destroy(&view->weakrefs); + isc_mem_free(view->mctx, view->nta_file); + isc_mem_free(view->mctx, view->name); + if (view->hooktable != NULL && view->hooktable_free != NULL) { + view->hooktable_free(view->mctx, &view->hooktable); + } + if (view->plugins != NULL && view->plugins_free != NULL) { + view->plugins_free(view->mctx, &view->plugins); + } + isc_mem_putanddetach(&view->mctx, view, sizeof(*view)); +} + +void +dns_view_attach(dns_view_t *source, dns_view_t **targetp) { + REQUIRE(DNS_VIEW_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +view_flushanddetach(dns_view_t **viewp, bool flush) { + REQUIRE(viewp != NULL && DNS_VIEW_VALID(*viewp)); + dns_view_t *view = *viewp; + *viewp = NULL; + + if (flush) { + view->flush = flush; + } + + if (isc_refcount_decrement(&view->references) == 1) { + dns_zone_t *mkzone = NULL, *rdzone = NULL; + dns_zt_t *zt = NULL; + + isc_refcount_destroy(&view->references); + + if (!RESSHUTDOWN(view)) { + dns_resolver_shutdown(view->resolver); + } + if (!ADBSHUTDOWN(view)) { + dns_adb_shutdown(view->adb); + } + if (!REQSHUTDOWN(view)) { + dns_requestmgr_shutdown(view->requestmgr); + } + + LOCK(&view->lock); + + if (view->zonetable != NULL) { + zt = view->zonetable; + view->zonetable = NULL; + if (view->flush) { + dns_zt_flush(zt); + } + } + + if (view->managed_keys != NULL) { + mkzone = view->managed_keys; + view->managed_keys = NULL; + if (view->flush) { + dns_zone_flush(mkzone); + } + } + if (view->redirect != NULL) { + rdzone = view->redirect; + view->redirect = NULL; + if (view->flush) { + dns_zone_flush(rdzone); + } + } + if (view->catzs != NULL) { + dns_catz_shutdown_catzs(view->catzs); + dns_catz_detach_catzs(&view->catzs); + } + if (view->ntatable_priv != NULL) { + dns_ntatable_shutdown(view->ntatable_priv); + } + UNLOCK(&view->lock); + + /* Need to detach zt and zones outside view lock */ + if (zt != NULL) { + dns_zt_detach(&zt); + } + + if (mkzone != NULL) { + dns_zone_detach(&mkzone); + } + + if (rdzone != NULL) { + dns_zone_detach(&rdzone); + } + + dns_view_weakdetach(&view); + } +} + +void +dns_view_flushanddetach(dns_view_t **viewp) { + view_flushanddetach(viewp, true); +} + +void +dns_view_detach(dns_view_t **viewp) { + view_flushanddetach(viewp, false); +} + +static isc_result_t +dialup(dns_zone_t *zone, void *dummy) { + UNUSED(dummy); + dns_zone_dialup(zone); + return (ISC_R_SUCCESS); +} + +void +dns_view_dialup(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); + + (void)dns_zt_apply(view->zonetable, isc_rwlocktype_read, false, NULL, + dialup, NULL); +} + +void +dns_view_weakattach(dns_view_t *source, dns_view_t **targetp) { + REQUIRE(DNS_VIEW_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->weakrefs); + + *targetp = source; +} + +void +dns_view_weakdetach(dns_view_t **viewp) { + dns_view_t *view; + + REQUIRE(viewp != NULL); + view = *viewp; + *viewp = NULL; + REQUIRE(DNS_VIEW_VALID(view)); + + if (isc_refcount_decrement(&view->weakrefs) == 1) { + destroy(view); + } +} + +static void +resolver_shutdown(isc_task_t *task, isc_event_t *event) { + dns_view_t *view = event->ev_arg; + + REQUIRE(event->ev_type == DNS_EVENT_VIEWRESSHUTDOWN); + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->task == task); + + UNUSED(task); + + isc_event_free(&event); + + atomic_fetch_or(&view->attributes, DNS_VIEWATTR_RESSHUTDOWN); + dns_view_weakdetach(&view); +} + +static void +adb_shutdown(isc_task_t *task, isc_event_t *event) { + dns_view_t *view = event->ev_arg; + + REQUIRE(event->ev_type == DNS_EVENT_VIEWADBSHUTDOWN); + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->task == task); + + UNUSED(task); + + isc_event_free(&event); + + atomic_fetch_or(&view->attributes, DNS_VIEWATTR_ADBSHUTDOWN); + + dns_view_weakdetach(&view); +} + +static void +req_shutdown(isc_task_t *task, isc_event_t *event) { + dns_view_t *view = event->ev_arg; + + REQUIRE(event->ev_type == DNS_EVENT_VIEWREQSHUTDOWN); + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->task == task); + + UNUSED(task); + + isc_event_free(&event); + + atomic_fetch_or(&view->attributes, DNS_VIEWATTR_REQSHUTDOWN); + + dns_view_weakdetach(&view); +} + +isc_result_t +dns_view_createzonetable(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->zonetable == NULL); + + return (dns_zt_create(view->mctx, view->rdclass, &view->zonetable)); +} + +isc_result_t +dns_view_createresolver(dns_view_t *view, isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, isc_nm_t *nm, + isc_timermgr_t *timermgr, unsigned int options, + dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, + dns_dispatch_t *dispatchv6) { + isc_result_t result; + isc_event_t *event; + isc_mem_t *mctx = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->resolver == NULL); + + result = isc_task_create(taskmgr, 0, &view->task); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_task_setname(view->task, "view", view); + + result = dns_resolver_create(view, taskmgr, ntasks, ndisp, nm, timermgr, + options, dispatchmgr, dispatchv4, + dispatchv6, &view->resolver); + if (result != ISC_R_SUCCESS) { + isc_task_detach(&view->task); + return (result); + } + event = &view->resevent; + dns_resolver_whenshutdown(view->resolver, view->task, &event); + atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_RESSHUTDOWN); + isc_refcount_increment(&view->weakrefs); + + isc_mem_create(&mctx); + isc_mem_setname(mctx, "ADB"); + + result = dns_adb_create(mctx, view, timermgr, taskmgr, &view->adb); + isc_mem_detach(&mctx); + if (result != ISC_R_SUCCESS) { + dns_resolver_shutdown(view->resolver); + return (result); + } + event = &view->adbevent; + dns_adb_whenshutdown(view->adb, view->task, &event); + atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_ADBSHUTDOWN); + isc_refcount_increment(&view->weakrefs); + + result = dns_requestmgr_create( + view->mctx, dns_resolver_taskmgr(view->resolver), + dns_resolver_dispatchmgr(view->resolver), dispatchv4, + dispatchv6, &view->requestmgr); + if (result != ISC_R_SUCCESS) { + dns_adb_shutdown(view->adb); + dns_resolver_shutdown(view->resolver); + return (result); + } + event = &view->reqevent; + dns_requestmgr_whenshutdown(view->requestmgr, view->task, &event); + atomic_fetch_and(&view->attributes, ~DNS_VIEWATTR_REQSHUTDOWN); + isc_refcount_increment(&view->weakrefs); + + return (ISC_R_SUCCESS); +} + +void +dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + + view->cacheshared = shared; + if (view->cache != NULL) { + dns_db_detach(&view->cachedb); + dns_cache_detach(&view->cache); + } + dns_cache_attach(cache, &view->cache); + dns_cache_attachdb(cache, &view->cachedb); + INSIST(DNS_DB_VALID(view->cachedb)); +} + +bool +dns_view_iscacheshared(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + + return (view->cacheshared); +} + +void +dns_view_sethints(dns_view_t *view, dns_db_t *hints) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->hints == NULL); + REQUIRE(dns_db_iszone(hints)); + + dns_db_attach(hints, &view->hints); +} + +void +dns_view_settransports(dns_view_t *view, dns_transport_list_t *list) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(list != NULL); + if (view->transports != NULL) { + dns_transport_list_detach(&view->transports); + } + dns_transport_list_attach(list, &view->transports); +} + +void +dns_view_setkeyring(dns_view_t *view, dns_tsig_keyring_t *ring) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ring != NULL); + if (view->statickeys != NULL) { + dns_tsigkeyring_detach(&view->statickeys); + } + dns_tsigkeyring_attach(ring, &view->statickeys); +} + +void +dns_view_setdynamickeyring(dns_view_t *view, dns_tsig_keyring_t *ring) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ring != NULL); + if (view->dynamickeys != NULL) { + dns_tsigkeyring_detach(&view->dynamickeys); + } + dns_tsigkeyring_attach(ring, &view->dynamickeys); +} + +void +dns_view_getdynamickeyring(dns_view_t *view, dns_tsig_keyring_t **ringp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ringp != NULL && *ringp == NULL); + if (view->dynamickeys != NULL) { + dns_tsigkeyring_attach(view->dynamickeys, ringp); + } +} + +void +dns_view_restorekeyring(dns_view_t *view) { + FILE *fp; + char keyfile[PATH_MAX]; + isc_result_t result; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->dynamickeys != NULL) { + result = isc_file_sanitize(NULL, view->name, "tsigkeys", + keyfile, sizeof(keyfile)); + if (result == ISC_R_SUCCESS) { + fp = fopen(keyfile, "r"); + if (fp != NULL) { + dns_keyring_restore(view->dynamickeys, fp); + (void)fclose(fp); + } + } + } +} + +void +dns_view_setdstport(dns_view_t *view, in_port_t dstport) { + REQUIRE(DNS_VIEW_VALID(view)); + view->dstport = dstport; +} + +void +dns_view_freeze(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + + if (view->resolver != NULL) { + INSIST(view->cachedb != NULL); + dns_resolver_freeze(view->resolver); + } + view->frozen = true; +} + +void +dns_view_thaw(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->frozen); + + view->frozen = false; +} + +isc_result_t +dns_view_addzone(dns_view_t *view, dns_zone_t *zone) { + isc_result_t result; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->zonetable != NULL); + + result = dns_zt_mount(view->zonetable, zone); + + return (result); +} + +isc_result_t +dns_view_findzone(dns_view_t *view, const dns_name_t *name, + dns_zone_t **zonep) { + isc_result_t result; + + REQUIRE(DNS_VIEW_VALID(view)); + + LOCK(&view->lock); + if (view->zonetable != NULL) { + result = dns_zt_find(view->zonetable, name, 0, NULL, zonep); + if (result == DNS_R_PARTIALMATCH) { + dns_zone_detach(zonep); + result = ISC_R_NOTFOUND; + } + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&view->lock); + + return (result); +} + +isc_result_t +dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, unsigned int options, bool use_hints, + bool use_static_stub, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset) { + isc_result_t result; + dns_db_t *db, *zdb; + dns_dbnode_t *node, *znode; + bool is_cache, is_staticstub_zone; + dns_rdataset_t zrdataset, zsigrdataset; + dns_zone_t *zone; + + /* + * Find an rdataset whose owner name is 'name', and whose type is + * 'type'. + */ + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->frozen); + REQUIRE(type != dns_rdatatype_rrsig); + REQUIRE(rdataset != NULL); /* XXXBEW - remove this */ + REQUIRE(nodep == NULL || *nodep == NULL); + + /* + * Initialize. + */ + dns_rdataset_init(&zrdataset); + dns_rdataset_init(&zsigrdataset); + zdb = NULL; + znode = NULL; + + /* + * Find a database to answer the query. + */ + db = NULL; + node = NULL; + is_staticstub_zone = false; + zone = NULL; + LOCK(&view->lock); + if (view->zonetable != NULL) { + result = dns_zt_find(view->zonetable, name, DNS_ZTFIND_MIRROR, + NULL, &zone); + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&view->lock); + if (zone != NULL && dns_zone_gettype(zone) == dns_zone_staticstub && + !use_static_stub) + { + result = ISC_R_NOTFOUND; + } + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + result = dns_zone_getdb(zone, &db); + if (result != ISC_R_SUCCESS && view->cachedb != NULL) { + dns_db_attach(view->cachedb, &db); + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (dns_zone_gettype(zone) == dns_zone_staticstub && + dns_name_equal(name, dns_zone_getorigin(zone))) + { + is_staticstub_zone = true; + } + } else if (result == ISC_R_NOTFOUND && view->cachedb != NULL) { + dns_db_attach(view->cachedb, &db); + } else { + goto cleanup; + } + + is_cache = dns_db_iscache(db); + +db_find: + /* + * Now look for an answer in the database. + */ + result = dns_db_find(db, name, NULL, type, options, now, &node, + foundname, rdataset, sigrdataset); + + if (result == DNS_R_DELEGATION || result == ISC_R_NOTFOUND) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (!is_cache) { + dns_db_detach(&db); + if (view->cachedb != NULL && !is_staticstub_zone) { + /* + * Either the answer is in the cache, or we + * don't know it. + * Note that if the result comes from a + * static-stub zone we stop the search here + * (see the function description in view.h). + */ + is_cache = true; + dns_db_attach(view->cachedb, &db); + goto db_find; + } + } else { + /* + * We don't have the data in the cache. If we've got + * glue from the zone, use it. + */ + if (dns_rdataset_isassociated(&zrdataset)) { + dns_rdataset_clone(&zrdataset, rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(&zsigrdataset)) + { + dns_rdataset_clone(&zsigrdataset, + sigrdataset); + } + result = DNS_R_GLUE; + if (db != NULL) { + dns_db_detach(&db); + } + dns_db_attach(zdb, &db); + dns_db_attachnode(db, znode, &node); + goto cleanup; + } + } + /* + * We don't know the answer. + */ + result = ISC_R_NOTFOUND; + } else if (result == DNS_R_GLUE) { + if (view->cachedb != NULL && !is_staticstub_zone) { + /* + * We found an answer, but the cache may be better. + * Remember what we've got and go look in the cache. + */ + is_cache = true; + dns_rdataset_clone(rdataset, &zrdataset); + dns_rdataset_disassociate(rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_clone(sigrdataset, &zsigrdataset); + dns_rdataset_disassociate(sigrdataset); + } + dns_db_attach(db, &zdb); + dns_db_attachnode(zdb, node, &znode); + dns_db_detachnode(db, &node); + dns_db_detach(&db); + dns_db_attach(view->cachedb, &db); + goto db_find; + } + /* + * Otherwise, the glue is the best answer. + */ + result = ISC_R_SUCCESS; + } + + if (result == ISC_R_NOTFOUND && use_hints && view->hints != NULL) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + if (db != NULL) { + if (node != NULL) { + dns_db_detachnode(db, &node); + } + dns_db_detach(&db); + } + result = dns_db_find(view->hints, name, NULL, type, options, + now, &node, foundname, rdataset, + sigrdataset); + if (result == ISC_R_SUCCESS || result == DNS_R_GLUE) { + /* + * We just used a hint. Let the resolver know it + * should consider priming. + */ + dns_resolver_prime(view->resolver); + dns_db_attach(view->hints, &db); + result = DNS_R_HINT; + } else if (result == DNS_R_NXRRSET) { + dns_db_attach(view->hints, &db); + result = DNS_R_HINTNXRRSET; + } else if (result == DNS_R_NXDOMAIN) { + result = ISC_R_NOTFOUND; + } + + /* + * Cleanup if non-standard hints are used. + */ + if (db == NULL && node != NULL) { + dns_db_detachnode(view->hints, &node); + } + } + +cleanup: + if (dns_rdataset_isassociated(&zrdataset)) { + dns_rdataset_disassociate(&zrdataset); + if (dns_rdataset_isassociated(&zsigrdataset)) { + dns_rdataset_disassociate(&zsigrdataset); + } + } + + if (zdb != NULL) { + if (znode != NULL) { + dns_db_detachnode(zdb, &znode); + } + dns_db_detach(&zdb); + } + + if (db != NULL) { + if (node != NULL) { + if (nodep != NULL) { + *nodep = node; + } else { + dns_db_detachnode(db, &node); + } + } + if (dbp != NULL) { + *dbp = db; + } else { + dns_db_detach(&db); + } + } else { + INSIST(node == NULL); + } + + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +isc_result_t +dns_view_simplefind(dns_view_t *view, const dns_name_t *name, + dns_rdatatype_t type, isc_stdtime_t now, + unsigned int options, bool use_hints, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + isc_result_t result; + dns_fixedname_t foundname; + + dns_fixedname_init(&foundname); + result = dns_view_find(view, name, type, now, options, use_hints, false, + NULL, NULL, dns_fixedname_name(&foundname), + rdataset, sigrdataset); + if (result == DNS_R_NXDOMAIN) { + /* + * The rdataset and sigrdataset of the relevant NSEC record + * may be returned, but the caller cannot use them because + * foundname is not returned by this simplified API. We + * disassociate them here to prevent any misuse by the caller. + */ + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + } else if (result != ISC_R_SUCCESS && result != DNS_R_GLUE && + result != DNS_R_HINT && result != DNS_R_NCACHENXDOMAIN && + result != DNS_R_NCACHENXRRSET && result != DNS_R_NXRRSET && + result != DNS_R_HINTNXRRSET && result != ISC_R_NOTFOUND) + { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + result = ISC_R_NOTFOUND; + } + + return (result); +} + +isc_result_t +dns_view_findzonecut(dns_view_t *view, const dns_name_t *name, + dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now, + unsigned int options, bool use_hints, bool use_cache, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + isc_result_t result; + dns_db_t *db; + bool is_cache, use_zone, try_hints; + dns_zone_t *zone; + dns_name_t *zfname; + dns_rdataset_t zrdataset, zsigrdataset; + dns_fixedname_t zfixedname; + unsigned int ztoptions = DNS_ZTFIND_MIRROR; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->frozen); + + db = NULL; + use_zone = false; + try_hints = false; + zfname = NULL; + + /* + * Initialize. + */ + dns_fixedname_init(&zfixedname); + dns_rdataset_init(&zrdataset); + dns_rdataset_init(&zsigrdataset); + + /* + * Find the right database. + */ + zone = NULL; + LOCK(&view->lock); + if (view->zonetable != NULL) { + if ((options & DNS_DBFIND_NOEXACT) != 0) { + ztoptions |= DNS_ZTFIND_NOEXACT; + } + result = dns_zt_find(view->zonetable, name, ztoptions, NULL, + &zone); + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&view->lock); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + result = dns_zone_getdb(zone, &db); + } + if (result == ISC_R_NOTFOUND) { + /* + * We're not directly authoritative for this query name, nor + * is it a subdomain of any zone for which we're + * authoritative. + */ + if (use_cache && view->cachedb != NULL) { + /* + * We have a cache; try it. + */ + dns_db_attach(view->cachedb, &db); + } else if (use_hints && view->hints != NULL) { + /* + * Maybe we have hints... + */ + try_hints = true; + goto finish; + } else { + result = DNS_R_NXDOMAIN; + goto cleanup; + } + } else if (result != ISC_R_SUCCESS) { + /* + * Something is broken. + */ + goto cleanup; + } + is_cache = dns_db_iscache(db); + +db_find: + /* + * Look for the zonecut. + */ + if (!is_cache) { + result = dns_db_find(db, name, NULL, dns_rdatatype_ns, options, + now, NULL, fname, rdataset, sigrdataset); + if (result == DNS_R_DELEGATION) { + result = ISC_R_SUCCESS; + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (use_cache && view->cachedb != NULL && db != view->hints) { + /* + * We found an answer, but the cache may be better. + */ + zfname = dns_fixedname_name(&zfixedname); + dns_name_copy(fname, zfname); + dns_rdataset_clone(rdataset, &zrdataset); + dns_rdataset_disassociate(rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_clone(sigrdataset, &zsigrdataset); + dns_rdataset_disassociate(sigrdataset); + } + dns_db_detach(&db); + dns_db_attach(view->cachedb, &db); + is_cache = true; + goto db_find; + } + } else { + result = dns_db_findzonecut(db, name, options, now, NULL, fname, + dcname, rdataset, sigrdataset); + if (result == ISC_R_SUCCESS) { + if (zfname != NULL && + (!dns_name_issubdomain(fname, zfname) || + (dns_zone_gettype(zone) == dns_zone_staticstub && + dns_name_equal(fname, zfname)))) + { + /* + * We found a zonecut in the cache, but our + * zone delegation is better. + */ + use_zone = true; + } + } else if (result == ISC_R_NOTFOUND) { + if (zfname != NULL) { + /* + * We didn't find anything in the cache, but we + * have a zone delegation, so use it. + */ + use_zone = true; + result = ISC_R_SUCCESS; + } else if (use_hints && view->hints != NULL) { + /* + * Maybe we have hints... + */ + try_hints = true; + result = ISC_R_SUCCESS; + } else { + result = DNS_R_NXDOMAIN; + } + } else { + /* + * Something bad happened. + */ + goto cleanup; + } + } + +finish: + if (use_zone) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(sigrdataset)) + { + dns_rdataset_disassociate(sigrdataset); + } + } + dns_name_copy(zfname, fname); + if (dcname != NULL) { + dns_name_copy(zfname, dcname); + } + dns_rdataset_clone(&zrdataset, rdataset); + if (sigrdataset != NULL && + dns_rdataset_isassociated(&zrdataset)) + { + dns_rdataset_clone(&zsigrdataset, sigrdataset); + } + } else if (try_hints) { + /* + * We've found nothing so far, but we have hints. + */ + result = dns_db_find(view->hints, dns_rootname, NULL, + dns_rdatatype_ns, 0, now, NULL, fname, + rdataset, NULL); + if (result != ISC_R_SUCCESS) { + /* + * We can't even find the hints for the root + * nameservers! + */ + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + result = ISC_R_NOTFOUND; + } else if (dcname != NULL) { + dns_name_copy(fname, dcname); + } + } + +cleanup: + if (dns_rdataset_isassociated(&zrdataset)) { + dns_rdataset_disassociate(&zrdataset); + if (dns_rdataset_isassociated(&zsigrdataset)) { + dns_rdataset_disassociate(&zsigrdataset); + } + } + if (db != NULL) { + dns_db_detach(&db); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + + return (result); +} + +isc_result_t +dns_viewlist_find(dns_viewlist_t *list, const char *name, + dns_rdataclass_t rdclass, dns_view_t **viewp) { + dns_view_t *view; + + REQUIRE(list != NULL); + + for (view = ISC_LIST_HEAD(*list); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (strcmp(view->name, name) == 0 && view->rdclass == rdclass) { + break; + } + } + if (view == NULL) { + return (ISC_R_NOTFOUND); + } + + dns_view_attach(view, viewp); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_viewlist_findzone(dns_viewlist_t *list, const dns_name_t *name, + bool allclasses, dns_rdataclass_t rdclass, + dns_zone_t **zonep) { + dns_view_t *view; + isc_result_t result; + dns_zone_t *zone1 = NULL, *zone2 = NULL; + dns_zone_t **zp = NULL; + + REQUIRE(list != NULL); + REQUIRE(zonep != NULL && *zonep == NULL); + + for (view = ISC_LIST_HEAD(*list); view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!allclasses && view->rdclass != rdclass) { + continue; + } + + /* + * If the zone is defined in more than one view, + * treat it as not found. + */ + zp = (zone1 == NULL) ? &zone1 : &zone2; + LOCK(&view->lock); + if (view->zonetable != NULL) { + result = dns_zt_find(view->zonetable, name, 0, NULL, + zp); + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&view->lock); + INSIST(result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND || + result == DNS_R_PARTIALMATCH); + + /* Treat a partial match as no match */ + if (result == DNS_R_PARTIALMATCH) { + dns_zone_detach(zp); + result = ISC_R_NOTFOUND; + POST(result); + } + + if (zone2 != NULL) { + dns_zone_detach(&zone1); + dns_zone_detach(&zone2); + return (ISC_R_MULTIPLE); + } + } + + if (zone1 != NULL) { + dns_zone_attach(zone1, zonep); + dns_zone_detach(&zone1); + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_view_load(dns_view_t *view, bool stop, bool newonly) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); + + return (dns_zt_load(view->zonetable, stop, newonly)); +} + +isc_result_t +dns_view_asyncload(dns_view_t *view, bool newonly, dns_zt_allloaded_t callback, + void *arg) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); + + return (dns_zt_asyncload(view->zonetable, newonly, callback, arg)); +} + +isc_result_t +dns_view_gettsig(dns_view_t *view, const dns_name_t *keyname, + dns_tsigkey_t **keyp) { + isc_result_t result; + REQUIRE(keyp != NULL && *keyp == NULL); + + result = dns_tsigkey_find(keyp, keyname, NULL, view->statickeys); + if (result == ISC_R_NOTFOUND) { + result = dns_tsigkey_find(keyp, keyname, NULL, + view->dynamickeys); + } + return (result); +} + +isc_result_t +dns_view_gettransport(dns_view_t *view, const dns_transport_type_t type, + const dns_name_t *name, dns_transport_t **transportp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(transportp != NULL && *transportp == NULL); + + dns_transport_t *transport = dns_transport_find(type, name, + view->transports); + if (transport == NULL) { + return (ISC_R_NOTFOUND); + } + + *transportp = transport; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_view_getpeertsig(dns_view_t *view, const isc_netaddr_t *peeraddr, + dns_tsigkey_t **keyp) { + isc_result_t result; + dns_name_t *keyname = NULL; + dns_peer_t *peer = NULL; + + result = dns_peerlist_peerbyaddr(view->peers, peeraddr, &peer); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_peer_getkey(peer, &keyname); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_view_gettsig(view, keyname, keyp); + return ((result == ISC_R_NOTFOUND) ? ISC_R_FAILURE : result); +} + +isc_result_t +dns_view_checksig(dns_view_t *view, isc_buffer_t *source, dns_message_t *msg) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(source != NULL); + + return (dns_tsig_verify(source, msg, view->statickeys, + view->dynamickeys)); +} + +isc_result_t +dns_view_dumpdbtostream(dns_view_t *view, FILE *fp) { + isc_result_t result; + + REQUIRE(DNS_VIEW_VALID(view)); + + (void)fprintf(fp, ";\n; Cache dump of view '%s'\n;\n", view->name); + result = dns_master_dumptostream(view->mctx, view->cachedb, NULL, + &dns_master_style_cache, + dns_masterformat_text, NULL, fp); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_adb_dump(view->adb, fp); + dns_resolver_printbadcache(view->resolver, fp); + dns_badcache_print(view->failcache, "SERVFAIL cache", fp); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_view_flushcache(dns_view_t *view, bool fixuponly) { + isc_result_t result; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->cachedb == NULL) { + return (ISC_R_SUCCESS); + } + if (!fixuponly) { + result = dns_cache_flush(view->cache); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + dns_db_detach(&view->cachedb); + dns_cache_attachdb(view->cache, &view->cachedb); + if (view->resolver != NULL) { + dns_resolver_flushbadcache(view->resolver, NULL); + } + if (view->failcache != NULL) { + dns_badcache_flush(view->failcache); + } + + dns_adb_flush(view->adb); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_view_flushname(dns_view_t *view, const dns_name_t *name) { + return (dns_view_flushnode(view, name, false)); +} + +isc_result_t +dns_view_flushnode(dns_view_t *view, const dns_name_t *name, bool tree) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (tree) { + if (view->adb != NULL) { + dns_adb_flushnames(view->adb, name); + } + if (view->resolver != NULL) { + dns_resolver_flushbadnames(view->resolver, name); + } + if (view->failcache != NULL) { + dns_badcache_flushtree(view->failcache, name); + } + } else { + if (view->adb != NULL) { + dns_adb_flushname(view->adb, name); + } + if (view->resolver != NULL) { + dns_resolver_flushbadcache(view->resolver, name); + } + if (view->failcache != NULL) { + dns_badcache_flushname(view->failcache, name); + } + } + + if (view->cache != NULL) { + result = dns_cache_flushnode(view->cache, name, tree); + } + + return (result); +} + +void +dns_view_adddelegationonly(dns_view_t *view, const dns_name_t *name) { + dns_name_t *item; + unsigned int hash; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->delonly == NULL) { + view->delonly = isc_mem_get(view->mctx, + sizeof(dns_namelist_t) * + DNS_VIEW_DELONLYHASH); + for (hash = 0; hash < DNS_VIEW_DELONLYHASH; hash++) { + ISC_LIST_INIT(view->delonly[hash]); + } + } + hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH; + item = ISC_LIST_HEAD(view->delonly[hash]); + while (item != NULL && !dns_name_equal(item, name)) { + item = ISC_LIST_NEXT(item, link); + } + if (item != NULL) { + return; + } + item = isc_mem_get(view->mctx, sizeof(*item)); + dns_name_init(item, NULL); + dns_name_dup(name, view->mctx, item); + ISC_LIST_APPEND(view->delonly[hash], item, link); +} + +void +dns_view_excludedelegationonly(dns_view_t *view, const dns_name_t *name) { + dns_name_t *item; + unsigned int hash; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->rootexclude == NULL) { + view->rootexclude = isc_mem_get(view->mctx, + sizeof(dns_namelist_t) * + DNS_VIEW_DELONLYHASH); + for (hash = 0; hash < DNS_VIEW_DELONLYHASH; hash++) { + ISC_LIST_INIT(view->rootexclude[hash]); + } + } + hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH; + item = ISC_LIST_HEAD(view->rootexclude[hash]); + while (item != NULL && !dns_name_equal(item, name)) { + item = ISC_LIST_NEXT(item, link); + } + if (item != NULL) { + return; + } + item = isc_mem_get(view->mctx, sizeof(*item)); + dns_name_init(item, NULL); + dns_name_dup(name, view->mctx, item); + ISC_LIST_APPEND(view->rootexclude[hash], item, link); +} + +bool +dns_view_isdelegationonly(dns_view_t *view, const dns_name_t *name) { + dns_name_t *item; + unsigned int hash; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (!view->rootdelonly && view->delonly == NULL) { + return (false); + } + + hash = dns_name_hash(name, false) % DNS_VIEW_DELONLYHASH; + if (view->rootdelonly && dns_name_countlabels(name) <= 2) { + if (view->rootexclude == NULL) { + return (true); + } + item = ISC_LIST_HEAD(view->rootexclude[hash]); + while (item != NULL && !dns_name_equal(item, name)) { + item = ISC_LIST_NEXT(item, link); + } + if (item == NULL) { + return (true); + } + } + + if (view->delonly == NULL) { + return (false); + } + + item = ISC_LIST_HEAD(view->delonly[hash]); + while (item != NULL && !dns_name_equal(item, name)) { + item = ISC_LIST_NEXT(item, link); + } + if (item == NULL) { + return (false); + } + return (true); +} + +void +dns_view_setrootdelonly(dns_view_t *view, bool value) { + REQUIRE(DNS_VIEW_VALID(view)); + view->rootdelonly = value; +} + +bool +dns_view_getrootdelonly(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + return (view->rootdelonly); +} + +isc_result_t +dns_view_freezezones(dns_view_t *view, bool value) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->zonetable != NULL); + + return (dns_zt_freezezones(view->zonetable, view, value)); +} + +void +dns_view_setadbstats(dns_view_t *view, isc_stats_t *stats) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->adbstats == NULL); + + isc_stats_attach(stats, &view->adbstats); +} + +void +dns_view_getadbstats(dns_view_t *view, isc_stats_t **statsp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(statsp != NULL && *statsp == NULL); + + if (view->adbstats != NULL) { + isc_stats_attach(view->adbstats, statsp); + } +} + +void +dns_view_setresstats(dns_view_t *view, isc_stats_t *stats) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->resstats == NULL); + + isc_stats_attach(stats, &view->resstats); +} + +void +dns_view_getresstats(dns_view_t *view, isc_stats_t **statsp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(statsp != NULL && *statsp == NULL); + + if (view->resstats != NULL) { + isc_stats_attach(view->resstats, statsp); + } +} + +void +dns_view_setresquerystats(dns_view_t *view, dns_stats_t *stats) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(!view->frozen); + REQUIRE(view->resquerystats == NULL); + + dns_stats_attach(stats, &view->resquerystats); +} + +void +dns_view_getresquerystats(dns_view_t *view, dns_stats_t **statsp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(statsp != NULL && *statsp == NULL); + + if (view->resquerystats != NULL) { + dns_stats_attach(view->resquerystats, statsp); + } +} + +isc_result_t +dns_view_initntatable(dns_view_t *view, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr) { + REQUIRE(DNS_VIEW_VALID(view)); + if (view->ntatable_priv != NULL) { + dns_ntatable_detach(&view->ntatable_priv); + } + return (dns_ntatable_create(view, taskmgr, timermgr, + &view->ntatable_priv)); +} + +isc_result_t +dns_view_getntatable(dns_view_t *view, dns_ntatable_t **ntp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ntp != NULL && *ntp == NULL); + if (view->ntatable_priv == NULL) { + return (ISC_R_NOTFOUND); + } + dns_ntatable_attach(view->ntatable_priv, ntp); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_view_initsecroots(dns_view_t *view, isc_mem_t *mctx) { + REQUIRE(DNS_VIEW_VALID(view)); + if (view->secroots_priv != NULL) { + dns_keytable_detach(&view->secroots_priv); + } + return (dns_keytable_create(mctx, &view->secroots_priv)); +} + +isc_result_t +dns_view_getsecroots(dns_view_t *view, dns_keytable_t **ktp) { + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(ktp != NULL && *ktp == NULL); + if (view->secroots_priv == NULL) { + return (ISC_R_NOTFOUND); + } + dns_keytable_attach(view->secroots_priv, ktp); + return (ISC_R_SUCCESS); +} + +bool +dns_view_ntacovers(dns_view_t *view, isc_stdtime_t now, const dns_name_t *name, + const dns_name_t *anchor) { + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->ntatable_priv == NULL) { + return (false); + } + + return (dns_ntatable_covered(view->ntatable_priv, now, name, anchor)); +} + +isc_result_t +dns_view_issecuredomain(dns_view_t *view, const dns_name_t *name, + isc_stdtime_t now, bool checknta, bool *ntap, + bool *secure_domain) { + isc_result_t result; + bool secure = false; + dns_fixedname_t fn; + dns_name_t *anchor; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->secroots_priv == NULL) { + return (ISC_R_NOTFOUND); + } + + anchor = dns_fixedname_initname(&fn); + + result = dns_keytable_issecuredomain(view->secroots_priv, name, anchor, + &secure); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (ntap != NULL) { + *ntap = false; + } + if (checknta && secure && view->ntatable_priv != NULL && + dns_ntatable_covered(view->ntatable_priv, now, name, anchor)) + { + if (ntap != NULL) { + *ntap = true; + } + secure = false; + } + + *secure_domain = secure; + return (ISC_R_SUCCESS); +} + +void +dns_view_untrust(dns_view_t *view, const dns_name_t *keyname, + const dns_rdata_dnskey_t *dnskey) { + isc_result_t result; + dns_keytable_t *sr = NULL; + dns_rdata_dnskey_t tmpkey; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(keyname != NULL); + REQUIRE(dnskey != NULL); + + result = dns_view_getsecroots(view, &sr); + if (result != ISC_R_SUCCESS) { + return; + } + + /* + * Clear the revoke bit, if set, so that the key will match what's + * in secroots now. + */ + tmpkey = *dnskey; + tmpkey.flags &= ~DNS_KEYFLAG_REVOKE; + + result = dns_keytable_deletekey(sr, keyname, &tmpkey); + if (result == ISC_R_SUCCESS) { + /* + * If key was found in secroots, then it was a + * configured trust anchor, and we want to fail + * secure. If there are no other configured keys, + * then leave a null key so that we can't validate + * anymore. + */ + dns_keytable_marksecure(sr, keyname); + } + + dns_keytable_detach(&sr); +} + +bool +dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname, + const dns_rdata_dnskey_t *dnskey) { + isc_result_t result; + dns_keytable_t *sr = NULL; + dns_keynode_t *knode = NULL; + bool answer = false; + dns_rdataset_t dsset; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(keyname != NULL); + REQUIRE(dnskey != NULL); + + result = dns_view_getsecroots(view, &sr); + if (result != ISC_R_SUCCESS) { + return (false); + } + + dns_rdataset_init(&dsset); + result = dns_keytable_find(sr, keyname, &knode); + if (result == ISC_R_SUCCESS) { + if (dns_keynode_dsset(knode, &dsset)) { + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[4096], digest[DNS_DS_BUFFERSIZE]; + dns_rdata_dnskey_t tmpkey = *dnskey; + dns_rdata_ds_t ds; + isc_buffer_t b; + dns_rdataclass_t rdclass = tmpkey.common.rdclass; + + /* + * Clear the revoke bit, if set, so that the key + * will match what's in secroots now. + */ + tmpkey.flags &= ~DNS_KEYFLAG_REVOKE; + + isc_buffer_init(&b, data, sizeof(data)); + result = dns_rdata_fromstruct(&rdata, rdclass, + dns_rdatatype_dnskey, + &tmpkey, &b); + if (result != ISC_R_SUCCESS) { + goto finish; + } + + result = dns_ds_fromkeyrdata(keyname, &rdata, + DNS_DSDIGEST_SHA256, + digest, &ds); + if (result != ISC_R_SUCCESS) { + goto finish; + } + + dns_rdata_reset(&rdata); + isc_buffer_init(&b, data, sizeof(data)); + result = dns_rdata_fromstruct( + &rdata, rdclass, dns_rdatatype_ds, &ds, &b); + if (result != ISC_R_SUCCESS) { + goto finish; + } + + result = dns_rdataset_first(&dsset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t this = DNS_RDATA_INIT; + dns_rdataset_current(&dsset, &this); + if (dns_rdata_compare(&rdata, &this) == 0) { + answer = true; + break; + } + result = dns_rdataset_next(&dsset); + } + } + } + +finish: + if (dns_rdataset_isassociated(&dsset)) { + dns_rdataset_disassociate(&dsset); + } + if (knode != NULL) { + dns_keytable_detachkeynode(sr, &knode); + } + dns_keytable_detach(&sr); + return (answer); +} + +/* + * Create path to a directory and a filename constructed from viewname. + * This is a front-end to isc_file_sanitize(), allowing backward + * compatibility to older versions when a file couldn't be expected + * to be in the specified directory but might be in the current working + * directory instead. + * + * It first tests for the existence of a file . in + * 'directory'. If the file does not exist, it checks again in the + * current working directory. If it does not exist there either, + * return the path inside the directory. + * + * Returns ISC_R_SUCCESS if a path to an existing file is found or + * a new path is created; returns ISC_R_NOSPACE if the path won't + * fit in 'buflen'. + */ + +static isc_result_t +nz_legacy(const char *directory, const char *viewname, const char *suffix, + char *buffer, size_t buflen) { + isc_result_t result; + char newbuf[PATH_MAX]; + + result = isc_file_sanitize(directory, viewname, suffix, buffer, buflen); + if (result != ISC_R_SUCCESS) { + return (result); + } else if (directory == NULL || isc_file_exists(buffer)) { + return (ISC_R_SUCCESS); + } else { + /* Save buffer */ + strlcpy(newbuf, buffer, sizeof(newbuf)); + } + + /* + * It isn't in the specified directory; check CWD. + */ + result = isc_file_sanitize(NULL, viewname, suffix, buffer, buflen); + if (result != ISC_R_SUCCESS || isc_file_exists(buffer)) { + return (result); + } + + /* + * File does not exist in either 'directory' or CWD, + * so use the path in 'directory'. + */ + strlcpy(buffer, newbuf, buflen); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx, + void (*cfg_destroy)(void **), uint64_t mapsize) { + isc_result_t result = ISC_R_SUCCESS; + char buffer[1024]; +#ifdef HAVE_LMDB + MDB_env *env = NULL; + int status; +#endif /* ifdef HAVE_LMDB */ + +#ifndef HAVE_LMDB + UNUSED(mapsize); +#endif /* ifndef HAVE_LMDB */ + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE((cfgctx != NULL && cfg_destroy != NULL) || !allow); + + if (view->new_zone_file != NULL) { + isc_mem_free(view->mctx, view->new_zone_file); + view->new_zone_file = NULL; + } + +#ifdef HAVE_LMDB + if (view->new_zone_dbenv != NULL) { + mdb_env_close((MDB_env *)view->new_zone_dbenv); + view->new_zone_dbenv = NULL; + } + + if (view->new_zone_db != NULL) { + isc_mem_free(view->mctx, view->new_zone_db); + view->new_zone_db = NULL; + } +#endif /* HAVE_LMDB */ + + if (view->new_zone_config != NULL) { + view->cfg_destroy(&view->new_zone_config); + view->cfg_destroy = NULL; + } + + if (!allow) { + return (ISC_R_SUCCESS); + } + + CHECK(nz_legacy(view->new_zone_dir, view->name, "nzf", buffer, + sizeof(buffer))); + + view->new_zone_file = isc_mem_strdup(view->mctx, buffer); + +#ifdef HAVE_LMDB + CHECK(nz_legacy(view->new_zone_dir, view->name, "nzd", buffer, + sizeof(buffer))); + + view->new_zone_db = isc_mem_strdup(view->mctx, buffer); + + status = mdb_env_create(&env); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_create failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + if (mapsize != 0ULL) { + status = mdb_env_set_mapsize(env, mapsize); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_set_mapsize failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + view->new_zone_mapsize = mapsize; + } + + status = mdb_env_open(env, view->new_zone_db, DNS_LMDB_FLAGS, 0600); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_open of '%s' failed: %s", + view->new_zone_db, mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + view->new_zone_dbenv = env; + env = NULL; +#endif /* HAVE_LMDB */ + + view->new_zone_config = cfgctx; + view->cfg_destroy = cfg_destroy; + +cleanup: + if (result != ISC_R_SUCCESS) { + if (view->new_zone_file != NULL) { + isc_mem_free(view->mctx, view->new_zone_file); + view->new_zone_file = NULL; + } + +#ifdef HAVE_LMDB + if (view->new_zone_db != NULL) { + isc_mem_free(view->mctx, view->new_zone_db); + view->new_zone_db = NULL; + } + if (env != NULL) { + mdb_env_close(env); + } +#endif /* HAVE_LMDB */ + view->new_zone_config = NULL; + view->cfg_destroy = NULL; + } + + return (result); +} + +void +dns_view_setnewzonedir(dns_view_t *view, const char *dir) { + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->new_zone_dir != NULL) { + isc_mem_free(view->mctx, view->new_zone_dir); + view->new_zone_dir = NULL; + } + + if (dir == NULL) { + return; + } + + view->new_zone_dir = isc_mem_strdup(view->mctx, dir); +} + +const char * +dns_view_getnewzonedir(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + + return (view->new_zone_dir); +} + +isc_result_t +dns_view_searchdlz(dns_view_t *view, const dns_name_t *name, + unsigned int minlabels, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo, dns_db_t **dbp) { + dns_fixedname_t fname; + dns_name_t *zonename; + unsigned int namelabels; + unsigned int i; + isc_result_t result; + dns_dlzfindzone_t findzone; + dns_dlzdb_t *dlzdb; + dns_db_t *db, *best = NULL; + + /* + * Performs checks to make sure data is as we expect it to be. + */ + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(name != NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + + /* setup a "fixed" dns name */ + zonename = dns_fixedname_initname(&fname); + + /* count the number of labels in the name */ + namelabels = dns_name_countlabels(name); + + for (dlzdb = ISC_LIST_HEAD(view->dlz_searched); dlzdb != NULL; + dlzdb = ISC_LIST_NEXT(dlzdb, link)) + { + REQUIRE(DNS_DLZ_VALID(dlzdb)); + + /* + * loop through starting with the longest domain name and + * trying shorter names portions of the name until we find a + * match, have an error, or are below the 'minlabels' + * threshold. minlabels is 0, if neither the standard + * database nor any previous DLZ database had a zone name + * match. Otherwise minlabels is the number of labels + * in that name. We need to beat that for a "better" + * match for this DLZ database to be authoritative. + */ + for (i = namelabels; i > minlabels && i > 1; i--) { + if (i == namelabels) { + dns_name_copy(name, zonename); + } else { + dns_name_split(name, i, NULL, zonename); + } + + /* ask SDLZ driver if the zone is supported */ + db = NULL; + findzone = dlzdb->implementation->methods->findzone; + result = (*findzone)(dlzdb->implementation->driverarg, + dlzdb->dbdata, dlzdb->mctx, + view->rdclass, zonename, methods, + clientinfo, &db); + + if (result != ISC_R_NOTFOUND) { + if (best != NULL) { + dns_db_detach(&best); + } + if (result == ISC_R_SUCCESS) { + INSIST(db != NULL); + dns_db_attach(db, &best); + dns_db_detach(&db); + minlabels = i; + } else { + if (db != NULL) { + dns_db_detach(&db); + } + break; + } + } else if (db != NULL) { + dns_db_detach(&db); + } + } + } + + if (best != NULL) { + dns_db_attach(best, dbp); + dns_db_detach(&best); + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +uint32_t +dns_view_getfailttl(dns_view_t *view) { + REQUIRE(DNS_VIEW_VALID(view)); + return (view->fail_ttl); +} + +void +dns_view_setfailttl(dns_view_t *view, uint32_t fail_ttl) { + REQUIRE(DNS_VIEW_VALID(view)); + view->fail_ttl = fail_ttl; +} + +isc_result_t +dns_view_saventa(dns_view_t *view) { + isc_result_t result; + bool removefile = false; + dns_ntatable_t *ntatable = NULL; + FILE *fp = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->nta_lifetime == 0) { + return (ISC_R_SUCCESS); + } + + /* Open NTA save file for overwrite. */ + CHECK(isc_stdio_open(view->nta_file, "w", &fp)); + + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + removefile = true; + result = ISC_R_SUCCESS; + goto cleanup; + } else { + CHECK(result); + } + + result = dns_ntatable_save(ntatable, fp); + if (result == ISC_R_NOTFOUND) { + removefile = true; + result = ISC_R_SUCCESS; + } else if (result == ISC_R_SUCCESS) { + result = isc_stdio_close(fp); + fp = NULL; + } + +cleanup: + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + + if (fp != NULL) { + (void)isc_stdio_close(fp); + } + + /* Don't leave half-baked NTA save files lying around. */ + if (result != ISC_R_SUCCESS || removefile) { + (void)isc_file_remove(view->nta_file); + } + + return (result); +} + +#define TSTR(t) ((t).value.as_textregion.base) +#define TLEN(t) ((t).value.as_textregion.length) + +isc_result_t +dns_view_loadnta(dns_view_t *view) { + isc_result_t result; + dns_ntatable_t *ntatable = NULL; + isc_lex_t *lex = NULL; + isc_token_t token; + isc_stdtime_t now; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->nta_lifetime == 0) { + return (ISC_R_SUCCESS); + } + + CHECK(isc_lex_create(view->mctx, 1025, &lex)); + CHECK(isc_lex_openfile(lex, view->nta_file)); + CHECK(dns_view_getntatable(view, &ntatable)); + isc_stdtime_get(&now); + + for (;;) { + int options = (ISC_LEXOPT_EOL | ISC_LEXOPT_EOF); + char *name, *type, *timestamp; + size_t len; + dns_fixedname_t fn; + const dns_name_t *ntaname; + isc_buffer_t b; + isc_stdtime_t t; + bool forced; + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type == isc_tokentype_eof) { + break; + } else if (token.type != isc_tokentype_string) { + CHECK(ISC_R_UNEXPECTEDTOKEN); + } + name = TSTR(token); + len = TLEN(token); + + if (strcmp(name, ".") == 0) { + ntaname = dns_rootname; + } else { + dns_name_t *fname; + fname = dns_fixedname_initname(&fn); + + isc_buffer_init(&b, name, (unsigned int)len); + isc_buffer_add(&b, (unsigned int)len); + CHECK(dns_name_fromtext(fname, &b, dns_rootname, 0, + NULL)); + ntaname = fname; + } + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_string) { + CHECK(ISC_R_UNEXPECTEDTOKEN); + } + type = TSTR(token); + + if (strcmp(type, "regular") == 0) { + forced = false; + } else if (strcmp(type, "forced") == 0) { + forced = true; + } else { + CHECK(ISC_R_UNEXPECTEDTOKEN); + } + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_string) { + CHECK(ISC_R_UNEXPECTEDTOKEN); + } + timestamp = TSTR(token); + CHECK(dns_time32_fromtext(timestamp, &t)); + + CHECK(isc_lex_gettoken(lex, options, &token)); + if (token.type != isc_tokentype_eol && + token.type != isc_tokentype_eof) + { + CHECK(ISC_R_UNEXPECTEDTOKEN); + } + + if (now <= t) { + if (t > (now + 604800)) { + t = now + 604800; + } + + (void)dns_ntatable_add(ntatable, ntaname, forced, 0, t); + } else { + char nb[DNS_NAME_FORMATSIZE]; + dns_name_format(ntaname, nb, sizeof(nb)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "ignoring expired NTA at %s", nb); + } + } + +cleanup: + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + + if (lex != NULL) { + isc_lex_close(lex); + isc_lex_destroy(&lex); + } + + return (result); +} + +void +dns_view_setviewcommit(dns_view_t *view) { + dns_zone_t *redirect = NULL, *managed_keys = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + LOCK(&view->lock); + + if (view->redirect != NULL) { + dns_zone_attach(view->redirect, &redirect); + } + if (view->managed_keys != NULL) { + dns_zone_attach(view->managed_keys, &managed_keys); + } + + UNLOCK(&view->lock); + + if (view->zonetable != NULL) { + dns_zt_setviewcommit(view->zonetable); + } + if (redirect != NULL) { + dns_zone_setviewcommit(redirect); + dns_zone_detach(&redirect); + } + if (managed_keys != NULL) { + dns_zone_setviewcommit(managed_keys); + dns_zone_detach(&managed_keys); + } +} + +void +dns_view_setviewrevert(dns_view_t *view) { + dns_zone_t *redirect = NULL, *managed_keys = NULL; + dns_zt_t *zonetable; + + REQUIRE(DNS_VIEW_VALID(view)); + + /* + * dns_zt_setviewrevert() attempts to lock this view, so we must + * release the lock. + */ + LOCK(&view->lock); + if (view->redirect != NULL) { + dns_zone_attach(view->redirect, &redirect); + } + if (view->managed_keys != NULL) { + dns_zone_attach(view->managed_keys, &managed_keys); + } + zonetable = view->zonetable; + UNLOCK(&view->lock); + + if (redirect != NULL) { + dns_zone_setviewrevert(redirect); + dns_zone_detach(&redirect); + } + if (managed_keys != NULL) { + dns_zone_setviewrevert(managed_keys); + dns_zone_detach(&managed_keys); + } + if (zonetable != NULL) { + dns_zt_setviewrevert(zonetable); + } +} + +bool +dns_view_staleanswerenabled(dns_view_t *view) { + uint32_t stale_ttl = 0; + bool result = false; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (dns_db_getservestalettl(view->cachedb, &stale_ttl) != ISC_R_SUCCESS) + { + return (false); + } + if (stale_ttl > 0) { + if (view->staleanswersok == dns_stale_answer_yes) { + result = true; + } else if (view->staleanswersok == dns_stale_answer_conf) { + result = view->staleanswersenable; + } + } + + return (result); +} + +static void +free_sfd(void *data, void *arg) { + isc_mem_put(arg, data, sizeof(unsigned int)); +} + +void +dns_view_sfd_add(dns_view_t *view, const dns_name_t *name) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + RWLOCK(&view->sfd_lock, isc_rwlocktype_write); + if (view->sfd == NULL) { + result = dns_rbt_create(view->mctx, free_sfd, view->mctx, + &view->sfd); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + result = dns_rbt_addnode(view->sfd, name, &node); + RUNTIME_CHECK(result == ISC_R_SUCCESS || result == ISC_R_EXISTS); + if (node->data != NULL) { + unsigned int *count = node->data; + (*count)++; + } else { + unsigned int *count = isc_mem_get(view->mctx, + sizeof(unsigned int)); + *count = 1; + node->data = count; + } + RWUNLOCK(&view->sfd_lock, isc_rwlocktype_write); +} + +void +dns_view_sfd_del(dns_view_t *view, const dns_name_t *name) { + isc_result_t result; + void *data = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + RWLOCK(&view->sfd_lock, isc_rwlocktype_write); + INSIST(view->sfd != NULL); + result = dns_rbt_findname(view->sfd, name, 0, NULL, &data); + if (result == ISC_R_SUCCESS) { + unsigned int *count = data; + INSIST(count != NULL); + if (--(*count) == 0U) { + result = dns_rbt_deletename(view->sfd, name, false); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + } + RWUNLOCK(&view->sfd_lock, isc_rwlocktype_write); +} + +void +dns_view_sfd_find(dns_view_t *view, const dns_name_t *name, + dns_name_t *foundname) { + REQUIRE(DNS_VIEW_VALID(view)); + + if (view->sfd != NULL) { + isc_result_t result; + void *data = NULL; + + RWLOCK(&view->sfd_lock, isc_rwlocktype_read); + result = dns_rbt_findname(view->sfd, name, 0, foundname, &data); + RWUNLOCK(&view->sfd_lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) { + dns_name_copy(dns_rootname, foundname); + } + } else { + dns_name_copy(dns_rootname, foundname); + } +} diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c new file mode 100644 index 0000000..393b557 --- /dev/null +++ b/lib/dns/xfrin.c @@ -0,0 +1,2034 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Incoming AXFR and IXFR. + */ + +/*% + * It would be non-sensical (or at least obtuse) to use FAIL() with an + * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ +#define FAIL(code) \ + do { \ + result = (code); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/*% + * The states of the *XFR state machine. We handle both IXFR and AXFR + * with a single integrated state machine because they cannot be distinguished + * immediately - an AXFR response to an IXFR request can only be detected + * when the first two (2) response RRs have already been received. + */ +typedef enum { + XFRST_SOAQUERY, + XFRST_GOTSOA, + XFRST_INITIALSOA, + XFRST_FIRSTDATA, + XFRST_IXFR_DELSOA, + XFRST_IXFR_DEL, + XFRST_IXFR_ADDSOA, + XFRST_IXFR_ADD, + XFRST_IXFR_END, + XFRST_AXFR, + XFRST_AXFR_END +} xfrin_state_t; + +/*% + * Incoming zone transfer context. + */ + +struct dns_xfrin_ctx { + unsigned int magic; + isc_mem_t *mctx; + dns_zone_t *zone; + + isc_refcount_t references; + + isc_nm_t *netmgr; + + isc_refcount_t connects; /*%< Connect in progress */ + isc_refcount_t sends; /*%< Send in progress */ + isc_refcount_t recvs; /*%< Receive in progress */ + + atomic_bool shuttingdown; + + isc_result_t shutdown_result; + + dns_name_t name; /*%< Name of zone to transfer */ + dns_rdataclass_t rdclass; + + dns_messageid_t id; + + /*% + * Requested transfer type (dns_rdatatype_axfr or + * dns_rdatatype_ixfr). The actual transfer type + * may differ due to IXFR->AXFR fallback. + */ + dns_rdatatype_t reqtype; + + isc_sockaddr_t primaryaddr; + isc_sockaddr_t sourceaddr; + + isc_nmhandle_t *handle; + isc_nmhandle_t *readhandle; + isc_nmhandle_t *sendhandle; + + /*% Buffer for IXFR/AXFR request message */ + isc_buffer_t qbuffer; + unsigned char qbuffer_data[512]; + + /*% + * Whether the zone originally had a database attached at the time this + * transfer context was created. Used by xfrin_destroy() when making + * logging decisions. + */ + bool zone_had_db; + + dns_db_t *db; + dns_dbversion_t *ver; + dns_diff_t diff; /*%< Pending database changes */ + int difflen; /*%< Number of pending tuples */ + + xfrin_state_t state; + uint32_t end_serial; + bool is_ixfr; + + unsigned int nmsg; /*%< Number of messages recvd */ + unsigned int nrecs; /*%< Number of records recvd */ + uint64_t nbytes; /*%< Number of bytes received */ + + unsigned int maxrecords; /*%< The maximum number of + * records set for the zone */ + + isc_time_t start; /*%< Start time of the transfer */ + isc_time_t end; /*%< End time of the transfer */ + + dns_tsigkey_t *tsigkey; /*%< Key used to create TSIG */ + isc_buffer_t *lasttsig; /*%< The last TSIG */ + dst_context_t *tsigctx; /*%< TSIG verification context */ + unsigned int sincetsig; /*%< recvd since the last TSIG */ + + dns_transport_t *transport; + + dns_xfrindone_t done; + + /*% + * AXFR- and IXFR-specific data. Only one is used at a time + * according to the is_ixfr flag, so this could be a union, + * but keeping them separate makes it a bit simpler to clean + * things up when destroying the context. + */ + dns_rdatacallbacks_t axfr; + + struct { + uint32_t request_serial; + uint32_t current_serial; + dns_journal_t *journal; + } ixfr; + + dns_rdata_t firstsoa; + unsigned char *firstsoa_data; + + isc_tlsctx_cache_t *tlsctx_cache; + + isc_timer_t *max_time_timer; + isc_timer_t *max_idle_timer; +}; + +#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I') +#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC) + +/**************************************************************************/ +/* + * Forward declarations. + */ + +static void +xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr, + dns_name_t *zonename, dns_rdataclass_t rdclass, + dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr, + const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, + dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache, + dns_xfrin_ctx_t **xfrp); + +static isc_result_t +axfr_init(dns_xfrin_ctx_t *xfr); +static isc_result_t +axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp); +static isc_result_t +axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata); +static isc_result_t +axfr_apply(dns_xfrin_ctx_t *xfr); +static isc_result_t +axfr_commit(dns_xfrin_ctx_t *xfr); +static isc_result_t +axfr_finalize(dns_xfrin_ctx_t *xfr); + +static isc_result_t +ixfr_init(dns_xfrin_ctx_t *xfr); +static isc_result_t +ixfr_apply(dns_xfrin_ctx_t *xfr); +static isc_result_t +ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata); +static isc_result_t +ixfr_commit(dns_xfrin_ctx_t *xfr); + +static isc_result_t +xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl, + dns_rdata_t *rdata); + +static isc_result_t +xfrin_start(dns_xfrin_ctx_t *xfr); + +static void +xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg); +static isc_result_t +xfrin_send_request(dns_xfrin_ctx_t *xfr); +static void +xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg); +static void +xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result, + isc_region_t *region, void *cbarg); + +static void +xfrin_destroy(dns_xfrin_ctx_t *xfr); + +static void +xfrin_timedout(struct isc_task *, struct isc_event *); +static void +xfrin_idledout(struct isc_task *, struct isc_event *); +static void +xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg); +static isc_result_t +render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf); + +static void +xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr, + const char *fmt, va_list ap) ISC_FORMAT_PRINTF(4, 0); + +static void +xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr, + const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5); + +static void +xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); + +/**************************************************************************/ +/* + * AXFR handling + */ + +static isc_result_t +axfr_init(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + + xfr->is_ixfr = false; + + if (xfr->db != NULL) { + dns_db_detach(&xfr->db); + } + + CHECK(axfr_makedb(xfr, &xfr->db)); + dns_rdatacallbacks_init(&xfr->axfr); + CHECK(dns_db_beginload(xfr->db, &xfr->axfr)); + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp) { + isc_result_t result; + + result = dns_db_create(xfr->mctx, /* XXX */ + "rbt", /* XXX guess */ + &xfr->name, dns_dbtype_zone, xfr->rdclass, 0, + NULL, /* XXX guess */ + dbp); + if (result == ISC_R_SUCCESS) { + dns_zone_rpz_enable_db(xfr->zone, *dbp); + dns_zone_catz_enable_db(xfr->zone, *dbp); + } + return (result); +} + +static isc_result_t +axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata) { + isc_result_t result; + + dns_difftuple_t *tuple = NULL; + + if (rdata->rdclass != xfr->rdclass) { + return (DNS_R_BADCLASS); + } + + CHECK(dns_zone_checknames(xfr->zone, name, rdata)); + CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, + &tuple)); + dns_diff_append(&xfr->diff, &tuple); + if (++xfr->difflen > 100) { + CHECK(axfr_apply(xfr)); + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/* + * Store a set of AXFR RRs in the database. + */ +static isc_result_t +axfr_apply(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + uint64_t records; + + CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private)); + xfr->difflen = 0; + dns_diff_clear(&xfr->diff); + if (xfr->maxrecords != 0U) { + result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); + if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { + result = DNS_R_TOOMANYRECORDS; + goto failure; + } + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +axfr_commit(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + + CHECK(axfr_apply(xfr)); + CHECK(dns_db_endload(xfr->db, &xfr->axfr)); + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL)); + + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +axfr_finalize(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + + CHECK(dns_zone_replacedb(xfr->zone, xfr->db, true)); + + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/**************************************************************************/ +/* + * IXFR handling + */ + +static isc_result_t +ixfr_init(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + char *journalfile = NULL; + + if (xfr->reqtype != dns_rdatatype_ixfr) { + xfrin_log(xfr, ISC_LOG_NOTICE, + "got incremental response to AXFR request"); + return (DNS_R_FORMERR); + } + + xfr->is_ixfr = true; + INSIST(xfr->db != NULL); + xfr->difflen = 0; + + journalfile = dns_zone_getjournal(xfr->zone); + if (journalfile != NULL) { + CHECK(dns_journal_open(xfr->mctx, journalfile, + DNS_JOURNAL_CREATE, &xfr->ixfr.journal)); + } + + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, + dns_ttl_t ttl, dns_rdata_t *rdata) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + + if (rdata->rdclass != xfr->rdclass) { + return (DNS_R_BADCLASS); + } + + if (op == DNS_DIFFOP_ADD) { + CHECK(dns_zone_checknames(xfr->zone, name, rdata)); + } + CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, + &tuple)); + dns_diff_append(&xfr->diff, &tuple); + if (++xfr->difflen > 100) { + CHECK(ixfr_apply(xfr)); + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/* + * Apply a set of IXFR changes to the database. + */ +static isc_result_t +ixfr_apply(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + uint64_t records; + + if (xfr->ver == NULL) { + CHECK(dns_db_newversion(xfr->db, &xfr->ver)); + if (xfr->ixfr.journal != NULL) { + CHECK(dns_journal_begin_transaction(xfr->ixfr.journal)); + } + } + CHECK(dns_diff_apply(&xfr->diff, xfr->db, xfr->ver)); + if (xfr->maxrecords != 0U) { + result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); + if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { + result = DNS_R_TOOMANYRECORDS; + goto failure; + } + } + if (xfr->ixfr.journal != NULL) { + result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + dns_diff_clear(&xfr->diff); + xfr->difflen = 0; + result = ISC_R_SUCCESS; +failure: + return (result); +} + +static isc_result_t +ixfr_commit(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + + CHECK(ixfr_apply(xfr)); + if (xfr->ver != NULL) { + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + /* XXX enter ready-to-commit state here */ + if (xfr->ixfr.journal != NULL) { + CHECK(dns_journal_commit(xfr->ixfr.journal)); + } + dns_db_closeversion(xfr->db, &xfr->ver, true); + dns_zone_markdirty(xfr->zone); + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/**************************************************************************/ +/* + * Common AXFR/IXFR protocol code + */ + +/* + * Handle a single incoming resource record according to the current + * state. + */ +static isc_result_t +xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl, + dns_rdata_t *rdata) { + isc_result_t result; + + xfr->nrecs++; + + if (rdata->type == dns_rdatatype_none || + dns_rdatatype_ismeta(rdata->type)) + { + char buf[64]; + dns_rdatatype_format(rdata->type, buf, sizeof(buf)); + xfrin_log(xfr, ISC_LOG_NOTICE, + "Unexpected %s record in zone transfer", buf); + FAIL(DNS_R_FORMERR); + } + + /* + * Immediately reject the entire transfer if the RR that is currently + * being processed is an SOA record that is not placed at the zone + * apex. + */ + if (rdata->type == dns_rdatatype_soa && + !dns_name_equal(&xfr->name, name)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + xfrin_log(xfr, ISC_LOG_DEBUG(3), "SOA name mismatch: '%s'", + namebuf); + FAIL(DNS_R_NOTZONETOP); + } + +redo: + switch (xfr->state) { + case XFRST_SOAQUERY: + if (rdata->type != dns_rdatatype_soa) { + xfrin_log(xfr, ISC_LOG_NOTICE, + "non-SOA response to SOA query"); + FAIL(DNS_R_FORMERR); + } + xfr->end_serial = dns_soa_getserial(rdata); + if (!DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) && + !dns_zone_isforced(xfr->zone)) + { + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "requested serial %u, " + "primary has %u, not updating", + xfr->ixfr.request_serial, xfr->end_serial); + FAIL(DNS_R_UPTODATE); + } + xfr->state = XFRST_GOTSOA; + break; + + case XFRST_GOTSOA: + /* + * Skip other records in the answer section. + */ + break; + + case XFRST_INITIALSOA: + if (rdata->type != dns_rdatatype_soa) { + xfrin_log(xfr, ISC_LOG_NOTICE, + "first RR in zone transfer must be SOA"); + FAIL(DNS_R_FORMERR); + } + /* + * Remember the serial number in the initial SOA. + * We need it to recognize the end of an IXFR. + */ + xfr->end_serial = dns_soa_getserial(rdata); + if (xfr->reqtype == dns_rdatatype_ixfr && + !DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) && + !dns_zone_isforced(xfr->zone)) + { + /* + * This must be the single SOA record that is + * sent when the current version on the primary + * is not newer than the version in the request. + */ + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "requested serial %u, " + "primary has %u, not updating", + xfr->ixfr.request_serial, xfr->end_serial); + FAIL(DNS_R_UPTODATE); + } + xfr->firstsoa = *rdata; + if (xfr->firstsoa_data != NULL) { + isc_mem_free(xfr->mctx, xfr->firstsoa_data); + } + xfr->firstsoa_data = isc_mem_allocate(xfr->mctx, rdata->length); + memcpy(xfr->firstsoa_data, rdata->data, rdata->length); + xfr->firstsoa.data = xfr->firstsoa_data; + xfr->state = XFRST_FIRSTDATA; + break; + + case XFRST_FIRSTDATA: + /* + * If the transfer begins with one SOA record, it is an AXFR, + * if it begins with two SOAs, it is an IXFR. + */ + if (xfr->reqtype == dns_rdatatype_ixfr && + rdata->type == dns_rdatatype_soa && + xfr->ixfr.request_serial == dns_soa_getserial(rdata)) + { + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "got incremental response"); + CHECK(ixfr_init(xfr)); + xfr->state = XFRST_IXFR_DELSOA; + } else { + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "got nonincremental response"); + CHECK(axfr_init(xfr)); + xfr->state = XFRST_AXFR; + } + goto redo; + + case XFRST_IXFR_DELSOA: + INSIST(rdata->type == dns_rdatatype_soa); + CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata)); + xfr->state = XFRST_IXFR_DEL; + break; + + case XFRST_IXFR_DEL: + if (rdata->type == dns_rdatatype_soa) { + uint32_t soa_serial = dns_soa_getserial(rdata); + xfr->state = XFRST_IXFR_ADDSOA; + xfr->ixfr.current_serial = soa_serial; + goto redo; + } + CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata)); + break; + + case XFRST_IXFR_ADDSOA: + INSIST(rdata->type == dns_rdatatype_soa); + CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); + xfr->state = XFRST_IXFR_ADD; + break; + + case XFRST_IXFR_ADD: + if (rdata->type == dns_rdatatype_soa) { + uint32_t soa_serial = dns_soa_getserial(rdata); + if (soa_serial == xfr->end_serial) { + CHECK(ixfr_commit(xfr)); + xfr->state = XFRST_IXFR_END; + break; + } else if (soa_serial != xfr->ixfr.current_serial) { + xfrin_log(xfr, ISC_LOG_NOTICE, + "IXFR out of sync: " + "expected serial %u, got %u", + xfr->ixfr.current_serial, soa_serial); + FAIL(DNS_R_FORMERR); + } else { + CHECK(ixfr_commit(xfr)); + xfr->state = XFRST_IXFR_DELSOA; + goto redo; + } + } + if (rdata->type == dns_rdatatype_ns && + dns_name_iswildcard(name)) + { + FAIL(DNS_R_INVALIDNS); + } + CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); + break; + + case XFRST_AXFR: + /* + * Old BINDs sent cross class A records for non IN classes. + */ + if (rdata->type == dns_rdatatype_a && + rdata->rdclass != xfr->rdclass && + xfr->rdclass != dns_rdataclass_in) + { + break; + } + CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); + if (rdata->type == dns_rdatatype_soa) { + /* + * Use dns_rdata_compare instead of memcmp to + * allow for case differences. + */ + if (dns_rdata_compare(rdata, &xfr->firstsoa) != 0) { + xfrin_log(xfr, ISC_LOG_NOTICE, + "start and ending SOA records " + "mismatch"); + FAIL(DNS_R_FORMERR); + } + CHECK(axfr_commit(xfr)); + xfr->state = XFRST_AXFR_END; + break; + } + break; + case XFRST_AXFR_END: + case XFRST_IXFR_END: + FAIL(DNS_R_EXTRADATA); + FALLTHROUGH; + default: + UNREACHABLE(); + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +isc_result_t +dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, + const isc_sockaddr_t *primaryaddr, + const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, + dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache, + isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done, + dns_xfrin_ctx_t **xfrp) { + dns_name_t *zonename = dns_zone_getorigin(zone); + dns_xfrin_ctx_t *xfr = NULL; + isc_result_t result; + dns_db_t *db = NULL; + + REQUIRE(xfrp != NULL && *xfrp == NULL); + REQUIRE(done != NULL); + REQUIRE(isc_sockaddr_getport(primaryaddr) != 0); + + (void)dns_zone_getdb(zone, &db); + + if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) { + REQUIRE(db != NULL); + } + + xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone), + xfrtype, primaryaddr, sourceaddr, tsigkey, transport, + tlsctx_cache, &xfr); + + if (db != NULL) { + xfr->zone_had_db = true; + } + + xfr->done = done; + + isc_refcount_init(&xfr->references, 1); + + /* + * Set *xfrp now, before calling xfrin_start(). Asynchronous + * netmgr processing could cause the 'done' callback to run in + * another thread before we reached the end of the present + * function. In that case, if *xfrp hadn't already been + * attached, the 'done' function would be unable to detach it. + */ + *xfrp = xfr; + + result = xfrin_start(xfr); + if (result != ISC_R_SUCCESS) { + atomic_store(&xfr->shuttingdown, true); + xfr->shutdown_result = result; + dns_xfrin_detach(xfrp); + } + + if (db != NULL) { + dns_db_detach(&db); + } + + if (result != ISC_R_SUCCESS) { + char zonetext[DNS_NAME_MAXTEXT + 32]; + dns_zone_name(zone, zonetext, sizeof(zonetext)); + xfrin_log1(ISC_LOG_ERROR, zonetext, primaryaddr, + "zone transfer setup failed"); + } + + return (result); +} + +static void +xfrin_cancelio(dns_xfrin_ctx_t *xfr); + +static void +xfrin_timedout(struct isc_task *task, struct isc_event *event) { + UNUSED(task); + + dns_xfrin_ctx_t *xfr = event->ev_arg; + REQUIRE(VALID_XFRIN(xfr)); + + xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum transfer time exceeded"); + isc_event_free(&event); +} + +static void +xfrin_idledout(struct isc_task *task, struct isc_event *event) { + UNUSED(task); + + dns_xfrin_ctx_t *xfr = event->ev_arg; + REQUIRE(VALID_XFRIN(xfr)); + + xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded"); + isc_event_free(&event); +} + +void +dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + xfrin_fail(xfr, ISC_R_CANCELED, "shut down"); +} + +void +dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) { + REQUIRE(VALID_XFRIN(source)); + REQUIRE(target != NULL && *target == NULL); + (void)isc_refcount_increment(&source->references); + + *target = source; +} + +void +dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) { + dns_xfrin_ctx_t *xfr = NULL; + + REQUIRE(xfrp != NULL && VALID_XFRIN(*xfrp)); + + xfr = *xfrp; + *xfrp = NULL; + + if (isc_refcount_decrement(&xfr->references) == 1) { + xfrin_destroy(xfr); + } +} + +static void +xfrin_cancelio(dns_xfrin_ctx_t *xfr) { + if (xfr->readhandle == NULL) { + return; + } + + isc_nm_cancelread(xfr->readhandle); + /* The xfr->readhandle detach will happen in xfrin_recv_done callback */ +} + +static void +xfrin_reset(dns_xfrin_ctx_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + xfrin_log(xfr, ISC_LOG_INFO, "resetting"); + + REQUIRE(xfr->readhandle == NULL); + REQUIRE(xfr->sendhandle == NULL); + + if (xfr->lasttsig != NULL) { + isc_buffer_free(&xfr->lasttsig); + } + + dns_diff_clear(&xfr->diff); + xfr->difflen = 0; + + if (xfr->ixfr.journal != NULL) { + dns_journal_destroy(&xfr->ixfr.journal); + } + + if (xfr->axfr.add_private != NULL) { + (void)dns_db_endload(xfr->db, &xfr->axfr); + } + + if (xfr->ver != NULL) { + dns_db_closeversion(xfr->db, &xfr->ver, false); + } +} + +static void +xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg) { + /* Make sure only the first xfrin_fail() trumps */ + if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false }, + true)) + { + (void)isc_timer_reset(xfr->max_time_timer, + isc_timertype_inactive, NULL, NULL, true); + (void)isc_timer_reset(xfr->max_idle_timer, + isc_timertype_inactive, NULL, NULL, true); + + if (result != DNS_R_UPTODATE && result != DNS_R_TOOMANYRECORDS) + { + xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg, + isc_result_totext(result)); + if (xfr->is_ixfr) { + /* Pass special result code to force AXFR retry + */ + result = DNS_R_BADIXFR; + } + } + xfrin_cancelio(xfr); + /* + * Close the journal. + */ + if (xfr->ixfr.journal != NULL) { + dns_journal_destroy(&xfr->ixfr.journal); + } + if (xfr->done != NULL) { + (xfr->done)(xfr->zone, result); + xfr->done = NULL; + } + xfr->shutdown_result = result; + } +} + +static void +xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr, + dns_name_t *zonename, dns_rdataclass_t rdclass, + dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr, + const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey, + dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache, + dns_xfrin_ctx_t **xfrp) { + dns_xfrin_ctx_t *xfr = NULL; + dns_zonemgr_t *zmgr = dns_zone_getmgr(zone); + isc_timermgr_t *timermgr = dns_zonemgr_gettimermgr(zmgr); + isc_task_t *ztask = NULL; + + xfr = isc_mem_get(mctx, sizeof(*xfr)); + *xfr = (dns_xfrin_ctx_t){ .netmgr = netmgr, + .shutdown_result = ISC_R_UNSET, + .rdclass = rdclass, + .reqtype = reqtype, + .id = (dns_messageid_t)isc_random16(), + .maxrecords = dns_zone_getmaxrecords(zone), + .primaryaddr = *primaryaddr, + .sourceaddr = *sourceaddr, + .firstsoa = DNS_RDATA_INIT, + .magic = XFRIN_MAGIC }; + + isc_mem_attach(mctx, &xfr->mctx); + dns_zone_iattach(zone, &xfr->zone); + dns_name_init(&xfr->name, NULL); + + isc_refcount_init(&xfr->connects, 0); + isc_refcount_init(&xfr->sends, 0); + isc_refcount_init(&xfr->recvs, 0); + + atomic_init(&xfr->shuttingdown, false); + + if (db != NULL) { + dns_db_attach(db, &xfr->db); + } + + dns_diff_init(xfr->mctx, &xfr->diff); + + if (reqtype == dns_rdatatype_soa) { + xfr->state = XFRST_SOAQUERY; + } else { + xfr->state = XFRST_INITIALSOA; + } + + isc_time_now(&xfr->start); + + if (tsigkey != NULL) { + dns_tsigkey_attach(tsigkey, &xfr->tsigkey); + } + + if (transport != NULL) { + dns_transport_attach(transport, &xfr->transport); + } + + dns_name_dup(zonename, mctx, &xfr->name); + + INSIST(isc_sockaddr_pf(primaryaddr) == isc_sockaddr_pf(sourceaddr)); + isc_sockaddr_setport(&xfr->sourceaddr, 0); + + /* + * Reserve 2 bytes for TCP length at the beginning of the buffer. + */ + isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2], + sizeof(xfr->qbuffer_data) - 2); + + isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache); + + dns_zone_gettask(zone, &ztask); + isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask, + xfrin_timedout, xfr, &xfr->max_time_timer); + isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, ztask, + xfrin_idledout, xfr, &xfr->max_idle_timer); + isc_task_detach(&ztask); /* dns_zone_task() attaches to the task */ + + *xfrp = xfr; +} + +static isc_result_t +get_create_tlsctx(const dns_xfrin_ctx_t *xfr, isc_tlsctx_t **pctx, + isc_tlsctx_client_session_cache_t **psess_cache) { + isc_result_t result = ISC_R_FAILURE; + isc_tlsctx_t *tlsctx = NULL, *found = NULL; + isc_tls_cert_store_t *store = NULL, *found_store = NULL; + isc_tlsctx_client_session_cache_t *sess_cache = NULL, + *found_sess_cache = NULL; + uint32_t tls_versions; + const char *ciphers = NULL; + bool prefer_server_ciphers; + const uint16_t family = isc_sockaddr_pf(&xfr->primaryaddr) == PF_INET6 + ? AF_INET6 + : AF_INET; + const char *tlsname = NULL; + + REQUIRE(psess_cache != NULL && *psess_cache == NULL); + REQUIRE(pctx != NULL && *pctx == NULL); + + INSIST(xfr->transport != NULL); + tlsname = dns_transport_get_tlsname(xfr->transport); + INSIST(tlsname != NULL && *tlsname != '\0'); + + /* + * Let's try to re-use the already created context. This way + * we have a chance to resume the TLS session, bypassing the + * full TLS handshake procedure, making establishing + * subsequent TLS connections for XoT faster. + */ + result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname, + isc_tlsctx_cache_tls, family, &found, + &found_store, &found_sess_cache); + if (result != ISC_R_SUCCESS) { + const char *hostname = + dns_transport_get_remote_hostname(xfr->transport); + const char *ca_file = dns_transport_get_cafile(xfr->transport); + const char *cert_file = + dns_transport_get_certfile(xfr->transport); + const char *key_file = + dns_transport_get_keyfile(xfr->transport); + char primary_addr_str[INET6_ADDRSTRLEN] = { 0 }; + isc_netaddr_t primary_netaddr = { 0 }; + bool hostname_ignore_subject; + /* + * So, no context exists. Let's create one using the + * parameters from the configuration file and try to + * store it for further reuse. + */ + result = isc_tlsctx_createclient(&tlsctx); + if (result != ISC_R_SUCCESS) { + goto failure; + } + tls_versions = dns_transport_get_tls_versions(xfr->transport); + if (tls_versions != 0) { + isc_tlsctx_set_protocols(tlsctx, tls_versions); + } + ciphers = dns_transport_get_ciphers(xfr->transport); + if (ciphers != NULL) { + isc_tlsctx_set_cipherlist(tlsctx, ciphers); + } + + if (dns_transport_get_prefer_server_ciphers( + xfr->transport, &prefer_server_ciphers)) + { + isc_tlsctx_prefer_server_ciphers(tlsctx, + prefer_server_ciphers); + } + + if (hostname != NULL || ca_file != NULL) { + /* + * The situation when 'found_store != NULL' while 'found + * == NULL' might appear as there is one to many + * relation between per transport TLS contexts and cert + * stores. That is, there could be one store shared + * between multiple contexts. + */ + if (found_store == NULL) { + /* + * 'ca_file' can equal 'NULL' here, in + * that case the store with system-wide + * CA certificates will be created, just + * as planned. + */ + result = isc_tls_cert_store_create(ca_file, + &store); + + if (result != ISC_R_SUCCESS) { + goto failure; + } + } else { + store = found_store; + } + + INSIST(store != NULL); + if (hostname == NULL) { + /* + * If CA bundle file is specified, but + * hostname is not, then use the primary + * IP address for validation, just like + * dig does. + */ + INSIST(ca_file != NULL); + isc_netaddr_fromsockaddr(&primary_netaddr, + &xfr->primaryaddr); + isc_netaddr_format(&primary_netaddr, + primary_addr_str, + sizeof(primary_addr_str)); + hostname = primary_addr_str; + } + /* + * According to RFC 8310, Subject field MUST NOT + * be inspected when verifying hostname for DoT. + * Only SubjectAltName must be checked. + */ + hostname_ignore_subject = true; + result = isc_tlsctx_enable_peer_verification( + tlsctx, false, store, hostname, + hostname_ignore_subject); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Let's load client certificate and enable + * Mutual TLS. We do that only in the case when + * Strict TLS is enabled, because Mutual TLS is + * an extension of it. + */ + if (cert_file != NULL) { + INSIST(key_file != NULL); + + result = isc_tlsctx_load_certificate( + tlsctx, key_file, cert_file); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + } + + isc_tlsctx_enable_dot_client_alpn(tlsctx); + + isc_tlsctx_client_session_cache_create( + xfr->mctx, tlsctx, + ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE, + &sess_cache); + + found_store = NULL; + result = isc_tlsctx_cache_add(xfr->tlsctx_cache, tlsname, + isc_tlsctx_cache_tls, family, + tlsctx, store, sess_cache, &found, + &found_store, &found_sess_cache); + if (result == ISC_R_EXISTS) { + /* + * It seems the entry has just been created from within + * another thread while we were initialising + * ours. Although this is unlikely, it could happen + * after startup/re-initialisation. In such a case, + * discard the new context and associated data and use + * the already established one from now on. + * + * Such situation will not occur after the + * initial 'warm-up', so it is not critical + * performance-wise. + */ + INSIST(found != NULL); + isc_tlsctx_free(&tlsctx); + isc_tls_cert_store_free(&store); + isc_tlsctx_client_session_cache_detach(&sess_cache); + /* Let's return the data from the cache. */ + *psess_cache = found_sess_cache; + *pctx = found; + } else { + /* + * Adding the fresh values into the cache has been + * successful, let's return them + */ + INSIST(result == ISC_R_SUCCESS); + *psess_cache = sess_cache; + *pctx = tlsctx; + } + } else { + /* + * The cache lookup has been successful, let's return the + * results. + */ + INSIST(result == ISC_R_SUCCESS); + *psess_cache = found_sess_cache; + *pctx = found; + } + + return (ISC_R_SUCCESS); + +failure: + if (tlsctx != NULL) { + isc_tlsctx_free(&tlsctx); + } + + /* + * The 'found_store' is being managed by the TLS context + * cache. Thus, we should keep it as it is, as it will get + * destroyed alongside the cache. As there is one store per + * multiple TLS contexts, we need to handle store deletion in a + * special way. + */ + if (store != NULL && store != found_store) { + isc_tls_cert_store_free(&store); + } + + return (result); +} + +static isc_result_t +xfrin_start(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + dns_xfrin_ctx_t *connect_xfr = NULL; + dns_transport_type_t transport_type = DNS_TRANSPORT_TCP; + isc_tlsctx_t *tlsctx = NULL; + isc_tlsctx_client_session_cache_t *sess_cache = NULL; + isc_interval_t interval; + isc_time_t next; + + (void)isc_refcount_increment0(&xfr->connects); + dns_xfrin_attach(xfr, &connect_xfr); + + if (xfr->transport != NULL) { + transport_type = dns_transport_get_type(xfr->transport); + } + + /* Set the maximum timer */ + isc_interval_set(&interval, dns_zone_getmaxxfrin(xfr->zone), 0); + isc_time_nowplusinterval(&next, &interval); + result = isc_timer_reset(xfr->max_time_timer, isc_timertype_once, &next, + NULL, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Set the idle timer */ + isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0); + isc_time_nowplusinterval(&next, &interval); + result = isc_timer_reset(xfr->max_idle_timer, isc_timertype_once, &next, + NULL, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * XXX: timeouts are hard-coded to 30 seconds; this needs to be + * configurable. + */ + switch (transport_type) { + case DNS_TRANSPORT_TCP: + isc_nm_tcpdnsconnect(xfr->netmgr, &xfr->sourceaddr, + &xfr->primaryaddr, xfrin_connect_done, + connect_xfr, 30000, 0); + break; + case DNS_TRANSPORT_TLS: { + result = get_create_tlsctx(xfr, &tlsctx, &sess_cache); + if (result != ISC_R_SUCCESS) { + goto failure; + } + INSIST(tlsctx != NULL); + isc_nm_tlsdnsconnect(xfr->netmgr, &xfr->sourceaddr, + &xfr->primaryaddr, xfrin_connect_done, + connect_xfr, 30000, 0, tlsctx, sess_cache); + } break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +failure: + isc_refcount_decrement0(&xfr->connects); + dns_xfrin_detach(&connect_xfr); + return (result); +} + +/* XXX the resolver could use this, too */ + +static isc_result_t +render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf) { + dns_compress_t cctx; + bool cleanup_cctx = false; + isc_result_t result; + + CHECK(dns_compress_init(&cctx, -1, mctx)); + cleanup_cctx = true; + CHECK(dns_message_renderbegin(msg, &cctx, buf)); + CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0)); + CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0)); + CHECK(dns_message_rendersection(msg, DNS_SECTION_AUTHORITY, 0)); + CHECK(dns_message_rendersection(msg, DNS_SECTION_ADDITIONAL, 0)); + CHECK(dns_message_renderend(msg)); + result = ISC_R_SUCCESS; +failure: + if (cleanup_cctx) { + dns_compress_invalidate(&cctx); + } + return (result); +} + +/* + * A connection has been established. + */ +static void +xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg; + char sourcetext[ISC_SOCKADDR_FORMATSIZE]; + char signerbuf[DNS_NAME_FORMATSIZE]; + const char *signer = "", *sep = ""; + isc_sockaddr_t sockaddr; + dns_zonemgr_t *zmgr = NULL; + + REQUIRE(VALID_XFRIN(xfr)); + + isc_refcount_decrement0(&xfr->connects); + + if (atomic_load(&xfr->shuttingdown)) { + result = ISC_R_SHUTTINGDOWN; + } + + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "failed to connect"); + goto failure; + } + + result = isc_nm_xfr_checkperm(handle); + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "connected but unable to transfer"); + goto failure; + } + + zmgr = dns_zone_getmgr(xfr->zone); + if (zmgr != NULL) { + dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr, + &xfr->sourceaddr); + } + + xfr->handle = handle; + sockaddr = isc_nmhandle_peeraddr(handle); + isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext)); + + if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) { + dns_name_format(dst_key_name(xfr->tsigkey->key), signerbuf, + sizeof(signerbuf)); + sep = " TSIG "; + signer = signerbuf; + } + + xfrin_log(xfr, ISC_LOG_INFO, "connected using %s%s%s", sourcetext, sep, + signer); + + result = xfrin_send_request(xfr); + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "connected but unable to send"); + } + +failure: + switch (result) { + case ISC_R_SUCCESS: + break; + case ISC_R_NETDOWN: + case ISC_R_HOSTDOWN: + case ISC_R_NETUNREACH: + case ISC_R_HOSTUNREACH: + case ISC_R_CONNREFUSED: + case ISC_R_TIMEDOUT: + /* + * Add the server to unreachable primaries table if + * the server has a permanent networking error or + * the connection attempt as timed out. + */ + zmgr = dns_zone_getmgr(xfr->zone); + if (zmgr != NULL) { + isc_time_t now; + + TIME_NOW(&now); + + dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr, + &xfr->sourceaddr, &now); + } + break; + default: + /* Retry sooner than in 10 minutes */ + break; + } + + dns_xfrin_detach(&xfr); +} + +/* + * Convert a tuple into a dns_name_t suitable for inserting + * into the given dns_message_t. + */ +static isc_result_t +tuple2msgname(dns_difftuple_t *tuple, dns_message_t *msg, dns_name_t **target) { + isc_result_t result; + dns_rdata_t *rdata = NULL; + dns_rdatalist_t *rdl = NULL; + dns_rdataset_t *rds = NULL; + dns_name_t *name = NULL; + + REQUIRE(target != NULL && *target == NULL); + + CHECK(dns_message_gettemprdata(msg, &rdata)); + dns_rdata_init(rdata); + dns_rdata_clone(&tuple->rdata, rdata); + + CHECK(dns_message_gettemprdatalist(msg, &rdl)); + dns_rdatalist_init(rdl); + rdl->type = tuple->rdata.type; + rdl->rdclass = tuple->rdata.rdclass; + rdl->ttl = tuple->ttl; + ISC_LIST_APPEND(rdl->rdata, rdata, link); + + CHECK(dns_message_gettemprdataset(msg, &rds)); + CHECK(dns_rdatalist_tordataset(rdl, rds)); + + CHECK(dns_message_gettempname(msg, &name)); + dns_name_clone(&tuple->name, name); + ISC_LIST_APPEND(name->list, rds, link); + + *target = name; + return (ISC_R_SUCCESS); + +failure: + + if (rds != NULL) { + dns_rdataset_disassociate(rds); + dns_message_puttemprdataset(msg, &rds); + } + if (rdl != NULL) { + ISC_LIST_UNLINK(rdl->rdata, rdata, link); + dns_message_puttemprdatalist(msg, &rdl); + } + if (rdata != NULL) { + dns_message_puttemprdata(msg, &rdata); + } + + return (result); +} + +/* + * Build an *XFR request and send its length prefix. + */ +static isc_result_t +xfrin_send_request(dns_xfrin_ctx_t *xfr) { + isc_result_t result; + isc_region_t region; + dns_rdataset_t *qrdataset = NULL; + dns_message_t *msg = NULL; + dns_difftuple_t *soatuple = NULL; + dns_name_t *qname = NULL; + dns_dbversion_t *ver = NULL; + dns_name_t *msgsoaname = NULL; + dns_xfrin_ctx_t *send_xfr = NULL; + + /* Create the request message */ + dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER, &msg); + CHECK(dns_message_settsigkey(msg, xfr->tsigkey)); + + /* Create a name for the question section. */ + CHECK(dns_message_gettempname(msg, &qname)); + dns_name_clone(&xfr->name, qname); + + /* Formulate the question and attach it to the question name. */ + CHECK(dns_message_gettemprdataset(msg, &qrdataset)); + dns_rdataset_makequestion(qrdataset, xfr->rdclass, xfr->reqtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + qrdataset = NULL; + + dns_message_addname(msg, qname, DNS_SECTION_QUESTION); + qname = NULL; + + if (xfr->reqtype == dns_rdatatype_ixfr) { + /* Get the SOA and add it to the authority section. */ + /* XXX is using the current version the right thing? */ + dns_db_currentversion(xfr->db, &ver); + CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx, + DNS_DIFFOP_EXISTS, &soatuple)); + xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata); + xfr->ixfr.current_serial = xfr->ixfr.request_serial; + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "requesting IXFR for serial %u", + xfr->ixfr.request_serial); + + CHECK(tuple2msgname(soatuple, msg, &msgsoaname)); + dns_message_addname(msg, msgsoaname, DNS_SECTION_AUTHORITY); + } else if (xfr->reqtype == dns_rdatatype_soa) { + CHECK(dns_db_getsoaserial(xfr->db, NULL, + &xfr->ixfr.request_serial)); + } + + xfr->id++; + xfr->nmsg = 0; + xfr->nrecs = 0; + xfr->nbytes = 0; + isc_time_now(&xfr->start); + msg->id = xfr->id; + if (xfr->tsigctx != NULL) { + dst_context_destroy(&xfr->tsigctx); + } + + CHECK(render(msg, xfr->mctx, &xfr->qbuffer)); + + /* + * Free the last tsig, if there is one. + */ + if (xfr->lasttsig != NULL) { + isc_buffer_free(&xfr->lasttsig); + } + + /* + * Save the query TSIG and don't let message_destroy free it. + */ + CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); + + isc_buffer_usedregion(&xfr->qbuffer, ®ion); + INSIST(region.length <= 65535); + + dns_xfrin_attach(xfr, &send_xfr); + isc_nmhandle_attach(send_xfr->handle, &xfr->sendhandle); + isc_refcount_increment0(&send_xfr->sends); + isc_nm_send(xfr->handle, ®ion, xfrin_send_done, send_xfr); + +failure: + if (qname != NULL) { + dns_message_puttempname(msg, &qname); + } + if (qrdataset != NULL) { + dns_message_puttemprdataset(msg, &qrdataset); + } + if (msg != NULL) { + dns_message_detach(&msg); + } + if (soatuple != NULL) { + dns_difftuple_free(&soatuple); + } + if (ver != NULL) { + dns_db_closeversion(xfr->db, &ver, false); + } + + return (result); +} + +static void +xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg; + dns_xfrin_ctx_t *recv_xfr = NULL; + + REQUIRE(VALID_XFRIN(xfr)); + + isc_refcount_decrement0(&xfr->sends); + if (atomic_load(&xfr->shuttingdown)) { + result = ISC_R_SHUTTINGDOWN; + } + + CHECK(result); + + xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data"); + + dns_xfrin_attach(xfr, &recv_xfr); + isc_nmhandle_attach(handle, &recv_xfr->readhandle); + isc_refcount_increment0(&recv_xfr->recvs); + isc_nm_read(recv_xfr->handle, xfrin_recv_done, recv_xfr); + +failure: + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "failed sending request data"); + } + + isc_nmhandle_detach(&xfr->sendhandle); + dns_xfrin_detach(&xfr); /* send_xfr */ +} + +static void +xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result, + isc_region_t *region, void *cbarg) { + dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg; + dns_message_t *msg = NULL; + dns_name_t *name = NULL; + const dns_name_t *tsigowner = NULL; + isc_buffer_t buffer; + isc_sockaddr_t peer; + + REQUIRE(VALID_XFRIN(xfr)); + + isc_refcount_decrement0(&xfr->recvs); + + if (atomic_load(&xfr->shuttingdown)) { + result = ISC_R_SHUTTINGDOWN; + } + + /* Stop the idle timer */ + (void)isc_timer_reset(xfr->max_idle_timer, isc_timertype_inactive, NULL, + NULL, true); + + CHECK(result); + + xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", region->length); + + dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + + CHECK(dns_message_settsigkey(msg, xfr->tsigkey)); + CHECK(dns_message_setquerytsig(msg, xfr->lasttsig)); + + msg->tsigctx = xfr->tsigctx; + xfr->tsigctx = NULL; + + dns_message_setclass(msg, xfr->rdclass); + + if (xfr->nmsg > 0) { + msg->tcp_continuation = 1; + } + + isc_buffer_init(&buffer, region->base, region->length); + isc_buffer_add(&buffer, region->length); + peer = isc_nmhandle_peeraddr(handle); + + result = dns_message_parse(msg, &buffer, + DNS_MESSAGEPARSE_PRESERVEORDER); + if (result == ISC_R_SUCCESS) { + dns_message_logpacket(msg, "received message from", &peer, + DNS_LOGCATEGORY_XFER_IN, + DNS_LOGMODULE_XFER_IN, ISC_LOG_DEBUG(10), + xfr->mctx); + } else { + xfrin_log(xfr, ISC_LOG_DEBUG(10), "dns_message_parse: %s", + isc_result_totext(result)); + } + + if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror || + msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass || + msg->id != xfr->id) + { + if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror) + { + result = dns_result_fromrcode(msg->rcode); + } else if (result == ISC_R_SUCCESS && + msg->opcode != dns_opcode_query) + { + result = DNS_R_UNEXPECTEDOPCODE; + } else if (result == ISC_R_SUCCESS && + msg->rdclass != xfr->rdclass) + { + result = DNS_R_BADCLASS; + } else if (result == ISC_R_SUCCESS || result == DNS_R_NOERROR) { + result = DNS_R_UNEXPECTEDID; + } + + if (xfr->reqtype == dns_rdatatype_axfr || + xfr->reqtype == dns_rdatatype_soa) + { + goto failure; + } + + xfrin_log(xfr, ISC_LOG_DEBUG(3), "got %s, retrying with AXFR", + isc_result_totext(result)); + try_axfr: + isc_nmhandle_detach(&xfr->readhandle); + dns_message_detach(&msg); + xfrin_reset(xfr); + xfr->reqtype = dns_rdatatype_soa; + xfr->state = XFRST_SOAQUERY; + result = xfrin_start(xfr); + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "failed setting up socket"); + } + dns_xfrin_detach(&xfr); /* recv_xfr */ + return; + } + + /* + * The question section should exist for SOA and in the first + * message of a AXFR or IXFR response. The question section + * may exist in the 2nd and subsequent messages in a AXFR or + * IXFR response. If the question section exists it should + * match the question that was sent. + */ + if (msg->counts[DNS_SECTION_QUESTION] > 1) { + xfrin_log(xfr, ISC_LOG_NOTICE, "too many questions (%u)", + msg->counts[DNS_SECTION_QUESTION]); + result = DNS_R_FORMERR; + goto failure; + } + + if ((xfr->state == XFRST_SOAQUERY || xfr->state == XFRST_INITIALSOA) && + msg->counts[DNS_SECTION_QUESTION] != 1) + { + xfrin_log(xfr, ISC_LOG_NOTICE, "missing question section"); + result = DNS_R_FORMERR; + goto failure; + } + + for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION); + result == ISC_R_SUCCESS; + result = dns_message_nextname(msg, DNS_SECTION_QUESTION)) + { + dns_rdataset_t *rds = NULL; + + name = NULL; + dns_message_currentname(msg, DNS_SECTION_QUESTION, &name); + if (!dns_name_equal(name, &xfr->name)) { + result = DNS_R_FORMERR; + xfrin_log(xfr, ISC_LOG_NOTICE, + "question name mismatch"); + goto failure; + } + rds = ISC_LIST_HEAD(name->list); + INSIST(rds != NULL); + if (rds->type != xfr->reqtype) { + result = DNS_R_FORMERR; + xfrin_log(xfr, ISC_LOG_NOTICE, + "question type mismatch"); + goto failure; + } + if (rds->rdclass != xfr->rdclass) { + result = DNS_R_FORMERR; + xfrin_log(xfr, ISC_LOG_NOTICE, + "question class mismatch"); + goto failure; + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } + + /* + * Does the server know about IXFR? If it doesn't we will get + * a message with a empty answer section or a potentially a CNAME / + * DNAME, the later is handled by xfr_rr() which will return FORMERR + * if the first RR in the answer section is not a SOA record. + */ + if (xfr->reqtype == dns_rdatatype_ixfr && + xfr->state == XFRST_INITIALSOA && + msg->counts[DNS_SECTION_ANSWER] == 0) + { + xfrin_log(xfr, ISC_LOG_DEBUG(3), + "empty answer section, retrying with AXFR"); + goto try_axfr; + } + + if (xfr->reqtype == dns_rdatatype_soa && + (msg->flags & DNS_MESSAGEFLAG_AA) == 0) + { + FAIL(DNS_R_NOTAUTHORITATIVE); + } + + result = dns_message_checksig(msg, dns_zone_getview(xfr->zone)); + if (result != ISC_R_SUCCESS) { + xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s", + isc_result_totext(result)); + goto failure; + } + + for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(msg, DNS_SECTION_ANSWER)) + { + dns_rdataset_t *rds = NULL; + + name = NULL; + dns_message_currentname(msg, DNS_SECTION_ANSWER, &name); + for (rds = ISC_LIST_HEAD(name->list); rds != NULL; + rds = ISC_LIST_NEXT(rds, link)) + { + for (result = dns_rdataset_first(rds); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rds)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(rds, &rdata); + CHECK(xfr_rr(xfr, name, rds->ttl, &rdata)); + } + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } + + if (dns_message_gettsig(msg, &tsigowner) != NULL) { + /* + * Reset the counter. + */ + xfr->sincetsig = 0; + + /* + * Free the last tsig, if there is one. + */ + if (xfr->lasttsig != NULL) { + isc_buffer_free(&xfr->lasttsig); + } + + /* + * Update the last tsig pointer. + */ + CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); + } else if (dns_message_gettsigkey(msg) != NULL) { + xfr->sincetsig++; + if (xfr->sincetsig > 100 || xfr->nmsg == 0 || + xfr->state == XFRST_AXFR_END || + xfr->state == XFRST_IXFR_END) + { + result = DNS_R_EXPECTEDTSIG; + goto failure; + } + } + + /* + * Update the number of messages received. + */ + xfr->nmsg++; + + /* + * Update the number of bytes received. + */ + xfr->nbytes += buffer.used; + + /* + * Take the context back. + */ + INSIST(xfr->tsigctx == NULL); + xfr->tsigctx = msg->tsigctx; + msg->tsigctx = NULL; + + switch (xfr->state) { + case XFRST_GOTSOA: + xfr->reqtype = dns_rdatatype_axfr; + xfr->state = XFRST_INITIALSOA; + CHECK(xfrin_send_request(xfr)); + break; + case XFRST_AXFR_END: + CHECK(axfr_finalize(xfr)); + FALLTHROUGH; + case XFRST_IXFR_END: + /* + * Close the journal. + */ + if (xfr->ixfr.journal != NULL) { + dns_journal_destroy(&xfr->ixfr.journal); + } + + /* + * Inform the caller we succeeded. + */ + if (xfr->done != NULL) { + (xfr->done)(xfr->zone, ISC_R_SUCCESS); + xfr->done = NULL; + } + + atomic_store(&xfr->shuttingdown, true); + (void)isc_timer_reset(xfr->max_time_timer, + isc_timertype_inactive, NULL, NULL, true); + xfr->shutdown_result = ISC_R_SUCCESS; + break; + default: + /* + * Read the next message. + */ + /* The readhandle is still attached */ + /* The recv_xfr is still attached */ + dns_message_detach(&msg); + isc_refcount_increment0(&xfr->recvs); + isc_nm_read(xfr->handle, xfrin_recv_done, xfr); + isc_time_t next; + isc_interval_t interval; + isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0); + isc_time_nowplusinterval(&next, &interval); + result = isc_timer_reset(xfr->max_idle_timer, + isc_timertype_once, &next, NULL, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + return; + } + +failure: + if (result != ISC_R_SUCCESS) { + xfrin_fail(xfr, result, "failed while receiving responses"); + } + + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_nmhandle_detach(&xfr->readhandle); + dns_xfrin_detach(&xfr); /* recv_xfr */ +} + +static void +xfrin_destroy(dns_xfrin_ctx_t *xfr) { + uint64_t msecs; + uint64_t persec; + const char *result_str; + + REQUIRE(VALID_XFRIN(xfr)); + + /* Safe-guards */ + REQUIRE(atomic_load(&xfr->shuttingdown)); + isc_refcount_destroy(&xfr->references); + isc_refcount_destroy(&xfr->connects); + isc_refcount_destroy(&xfr->recvs); + isc_refcount_destroy(&xfr->sends); + + INSIST(xfr->shutdown_result != ISC_R_UNSET); + + /* + * If we're called through dns_xfrin_detach() and are not + * shutting down, we can't know what the transfer status is as + * we are only called when the last reference is lost. + */ + result_str = isc_result_totext(xfr->shutdown_result); + xfrin_log(xfr, ISC_LOG_INFO, "Transfer status: %s", result_str); + + /* + * Calculate the length of time the transfer took, + * and print a log message with the bytes and rate. + */ + isc_time_now(&xfr->end); + msecs = isc_time_microdiff(&xfr->end, &xfr->start) / 1000; + if (msecs == 0) { + msecs = 1; + } + persec = (xfr->nbytes * 1000) / msecs; + xfrin_log(xfr, ISC_LOG_INFO, + "Transfer completed: %d messages, %d records, " + "%" PRIu64 " bytes, " + "%u.%03u secs (%u bytes/sec) (serial %u)", + xfr->nmsg, xfr->nrecs, xfr->nbytes, + (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000), + (unsigned int)persec, xfr->end_serial); + + if (xfr->readhandle != NULL) { + isc_nmhandle_detach(&xfr->readhandle); + } + if (xfr->sendhandle != NULL) { + isc_nmhandle_detach(&xfr->sendhandle); + } + + if (xfr->transport != NULL) { + dns_transport_detach(&xfr->transport); + } + + if (xfr->tsigkey != NULL) { + dns_tsigkey_detach(&xfr->tsigkey); + } + + if (xfr->lasttsig != NULL) { + isc_buffer_free(&xfr->lasttsig); + } + + dns_diff_clear(&xfr->diff); + + if (xfr->ixfr.journal != NULL) { + dns_journal_destroy(&xfr->ixfr.journal); + } + + if (xfr->axfr.add_private != NULL) { + (void)dns_db_endload(xfr->db, &xfr->axfr); + } + + if (xfr->tsigctx != NULL) { + dst_context_destroy(&xfr->tsigctx); + } + + if ((xfr->name.attributes & DNS_NAMEATTR_DYNAMIC) != 0) { + dns_name_free(&xfr->name, xfr->mctx); + } + + if (xfr->ver != NULL) { + dns_db_closeversion(xfr->db, &xfr->ver, false); + } + + if (xfr->db != NULL) { + dns_db_detach(&xfr->db); + } + + if (xfr->zone != NULL) { + if (!xfr->zone_had_db && + xfr->shutdown_result == ISC_R_SUCCESS && + dns_zone_gettype(xfr->zone) == dns_zone_mirror) + { + dns_zone_log(xfr->zone, ISC_LOG_INFO, + "mirror zone is now in use"); + } + xfrin_log(xfr, ISC_LOG_DEBUG(99), "freeing transfer context"); + /* + * xfr->zone must not be detached before xfrin_log() is called. + */ + dns_zone_idetach(&xfr->zone); + } + + if (xfr->firstsoa_data != NULL) { + isc_mem_free(xfr->mctx, xfr->firstsoa_data); + } + + if (xfr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&xfr->tlsctx_cache); + } + + isc_timer_destroy(&xfr->max_idle_timer); + isc_timer_destroy(&xfr->max_time_timer); + + isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr)); +} + +/* + * Log incoming zone transfer messages in a format like + * transfer of from
: + */ +static void +xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr, + const char *fmt, va_list ap) { + char primarytext[ISC_SOCKADDR_FORMATSIZE]; + char msgtext[2048]; + + isc_sockaddr_format(primaryaddr, primarytext, sizeof(primarytext)); + vsnprintf(msgtext, sizeof(msgtext), fmt, ap); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN, + level, "transfer of '%s' from %s: %s", zonetext, + primarytext, msgtext); +} + +/* + * Logging function for use when a xfrin_ctx_t has not yet been created. + */ + +static void +xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr, + const char *fmt, ...) { + va_list ap; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + xfrin_logv(level, zonetext, primaryaddr, fmt, ap); + va_end(ap); +} + +/* + * Logging function for use when there is a xfrin_ctx_t. + */ + +static void +xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...) { + va_list ap; + char zonetext[DNS_NAME_MAXTEXT + 32]; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + dns_zone_name(xfr->zone, zonetext, sizeof(zonetext)); + + va_start(ap, fmt); + xfrin_logv(level, zonetext, &xfr->primaryaddr, fmt, ap); + va_end(ap); +} diff --git a/lib/dns/zone.c b/lib/dns/zone.c new file mode 100644 index 0000000..4428e3d --- /dev/null +++ b/lib/dns/zone.c @@ -0,0 +1,23706 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "zone_p.h" + +#define ZONE_MAGIC ISC_MAGIC('Z', 'O', 'N', 'E') +#define DNS_ZONE_VALID(zone) ISC_MAGIC_VALID(zone, ZONE_MAGIC) + +#define NOTIFY_MAGIC ISC_MAGIC('N', 't', 'f', 'y') +#define DNS_NOTIFY_VALID(notify) ISC_MAGIC_VALID(notify, NOTIFY_MAGIC) + +#define CHECKDS_MAGIC ISC_MAGIC('C', 'h', 'D', 'S') +#define DNS_CHECKDS_VALID(checkds) ISC_MAGIC_VALID(checkds, CHECKDS_MAGIC) + +#define STUB_MAGIC ISC_MAGIC('S', 't', 'u', 'b') +#define DNS_STUB_VALID(stub) ISC_MAGIC_VALID(stub, STUB_MAGIC) + +#define ZONEMGR_MAGIC ISC_MAGIC('Z', 'm', 'g', 'r') +#define DNS_ZONEMGR_VALID(stub) ISC_MAGIC_VALID(stub, ZONEMGR_MAGIC) + +#define LOAD_MAGIC ISC_MAGIC('L', 'o', 'a', 'd') +#define DNS_LOAD_VALID(load) ISC_MAGIC_VALID(load, LOAD_MAGIC) + +#define FORWARD_MAGIC ISC_MAGIC('F', 'o', 'r', 'w') +#define DNS_FORWARD_VALID(load) ISC_MAGIC_VALID(load, FORWARD_MAGIC) + +#define IO_MAGIC ISC_MAGIC('Z', 'm', 'I', 'O') +#define DNS_IO_VALID(load) ISC_MAGIC_VALID(load, IO_MAGIC) + +#define KEYMGMT_MAGIC ISC_MAGIC('M', 'g', 'm', 't') +#define DNS_KEYMGMT_VALID(load) ISC_MAGIC_VALID(load, KEYMGMT_MAGIC) + +#define KEYFILEIO_MAGIC ISC_MAGIC('K', 'y', 'I', 'O') +#define DNS_KEYFILEIO_VALID(kfio) ISC_MAGIC_VALID(kfio, KEYFILEIO_MAGIC) + +/*% + * Ensure 'a' is at least 'min' but not more than 'max'. + */ +#define RANGE(a, min, max) (((a) < (min)) ? (min) : ((a) < (max) ? (a) : (max))) + +#define NSEC3REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0) + +/*% + * Key flags + */ +#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0) +#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0) +#define ID(x) dst_key_id(x) +#define ALG(x) dst_key_alg(x) + +/*% + * KASP flags + */ +#define KASP_LOCK(k) \ + if ((k) != NULL) { \ + LOCK((&((k)->lock))); \ + } + +#define KASP_UNLOCK(k) \ + if ((k) != NULL) { \ + UNLOCK((&((k)->lock))); \ + } + +/* + * Default values. + */ +#define DNS_DEFAULT_IDLEIN 3600 /*%< 1 hour */ +#define DNS_DEFAULT_IDLEOUT 3600 /*%< 1 hour */ +#define MAX_XFER_TIME (2 * 3600) /*%< Documented default is 2 hours */ +#define RESIGN_DELAY 3600 /*%< 1 hour */ + +#ifndef DNS_MAX_EXPIRE +#define DNS_MAX_EXPIRE 14515200 /*%< 24 weeks */ +#endif /* ifndef DNS_MAX_EXPIRE */ + +#ifndef DNS_DUMP_DELAY +#define DNS_DUMP_DELAY 900 /*%< 15 minutes */ +#endif /* ifndef DNS_DUMP_DELAY */ + +typedef struct dns_notify dns_notify_t; +typedef struct dns_checkds dns_checkds_t; +typedef struct dns_stub dns_stub_t; +typedef struct dns_load dns_load_t; +typedef struct dns_forward dns_forward_t; +typedef ISC_LIST(dns_forward_t) dns_forwardlist_t; +typedef struct dns_io dns_io_t; +typedef ISC_LIST(dns_io_t) dns_iolist_t; +typedef struct dns_keymgmt dns_keymgmt_t; +typedef struct dns_signing dns_signing_t; +typedef ISC_LIST(dns_signing_t) dns_signinglist_t; +typedef struct dns_nsec3chain dns_nsec3chain_t; +typedef ISC_LIST(dns_nsec3chain_t) dns_nsec3chainlist_t; +typedef struct dns_keyfetch dns_keyfetch_t; +typedef struct dns_asyncload dns_asyncload_t; +typedef struct dns_include dns_include_t; + +#define DNS_ZONE_CHECKLOCK +#ifdef DNS_ZONE_CHECKLOCK +#define LOCK_ZONE(z) \ + do { \ + LOCK(&(z)->lock); \ + INSIST(!(z)->locked); \ + (z)->locked = true; \ + } while (0) +#define UNLOCK_ZONE(z) \ + do { \ + (z)->locked = false; \ + UNLOCK(&(z)->lock); \ + } while (0) +#define LOCKED_ZONE(z) ((z)->locked) +#define TRYLOCK_ZONE(result, z) \ + do { \ + result = isc_mutex_trylock(&(z)->lock); \ + if (result == ISC_R_SUCCESS) { \ + INSIST(!(z)->locked); \ + (z)->locked = true; \ + } \ + } while (0) +#else /* ifdef DNS_ZONE_CHECKLOCK */ +#define LOCK_ZONE(z) LOCK(&(z)->lock) +#define UNLOCK_ZONE(z) UNLOCK(&(z)->lock) +#define LOCKED_ZONE(z) true +#define TRYLOCK_ZONE(result, z) \ + do { \ + result = isc_mutex_trylock(&(z)->lock); \ + } while (0) +#endif /* ifdef DNS_ZONE_CHECKLOCK */ + +#define ZONEDB_INITLOCK(l) isc_rwlock_init((l), 0, 0) +#define ZONEDB_DESTROYLOCK(l) isc_rwlock_destroy(l) +#define ZONEDB_LOCK(l, t) RWLOCK((l), (t)) +#define ZONEDB_UNLOCK(l, t) RWUNLOCK((l), (t)) + +#ifdef ENABLE_AFL +extern bool dns_fuzzing_resolver; +#endif /* ifdef ENABLE_AFL */ + +/*% + * Hold key file IO locks. + */ +typedef struct dns_keyfileio { + unsigned int magic; + struct dns_keyfileio *next; + uint32_t hashval; + dns_fixedname_t fname; + dns_name_t *name; + isc_refcount_t references; + isc_mutex_t lock; +} dns_keyfileio_t; + +struct dns_keymgmt { + unsigned int magic; + isc_rwlock_t lock; + isc_mem_t *mctx; + + dns_keyfileio_t **table; + + atomic_uint_fast32_t count; + + uint32_t bits; +}; + +struct dns_zone { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; +#ifdef DNS_ZONE_CHECKLOCK + bool locked; +#endif /* ifdef DNS_ZONE_CHECKLOCK */ + isc_mem_t *mctx; + isc_refcount_t erefs; + + isc_rwlock_t dblock; + dns_db_t *db; /* Locked by dblock */ + + /* Locked */ + dns_zonemgr_t *zmgr; + ISC_LINK(dns_zone_t) link; /* Used by zmgr. */ + isc_timer_t *timer; + isc_refcount_t irefs; + dns_name_t origin; + char *masterfile; + const FILE *stream; /* loading from a stream? */ + ISC_LIST(dns_include_t) includes; /* Include files */ + ISC_LIST(dns_include_t) newincludes; /* Loading */ + unsigned int nincludes; + dns_masterformat_t masterformat; + const dns_master_style_t *masterstyle; + char *journal; + int32_t journalsize; + dns_rdataclass_t rdclass; + dns_zonetype_t type; + atomic_uint_fast64_t flags; + atomic_uint_fast64_t options; + unsigned int db_argc; + char **db_argv; + isc_time_t expiretime; + isc_time_t refreshtime; + isc_time_t dumptime; + isc_time_t loadtime; + isc_time_t notifytime; + isc_time_t resigntime; + isc_time_t keywarntime; + isc_time_t signingtime; + isc_time_t nsec3chaintime; + isc_time_t refreshkeytime; + uint32_t refreshkeyinterval; + uint32_t refreshkeycount; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + isc_stdtime_t key_expiry; + isc_stdtime_t log_key_expired_timer; + char *keydirectory; + dns_keyfileio_t *kfio; + + uint32_t maxrefresh; + uint32_t minrefresh; + uint32_t maxretry; + uint32_t minretry; + + uint32_t maxrecords; + + isc_sockaddr_t *primaries; + dns_name_t **primarykeynames; + dns_name_t **primarytlsnames; + bool *primariesok; + unsigned int primariescnt; + unsigned int curprimary; + isc_sockaddr_t primaryaddr; + + isc_sockaddr_t *parentals; + dns_name_t **parentalkeynames; + dns_name_t **parentaltlsnames; + dns_dnsseckeylist_t checkds_ok; + unsigned int parentalscnt; + isc_sockaddr_t parentaladdr; + + dns_notifytype_t notifytype; + isc_sockaddr_t *notify; + dns_name_t **notifykeynames; + dns_name_t **notifytlsnames; + unsigned int notifycnt; + isc_sockaddr_t notifyfrom; + isc_task_t *task; + isc_task_t *loadtask; + isc_sockaddr_t notifysrc4; + isc_sockaddr_t notifysrc6; + isc_sockaddr_t parentalsrc4; + isc_sockaddr_t parentalsrc6; + isc_sockaddr_t xfrsource4; + isc_sockaddr_t xfrsource6; + isc_sockaddr_t altxfrsource4; + isc_sockaddr_t altxfrsource6; + isc_sockaddr_t sourceaddr; + dns_xfrin_ctx_t *xfr; /* task locked */ + dns_tsigkey_t *tsigkey; /* key used for xfr */ + dns_transport_t *transport; /* transport used for xfr */ + /* Access Control Lists */ + dns_acl_t *update_acl; + dns_acl_t *forward_acl; + dns_acl_t *notify_acl; + dns_acl_t *query_acl; + dns_acl_t *queryon_acl; + dns_acl_t *xfr_acl; + bool update_disabled; + bool zero_no_soa_ttl; + dns_severity_t check_names; + ISC_LIST(dns_notify_t) notifies; + ISC_LIST(dns_checkds_t) checkds_requests; + dns_request_t *request; + dns_loadctx_t *lctx; + dns_io_t *readio; + dns_dumpctx_t *dctx; + dns_io_t *writeio; + uint32_t maxxfrin; + uint32_t maxxfrout; + uint32_t idlein; + uint32_t idleout; + isc_event_t ctlevent; + dns_ssutable_t *ssutable; + uint32_t sigvalidityinterval; + uint32_t keyvalidityinterval; + uint32_t sigresigninginterval; + dns_view_t *view; + dns_view_t *prev_view; + dns_kasp_t *kasp; + dns_checkmxfunc_t checkmx; + dns_checksrvfunc_t checksrv; + dns_checknsfunc_t checkns; + /*% + * Zones in certain states such as "waiting for zone transfer" + * or "zone transfer in progress" are kept on per-state linked lists + * in the zone manager using the 'statelink' field. The 'statelist' + * field points at the list the zone is currently on. It the zone + * is not on any such list, statelist is NULL. + */ + ISC_LINK(dns_zone_t) statelink; + dns_zonelist_t *statelist; + /*% + * Statistics counters about zone management. + */ + isc_stats_t *stats; + /*% + * Optional per-zone statistics counters. Counted outside of this + * module. + */ + dns_zonestat_level_t statlevel; + bool requeststats_on; + isc_stats_t *requeststats; + dns_stats_t *rcvquerystats; + dns_stats_t *dnssecsignstats; + uint32_t notifydelay; + dns_isselffunc_t isself; + void *isselfarg; + + char *strnamerd; + char *strname; + char *strrdclass; + char *strviewname; + + /*% + * Serial number for deferred journal compaction. + */ + uint32_t compact_serial; + /*% + * Keys that are signing the zone for the first time. + */ + dns_signinglist_t signing; + dns_nsec3chainlist_t nsec3chain; + /*% + * List of outstanding NSEC3PARAM change requests. + */ + isc_eventlist_t setnsec3param_queue; + /*% + * Signing / re-signing quantum stopping parameters. + */ + uint32_t signatures; + uint32_t nodes; + dns_rdatatype_t privatetype; + + /*% + * Autosigning/key-maintenance options + */ + atomic_uint_fast64_t keyopts; + + /*% + * True if added by "rndc addzone" + */ + bool added; + + /*% + * True if added by automatically by named. + */ + bool automatic; + + /*% + * response policy data to be relayed to the database + */ + dns_rpz_zones_t *rpzs; + dns_rpz_num_t rpz_num; + + /*% + * catalog zone data + */ + dns_catz_zones_t *catzs; + + /*% + * parent catalog zone + */ + dns_catz_zone_t *parentcatz; + + /*% + * Serial number update method. + */ + dns_updatemethod_t updatemethod; + + /*% + * whether ixfr is requested + */ + bool requestixfr; + uint32_t ixfr_ratio; + + /*% + * whether EDNS EXPIRE is requested + */ + bool requestexpire; + + /*% + * Outstanding forwarded UPDATE requests. + */ + dns_forwardlist_t forwards; + + dns_zone_t *raw; + dns_zone_t *secure; + + bool sourceserialset; + uint32_t sourceserial; + + /*% + * soa and maximum zone ttl + */ + dns_ttl_t soattl; + dns_ttl_t maxttl; + + /* + * Inline zone signing state. + */ + dns_diff_t rss_diff; + isc_eventlist_t rss_events; + isc_eventlist_t rss_post; + dns_dbversion_t *rss_newver; + dns_dbversion_t *rss_oldver; + dns_db_t *rss_db; + dns_zone_t *rss_raw; + isc_event_t *rss_event; + dns_update_state_t *rss_state; + + isc_stats_t *gluecachestats; +}; + +#define zonediff_init(z, d) \ + do { \ + dns__zonediff_t *_z = (z); \ + (_z)->diff = (d); \ + (_z)->offline = false; \ + } while (0) + +#define DNS_ZONE_FLAG(z, f) ((atomic_load_relaxed(&(z)->flags) & (f)) != 0) +#define DNS_ZONE_SETFLAG(z, f) atomic_fetch_or(&(z)->flags, (f)) +#define DNS_ZONE_CLRFLAG(z, f) atomic_fetch_and(&(z)->flags, ~(f)) +typedef enum { + DNS_ZONEFLG_REFRESH = 0x00000001U, /*%< refresh check in progress */ + DNS_ZONEFLG_NEEDDUMP = 0x00000002U, /*%< zone need consolidation */ + DNS_ZONEFLG_USEVC = 0x00000004U, /*%< use tcp for refresh query */ + DNS_ZONEFLG_DUMPING = 0x00000008U, /*%< a dump is in progress */ + DNS_ZONEFLG_HASINCLUDE = 0x00000010U, /*%< $INCLUDE in zone file */ + DNS_ZONEFLG_LOADED = 0x00000020U, /*%< database has loaded */ + DNS_ZONEFLG_EXITING = 0x00000040U, /*%< zone is being destroyed */ + DNS_ZONEFLG_EXPIRED = 0x00000080U, /*%< zone has expired */ + DNS_ZONEFLG_NEEDREFRESH = 0x00000100U, /*%< refresh check needed */ + DNS_ZONEFLG_UPTODATE = 0x00000200U, /*%< zone contents are + * up-to-date */ + DNS_ZONEFLG_NEEDNOTIFY = 0x00000400U, /*%< need to send out notify + * messages */ + DNS_ZONEFLG_FIXJOURNAL = 0x00000800U, /*%< journal file had + * recoverable error, + * needs rewriting */ + DNS_ZONEFLG_NOPRIMARIES = 0x00001000U, /*%< an attempt to refresh a + * zone with no primaries + * occurred */ + DNS_ZONEFLG_LOADING = 0x00002000U, /*%< load from disk in progress*/ + DNS_ZONEFLG_HAVETIMERS = 0x00004000U, /*%< timer values have been set + * from SOA (if not set, we + * are still using + * default timer values) */ + DNS_ZONEFLG_FORCEXFER = 0x00008000U, /*%< Force a zone xfer */ + DNS_ZONEFLG_NOREFRESH = 0x00010000U, + DNS_ZONEFLG_DIALNOTIFY = 0x00020000U, + DNS_ZONEFLG_DIALREFRESH = 0x00040000U, + DNS_ZONEFLG_SHUTDOWN = 0x00080000U, + DNS_ZONEFLG_NOIXFR = 0x00100000U, /*%< IXFR failed, force AXFR */ + DNS_ZONEFLG_FLUSH = 0x00200000U, + DNS_ZONEFLG_NOEDNS = 0x00400000U, + DNS_ZONEFLG_USEALTXFRSRC = 0x00800000U, + DNS_ZONEFLG_SOABEFOREAXFR = 0x01000000U, + DNS_ZONEFLG_NEEDCOMPACT = 0x02000000U, + DNS_ZONEFLG_REFRESHING = 0x04000000U, /*%< Refreshing keydata */ + DNS_ZONEFLG_THAW = 0x08000000U, + DNS_ZONEFLG_LOADPENDING = 0x10000000U, /*%< Loading scheduled */ + DNS_ZONEFLG_NODELAY = 0x20000000U, + DNS_ZONEFLG_SENDSECURE = 0x40000000U, + DNS_ZONEFLG_NEEDSTARTUPNOTIFY = 0x80000000U, /*%< need to send out + * notify due to the zone + * just being loaded for + * the first time. */ + /* + * DO NOT add any new zone flags here until all platforms + * support 64-bit enum values. Currently they fail on + * Windows. + */ + DNS_ZONEFLG___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ +} dns_zoneflg_t; + +#define DNS_ZONE_OPTION(z, o) ((atomic_load_relaxed(&(z)->options) & (o)) != 0) +#define DNS_ZONE_SETOPTION(z, o) atomic_fetch_or(&(z)->options, (o)) +#define DNS_ZONE_CLROPTION(z, o) atomic_fetch_and(&(z)->options, ~(o)) + +#define DNS_ZONEKEY_OPTION(z, o) \ + ((atomic_load_relaxed(&(z)->keyopts) & (o)) != 0) +#define DNS_ZONEKEY_SETOPTION(z, o) atomic_fetch_or(&(z)->keyopts, (o)) +#define DNS_ZONEKEY_CLROPTION(z, o) atomic_fetch_and(&(z)->keyopts, ~(o)) + +/* Flags for zone_load() */ +typedef enum { + DNS_ZONELOADFLAG_NOSTAT = 0x00000001U, /* Do not stat() master files */ + DNS_ZONELOADFLAG_THAW = 0x00000002U, /* Thaw the zone on successful + * load. */ +} dns_zoneloadflag_t; + +#define UNREACH_CACHE_SIZE 10U +#define UNREACH_HOLD_TIME 600 /* 10 minutes */ + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +struct dns_unreachable { + isc_sockaddr_t remote; + isc_sockaddr_t local; + atomic_uint_fast32_t expire; + atomic_uint_fast32_t last; + uint32_t count; +}; + +struct dns_zonemgr { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refs; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + isc_nm_t *netmgr; + isc_taskpool_t *zonetasks; + isc_taskpool_t *loadtasks; + isc_task_t *task; + isc_pool_t *mctxpool; + isc_ratelimiter_t *checkdsrl; + isc_ratelimiter_t *notifyrl; + isc_ratelimiter_t *refreshrl; + isc_ratelimiter_t *startupnotifyrl; + isc_ratelimiter_t *startuprefreshrl; + isc_rwlock_t rwlock; + isc_mutex_t iolock; + isc_rwlock_t urlock; + + /* Locked by rwlock. */ + dns_zonelist_t zones; + dns_zonelist_t waiting_for_xfrin; + dns_zonelist_t xfrin_in_progress; + + /* Configuration data. */ + uint32_t transfersin; + uint32_t transfersperns; + unsigned int checkdsrate; + unsigned int notifyrate; + unsigned int startupnotifyrate; + unsigned int serialqueryrate; + unsigned int startupserialqueryrate; + + /* Locked by iolock */ + uint32_t iolimit; + uint32_t ioactive; + dns_iolist_t high; + dns_iolist_t low; + + /* Locked by urlock. */ + /* LRU cache */ + struct dns_unreachable unreachable[UNREACH_CACHE_SIZE]; + + dns_keymgmt_t *keymgmt; + + isc_tlsctx_cache_t *tlsctx_cache; + isc_rwlock_t tlsctx_cache_rwlock; +}; + +/*% + * Hold notify state. + */ +struct dns_notify { + unsigned int magic; + unsigned int flags; + isc_mem_t *mctx; + dns_zone_t *zone; + dns_adbfind_t *find; + dns_request_t *request; + dns_name_t ns; + isc_sockaddr_t dst; + dns_tsigkey_t *key; + dns_transport_t *transport; + ISC_LINK(dns_notify_t) link; + isc_event_t *event; +}; + +#define DNS_NOTIFY_NOSOA 0x0001U +#define DNS_NOTIFY_STARTUP 0x0002U + +/*% + * Hold checkds state. + */ +struct dns_checkds { + unsigned int magic; + unsigned int flags; + isc_mem_t *mctx; + dns_zone_t *zone; + dns_request_t *request; + isc_sockaddr_t dst; + dns_tsigkey_t *key; + dns_transport_t *transport; + ISC_LINK(dns_checkds_t) link; + isc_event_t *event; +}; + +/*% + * dns_stub holds state while performing a 'stub' transfer. + * 'db' is the zone's 'db' or a new one if this is the initial + * transfer. + */ + +struct dns_stub { + unsigned int magic; + isc_mem_t *mctx; + dns_zone_t *zone; + dns_db_t *db; + dns_dbversion_t *version; + atomic_uint_fast32_t pending_requests; +}; + +/*% + * Hold load state. + */ +struct dns_load { + unsigned int magic; + isc_mem_t *mctx; + dns_zone_t *zone; + dns_db_t *db; + isc_time_t loadtime; + dns_rdatacallbacks_t callbacks; +}; + +/*% + * Hold forward state. + */ +struct dns_forward { + unsigned int magic; + isc_mem_t *mctx; + dns_zone_t *zone; + isc_buffer_t *msgbuf; + dns_request_t *request; + uint32_t which; + isc_sockaddr_t addr; + dns_updatecallback_t callback; + void *callback_arg; + unsigned int options; + ISC_LINK(dns_forward_t) link; +}; + +/*% + * Hold IO request state. + */ +struct dns_io { + unsigned int magic; + dns_zonemgr_t *zmgr; + bool high; + isc_task_t *task; + ISC_LINK(dns_io_t) link; + isc_event_t *event; +}; + +/*% + * Hold state for when we are signing a zone with a new + * DNSKEY as result of an update. + */ +struct dns_signing { + unsigned int magic; + dns_db_t *db; + dns_dbiterator_t *dbiterator; + dns_secalg_t algorithm; + uint16_t keyid; + bool deleteit; + bool done; + ISC_LINK(dns_signing_t) link; +}; + +struct dns_nsec3chain { + unsigned int magic; + dns_db_t *db; + dns_dbiterator_t *dbiterator; + dns_rdata_nsec3param_t nsec3param; + unsigned char salt[255]; + bool done; + bool seen_nsec; + bool delete_nsec; + bool save_delete_nsec; + ISC_LINK(dns_nsec3chain_t) link; +}; + +/*%< + * 'dbiterator' contains a iterator for the database. If we are creating + * a NSEC3 chain only the non-NSEC3 nodes will be iterated. If we are + * removing a NSEC3 chain then both NSEC3 and non-NSEC3 nodes will be + * iterated. + * + * 'nsec3param' contains the parameters of the NSEC3 chain being created + * or removed. + * + * 'salt' is buffer space and is referenced via 'nsec3param.salt'. + * + * 'seen_nsec' will be set to true if, while iterating the zone to create a + * NSEC3 chain, a NSEC record is seen. + * + * 'delete_nsec' will be set to true if, at the completion of the creation + * of a NSEC3 chain, 'seen_nsec' is true. If 'delete_nsec' is true then we + * are in the process of deleting the NSEC chain. + * + * 'save_delete_nsec' is used to store the initial state of 'delete_nsec' + * so it can be recovered in the event of a error. + */ + +struct dns_keyfetch { + isc_mem_t *mctx; + dns_fixedname_t name; + dns_rdataset_t keydataset; + dns_rdataset_t dnskeyset; + dns_rdataset_t dnskeysigset; + dns_zone_t *zone; + dns_db_t *db; + dns_fetch_t *fetch; +}; + +/*% + * Hold state for an asynchronous load + */ +struct dns_asyncload { + dns_zone_t *zone; + unsigned int flags; + dns_zt_zoneloaded_t loaded; + void *loaded_arg; +}; + +/*% + * Reference to an include file encountered during loading + */ +struct dns_include { + char *name; + isc_time_t filetime; + ISC_LINK(dns_include_t) link; +}; + +/* + * These can be overridden by the -T mkeytimers option on the command + * line, so that we can test with shorter periods than specified in + * RFC 5011. + */ +#define HOUR 3600 +#define DAY (24 * HOUR) +#define MONTH (30 * DAY) +unsigned int dns_zone_mkey_hour = HOUR; +unsigned int dns_zone_mkey_day = DAY; +unsigned int dns_zone_mkey_month = MONTH; + +#define SEND_BUFFER_SIZE 2048 + +static void +zone_settimer(dns_zone_t *, isc_time_t *); +static void +cancel_refresh(dns_zone_t *); +static void +zone_debuglog(dns_zone_t *zone, const char *, int debuglevel, const char *msg, + ...) ISC_FORMAT_PRINTF(4, 5); +static void +notify_log(dns_zone_t *zone, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); +static void +dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(3, 4); +static void +queue_xfrin(dns_zone_t *zone); +static isc_result_t +update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, + dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, + dns_rdata_t *rdata); +static void +zone_unload(dns_zone_t *zone); +static void +zone_expire(dns_zone_t *zone); +static void +zone_refresh(dns_zone_t *zone); +static void +zone_iattach(dns_zone_t *source, dns_zone_t **target); +static void +zone_idetach(dns_zone_t **zonep); +static isc_result_t +zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump); +static void +zone_attachdb(dns_zone_t *zone, dns_db_t *db); +static void +zone_detachdb(dns_zone_t *zone); +static void +zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs); +static void +zone_catz_disable(dns_zone_t *zone); +static isc_result_t +default_journal(dns_zone_t *zone); +static void +zone_xfrdone(dns_zone_t *zone, isc_result_t result); +static isc_result_t +zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, + isc_result_t result); +static void +zone_needdump(dns_zone_t *zone, unsigned int delay); +static void +zone_shutdown(isc_task_t *, isc_event_t *); +static void +zone_loaddone(void *arg, isc_result_t result); +static isc_result_t +zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime); +static void +zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length); +static void +zone_name_tostr(dns_zone_t *zone, char *buf, size_t length); +static void +zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length); +static void +zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length); +static isc_result_t +zone_send_secureserial(dns_zone_t *zone, uint32_t serial); +static void +refresh_callback(isc_task_t *, isc_event_t *); +static void +stub_callback(isc_task_t *, isc_event_t *); +static void +queue_soa_query(dns_zone_t *zone); +static void +soa_query(isc_task_t *, isc_event_t *); +static void +ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub); +static int +message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type); +static void +checkds_cancel(dns_zone_t *zone); +static void +checkds_send(dns_zone_t *zone); +static isc_result_t +checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep); +static void +checkds_done(isc_task_t *task, isc_event_t *event); +static void +checkds_send_toaddr(isc_task_t *task, isc_event_t *event); +static void +notify_cancel(dns_zone_t *zone); +static void +notify_find_address(dns_notify_t *notify); +static void +notify_send(dns_notify_t *notify); +static isc_result_t +notify_createmessage(dns_zone_t *zone, unsigned int flags, + dns_message_t **messagep); +static void +notify_done(isc_task_t *task, isc_event_t *event); +static void +notify_send_toaddr(isc_task_t *task, isc_event_t *event); +static isc_result_t +zone_dump(dns_zone_t *, bool); +static void +got_transfer_quota(isc_task_t *task, isc_event_t *event); +static isc_result_t +zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone); +static void +zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi); +static void +zonemgr_free(dns_zonemgr_t *zmgr); +static isc_result_t +zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_io_t **iop); +static void +zonemgr_putio(dns_io_t **iop); +static void +zonemgr_cancelio(dns_io_t *io); +static void +rss_post(dns_zone_t *, isc_event_t *); + +static isc_result_t +zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount, + unsigned int *soacount, uint32_t *soattl, uint32_t *serial, + uint32_t *refresh, uint32_t *retry, uint32_t *expire, + uint32_t *minimum, unsigned int *errors); + +static void +zone_freedbargs(dns_zone_t *zone); +static void +forward_callback(isc_task_t *task, isc_event_t *event); +static void +zone_saveunique(dns_zone_t *zone, const char *path, const char *templat); +static void +zone_maintenance(dns_zone_t *zone); +static void +zone_notify(dns_zone_t *zone, isc_time_t *now); +static void +dump_done(void *arg, isc_result_t result); +static isc_result_t +zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, + bool deleteit); +static isc_result_t +delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, + dns_name_t *name, dns_diff_t *diff); +static void +zone_rekey(dns_zone_t *zone); +static isc_result_t +zone_send_securedb(dns_zone_t *zone, dns_db_t *db); +static dns_ttl_t +zone_nsecttl(dns_zone_t *zone); +static void +setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value); +static void +zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial); +static isc_result_t +zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump, + bool *fixjournal); + +#define ENTER zone_debuglog(zone, me, 1, "enter") + +static void +zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache); +/*%< + * Attach to TLS client context cache used for zone transfers via + * encrypted transports (e.g. XoT). + * + * The obtained reference needs to be detached by a call to + * 'isc_tlsctx_cache_detach()' when not needed anymore. + * + * Requires: + *\li 'zmgr' is a valid zone manager. + *\li 'ptlsctx_cache' is not 'NULL' and points to 'NULL'. + */ + +static const unsigned int dbargc_default = 1; +static const char *dbargv_default[] = { "rbt" }; + +#define DNS_ZONE_JITTER_ADD(a, b, c) \ + do { \ + isc_interval_t _i; \ + uint32_t _j; \ + _j = (b)-isc_random_uniform((b) / 4); \ + isc_interval_set(&_i, _j, 0); \ + if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \ + dns_zone_log(zone, ISC_LOG_WARNING, \ + "epoch approaching: upgrade required: " \ + "now + %s failed", \ + #b); \ + isc_interval_set(&_i, _j / 2, 0); \ + (void)isc_time_add((a), &_i, (c)); \ + } \ + } while (0) + +#define DNS_ZONE_TIME_ADD(a, b, c) \ + do { \ + isc_interval_t _i; \ + isc_interval_set(&_i, (b), 0); \ + if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \ + dns_zone_log(zone, ISC_LOG_WARNING, \ + "epoch approaching: upgrade required: " \ + "now + %s failed", \ + #b); \ + isc_interval_set(&_i, (b) / 2, 0); \ + (void)isc_time_add((a), &_i, (c)); \ + } \ + } while (0) + +typedef struct nsec3param nsec3param_t; +struct nsec3param { + dns_rdata_nsec3param_t rdata; + unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1]; + unsigned int length; + bool nsec; + bool replace; + bool resalt; + bool lookup; + ISC_LINK(nsec3param_t) link; +}; +typedef ISC_LIST(nsec3param_t) nsec3paramlist_t; +struct np3event { + isc_event_t event; + nsec3param_t params; +}; + +struct ssevent { + isc_event_t event; + uint32_t serial; +}; + +struct stub_cb_args { + dns_stub_t *stub; + dns_tsigkey_t *tsig_key; + uint16_t udpsize; + int timeout; + bool reqnsid; +}; + +struct stub_glue_request { + dns_request_t *request; + dns_name_t name; + struct stub_cb_args *args; + bool ipv4; +}; + +/*% + * Increment resolver-related statistics counters. Zone must be locked. + */ +static void +inc_stats(dns_zone_t *zone, isc_statscounter_t counter) { + if (zone->stats != NULL) { + isc_stats_increment(zone->stats, counter); + } +} + +/*** + *** Public functions. + ***/ + +isc_result_t +dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { + isc_result_t result; + isc_time_t now; + dns_zone_t *zone = NULL; + dns_zone_t z = { .masterformat = dns_masterformat_none, + .journalsize = -1, + .rdclass = dns_rdataclass_none, + .type = dns_zone_none, + .refresh = DNS_ZONE_DEFAULTREFRESH, + .retry = DNS_ZONE_DEFAULTRETRY, + .maxrefresh = DNS_ZONE_MAXREFRESH, + .minrefresh = DNS_ZONE_MINREFRESH, + .maxretry = DNS_ZONE_MAXRETRY, + .minretry = DNS_ZONE_MINRETRY, + .notifytype = dns_notifytype_yes, + .zero_no_soa_ttl = true, + .check_names = dns_severity_ignore, + .idlein = DNS_DEFAULT_IDLEIN, + .idleout = DNS_DEFAULT_IDLEOUT, + .maxxfrin = MAX_XFER_TIME, + .maxxfrout = MAX_XFER_TIME, + .sigvalidityinterval = 30 * 24 * 3600, + .sigresigninginterval = 7 * 24 * 3600, + .statlevel = dns_zonestat_none, + .notifydelay = 5, + .signatures = 10, + .nodes = 100, + .privatetype = (dns_rdatatype_t)0xffffU, + .rpz_num = DNS_RPZ_INVALID_NUM, + .requestixfr = true, + .ixfr_ratio = 100, + .requestexpire = true, + .updatemethod = dns_updatemethod_increment, + .magic = ZONE_MAGIC }; + + REQUIRE(zonep != NULL && *zonep == NULL); + REQUIRE(mctx != NULL); + + TIME_NOW(&now); + zone = isc_mem_get(mctx, sizeof(*zone)); + *zone = z; + + zone->mctx = NULL; + isc_mem_attach(mctx, &zone->mctx); + isc_mutex_init(&zone->lock); + ZONEDB_INITLOCK(&zone->dblock); + /* XXX MPA check that all elements are initialised */ +#ifdef DNS_ZONE_CHECKLOCK + zone->locked = false; +#endif /* ifdef DNS_ZONE_CHECKLOCK */ + + zone->notifytime = now; + + ISC_LINK_INIT(zone, link); + isc_refcount_init(&zone->erefs, 1); + isc_refcount_init(&zone->irefs, 0); + dns_name_init(&zone->origin, NULL); + ISC_LIST_INIT(zone->includes); + ISC_LIST_INIT(zone->newincludes); + atomic_init(&zone->flags, 0); + atomic_init(&zone->options, 0); + atomic_init(&zone->keyopts, 0); + isc_time_settoepoch(&zone->expiretime); + isc_time_settoepoch(&zone->refreshtime); + isc_time_settoepoch(&zone->dumptime); + isc_time_settoepoch(&zone->loadtime); + isc_time_settoepoch(&zone->resigntime); + isc_time_settoepoch(&zone->keywarntime); + isc_time_settoepoch(&zone->signingtime); + isc_time_settoepoch(&zone->nsec3chaintime); + isc_time_settoepoch(&zone->refreshkeytime); + ISC_LIST_INIT(zone->notifies); + ISC_LIST_INIT(zone->checkds_requests); + isc_sockaddr_any(&zone->notifysrc4); + isc_sockaddr_any6(&zone->notifysrc6); + isc_sockaddr_any(&zone->parentalsrc4); + isc_sockaddr_any6(&zone->parentalsrc6); + isc_sockaddr_any(&zone->xfrsource4); + isc_sockaddr_any6(&zone->xfrsource6); + isc_sockaddr_any(&zone->altxfrsource4); + isc_sockaddr_any6(&zone->altxfrsource6); + ISC_LINK_INIT(zone, statelink); + ISC_LIST_INIT(zone->signing); + ISC_LIST_INIT(zone->nsec3chain); + ISC_LIST_INIT(zone->setnsec3param_queue); + ISC_LIST_INIT(zone->forwards); + ISC_LIST_INIT(zone->rss_events); + ISC_LIST_INIT(zone->rss_post); + + result = isc_stats_create(mctx, &zone->gluecachestats, + dns_gluecachestatscounter_max); + if (result != ISC_R_SUCCESS) { + goto free_refs; + } + + /* Must be after magic is set. */ + dns_zone_setdbtype(zone, dbargc_default, dbargv_default); + + ISC_EVENT_INIT(&zone->ctlevent, sizeof(zone->ctlevent), 0, NULL, + DNS_EVENT_ZONECONTROL, zone_shutdown, zone, zone, NULL, + NULL); + *zonep = zone; + return (ISC_R_SUCCESS); + +free_refs: + isc_refcount_decrement0(&zone->erefs); + isc_refcount_destroy(&zone->erefs); + isc_refcount_destroy(&zone->irefs); + ZONEDB_DESTROYLOCK(&zone->dblock); + isc_mutex_destroy(&zone->lock); + isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone)); + return (result); +} + +static void +clear_keylist(dns_dnsseckeylist_t *list, isc_mem_t *mctx) { + dns_dnsseckey_t *key; + while (!ISC_LIST_EMPTY(*list)) { + key = ISC_LIST_HEAD(*list); + ISC_LIST_UNLINK(*list, key, link); + dns_dnsseckey_destroy(mctx, &key); + } +} + +/* + * Free a zone. Because we require that there be no more + * outstanding events or references, no locking is necessary. + */ +static void +zone_free(dns_zone_t *zone) { + dns_signing_t *signing; + dns_nsec3chain_t *nsec3chain; + isc_event_t *event; + dns_include_t *include; + + REQUIRE(DNS_ZONE_VALID(zone)); + isc_refcount_destroy(&zone->erefs); + isc_refcount_destroy(&zone->irefs); + REQUIRE(!LOCKED_ZONE(zone)); + REQUIRE(zone->timer == NULL); + REQUIRE(zone->zmgr == NULL); + + /* + * Managed objects. Order is important. + */ + if (zone->request != NULL) { + dns_request_destroy(&zone->request); /* XXXMPA */ + } + INSIST(zone->readio == NULL); + INSIST(zone->statelist == NULL); + INSIST(zone->writeio == NULL); + INSIST(zone->view == NULL); + INSIST(zone->prev_view == NULL); + + if (zone->task != NULL) { + isc_task_detach(&zone->task); + } + if (zone->loadtask != NULL) { + isc_task_detach(&zone->loadtask); + } + + /* Unmanaged objects */ + while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { + event = ISC_LIST_HEAD(zone->setnsec3param_queue); + ISC_LIST_UNLINK(zone->setnsec3param_queue, event, ev_link); + isc_event_free(&event); + } + while (!ISC_LIST_EMPTY(zone->rss_post)) { + event = ISC_LIST_HEAD(zone->rss_post); + ISC_LIST_UNLINK(zone->rss_post, event, ev_link); + isc_event_free(&event); + } + for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; + signing = ISC_LIST_HEAD(zone->signing)) + { + ISC_LIST_UNLINK(zone->signing, signing, link); + dns_db_detach(&signing->db); + dns_dbiterator_destroy(&signing->dbiterator); + isc_mem_put(zone->mctx, signing, sizeof *signing); + } + for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; + nsec3chain = ISC_LIST_HEAD(zone->nsec3chain)) + { + ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); + dns_db_detach(&nsec3chain->db); + dns_dbiterator_destroy(&nsec3chain->dbiterator); + isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); + } + for (include = ISC_LIST_HEAD(zone->includes); include != NULL; + include = ISC_LIST_HEAD(zone->includes)) + { + ISC_LIST_UNLINK(zone->includes, include, link); + isc_mem_free(zone->mctx, include->name); + isc_mem_put(zone->mctx, include, sizeof *include); + } + for (include = ISC_LIST_HEAD(zone->newincludes); include != NULL; + include = ISC_LIST_HEAD(zone->newincludes)) + { + ISC_LIST_UNLINK(zone->newincludes, include, link); + isc_mem_free(zone->mctx, include->name); + isc_mem_put(zone->mctx, include, sizeof *include); + } + if (zone->masterfile != NULL) { + isc_mem_free(zone->mctx, zone->masterfile); + } + zone->masterfile = NULL; + if (zone->keydirectory != NULL) { + isc_mem_free(zone->mctx, zone->keydirectory); + } + zone->keydirectory = NULL; + if (zone->kasp != NULL) { + dns_kasp_detach(&zone->kasp); + } + if (!ISC_LIST_EMPTY(zone->checkds_ok)) { + clear_keylist(&zone->checkds_ok, zone->mctx); + } + + zone->journalsize = -1; + if (zone->journal != NULL) { + isc_mem_free(zone->mctx, zone->journal); + } + zone->journal = NULL; + if (zone->stats != NULL) { + isc_stats_detach(&zone->stats); + } + if (zone->requeststats != NULL) { + isc_stats_detach(&zone->requeststats); + } + if (zone->rcvquerystats != NULL) { + dns_stats_detach(&zone->rcvquerystats); + } + if (zone->dnssecsignstats != NULL) { + dns_stats_detach(&zone->dnssecsignstats); + } + if (zone->db != NULL) { + zone_detachdb(zone); + } + if (zone->rpzs != NULL) { + REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&zone->rpzs); + zone->rpz_num = DNS_RPZ_INVALID_NUM; + } + if (zone->catzs != NULL) { + dns_catz_detach_catzs(&zone->catzs); + } + zone_freedbargs(zone); + dns_zone_setparentals(zone, NULL, NULL, NULL, 0); + dns_zone_setprimaries(zone, NULL, NULL, NULL, 0); + dns_zone_setalsonotify(zone, NULL, NULL, NULL, 0); + + zone->check_names = dns_severity_ignore; + if (zone->update_acl != NULL) { + dns_acl_detach(&zone->update_acl); + } + if (zone->forward_acl != NULL) { + dns_acl_detach(&zone->forward_acl); + } + if (zone->notify_acl != NULL) { + dns_acl_detach(&zone->notify_acl); + } + if (zone->query_acl != NULL) { + dns_acl_detach(&zone->query_acl); + } + if (zone->queryon_acl != NULL) { + dns_acl_detach(&zone->queryon_acl); + } + if (zone->xfr_acl != NULL) { + dns_acl_detach(&zone->xfr_acl); + } + if (dns_name_dynamic(&zone->origin)) { + dns_name_free(&zone->origin, zone->mctx); + } + if (zone->strnamerd != NULL) { + isc_mem_free(zone->mctx, zone->strnamerd); + } + if (zone->strname != NULL) { + isc_mem_free(zone->mctx, zone->strname); + } + if (zone->strrdclass != NULL) { + isc_mem_free(zone->mctx, zone->strrdclass); + } + if (zone->strviewname != NULL) { + isc_mem_free(zone->mctx, zone->strviewname); + } + if (zone->ssutable != NULL) { + dns_ssutable_detach(&zone->ssutable); + } + if (zone->gluecachestats != NULL) { + isc_stats_detach(&zone->gluecachestats); + } + + /* last stuff */ + ZONEDB_DESTROYLOCK(&zone->dblock); + isc_mutex_destroy(&zone->lock); + zone->magic = 0; + isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone)); +} + +/* + * Returns true iff this the signed side of an inline-signing zone. + * Caller should hold zone lock. + */ +static bool +inline_secure(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + if (zone->raw != NULL) { + return (true); + } + return (false); +} + +/* + * Returns true iff this the unsigned side of an inline-signing zone + * Caller should hold zone lock. + */ +static bool +inline_raw(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + if (zone->secure != NULL) { + return (true); + } + return (false); +} + +/* + * Single shot. + */ +void +dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass) { + char namebuf[1024]; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(rdclass != dns_rdataclass_none); + + /* + * Test and set. + */ + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + REQUIRE(zone->rdclass == dns_rdataclass_none || + zone->rdclass == rdclass); + zone->rdclass = rdclass; + + if (zone->strnamerd != NULL) { + isc_mem_free(zone->mctx, zone->strnamerd); + } + if (zone->strrdclass != NULL) { + isc_mem_free(zone->mctx, zone->strrdclass); + } + + zone_namerd_tostr(zone, namebuf, sizeof namebuf); + zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); + zone_rdclass_tostr(zone, namebuf, sizeof namebuf); + zone->strrdclass = isc_mem_strdup(zone->mctx, namebuf); + + if (inline_secure(zone)) { + dns_zone_setclass(zone->raw, rdclass); + } + UNLOCK_ZONE(zone); +} + +dns_rdataclass_t +dns_zone_getclass(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->rdclass); +} + +void +dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->notifytype = notifytype; + UNLOCK_ZONE(zone); +} + +isc_result_t +dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp) { + isc_result_t result; + unsigned int soacount; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(serialp != NULL); + + LOCK_ZONE(zone); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, + serialp, NULL, NULL, NULL, NULL, + NULL); + if (result == ISC_R_SUCCESS && soacount == 0) { + result = ISC_R_FAILURE; + } + } else { + result = DNS_R_NOTLOADED; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + UNLOCK_ZONE(zone); + + return (result); +} + +/* + * Single shot. + */ +void +dns_zone_settype(dns_zone_t *zone, dns_zonetype_t type) { + char namebuf[1024]; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(type != dns_zone_none); + + /* + * Test and set. + */ + LOCK_ZONE(zone); + REQUIRE(zone->type == dns_zone_none || zone->type == type); + zone->type = type; + + if (zone->strnamerd != NULL) { + isc_mem_free(zone->mctx, zone->strnamerd); + } + + zone_namerd_tostr(zone, namebuf, sizeof namebuf); + zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); + UNLOCK_ZONE(zone); +} + +static void +zone_freedbargs(dns_zone_t *zone) { + unsigned int i; + + /* Free the old database argument list. */ + if (zone->db_argv != NULL) { + for (i = 0; i < zone->db_argc; i++) { + isc_mem_free(zone->mctx, zone->db_argv[i]); + } + isc_mem_put(zone->mctx, zone->db_argv, + zone->db_argc * sizeof(*zone->db_argv)); + } + zone->db_argc = 0; + zone->db_argv = NULL; +} + +isc_result_t +dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx) { + size_t size = 0; + unsigned int i; + isc_result_t result = ISC_R_SUCCESS; + void *mem; + char **tmp, *tmp2, *base; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(argv != NULL && *argv == NULL); + + LOCK_ZONE(zone); + size = (zone->db_argc + 1) * sizeof(char *); + for (i = 0; i < zone->db_argc; i++) { + size += strlen(zone->db_argv[i]) + 1; + } + mem = isc_mem_allocate(mctx, size); + { + tmp = mem; + tmp2 = mem; + base = mem; + tmp2 += (zone->db_argc + 1) * sizeof(char *); + for (i = 0; i < zone->db_argc; i++) { + *tmp++ = tmp2; + strlcpy(tmp2, zone->db_argv[i], size - (tmp2 - base)); + tmp2 += strlen(tmp2) + 1; + } + *tmp = NULL; + } + UNLOCK_ZONE(zone); + *argv = mem; + return (result); +} + +void +dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc, + const char *const *dbargv) { + char **argv = NULL; + unsigned int i; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(dbargc >= 1); + REQUIRE(dbargv != NULL); + + LOCK_ZONE(zone); + + /* Set up a new database argument list. */ + argv = isc_mem_get(zone->mctx, dbargc * sizeof(*argv)); + for (i = 0; i < dbargc; i++) { + argv[i] = NULL; + } + for (i = 0; i < dbargc; i++) { + argv[i] = isc_mem_strdup(zone->mctx, dbargv[i]); + } + + /* Free the old list. */ + zone_freedbargs(zone); + + zone->db_argc = dbargc; + zone->db_argv = argv; + + UNLOCK_ZONE(zone); +} + +static void +dns_zone_setview_helper(dns_zone_t *zone, dns_view_t *view) { + char namebuf[1024]; + + if (zone->prev_view == NULL && zone->view != NULL) { + dns_view_weakattach(zone->view, &zone->prev_view); + } + + INSIST(zone != zone->raw); + if (zone->view != NULL) { + dns_view_sfd_del(zone->view, &zone->origin); + dns_view_weakdetach(&zone->view); + } + dns_view_weakattach(view, &zone->view); + dns_view_sfd_add(view, &zone->origin); + + if (zone->strviewname != NULL) { + isc_mem_free(zone->mctx, zone->strviewname); + } + if (zone->strnamerd != NULL) { + isc_mem_free(zone->mctx, zone->strnamerd); + } + + zone_namerd_tostr(zone, namebuf, sizeof namebuf); + zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); + zone_viewname_tostr(zone, namebuf, sizeof namebuf); + zone->strviewname = isc_mem_strdup(zone->mctx, namebuf); + + if (inline_secure(zone)) { + dns_zone_setview(zone->raw, view); + } +} + +void +dns_zone_setview(dns_zone_t *zone, dns_view_t *view) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + dns_zone_setview_helper(zone, view); + UNLOCK_ZONE(zone); +} + +dns_view_t * +dns_zone_getview(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->view); +} + +void +dns_zone_setviewcommit(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->prev_view != NULL) { + dns_view_weakdetach(&zone->prev_view); + } + if (inline_secure(zone)) { + dns_zone_setviewcommit(zone->raw); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_setviewrevert(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->prev_view != NULL) { + dns_zone_setview_helper(zone, zone->prev_view); + dns_view_weakdetach(&zone->prev_view); + } + if (zone->catzs != NULL) { + zone_catz_enable(zone, zone->catzs); + } + if (inline_secure(zone)) { + dns_zone_setviewrevert(zone->raw); + } + UNLOCK_ZONE(zone); +} + +isc_result_t +dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin) { + isc_result_t result = ISC_R_SUCCESS; + char namebuf[1024]; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(origin != NULL); + + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + if (dns_name_dynamic(&zone->origin)) { + dns_name_free(&zone->origin, zone->mctx); + dns_name_init(&zone->origin, NULL); + } + dns_name_dup(origin, zone->mctx, &zone->origin); + + if (zone->strnamerd != NULL) { + isc_mem_free(zone->mctx, zone->strnamerd); + } + if (zone->strname != NULL) { + isc_mem_free(zone->mctx, zone->strname); + } + + zone_namerd_tostr(zone, namebuf, sizeof namebuf); + zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); + zone_name_tostr(zone, namebuf, sizeof namebuf); + zone->strname = isc_mem_strdup(zone->mctx, namebuf); + + if (inline_secure(zone)) { + result = dns_zone_setorigin(zone->raw, origin); + } + UNLOCK_ZONE(zone); + return (result); +} + +static isc_result_t +dns_zone_setstring(dns_zone_t *zone, char **field, const char *value) { + char *copy; + + if (value != NULL) { + copy = isc_mem_strdup(zone->mctx, value); + } else { + copy = NULL; + } + + if (*field != NULL) { + isc_mem_free(zone->mctx, *field); + } + + *field = copy; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format, + const dns_master_style_t *style) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(zone->stream == NULL); + + LOCK_ZONE(zone); + result = dns_zone_setstring(zone, &zone->masterfile, file); + if (result == ISC_R_SUCCESS) { + zone->masterformat = format; + if (format == dns_masterformat_text) { + zone->masterstyle = style; + } + result = default_journal(zone); + } + UNLOCK_ZONE(zone); + + return (result); +} + +const char * +dns_zone_getfile(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->masterfile); +} + +isc_result_t +dns_zone_setstream(dns_zone_t *zone, const FILE *stream, + dns_masterformat_t format, const dns_master_style_t *style) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(stream != NULL); + REQUIRE(zone->masterfile == NULL); + + LOCK_ZONE(zone); + zone->stream = stream; + zone->masterformat = format; + if (format == dns_masterformat_text) { + zone->masterstyle = style; + } + result = default_journal(zone); + UNLOCK_ZONE(zone); + + return (result); +} + +dns_ttl_t +dns_zone_getmaxttl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->maxttl); +} + +void +dns_zone_setmaxttl(dns_zone_t *zone, dns_ttl_t maxttl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (maxttl != 0) { + DNS_ZONE_SETOPTION(zone, DNS_ZONEOPT_CHECKTTL); + } else { + DNS_ZONE_CLROPTION(zone, DNS_ZONEOPT_CHECKTTL); + } + zone->maxttl = maxttl; + UNLOCK_ZONE(zone); + + return; +} + +static isc_result_t +default_journal(dns_zone_t *zone) { + isc_result_t result; + char *journal; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + + if (zone->masterfile != NULL) { + /* Calculate string length including '\0'. */ + int len = strlen(zone->masterfile) + sizeof(".jnl"); + journal = isc_mem_allocate(zone->mctx, len); + strlcpy(journal, zone->masterfile, len); + strlcat(journal, ".jnl", len); + } else { + journal = NULL; + } + result = dns_zone_setstring(zone, &zone->journal, journal); + if (journal != NULL) { + isc_mem_free(zone->mctx, journal); + } + return (result); +} + +isc_result_t +dns_zone_setjournal(dns_zone_t *zone, const char *myjournal) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + result = dns_zone_setstring(zone, &zone->journal, myjournal); + UNLOCK_ZONE(zone); + + return (result); +} + +char * +dns_zone_getjournal(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->journal); +} + +/* + * Return true iff the zone is "dynamic", in the sense that the zone's + * master file (if any) is written by the server, rather than being + * updated manually and read by the server. + * + * This is true for secondary zones, mirror zones, stub zones, key zones, + * and zones that allow dynamic updates either by having an update + * policy ("ssutable") or an "allow-update" ACL with a value other than + * exactly "{ none; }". + */ +bool +dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || + zone->type == dns_zone_stub || zone->type == dns_zone_key || + (zone->type == dns_zone_redirect && zone->primaries != NULL)) + { + return (true); + } + + /* Inline zones are always dynamic. */ + if (zone->type == dns_zone_primary && zone->raw != NULL) { + return (true); + } + + /* If !ignore_freeze, we need check whether updates are disabled. */ + if (zone->type == dns_zone_primary && + (!zone->update_disabled || ignore_freeze) && + ((zone->ssutable != NULL) || + (zone->update_acl != NULL && !dns_acl_isnone(zone->update_acl)))) + { + return (true); + } + + return (false); +} + +/* + * Set the response policy index and information for a zone. + */ +isc_result_t +dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num) { + /* + * Only RBTDB zones can be used for response policy zones, + * because only they have the code to create the summary data. + * Only zones that are loaded instead of mmap()ed create the + * summary data and so can be policy zones. + */ + if (strcmp(zone->db_argv[0], "rbt") != 0 && + strcmp(zone->db_argv[0], "rbt64") != 0) + { + return (ISC_R_NOTIMPLEMENTED); + } + + /* + * This must happen only once or be redundant. + */ + LOCK_ZONE(zone); + if (zone->rpzs != NULL) { + REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num); + } else { + REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM); + dns_rpz_attach_rpzs(rpzs, &zone->rpzs); + zone->rpz_num = rpz_num; + } + rpzs->defined |= DNS_RPZ_ZBIT(rpz_num); + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +dns_rpz_num_t +dns_zone_get_rpz_num(dns_zone_t *zone) { + return (zone->rpz_num); +} + +/* + * If a zone is a response policy zone, mark its new database. + */ +void +dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) { + isc_result_t result; + if (zone->rpz_num == DNS_RPZ_INVALID_NUM) { + return; + } + REQUIRE(zone->rpzs != NULL); + result = dns_db_updatenotify_register(db, dns_rpz_dbupdate_callback, + zone->rpzs->zones[zone->rpz_num]); + REQUIRE(result == ISC_R_SUCCESS); +} + +static void +dns_zone_rpz_disable_db(dns_zone_t *zone, dns_db_t *db) { + if (zone->rpz_num == DNS_RPZ_INVALID_NUM) { + return; + } + REQUIRE(zone->rpzs != NULL); + (void)dns_db_updatenotify_unregister(db, dns_rpz_dbupdate_callback, + zone->rpzs->zones[zone->rpz_num]); +} + +/* + * If a zone is a catalog zone, attach it to update notification in database. + */ +void +dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + if (zone->catzs != NULL) { + dns_catz_dbupdate_register(db, zone->catzs); + } +} + +static void +dns_zone_catz_disable_db(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + if (zone->catzs != NULL) { + dns_catz_dbupdate_unregister(db, zone->catzs); + } +} + +static void +zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(catzs != NULL); + + INSIST(zone->catzs == NULL || zone->catzs == catzs); + dns_catz_catzs_set_view(catzs, zone->view); + if (zone->catzs == NULL) { + dns_catz_attach_catzs(catzs, &zone->catzs); + } +} + +void +dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone_catz_enable(zone, catzs); + UNLOCK_ZONE(zone); +} + +static void +zone_catz_disable(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->catzs != NULL) { + if (zone->db != NULL) { + dns_zone_catz_disable_db(zone, zone->db); + } + dns_catz_detach_catzs(&zone->catzs); + } +} + +void +dns_zone_catz_disable(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone_catz_disable(zone); + UNLOCK_ZONE(zone); +} + +bool +dns_zone_catz_is_enabled(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->catzs != NULL); +} + +/* + * Set catalog zone ownership of the zone + */ +void +dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(catz != NULL); + LOCK_ZONE(zone); + INSIST(zone->parentcatz == NULL || zone->parentcatz == catz); + zone->parentcatz = catz; + UNLOCK_ZONE(zone); +} + +dns_catz_zone_t * +dns_zone_get_parentcatz(const dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->parentcatz); +} + +static bool +zone_touched(dns_zone_t *zone) { + isc_result_t result; + isc_time_t modtime; + dns_include_t *include; + + REQUIRE(DNS_ZONE_VALID(zone)); + + result = isc_file_getmodtime(zone->masterfile, &modtime); + if (result != ISC_R_SUCCESS || + isc_time_compare(&modtime, &zone->loadtime) > 0) + { + return (true); + } + + for (include = ISC_LIST_HEAD(zone->includes); include != NULL; + include = ISC_LIST_NEXT(include, link)) + { + result = isc_file_getmodtime(include->name, &modtime); + if (result != ISC_R_SUCCESS || + isc_time_compare(&modtime, &include->filetime) > 0) + { + return (true); + } + } + + return (false); +} + +/* + * Note: when dealing with inline-signed zones, external callers will always + * call zone_load() for the secure zone; zone_load() calls itself recursively + * in order to load the raw zone. + */ +static isc_result_t +zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { + isc_result_t result; + isc_time_t now; + isc_time_t loadtime; + dns_db_t *db = NULL; + bool rbt, hasraw, is_dynamic; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (!locked) { + LOCK_ZONE(zone); + } + + INSIST(zone != zone->raw); + hasraw = inline_secure(zone); + if (hasraw) { + /* + * We are trying to load an inline-signed zone. First call + * self recursively to try loading the raw version of the zone. + * Assuming the raw zone file is readable, there are two + * possibilities: + * + * a) the raw zone was not yet loaded and thus it will be + * loaded now, synchronously; if this succeeds, a + * subsequent attempt to load the signed zone file will + * take place and thus zone_postload() will be called + * twice: first for the raw zone and then for the secure + * zone; the latter call will take care of syncing the raw + * version with the secure version, + * + * b) the raw zone was already loaded and we are trying to + * reload it, which will happen asynchronously; this means + * zone_postload() will only be called for the raw zone + * because "result" returned by the zone_load() call below + * will not be ISC_R_SUCCESS but rather DNS_R_CONTINUE; + * zone_postload() called for the raw zone will take care + * of syncing the raw version with the secure version. + */ + result = zone_load(zone->raw, flags, false); + if (result != ISC_R_SUCCESS) { + if (!locked) { + UNLOCK_ZONE(zone); + } + return (result); + } + LOCK_ZONE(zone->raw); + } + + TIME_NOW(&now); + + INSIST(zone->type != dns_zone_none); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING)) { + if ((flags & DNS_ZONELOADFLAG_THAW) != 0) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW); + } + result = DNS_R_CONTINUE; + goto cleanup; + } + + INSIST(zone->db_argc >= 1); + + rbt = strcmp(zone->db_argv[0], "rbt") == 0 || + strcmp(zone->db_argv[0], "rbt64") == 0; + + if (zone->db != NULL && zone->masterfile == NULL && rbt) { + /* + * The zone has no master file configured. + */ + result = ISC_R_SUCCESS; + goto cleanup; + } + + is_dynamic = dns_zone_isdynamic(zone, false); + if (zone->db != NULL && is_dynamic) { + /* + * This is a secondary, stub, or dynamically updated zone + * being reloaded. Do nothing - the database we already + * have is guaranteed to be up-to-date. + */ + if (zone->type == dns_zone_primary && !hasraw) { + result = DNS_R_DYNAMIC; + } else { + result = ISC_R_SUCCESS; + } + goto cleanup; + } + + /* + * Store the current time before the zone is loaded, so that if the + * file changes between the time of the load and the time that + * zone->loadtime is set, then the file will still be reloaded + * the next time dns_zone_load is called. + */ + TIME_NOW(&loadtime); + + /* + * Don't do the load if the file that stores the zone is older + * than the last time the zone was loaded. If the zone has not + * been loaded yet, zone->loadtime will be the epoch. + */ + if (zone->masterfile != NULL) { + isc_time_t filetime; + + /* + * The file is already loaded. If we are just doing a + * "rndc reconfig", we are done. + */ + if (!isc_time_isepoch(&zone->loadtime) && + (flags & DNS_ZONELOADFLAG_NOSTAT) != 0) + { + result = ISC_R_SUCCESS; + goto cleanup; + } + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && + !zone_touched(zone)) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "skipping load: master file " + "older than last load"); + result = DNS_R_UPTODATE; + goto cleanup; + } + + /* + * If the file modification time is in the past + * set loadtime to that value. + */ + result = isc_file_getmodtime(zone->masterfile, &filetime); + if (result == ISC_R_SUCCESS && + isc_time_compare(&loadtime, &filetime) > 0) + { + loadtime = filetime; + } + } + + /* + * Built in zones (with the exception of empty zones) don't need + * to be reloaded. + */ + if (zone->type == dns_zone_primary && + strcmp(zone->db_argv[0], "_builtin") == 0 && + (zone->db_argc < 2 || strcmp(zone->db_argv[1], "empty") != 0) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* + * Zones associated with a DLZ don't need to be loaded either, + * but we need to associate the database with the zone object. + */ + if (strcmp(zone->db_argv[0], "dlz") == 0) { + dns_dlzdb_t *dlzdb; + dns_dlzfindzone_t findzone; + + for (dlzdb = ISC_LIST_HEAD(zone->view->dlz_unsearched); + dlzdb != NULL; dlzdb = ISC_LIST_NEXT(dlzdb, link)) + { + INSIST(DNS_DLZ_VALID(dlzdb)); + if (strcmp(zone->db_argv[1], dlzdb->dlzname) == 0) { + break; + } + } + + if (dlzdb == NULL) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "DLZ %s does not exist or is set " + "to 'search yes;'", + zone->db_argv[1]); + result = ISC_R_NOTFOUND; + goto cleanup; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + /* ask SDLZ driver if the zone is supported */ + findzone = dlzdb->implementation->methods->findzone; + result = (*findzone)(dlzdb->implementation->driverarg, + dlzdb->dbdata, dlzdb->mctx, + zone->view->rdclass, &zone->origin, NULL, + NULL, &db); + if (result != ISC_R_NOTFOUND) { + if (zone->db != NULL) { + zone_detachdb(zone); + } + zone_attachdb(zone, db); + dns_db_detach(&db); + result = ISC_R_SUCCESS; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + + if (result == ISC_R_SUCCESS) { + if (dlzdb->configure_callback == NULL) { + goto cleanup; + } + + result = (*dlzdb->configure_callback)(zone->view, dlzdb, + zone); + if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "DLZ configuration callback: %s", + isc_result_totext(result)); + } + } + goto cleanup; + } + + if ((zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || zone->type == dns_zone_stub || + (zone->type == dns_zone_redirect && zone->primaries != NULL)) && + rbt) + { + if (zone->stream == NULL && + (zone->masterfile == NULL || + !isc_file_exists(zone->masterfile))) + { + if (zone->masterfile != NULL) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "no master file"); + } + zone->refreshtime = now; + if (zone->task != NULL) { + zone_settimer(zone, &now); + } + result = ISC_R_SUCCESS; + goto cleanup; + } + } + + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), + "starting load"); + + result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin, + (zone->type == dns_zone_stub) ? dns_dbtype_stub + : dns_dbtype_zone, + zone->rdclass, zone->db_argc - 1, + zone->db_argv + 1, &db); + + if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, + "loading zone: creating database: %s", + isc_result_totext(result)); + goto cleanup; + } + dns_db_settask(db, zone->task); + + if (zone->type == dns_zone_primary || + zone->type == dns_zone_secondary || zone->type == dns_zone_mirror) + { + result = dns_db_setgluecachestats(db, zone->gluecachestats); + if (result == ISC_R_NOTIMPLEMENTED) { + result = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + if (!dns_db_ispersistent(db)) { + if (zone->masterfile != NULL || zone->stream != NULL) { + result = zone_startload(db, zone, loadtime); + } else { + result = DNS_R_NOMASTERFILE; + if (zone->type == dns_zone_primary || + (zone->type == dns_zone_redirect && + zone->primaries == NULL)) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "loading zone: " + "no master file configured"); + goto cleanup; + } + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_INFO, + "loading zone: " + "no master file configured: continuing"); + } + } + + if (result == DNS_R_CONTINUE) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADING); + if ((flags & DNS_ZONELOADFLAG_THAW) != 0) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW); + } + goto cleanup; + } + + result = zone_postload(zone, db, loadtime, result); + +cleanup: + if (hasraw) { + UNLOCK_ZONE(zone->raw); + } + if (!locked) { + UNLOCK_ZONE(zone); + } + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +isc_result_t +dns_zone_load(dns_zone_t *zone, bool newonly) { + return (zone_load(zone, newonly ? DNS_ZONELOADFLAG_NOSTAT : 0, false)); +} + +static void +zone_asyncload(isc_task_t *task, isc_event_t *event) { + dns_asyncload_t *asl = event->ev_arg; + dns_zone_t *zone = asl->zone; + isc_result_t result; + + UNUSED(task); + + REQUIRE(DNS_ZONE_VALID(zone)); + + isc_event_free(&event); + + LOCK_ZONE(zone); + result = zone_load(zone, asl->flags, true); + if (result != DNS_R_CONTINUE) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); + } + UNLOCK_ZONE(zone); + + /* Inform the zone table we've finished loading */ + if (asl->loaded != NULL) { + (asl->loaded)(asl->loaded_arg, zone, task); + } + + /* Reduce the quantum */ + isc_task_setquantum(zone->loadtask, 1); + + isc_mem_put(zone->mctx, asl, sizeof(*asl)); + dns_zone_idetach(&zone); +} + +isc_result_t +dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done, + void *arg) { + isc_event_t *e; + dns_asyncload_t *asl = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->zmgr == NULL) { + return (ISC_R_FAILURE); + } + + /* If we already have a load pending, stop now */ + LOCK_ZONE(zone); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)) { + UNLOCK_ZONE(zone); + return (ISC_R_ALREADYRUNNING); + } + + asl = isc_mem_get(zone->mctx, sizeof(*asl)); + + asl->zone = NULL; + asl->flags = newonly ? DNS_ZONELOADFLAG_NOSTAT : 0; + asl->loaded = done; + asl->loaded_arg = arg; + + e = isc_event_allocate(zone->zmgr->mctx, zone->zmgr, DNS_EVENT_ZONELOAD, + zone_asyncload, asl, sizeof(isc_event_t)); + + zone_iattach(zone, &asl->zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADPENDING); + isc_task_send(zone->loadtask, &e); + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +bool +dns__zone_loadpending(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)); +} + +isc_result_t +dns_zone_loadandthaw(dns_zone_t *zone) { + isc_result_t result; + + if (inline_raw(zone)) { + result = zone_load(zone->secure, DNS_ZONELOADFLAG_THAW, false); + } else { + /* + * When thawing a zone, we don't know what changes + * have been made. If we do DNSSEC maintenance on this + * zone, schedule a full sign for this zone. + */ + if (zone->type == dns_zone_primary && + DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) + { + DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN); + } + result = zone_load(zone, DNS_ZONELOADFLAG_THAW, false); + } + + switch (result) { + case DNS_R_CONTINUE: + /* Deferred thaw. */ + break; + case DNS_R_UPTODATE: + case ISC_R_SUCCESS: + case DNS_R_SEENINCLUDE: + zone->update_disabled = false; + break; + case DNS_R_NOMASTERFILE: + zone->update_disabled = false; + break; + default: + /* Error, remain in disabled state. */ + break; + } + return (result); +} + +static unsigned int +get_primary_options(dns_zone_t *zone) { + unsigned int options; + + options = DNS_MASTER_ZONE | DNS_MASTER_RESIGN; + if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || + (zone->type == dns_zone_redirect && zone->primaries == NULL)) + { + options |= DNS_MASTER_SECONDARY; + } + if (zone->type == dns_zone_key) { + options |= DNS_MASTER_KEY; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNS)) { + options |= DNS_MASTER_CHECKNS; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_FATALNS)) { + options |= DNS_MASTER_FATALNS; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES)) { + options |= DNS_MASTER_CHECKNAMES; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL)) { + options |= DNS_MASTER_CHECKNAMESFAIL; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMX)) { + options |= DNS_MASTER_CHECKMX; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) { + options |= DNS_MASTER_CHECKMXFAIL; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKWILDCARD)) { + options |= DNS_MASTER_CHECKWILDCARD; + } + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKTTL)) { + options |= DNS_MASTER_CHECKTTL; + } + + return (options); +} + +static void +zone_registerinclude(const char *filename, void *arg) { + isc_result_t result; + dns_zone_t *zone = (dns_zone_t *)arg; + dns_include_t *inc = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (filename == NULL) { + return; + } + + /* + * Suppress duplicates. + */ + for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; + inc = ISC_LIST_NEXT(inc, link)) + { + if (strcmp(filename, inc->name) == 0) { + return; + } + } + + inc = isc_mem_get(zone->mctx, sizeof(dns_include_t)); + inc->name = isc_mem_strdup(zone->mctx, filename); + ISC_LINK_INIT(inc, link); + + result = isc_file_getmodtime(filename, &inc->filetime); + if (result != ISC_R_SUCCESS) { + isc_time_settoepoch(&inc->filetime); + } + + ISC_LIST_APPEND(zone->newincludes, inc, link); +} + +static void +zone_gotreadhandle(isc_task_t *task, isc_event_t *event) { + dns_load_t *load = event->ev_arg; + isc_result_t result = ISC_R_SUCCESS; + unsigned int options; + + REQUIRE(DNS_LOAD_VALID(load)); + + if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) { + result = ISC_R_CANCELED; + } + isc_event_free(&event); + if (result == ISC_R_CANCELED) { + goto fail; + } + + options = get_primary_options(load->zone); + + result = dns_master_loadfileinc( + load->zone->masterfile, dns_db_origin(load->db), + dns_db_origin(load->db), load->zone->rdclass, options, 0, + &load->callbacks, task, zone_loaddone, load, &load->zone->lctx, + zone_registerinclude, load->zone, load->zone->mctx, + load->zone->masterformat, load->zone->maxttl); + if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE && + result != DNS_R_SEENINCLUDE) + { + goto fail; + } + return; + +fail: + zone_loaddone(load, result); +} + +static void +get_raw_serial(dns_zone_t *raw, dns_masterrawheader_t *rawdata) { + isc_result_t result; + unsigned int soacount; + + LOCK(&raw->lock); + if (raw->db != NULL) { + result = zone_get_from_db(raw, raw->db, NULL, &soacount, NULL, + &rawdata->sourceserial, NULL, NULL, + NULL, NULL, NULL); + if (result == ISC_R_SUCCESS && soacount > 0U) { + rawdata->flags |= DNS_MASTERRAW_SOURCESERIALSET; + } + } + UNLOCK(&raw->lock); +} + +static void +zone_gotwritehandle(isc_task_t *task, isc_event_t *event) { + const char me[] = "zone_gotwritehandle"; + dns_zone_t *zone = event->ev_arg; + isc_result_t result = ISC_R_SUCCESS; + dns_dbversion_t *version = NULL; + dns_masterrawheader_t rawdata; + dns_db_t *db = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + INSIST(task == zone->task); + ENTER; + + if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) { + result = ISC_R_CANCELED; + } + isc_event_free(&event); + if (result == ISC_R_CANCELED) { + goto fail; + } + + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db != NULL) { + const dns_master_style_t *output_style; + dns_db_currentversion(db, &version); + dns_master_initrawheader(&rawdata); + if (inline_secure(zone)) { + get_raw_serial(zone->raw, &rawdata); + } + if (zone->type == dns_zone_key) { + output_style = &dns_master_style_keyzone; + } else if (zone->masterstyle != NULL) { + output_style = zone->masterstyle; + } else { + output_style = &dns_master_style_default; + } + result = dns_master_dumpasync( + zone->mctx, db, version, output_style, zone->masterfile, + zone->task, dump_done, zone, &zone->dctx, + zone->masterformat, &rawdata); + dns_db_closeversion(db, &version, false); + } else { + result = ISC_R_CANCELED; + } + if (db != NULL) { + dns_db_detach(&db); + } + UNLOCK_ZONE(zone); + if (result != DNS_R_CONTINUE) { + goto fail; + } + return; + +fail: + dump_done(zone, result); +} + +/* + * Save the raw serial number for inline-signing zones. + * (XXX: Other information from the header will be used + * for other purposes in the future, but for now this is + * all we're interested in.) + */ +static void +zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) { + if ((header->flags & DNS_MASTERRAW_SOURCESERIALSET) == 0) { + return; + } + + zone->sourceserial = header->sourceserial; + zone->sourceserialset = true; +} + +void +dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) { + if (zone == NULL) { + return; + } + + LOCK_ZONE(zone); + zone_setrawdata(zone, header); + UNLOCK_ZONE(zone); +} + +static isc_result_t +zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { + const char me[] = "zone_startload"; + dns_load_t *load; + isc_result_t result; + isc_result_t tresult; + unsigned int options; + + ENTER; + + dns_zone_rpz_enable_db(zone, db); + dns_zone_catz_enable_db(zone, db); + + options = get_primary_options(zone); + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) { + options |= DNS_MASTER_MANYERRORS; + } + + if (zone->zmgr != NULL && zone->db != NULL && zone->loadtask != NULL) { + load = isc_mem_get(zone->mctx, sizeof(*load)); + + load->mctx = NULL; + load->zone = NULL; + load->db = NULL; + load->loadtime = loadtime; + load->magic = LOAD_MAGIC; + + isc_mem_attach(zone->mctx, &load->mctx); + zone_iattach(zone, &load->zone); + dns_db_attach(db, &load->db); + dns_rdatacallbacks_init(&load->callbacks); + load->callbacks.rawdata = zone_setrawdata; + zone_iattach(zone, &load->callbacks.zone); + result = dns_db_beginload(db, &load->callbacks); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = zonemgr_getio(zone->zmgr, true, zone->loadtask, + zone_gotreadhandle, load, &zone->readio); + if (result != ISC_R_SUCCESS) { + /* + * We can't report multiple errors so ignore + * the result of dns_db_endload(). + */ + (void)dns_db_endload(load->db, &load->callbacks); + goto cleanup; + } else { + result = DNS_R_CONTINUE; + } + } else { + dns_rdatacallbacks_t callbacks; + + dns_rdatacallbacks_init(&callbacks); + callbacks.rawdata = zone_setrawdata; + zone_iattach(zone, &callbacks.zone); + result = dns_db_beginload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + zone_idetach(&callbacks.zone); + return (result); + } + + if (zone->stream != NULL) { + FILE *stream = NULL; + DE_CONST(zone->stream, stream); + result = dns_master_loadstream( + stream, &zone->origin, &zone->origin, + zone->rdclass, options, &callbacks, zone->mctx); + } else { + result = dns_master_loadfile( + zone->masterfile, &zone->origin, &zone->origin, + zone->rdclass, options, 0, &callbacks, + zone_registerinclude, zone, zone->mctx, + zone->masterformat, zone->maxttl); + } + + tresult = dns_db_endload(db, &callbacks); + if (result == ISC_R_SUCCESS) { + result = tresult; + } + zone_idetach(&callbacks.zone); + } + + return (result); + +cleanup: + load->magic = 0; + dns_db_detach(&load->db); + zone_idetach(&load->zone); + zone_idetach(&load->callbacks.zone); + isc_mem_detach(&load->mctx); + isc_mem_put(zone->mctx, load, sizeof(*load)); + return (result); +} + +static bool +zone_check_mx(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, + dns_name_t *owner) { + isc_result_t result; + char ownerbuf[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char altbuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed; + dns_name_t *foundname; + int level; + + /* + * "." means the services does not exist. + */ + if (dns_name_equal(name, dns_rootname)) { + return (true); + } + + /* + * Outside of zone. + */ + if (!dns_name_issubdomain(name, &zone->origin)) { + if (zone->checkmx != NULL) { + return ((zone->checkmx)(zone, name, owner)); + } + return (true); + } + + if (zone->type == dns_zone_primary) { + level = ISC_LOG_ERROR; + } else { + level = ISC_LOG_WARNING; + } + + foundname = dns_fixedname_initname(&fixed); + + result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, + foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + + if (result == DNS_R_NXRRSET) { + result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0, + NULL, foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + } + + dns_name_format(owner, ownerbuf, sizeof ownerbuf); + dns_name_format(name, namebuf, sizeof namebuf); + if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || + result == DNS_R_EMPTYNAME) + { + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) { + level = ISC_LOG_WARNING; + } + dns_zone_log(zone, level, + "%s/MX '%s' has no address records (A or AAAA)", + ownerbuf, namebuf); + return ((level == ISC_LOG_WARNING) ? true : false); + } + + if (result == DNS_R_CNAME) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) || + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) + { + level = ISC_LOG_WARNING; + } + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { + dns_zone_log(zone, level, + "%s/MX '%s' is a CNAME (illegal)", + ownerbuf, namebuf); + } + return ((level == ISC_LOG_WARNING) ? true : false); + } + + if (result == DNS_R_DNAME) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) || + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) + { + level = ISC_LOG_WARNING; + } + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { + dns_name_format(foundname, altbuf, sizeof altbuf); + dns_zone_log(zone, level, + "%s/MX '%s' is below a DNAME" + " '%s' (illegal)", + ownerbuf, namebuf, altbuf); + } + return ((level == ISC_LOG_WARNING) ? true : false); + } + + if (zone->checkmx != NULL && result == DNS_R_DELEGATION) { + return ((zone->checkmx)(zone, name, owner)); + } + + return (true); +} + +static bool +zone_check_srv(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, + dns_name_t *owner) { + isc_result_t result; + char ownerbuf[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char altbuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed; + dns_name_t *foundname; + int level; + + /* + * "." means the services does not exist. + */ + if (dns_name_equal(name, dns_rootname)) { + return (true); + } + + /* + * Outside of zone. + */ + if (!dns_name_issubdomain(name, &zone->origin)) { + if (zone->checksrv != NULL) { + return ((zone->checksrv)(zone, name, owner)); + } + return (true); + } + + if (zone->type == dns_zone_primary) { + level = ISC_LOG_ERROR; + } else { + level = ISC_LOG_WARNING; + } + + foundname = dns_fixedname_initname(&fixed); + + result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, + foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + + if (result == DNS_R_NXRRSET) { + result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0, + NULL, foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + } + + dns_name_format(owner, ownerbuf, sizeof ownerbuf); + dns_name_format(name, namebuf, sizeof namebuf); + if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || + result == DNS_R_EMPTYNAME) + { + dns_zone_log(zone, level, + "%s/SRV '%s' has no address records (A or AAAA)", + ownerbuf, namebuf); + /* XXX950 make fatal for 9.5.0. */ + return (true); + } + + if (result == DNS_R_CNAME) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) || + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) + { + level = ISC_LOG_WARNING; + } + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { + dns_zone_log(zone, level, + "%s/SRV '%s' is a CNAME (illegal)", + ownerbuf, namebuf); + } + return ((level == ISC_LOG_WARNING) ? true : false); + } + + if (result == DNS_R_DNAME) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) || + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) + { + level = ISC_LOG_WARNING; + } + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { + dns_name_format(foundname, altbuf, sizeof altbuf); + dns_zone_log(zone, level, + "%s/SRV '%s' is below a " + "DNAME '%s' (illegal)", + ownerbuf, namebuf, altbuf); + } + return ((level == ISC_LOG_WARNING) ? true : false); + } + + if (zone->checksrv != NULL && result == DNS_R_DELEGATION) { + return ((zone->checksrv)(zone, name, owner)); + } + + return (true); +} + +static bool +zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, + dns_name_t *owner) { + bool answer = true; + isc_result_t result, tresult; + char ownerbuf[DNS_NAME_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char altbuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed; + dns_name_t *foundname; + dns_rdataset_t a; + dns_rdataset_t aaaa; + int level; + + /* + * Outside of zone. + */ + if (!dns_name_issubdomain(name, &zone->origin)) { + if (zone->checkns != NULL) { + return ((zone->checkns)(zone, name, owner, NULL, NULL)); + } + return (true); + } + + if (zone->type == dns_zone_primary) { + level = ISC_LOG_ERROR; + } else { + level = ISC_LOG_WARNING; + } + + foundname = dns_fixedname_initname(&fixed); + dns_rdataset_init(&a); + dns_rdataset_init(&aaaa); + + /* + * Perform a regular lookup to catch DNAME records then look + * for glue. + */ + result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, + foundname, &a, NULL); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_DNAME: + case DNS_R_CNAME: + break; + default: + if (dns_rdataset_isassociated(&a)) { + dns_rdataset_disassociate(&a); + } + result = dns_db_find(db, name, NULL, dns_rdatatype_a, + DNS_DBFIND_GLUEOK, 0, NULL, foundname, &a, + NULL); + } + if (result == ISC_R_SUCCESS) { + dns_rdataset_disassociate(&a); + return (true); + } else if (result == DNS_R_DELEGATION) { + dns_rdataset_disassociate(&a); + } + + if (result == DNS_R_NXRRSET || result == DNS_R_DELEGATION || + result == DNS_R_GLUE) + { + tresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, + DNS_DBFIND_GLUEOK, 0, NULL, foundname, + &aaaa, NULL); + if (tresult == ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&a)) { + dns_rdataset_disassociate(&a); + } + dns_rdataset_disassociate(&aaaa); + return (true); + } + if (tresult == DNS_R_DELEGATION || tresult == DNS_R_DNAME) { + dns_rdataset_disassociate(&aaaa); + } + if (result == DNS_R_GLUE || tresult == DNS_R_GLUE) { + /* + * Check glue against child zone. + */ + if (zone->checkns != NULL) { + answer = (zone->checkns)(zone, name, owner, &a, + &aaaa); + } + if (dns_rdataset_isassociated(&a)) { + dns_rdataset_disassociate(&a); + } + if (dns_rdataset_isassociated(&aaaa)) { + dns_rdataset_disassociate(&aaaa); + } + return (answer); + } + } + + dns_name_format(owner, ownerbuf, sizeof ownerbuf); + dns_name_format(name, namebuf, sizeof namebuf); + if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || + result == DNS_R_EMPTYNAME || result == DNS_R_DELEGATION) + { + const char *what; + bool required = false; + if (dns_name_issubdomain(name, owner)) { + what = "REQUIRED GLUE "; + required = true; + } else if (result == DNS_R_DELEGATION) { + what = "SIBLING GLUE "; + } else { + what = ""; + } + + if (result != DNS_R_DELEGATION || required || + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSIBLING)) + { + dns_zone_log(zone, level, + "%s/NS '%s' has no %s" + "address records (A or AAAA)", + ownerbuf, namebuf, what); + /* + * Log missing address record. + */ + if (result == DNS_R_DELEGATION && zone->checkns != NULL) + { + (void)(zone->checkns)(zone, name, owner, &a, + &aaaa); + } + /* XXX950 make fatal for 9.5.0. */ + /* answer = false; */ + } + } else if (result == DNS_R_CNAME) { + dns_zone_log(zone, level, "%s/NS '%s' is a CNAME (illegal)", + ownerbuf, namebuf); + /* XXX950 make fatal for 9.5.0. */ + /* answer = false; */ + } else if (result == DNS_R_DNAME) { + dns_name_format(foundname, altbuf, sizeof altbuf); + dns_zone_log(zone, level, + "%s/NS '%s' is below a DNAME '%s' (illegal)", + ownerbuf, namebuf, altbuf); + /* XXX950 make fatal for 9.5.0. */ + /* answer = false; */ + } + + if (dns_rdataset_isassociated(&a)) { + dns_rdataset_disassociate(&a); + } + if (dns_rdataset_isassociated(&aaaa)) { + dns_rdataset_disassociate(&aaaa); + } + return (answer); +} + +static bool +zone_rrset_check_dup(dns_zone_t *zone, dns_name_t *owner, + dns_rdataset_t *rdataset) { + dns_rdataset_t tmprdataset; + isc_result_t result; + bool answer = true; + bool format = true; + int level = ISC_LOG_WARNING; + char ownerbuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + unsigned int count1 = 0; + + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRRFAIL)) { + level = ISC_LOG_ERROR; + } + + dns_rdataset_init(&tmprdataset); + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + unsigned int count2 = 0; + + count1++; + dns_rdataset_current(rdataset, &rdata1); + dns_rdataset_clone(rdataset, &tmprdataset); + for (result = dns_rdataset_first(&tmprdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&tmprdataset)) + { + dns_rdata_t rdata2 = DNS_RDATA_INIT; + count2++; + if (count1 >= count2) { + continue; + } + dns_rdataset_current(&tmprdataset, &rdata2); + if (dns_rdata_casecompare(&rdata1, &rdata2) == 0) { + if (format) { + dns_name_format(owner, ownerbuf, + sizeof ownerbuf); + dns_rdatatype_format(rdata1.type, + typebuf, + sizeof(typebuf)); + format = false; + } + dns_zone_log(zone, level, + "%s/%s has " + "semantically identical records", + ownerbuf, typebuf); + if (level == ISC_LOG_ERROR) { + answer = false; + } + break; + } + } + dns_rdataset_disassociate(&tmprdataset); + if (!format) { + break; + } + } + return (answer); +} + +static bool +zone_check_dup(dns_zone_t *zone, dns_db_t *db) { + dns_dbiterator_t *dbiterator = NULL; + dns_dbnode_t *node = NULL; + dns_fixedname_t fixed; + dns_name_t *name; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsit = NULL; + bool ok = true; + isc_result_t result; + + name = dns_fixedname_initname(&fixed); + dns_rdataset_init(&rdataset); + + result = dns_db_createiterator(db, 0, &dbiterator); + if (result != ISC_R_SUCCESS) { + return (true); + } + + for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiterator)) + { + result = dns_dbiterator_current(dbiterator, &node, name); + if (result != ISC_R_SUCCESS) { + continue; + } + + result = dns_db_allrdatasets(db, node, NULL, 0, 0, &rdsit); + if (result != ISC_R_SUCCESS) { + continue; + } + + for (result = dns_rdatasetiter_first(rdsit); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsit)) + { + dns_rdatasetiter_current(rdsit, &rdataset); + if (!zone_rrset_check_dup(zone, name, &rdataset)) { + ok = false; + } + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&rdsit); + dns_db_detachnode(db, &node); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + dns_dbiterator_destroy(&dbiterator); + + return (ok); +} + +static bool +isspf(const dns_rdata_t *rdata) { + char buf[1024]; + const unsigned char *data = rdata->data; + unsigned int rdl = rdata->length, i = 0, tl, len; + + while (rdl > 0U) { + len = tl = *data; + ++data; + --rdl; + INSIST(tl <= rdl); + if (len > sizeof(buf) - i - 1) { + len = sizeof(buf) - i - 1; + } + memmove(buf + i, data, len); + i += len; + data += tl; + rdl -= tl; + } + + if (i < 6U) { + return (false); + } + + buf[i] = 0; + if (strncmp(buf, "v=spf1", 6) == 0 && (buf[6] == 0 || buf[6] == ' ')) { + return (true); + } + return (false); +} + +static bool +integrity_checks(dns_zone_t *zone, dns_db_t *db) { + dns_dbiterator_t *dbiterator = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_fixedname_t fixedbottom; + dns_rdata_mx_t mx; + dns_rdata_ns_t ns; + dns_rdata_in_srv_t srv; + dns_rdata_t rdata; + dns_name_t *name; + dns_name_t *bottom; + isc_result_t result; + bool ok = true, have_spf, have_txt; + + name = dns_fixedname_initname(&fixed); + bottom = dns_fixedname_initname(&fixedbottom); + dns_rdataset_init(&rdataset); + dns_rdata_init(&rdata); + + result = dns_db_createiterator(db, 0, &dbiterator); + if (result != ISC_R_SUCCESS) { + return (true); + } + + result = dns_dbiterator_first(dbiterator); + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiterator, &node, name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Is this name visible in the zone? + */ + if (!dns_name_issubdomain(name, &zone->origin) || + (dns_name_countlabels(bottom) > 0 && + dns_name_issubdomain(name, bottom))) + { + goto next; + } + + dns_dbiterator_pause(dbiterator); + + /* + * Don't check the NS records at the origin. + */ + if (dns_name_equal(name, &zone->origin)) { + goto checkfordname; + } + + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ns, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto checkfordname; + } + /* + * Remember bottom of zone due to NS. + */ + dns_name_copy(name, bottom); + + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (!zone_check_glue(zone, db, &ns.name, name)) { + ok = false; + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + goto next; + + checkfordname: + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_dname, 0, 0, + &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + /* + * Remember bottom of zone due to DNAME. + */ + dns_name_copy(name, bottom); + dns_rdataset_disassociate(&rdataset); + } + + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_mx, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto checksrv; + } + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &mx, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (!zone_check_mx(zone, db, &mx.mx, name)) { + ok = false; + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + + checksrv: + if (zone->rdclass != dns_rdataclass_in) { + goto next; + } + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_srv, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto checkspf; + } + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &srv, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (!zone_check_srv(zone, db, &srv.target, name)) { + ok = false; + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + + checkspf: + /* + * Check if there is a type SPF record without an + * SPF-formatted type TXT record also being present. + */ + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSPF)) { + goto next; + } + if (zone->rdclass != dns_rdataclass_in) { + goto next; + } + have_spf = have_txt = false; + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_spf, + 0, 0, &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + have_spf = true; + } + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_txt, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto notxt; + } + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + have_txt = isspf(&rdata); + dns_rdata_reset(&rdata); + if (have_txt) { + break; + } + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + + notxt: + if (have_spf && !have_txt) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_zone_log(zone, ISC_LOG_WARNING, + "'%s' found type " + "SPF record but no SPF TXT record found, " + "add matching type TXT record", + namebuf); + } + + next: + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(dbiterator); + } + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + dns_dbiterator_destroy(&dbiterator); + + return (ok); +} + +/* + * OpenSSL verification of RSA keys with exponent 3 is known to be + * broken prior OpenSSL 0.9.8c/0.9.7k. Look for such keys and warn + * if they are in use. + */ +static void +zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) { + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdata_dnskey_t dnskey; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + isc_result_t result; + + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_db_currentversion(db, &version); + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dnskey, NULL); + INSIST(result == ISC_R_SUCCESS); + + /* + * RFC 3110, section 4: Performance Considerations: + * + * A public exponent of 3 minimizes the effort needed to verify + * a signature. Use of 3 as the public exponent is weak for + * confidentiality uses since, if the same data can be collected + * encrypted under three different keys with an exponent of 3 + * then, using the Chinese Remainder Theorem [NETSEC], the + * original plain text can be easily recovered. If a key is + * known to be used only for authentication, as is the case with + * DNSSEC, then an exponent of 3 is acceptable. However other + * applications in the future may wish to leverage DNS + * distributed keys for applications that do require + * confidentiality. For keys which might have such other uses, + * a more conservative choice would be 65537 (F4, the fourth + * fermat number). + */ + if (dnskey.datalen > 1 && dnskey.data[0] == 1 && + dnskey.data[1] == 3 && + (dnskey.algorithm == DNS_KEYALG_RSAMD5 || + dnskey.algorithm == DNS_KEYALG_RSASHA1 || + dnskey.algorithm == DNS_KEYALG_NSEC3RSASHA1 || + dnskey.algorithm == DNS_KEYALG_RSASHA256 || + dnskey.algorithm == DNS_KEYALG_RSASHA512)) + { + char algorithm[DNS_SECALG_FORMATSIZE]; + isc_region_t r; + + dns_rdata_toregion(&rdata, &r); + dns_secalg_format(dnskey.algorithm, algorithm, + sizeof(algorithm)); + + dnssec_log(zone, ISC_LOG_WARNING, + "weak %s (%u) key found (exponent=3, id=%u)", + algorithm, dnskey.algorithm, + dst_region_computeid(&r)); + } + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&rdataset); + +cleanup: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } +} + +static void +resume_signingwithkey(dns_zone_t *zone) { + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + isc_result_t result; + dns_db_t *db = NULL; + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + goto cleanup; + } + + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_db_currentversion(db, &version); + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, zone->privatetype, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto cleanup; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + if (rdata.length != 5 || rdata.data[0] == 0 || + rdata.data[4] != 0) + { + dns_rdata_reset(&rdata); + continue; + } + + result = zone_signwithkey(zone, rdata.data[0], + (rdata.data[1] << 8) | rdata.data[2], + rdata.data[3]); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: %s", + isc_result_totext(result)); + } + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&rdataset); + +cleanup: + if (db != NULL) { + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + dns_db_detach(&db); + } +} + +/* + * Initiate adding/removing NSEC3 records belonging to the chain defined by the + * supplied NSEC3PARAM RDATA. + * + * Zone must be locked by caller. + */ +static isc_result_t +zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) { + dns_nsec3chain_t *nsec3chain, *current; + dns_dbversion_t *version = NULL; + bool nseconly = false, nsec3ok = false; + isc_result_t result; + isc_time_t now; + unsigned int options = 0; + char saltbuf[255 * 2 + 1]; + char flags[sizeof("INITIAL|REMOVE|CREATE|NONSEC|OPTOUT")]; + dns_db_t *db = NULL; + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + if (db == NULL) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* + * If this zone is not NSEC3-capable, attempting to remove any NSEC3 + * chain from it is pointless as it would not be possible for the + * latter to exist in the first place. + */ + dns_db_currentversion(db, &version); + result = dns_nsec_nseconly(db, version, NULL, &nseconly); + nsec3ok = (result == ISC_R_SUCCESS && !nseconly); + dns_db_closeversion(db, &version, false); + if (!nsec3ok && (nsec3param->flags & DNS_NSEC3FLAG_REMOVE) == 0) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + /* + * Allocate and initialize structure preserving state of + * adding/removing records belonging to this NSEC3 chain between + * separate zone_nsec3chain() calls. + */ + nsec3chain = isc_mem_get(zone->mctx, sizeof *nsec3chain); + + nsec3chain->magic = 0; + nsec3chain->done = false; + nsec3chain->db = NULL; + nsec3chain->dbiterator = NULL; + nsec3chain->nsec3param.common.rdclass = nsec3param->common.rdclass; + nsec3chain->nsec3param.common.rdtype = nsec3param->common.rdtype; + nsec3chain->nsec3param.hash = nsec3param->hash; + nsec3chain->nsec3param.iterations = nsec3param->iterations; + nsec3chain->nsec3param.flags = nsec3param->flags; + nsec3chain->nsec3param.salt_length = nsec3param->salt_length; + memmove(nsec3chain->salt, nsec3param->salt, nsec3param->salt_length); + nsec3chain->nsec3param.salt = nsec3chain->salt; + nsec3chain->seen_nsec = false; + nsec3chain->delete_nsec = false; + nsec3chain->save_delete_nsec = false; + + /* + * Log NSEC3 parameters defined by supplied NSEC3PARAM RDATA. + */ + if (nsec3param->flags == 0) { + strlcpy(flags, "NONE", sizeof(flags)); + } else { + flags[0] = '\0'; + if ((nsec3param->flags & DNS_NSEC3FLAG_REMOVE) != 0) { + strlcat(flags, "REMOVE", sizeof(flags)); + } + if ((nsec3param->flags & DNS_NSEC3FLAG_INITIAL) != 0) { + if (flags[0] == '\0') { + strlcpy(flags, "INITIAL", sizeof(flags)); + } else { + strlcat(flags, "|INITIAL", sizeof(flags)); + } + } + if ((nsec3param->flags & DNS_NSEC3FLAG_CREATE) != 0) { + if (flags[0] == '\0') { + strlcpy(flags, "CREATE", sizeof(flags)); + } else { + strlcat(flags, "|CREATE", sizeof(flags)); + } + } + if ((nsec3param->flags & DNS_NSEC3FLAG_NONSEC) != 0) { + if (flags[0] == '\0') { + strlcpy(flags, "NONSEC", sizeof(flags)); + } else { + strlcat(flags, "|NONSEC", sizeof(flags)); + } + } + if ((nsec3param->flags & DNS_NSEC3FLAG_OPTOUT) != 0) { + if (flags[0] == '\0') { + strlcpy(flags, "OPTOUT", sizeof(flags)); + } else { + strlcat(flags, "|OPTOUT", sizeof(flags)); + } + } + } + result = dns_nsec3param_salttotext(nsec3param, saltbuf, + sizeof(saltbuf)); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dnssec_log(zone, ISC_LOG_INFO, "zone_addnsec3chain(%u,%s,%u,%s)", + nsec3param->hash, flags, nsec3param->iterations, saltbuf); + + /* + * If the NSEC3 chain defined by the supplied NSEC3PARAM RDATA is + * currently being processed, interrupt its processing to avoid + * simultaneously adding and removing records for the same NSEC3 chain. + */ + for (current = ISC_LIST_HEAD(zone->nsec3chain); current != NULL; + current = ISC_LIST_NEXT(current, link)) + { + if ((current->db == db) && + (current->nsec3param.hash == nsec3param->hash) && + (current->nsec3param.iterations == + nsec3param->iterations) && + (current->nsec3param.salt_length == + nsec3param->salt_length) && + memcmp(current->nsec3param.salt, nsec3param->salt, + nsec3param->salt_length) == 0) + { + current->done = true; + } + } + + /* + * Attach zone database to the structure initialized above and create + * an iterator for it with appropriate options in order to avoid + * creating NSEC3 records for NSEC3 records. + */ + dns_db_attach(db, &nsec3chain->db); + if ((nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0) { + options = DNS_DB_NONSEC3; + } + result = dns_db_createiterator(nsec3chain->db, options, + &nsec3chain->dbiterator); + if (result == ISC_R_SUCCESS) { + result = dns_dbiterator_first(nsec3chain->dbiterator); + } + if (result == ISC_R_SUCCESS) { + /* + * Database iterator initialization succeeded. We are now + * ready to kick off adding/removing records belonging to this + * NSEC3 chain. Append the structure initialized above to the + * "nsec3chain" list for the zone and set the appropriate zone + * timer so that zone_nsec3chain() is called as soon as + * possible. + */ + dns_dbiterator_pause(nsec3chain->dbiterator); + ISC_LIST_INITANDAPPEND(zone->nsec3chain, nsec3chain, link); + nsec3chain = NULL; + if (isc_time_isepoch(&zone->nsec3chaintime)) { + TIME_NOW(&now); + zone->nsec3chaintime = now; + if (zone->task != NULL) { + zone_settimer(zone, &now); + } + } + } + + if (nsec3chain != NULL) { + if (nsec3chain->db != NULL) { + dns_db_detach(&nsec3chain->db); + } + if (nsec3chain->dbiterator != NULL) { + dns_dbiterator_destroy(&nsec3chain->dbiterator); + } + isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); + } + +cleanup: + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +/* + * Find private-type records at the zone apex which signal that an NSEC3 chain + * should be added or removed. For each such record, extract NSEC3PARAM RDATA + * and pass it to zone_addnsec3chain(). + * + * Zone must be locked by caller. + */ +static void +resume_addnsec3chain(dns_zone_t *zone) { + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdataset_t rdataset; + isc_result_t result; + dns_rdata_nsec3param_t nsec3param; + bool nseconly = false, nsec3ok = false; + dns_db_t *db = NULL; + + INSIST(LOCKED_ZONE(zone)); + + if (zone->privatetype == 0) { + return; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + goto cleanup; + } + + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_db_currentversion(db, &version); + + /* + * In order to create NSEC3 chains we need the DNSKEY RRset at zone + * apex to exist and contain no keys using NSEC-only algorithms. + */ + result = dns_nsec_nseconly(db, version, NULL, &nseconly); + nsec3ok = (result == ISC_R_SUCCESS && !nseconly); + + /* + * Get the RRset containing all private-type records at the zone apex. + */ + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, zone->privatetype, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto cleanup; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_t private = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &private); + /* + * Try extracting NSEC3PARAM RDATA from this private-type + * record. Failure means this private-type record does not + * represent an NSEC3PARAM record, so skip it. + */ + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) || + ((nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0 && nsec3ok)) + { + /* + * Pass the NSEC3PARAM RDATA contained in this + * private-type record to zone_addnsec3chain() so that + * it can kick off adding or removing NSEC3 records. + */ + result = zone_addnsec3chain(zone, &nsec3param); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_addnsec3chain failed: %s", + isc_result_totext(result)); + } + } + } + dns_rdataset_disassociate(&rdataset); + +cleanup: + if (db != NULL) { + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + dns_db_detach(&db); + } +} + +static void +set_resigntime(dns_zone_t *zone) { + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + unsigned int resign; + isc_result_t result; + uint32_t nanosecs; + dns_db_t *db = NULL; + + INSIST(LOCKED_ZONE(zone)); + + /* We only re-sign zones that can be dynamically updated */ + if (zone->update_disabled) { + return; + } + + if (!inline_secure(zone) && + (zone->type != dns_zone_primary || + (zone->ssutable == NULL && + (zone->update_acl == NULL || dns_acl_isnone(zone->update_acl))))) + { + return; + } + + dns_rdataset_init(&rdataset); + dns_fixedname_init(&fixed); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + isc_time_settoepoch(&zone->resigntime); + return; + } + + result = dns_db_getsigningtime(db, &rdataset, + dns_fixedname_name(&fixed)); + if (result != ISC_R_SUCCESS) { + isc_time_settoepoch(&zone->resigntime); + goto cleanup; + } + + resign = rdataset.resign - dns_zone_getsigresigninginterval(zone); + dns_rdataset_disassociate(&rdataset); + nanosecs = isc_random_uniform(1000000000); + isc_time_set(&zone->resigntime, resign, nanosecs); + +cleanup: + dns_db_detach(&db); + return; +} + +static isc_result_t +check_nsec3param(dns_zone_t *zone, dns_db_t *db) { + bool ok = false; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdata_nsec3param_t nsec3param; + dns_rdataset_t rdataset; + isc_result_t result; + bool dynamic = (zone->type == dns_zone_primary) + ? dns_zone_isdynamic(zone, false) + : false; + + dns_rdataset_init(&rdataset); + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "nsec3param lookup failure: %s", + isc_result_totext(result)); + return (result); + } + dns_db_currentversion(db, &version); + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + result = ISC_R_SUCCESS; + goto cleanup; + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + dns_zone_log(zone, ISC_LOG_ERROR, + "nsec3param lookup failure: %s", + isc_result_totext(result)); + goto cleanup; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * For dynamic zones we must support every algorithm so we + * can regenerate all the NSEC3 chains. + * For non-dynamic zones we only need to find a supported + * algorithm. + */ + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NSEC3TESTZONE) && + nsec3param.hash == DNS_NSEC3_UNKNOWNALG && !dynamic) + { + dns_zone_log(zone, ISC_LOG_WARNING, + "nsec3 test \"unknown\" hash algorithm " + "found: %u", + nsec3param.hash); + ok = true; + } else if (!dns_nsec3_supportedhash(nsec3param.hash)) { + if (dynamic) { + dns_zone_log(zone, ISC_LOG_ERROR, + "unsupported nsec3 hash algorithm" + " in dynamic zone: %u", + nsec3param.hash); + result = DNS_R_BADZONE; + /* Stop second error message. */ + ok = true; + break; + } else { + dns_zone_log(zone, ISC_LOG_WARNING, + "unsupported nsec3 hash " + "algorithm: %u", + nsec3param.hash); + } + } else { + ok = true; + } + + /* + * Warn if the zone has excessive NSEC3 iterations. + */ + if (nsec3param.iterations > dns_nsec3_maxiterations()) { + dnssec_log(zone, ISC_LOG_WARNING, + "excessive NSEC3PARAM iterations %u > %u", + nsec3param.iterations, + dns_nsec3_maxiterations()); + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + if (!ok) { + result = DNS_R_BADZONE; + dns_zone_log(zone, ISC_LOG_ERROR, + "no supported nsec3 hash algorithm"); + } + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + dns_db_closeversion(db, &version, false); + dns_db_detachnode(db, &node); + return (result); +} + +/* + * Set the timer for refreshing the key zone to the soonest future time + * of the set (current timer, keydata->refresh, keydata->addhd, + * keydata->removehd). + */ +static void +set_refreshkeytimer(dns_zone_t *zone, dns_rdata_keydata_t *key, + isc_stdtime_t now, bool force) { + const char me[] = "set_refreshkeytimer"; + isc_stdtime_t then; + isc_time_t timenow, timethen; + char timebuf[80]; + + ENTER; + then = key->refresh; + if (force) { + then = now; + } + if (key->addhd > now && key->addhd < then) { + then = key->addhd; + } + if (key->removehd > now && key->removehd < then) { + then = key->removehd; + } + + TIME_NOW(&timenow); + if (then > now) { + DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen); + } else { + timethen = timenow; + } + if (isc_time_compare(&zone->refreshkeytime, &timenow) < 0 || + isc_time_compare(&timethen, &zone->refreshkeytime) < 0) + { + zone->refreshkeytime = timethen; + } + + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dns_zone_log(zone, ISC_LOG_DEBUG(1), "next key refresh: %s", timebuf); + zone_settimer(zone, &timenow); +} + +/* + * If keynode references a key or a DS rdataset, and if the key + * zone does not contain a KEYDATA record for the corresponding name, + * then create an empty KEYDATA and push it into the zone as a placeholder, + * then schedule a key refresh immediately. This new KEYDATA record will be + * updated during the refresh. + * + * If the key zone is changed, set '*changed' to true. + */ +static isc_result_t +create_keydata(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff, dns_keynode_t *keynode, dns_name_t *keyname, + bool *changed) { + const char me[] = "create_keydata"; + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t kd; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_stdtime_t now; + + REQUIRE(keynode != NULL); + + ENTER; + isc_stdtime_get(&now); + + /* + * If the keynode has no trust anchor set, we shouldn't be here. + */ + if (!dns_keynode_dsset(keynode, NULL)) { + return (ISC_R_FAILURE); + } + + memset(&kd, 0, sizeof(kd)); + kd.common.rdclass = zone->rdclass; + kd.common.rdtype = dns_rdatatype_keydata; + ISC_LINK_INIT(&kd.common, link); + + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + + CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass, dns_rdatatype_keydata, + &kd, &rrdatabuf)); + /* Add rdata to zone. */ + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, keyname, 0, &rdata)); + *changed = true; + + /* Refresh new keys from the zone apex as soon as possible. */ + set_refreshkeytimer(zone, &kd, now, true); + return (ISC_R_SUCCESS); + +failure: + return (result); +} + +/* + * Remove from the key zone all the KEYDATA records found in rdataset. + */ +static isc_result_t +delete_keydata(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, + dns_name_t *name, dns_rdataset_t *rdataset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result, uresult; + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + uresult = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, 0, + &rdata); + if (uresult != ISC_R_SUCCESS) { + return (uresult); + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + return (result); +} + +/* + * Compute the DNSSEC key ID for a DNSKEY record. + */ +static isc_result_t +compute_tag(dns_name_t *name, dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx, + dns_keytag_t *tag) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[4096]; + isc_buffer_t buffer; + dst_key_t *dstkey = NULL; + + isc_buffer_init(&buffer, data, sizeof(data)); + dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, + dns_rdatatype_dnskey, dnskey, &buffer); + + result = dns_dnssec_keyfromrdata(name, &rdata, mctx, &dstkey); + if (result == ISC_R_SUCCESS) { + *tag = dst_key_id(dstkey); + dst_key_free(&dstkey); + } + + return (result); +} + +/* + * Synth-from-dnssec callbacks to add/delete names from namespace tree. + */ +static void +sfd_add(const dns_name_t *name, void *arg) { + if (arg != NULL) { + dns_view_sfd_add(arg, name); + } +} + +static void +sfd_del(const dns_name_t *name, void *arg) { + if (arg != NULL) { + dns_view_sfd_del(arg, name); + } +} + +/* + * Add key to the security roots. + */ +static void +trust_key(dns_zone_t *zone, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey, + bool initial) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[4096], digest[ISC_MAX_MD_SIZE]; + isc_buffer_t buffer; + dns_keytable_t *sr = NULL; + dns_rdata_ds_t ds; + + result = dns_view_getsecroots(zone->view, &sr); + if (result != ISC_R_SUCCESS) { + return; + } + + /* Build DS record for key. */ + isc_buffer_init(&buffer, data, sizeof(data)); + dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, + dns_rdatatype_dnskey, dnskey, &buffer); + CHECK(dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, digest, + &ds)); + CHECK(dns_keytable_add(sr, true, initial, keyname, &ds, sfd_add, + zone->view)); + + dns_keytable_detach(&sr); + +failure: + if (sr != NULL) { + dns_keytable_detach(&sr); + } + return; +} + +/* + * Add a null key to the security roots for so that all queries + * to the zone will fail. + */ +static void +fail_secure(dns_zone_t *zone, dns_name_t *keyname) { + isc_result_t result; + dns_keytable_t *sr = NULL; + + result = dns_view_getsecroots(zone->view, &sr); + if (result == ISC_R_SUCCESS) { + dns_keytable_marksecure(sr, keyname); + dns_keytable_detach(&sr); + } +} + +/* + * Scan a set of KEYDATA records from the key zone. The ones that are + * valid (i.e., the add holddown timer has expired) become trusted keys. + */ +static void +load_secroots(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t keydata; + dns_rdata_dnskey_t dnskey; + int trusted = 0, revoked = 0, pending = 0; + isc_stdtime_t now; + dns_keytable_t *sr = NULL; + + isc_stdtime_get(&now); + + result = dns_view_getsecroots(zone->view, &sr); + if (result == ISC_R_SUCCESS) { + dns_keytable_delete(sr, name, sfd_del, zone->view); + dns_keytable_detach(&sr); + } + + /* Now insert all the accepted trust anchors from this keydata set. */ + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + + /* Convert rdata to keydata. */ + result = dns_rdata_tostruct(&rdata, &keydata, NULL); + if (result == ISC_R_UNEXPECTEDEND) { + continue; + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Set the key refresh timer to force a fast refresh. */ + set_refreshkeytimer(zone, &keydata, now, true); + + /* If the removal timer is nonzero, this key was revoked. */ + if (keydata.removehd != 0) { + revoked++; + continue; + } + + /* + * If the add timer is still pending, this key is not + * trusted yet. + */ + if (now < keydata.addhd) { + pending++; + continue; + } + + /* Convert keydata to dnskey. */ + dns_keydata_todnskey(&keydata, &dnskey, NULL); + + /* Add to keytables. */ + trusted++; + trust_key(zone, name, &dnskey, (keydata.addhd == 0)); + } + + if (trusted == 0 && pending != 0) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof namebuf); + dnssec_log(zone, ISC_LOG_ERROR, + "No valid trust anchors for '%s'!", namebuf); + dnssec_log(zone, ISC_LOG_ERROR, + "%d key(s) revoked, %d still pending", revoked, + pending); + dnssec_log(zone, ISC_LOG_ERROR, "All queries to '%s' will fail", + namebuf); + fail_secure(zone, name); + } +} + +static isc_result_t +do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff) { + dns_diff_t temp_diff; + isc_result_t result; + + /* + * Create a singleton diff. + */ + dns_diff_init(diff->mctx, &temp_diff); + ISC_LIST_APPEND(temp_diff.tuples, *tuple, link); + + /* + * Apply it to the database. + */ + result = dns_diff_apply(&temp_diff, db, ver); + ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link); + if (result != ISC_R_SUCCESS) { + dns_difftuple_free(tuple); + return (result); + } + + /* + * Merge it into the current pending journal entry. + */ + dns_diff_appendminimal(diff, tuple); + + /* + * Do not clear temp_diff. + */ + return (ISC_R_SUCCESS); +} + +static isc_result_t +update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, + dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, + dns_rdata_t *rdata) { + dns_difftuple_t *tuple = NULL; + isc_result_t result; + result = dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple); + if (result != ISC_R_SUCCESS) { + return (result); + } + return (do_one_tuple(&tuple, db, ver, diff)); +} + +static isc_result_t +update_soa_serial(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff, isc_mem_t *mctx, + dns_updatemethod_t method) { + dns_difftuple_t *deltuple = NULL; + dns_difftuple_t *addtuple = NULL; + uint32_t serial; + isc_result_t result; + dns_updatemethod_t used = dns_updatemethod_none; + + INSIST(method != dns_updatemethod_none); + + CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_DEL, &deltuple)); + CHECK(dns_difftuple_copy(deltuple, &addtuple)); + addtuple->op = DNS_DIFFOP_ADD; + + serial = dns_soa_getserial(&addtuple->rdata); + serial = dns_update_soaserial(serial, method, &used); + if (method != used) { + dns_zone_log(zone, ISC_LOG_WARNING, + "update_soa_serial:new serial would be lower than " + "old serial, using increment method instead"); + } + dns_soa_setserial(serial, &addtuple->rdata); + CHECK(do_one_tuple(&deltuple, db, ver, diff)); + CHECK(do_one_tuple(&addtuple, db, ver, diff)); + result = ISC_R_SUCCESS; + +failure: + if (addtuple != NULL) { + dns_difftuple_free(&addtuple); + } + if (deltuple != NULL) { + dns_difftuple_free(&deltuple); + } + return (result); +} + +/* + * Write all transactions in 'diff' to the zone journal file. + */ +static isc_result_t +zone_journal(dns_zone_t *zone, dns_diff_t *diff, uint32_t *sourceserial, + const char *caller) { + const char me[] = "zone_journal"; + const char *journalfile; + isc_result_t result = ISC_R_SUCCESS; + dns_journal_t *journal = NULL; + unsigned int mode = DNS_JOURNAL_CREATE | DNS_JOURNAL_WRITE; + + ENTER; + journalfile = dns_zone_getjournal(zone); + if (journalfile != NULL) { + result = dns_journal_open(zone->mctx, journalfile, mode, + &journal); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "%s:dns_journal_open -> %s", caller, + isc_result_totext(result)); + return (result); + } + + if (sourceserial != NULL) { + dns_journal_set_sourceserial(journal, *sourceserial); + } + + result = dns_journal_write_transaction(journal, diff); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "%s:dns_journal_write_transaction -> %s", + caller, isc_result_totext(result)); + } + dns_journal_destroy(&journal); + } + + return (result); +} + +/* + * Create an SOA record for a newly-created zone + */ +static isc_result_t +add_soa(dns_zone_t *zone, dns_db_t *db) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char buf[DNS_SOA_BUFFERSIZE]; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + + dns_zone_log(zone, ISC_LOG_DEBUG(1), "creating SOA"); + + dns_diff_init(zone->mctx, &diff); + result = dns_db_newversion(db, &ver); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "add_soa:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + /* Build SOA record */ + result = dns_soa_buildrdata(&zone->origin, dns_rootname, zone->rdclass, + 0, 0, 0, 0, 0, buf, &rdata); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "add_soa:dns_soa_buildrdata -> %s", + isc_result_totext(result)); + goto failure; + } + + result = update_one_rr(db, ver, &diff, DNS_DIFFOP_ADD, &zone->origin, 0, + &rdata); + +failure: + dns_diff_clear(&diff); + if (ver != NULL) { + dns_db_closeversion(db, &ver, (result == ISC_R_SUCCESS)); + } + + INSIST(ver == NULL); + + return (result); +} + +struct addifmissing_arg { + dns_db_t *db; + dns_dbversion_t *ver; + dns_diff_t *diff; + dns_zone_t *zone; + bool *changed; + isc_result_t result; +}; + +static void +addifmissing(dns_keytable_t *keytable, dns_keynode_t *keynode, + dns_name_t *keyname, void *arg) { + dns_db_t *db = ((struct addifmissing_arg *)arg)->db; + dns_dbversion_t *ver = ((struct addifmissing_arg *)arg)->ver; + dns_diff_t *diff = ((struct addifmissing_arg *)arg)->diff; + dns_zone_t *zone = ((struct addifmissing_arg *)arg)->zone; + bool *changed = ((struct addifmissing_arg *)arg)->changed; + isc_result_t result; + dns_fixedname_t fname; + + UNUSED(keytable); + + if (((struct addifmissing_arg *)arg)->result != ISC_R_SUCCESS) { + return; + } + + if (!dns_keynode_managed(keynode)) { + return; + } + + /* + * If the keynode has no trust anchor set, return. + */ + if (!dns_keynode_dsset(keynode, NULL)) { + return; + } + + /* + * Check whether there's already a KEYDATA entry for this name; + * if so, we don't need to add another. + */ + dns_fixedname_init(&fname); + result = dns_db_find(db, keyname, ver, dns_rdatatype_keydata, + DNS_DBFIND_NOWILD, 0, NULL, + dns_fixedname_name(&fname), NULL, NULL); + if (result == ISC_R_SUCCESS) { + return; + } + + /* + * Create the keydata. + */ + result = create_keydata(zone, db, ver, diff, keynode, keyname, changed); + if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) { + ((struct addifmissing_arg *)arg)->result = result; + } +} + +/* + * Synchronize the set of initializing keys found in managed-keys {} + * statements with the set of trust anchors found in the managed-keys.bind + * zone. If a domain is no longer named in managed-keys, delete all keys + * from that domain from the key zone. If a domain is configured as an + * initial-key in trust-anchors, but there are no references to it in the + * key zone, load the key zone with the initializing key(s) for that + * domain and schedule a key refresh. If a domain is configured as + * an initial-ds in trust-anchors, fetch the DNSKEY RRset, load the key + * zone with the matching key, and schedule a key refresh. + */ +static isc_result_t +sync_keyzone(dns_zone_t *zone, dns_db_t *db) { + isc_result_t result = ISC_R_SUCCESS; + bool changed = false; + bool commit = false; + dns_keynode_t *keynode = NULL; + dns_view_t *view = zone->view; + dns_keytable_t *sr = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + dns_rriterator_t rrit; + struct addifmissing_arg arg; + + dns_zone_log(zone, ISC_LOG_DEBUG(1), "synchronizing trusted keys"); + + dns_diff_init(zone->mctx, &diff); + + CHECK(dns_view_getsecroots(view, &sr)); + + result = dns_db_newversion(db, &ver); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sync_keyzone:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * Walk the zone DB. If we find any keys whose names are no longer + * in trust-anchors, or which have been changed from initial to static, + * (meaning they are permanent and not RFC5011-maintained), delete + * them from the zone. Otherwise call load_secroots(), which + * loads keys into secroots as appropriate. + */ + dns_rriterator_init(&rrit, db, ver, 0); + for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS; + result = dns_rriterator_nextrrset(&rrit)) + { + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t keydata; + isc_stdtime_t now; + bool load = true; + dns_name_t *rrname = NULL; + uint32_t ttl; + + isc_stdtime_get(&now); + + dns_rriterator_current(&rrit, &rrname, &ttl, &rdataset, NULL); + if (!dns_rdataset_isassociated(rdataset)) { + dns_rriterator_destroy(&rrit); + goto failure; + } + + if (rdataset->type != dns_rdatatype_keydata) { + continue; + } + + /* + * The managed-keys zone can contain a placeholder instead of + * legitimate data, in which case we will not use it, and we + * will try to refresh it. + */ + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + isc_result_t iresult; + + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + + iresult = dns_rdata_tostruct(&rdata, &keydata, NULL); + /* Do we have a valid placeholder KEYDATA record? */ + if (iresult == ISC_R_SUCCESS && keydata.flags == 0 && + keydata.protocol == 0 && keydata.algorithm == 0) + { + set_refreshkeytimer(zone, &keydata, now, true); + load = false; + } + } + + /* + * Release db wrlock to prevent LOR reports against + * dns_keytable_forall() call below. + */ + dns_rriterator_pause(&rrit); + result = dns_keytable_find(sr, rrname, &keynode); + if (result != ISC_R_SUCCESS || !dns_keynode_managed(keynode)) { + CHECK(delete_keydata(db, ver, &diff, rrname, rdataset)); + changed = true; + } else if (load) { + load_secroots(zone, rrname, rdataset); + } + + if (keynode != NULL) { + dns_keytable_detachkeynode(sr, &keynode); + } + } + dns_rriterator_destroy(&rrit); + + /* + * Walk secroots to find any initial keys that aren't in + * the zone. If we find any, add them to the zone directly. + * If any DS-style initial keys are found, refresh the key + * zone so that they'll be looked up. + */ + arg.db = db; + arg.ver = ver; + arg.result = ISC_R_SUCCESS; + arg.diff = &diff; + arg.zone = zone; + arg.changed = &changed; + dns_keytable_forall(sr, addifmissing, &arg); + result = arg.result; + if (changed) { + /* Write changes to journal file. */ + CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx, + zone->updatemethod)); + CHECK(zone_journal(zone, &diff, NULL, "sync_keyzone")); + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + zone_needdump(zone, 30); + commit = true; + } + +failure: + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "unable to synchronize managed keys: %s", + isc_result_totext(result)); + isc_time_settoepoch(&zone->refreshkeytime); + } + if (keynode != NULL) { + dns_keytable_detachkeynode(sr, &keynode); + } + if (sr != NULL) { + dns_keytable_detach(&sr); + } + if (ver != NULL) { + dns_db_closeversion(db, &ver, commit); + } + dns_diff_clear(&diff); + + INSIST(ver == NULL); + + return (result); +} + +isc_result_t +dns_zone_synckeyzone(dns_zone_t *zone) { + isc_result_t result; + dns_db_t *db = NULL; + + if (zone->type != dns_zone_key) { + return (DNS_R_BADZONE); + } + + CHECK(dns_zone_getdb(zone, &db)); + + LOCK_ZONE(zone); + result = sync_keyzone(zone, db); + UNLOCK_ZONE(zone); + +failure: + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +static void +maybe_send_secure(dns_zone_t *zone) { + isc_result_t result; + + /* + * We've finished loading, or else failed to load, an inline-signing + * 'secure' zone. We now need information about the status of the + * 'raw' zone. If we failed to load, then we need it to send a + * copy of its database; if we succeeded, we need it to send its + * serial number so that we can sync with it. If it has not yet + * loaded, we set a flag so that it will send the necessary + * information when it has finished loading. + */ + if (zone->raw->db != NULL) { + if (zone->db != NULL) { + uint32_t serial; + unsigned int soacount; + + result = zone_get_from_db( + zone->raw, zone->raw->db, NULL, &soacount, NULL, + &serial, NULL, NULL, NULL, NULL, NULL); + if (result == ISC_R_SUCCESS && soacount > 0U) { + zone_send_secureserial(zone->raw, serial); + } + } else { + zone_send_securedb(zone->raw, zone->raw->db); + } + } else { + DNS_ZONE_SETFLAG(zone->raw, DNS_ZONEFLG_SENDSECURE); + } +} + +static bool +zone_unchanged(dns_db_t *db1, dns_db_t *db2, isc_mem_t *mctx) { + isc_result_t result; + bool answer = false; + dns_diff_t diff; + + dns_diff_init(mctx, &diff); + result = dns_db_diffx(&diff, db1, NULL, db2, NULL, NULL); + if (result == ISC_R_SUCCESS && ISC_LIST_EMPTY(diff.tuples)) { + answer = true; + } + dns_diff_clear(&diff); + return (answer); +} + +/* + * The zone is presumed to be locked. + * If this is a inline_raw zone the secure version is also locked. + */ +static isc_result_t +zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, + isc_result_t result) { + unsigned int soacount = 0; + unsigned int nscount = 0; + unsigned int errors = 0; + uint32_t serial, oldserial, refresh, retry, expire, minimum, soattl; + isc_time_t now; + bool needdump = false; + bool fixjournal = false; + bool hasinclude = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE); + bool noprimary = false; + bool had_db = false; + dns_include_t *inc; + bool is_dynamic = false; + + INSIST(LOCKED_ZONE(zone)); + if (inline_raw(zone)) { + INSIST(LOCKED_ZONE(zone->secure)); + } + + TIME_NOW(&now); + + /* + * Initiate zone transfer? We may need a error code that + * indicates that the "permanent" form does not exist. + * XXX better error feedback to log. + */ + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + if (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_stub || + (zone->type == dns_zone_redirect && + zone->primaries == NULL)) + { + if (result == ISC_R_FILENOTFOUND) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "no master file"); + } else if (result != DNS_R_NOMASTERFILE) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "loading from master file %s " + "failed: %s", + zone->masterfile, + isc_result_totext(result)); + } + } else if (zone->type == dns_zone_primary && + inline_secure(zone) && result == ISC_R_FILENOTFOUND) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "no master file, requesting db"); + maybe_send_secure(zone); + } else { + int level = ISC_LOG_ERROR; + if (zone->type == dns_zone_key && + result == ISC_R_FILENOTFOUND) + { + level = ISC_LOG_DEBUG(1); + } + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, level, + "loading from master file %s failed: %s", + zone->masterfile, + isc_result_totext(result)); + noprimary = true; + } + + if (zone->type != dns_zone_key) { + goto cleanup; + } + } + + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(2), + "number of nodes in database: %u", + dns_db_nodecount(db, dns_dbtree_main)); + + if (result == DNS_R_SEENINCLUDE) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HASINCLUDE); + } else { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HASINCLUDE); + } + + /* + * If there's no master file for a key zone, then the zone is new: + * create an SOA record. (We do this now, instead of later, so that + * if there happens to be a journal file, we can roll forward from + * a sane starting point.) + */ + if (noprimary && zone->type == dns_zone_key) { + result = add_soa(zone, db); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + /* + * Apply update log, if any, on initial load. + */ + if (zone->journal != NULL && + !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOMERGE) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + result = zone_journal_rollforward(zone, db, &needdump, + &fixjournal); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + /* + * Obtain ns, soa and cname counts for top of zone. + */ + INSIST(db != NULL); + result = zone_get_from_db(zone, db, &nscount, &soacount, &soattl, + &serial, &refresh, &retry, &expire, &minimum, + &errors); + if (result != ISC_R_SUCCESS && zone->type != dns_zone_key) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, + "could not find NS and/or SOA records"); + } + + /* + * Process any queued NSEC3PARAM change requests. Only for dynamic + * zones, an inline-signing zone will perform this action when + * receiving the secure db (receive_secure_db). + */ + is_dynamic = dns_zone_isdynamic(zone, true); + if (is_dynamic) { + isc_event_t *setnsec3param_event; + dns_zone_t *dummy; + + while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { + setnsec3param_event = + ISC_LIST_HEAD(zone->setnsec3param_queue); + ISC_LIST_UNLINK(zone->setnsec3param_queue, + setnsec3param_event, ev_link); + dummy = NULL; + zone_iattach(zone, &dummy); + isc_task_send(zone->task, &setnsec3param_event); + } + } + + /* + * Check to make sure the journal is up to date, and remove the + * journal file if it isn't, as we wouldn't be able to apply + * updates otherwise. + */ + if (zone->journal != NULL && is_dynamic && + !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS)) + { + uint32_t jserial; + dns_journal_t *journal = NULL; + bool empty = false; + + result = dns_journal_open(zone->mctx, zone->journal, + DNS_JOURNAL_READ, &journal); + if (result == ISC_R_SUCCESS) { + jserial = dns_journal_last_serial(journal); + empty = dns_journal_empty(journal); + dns_journal_destroy(&journal); + } else { + jserial = serial; + result = ISC_R_SUCCESS; + } + + if (jserial != serial) { + if (!empty) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_INFO, + "journal file is out of date: " + "removing journal file"); + } + if (remove(zone->journal) < 0 && errno != ENOENT) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, + ISC_LOG_WARNING, + "unable to remove journal " + "'%s': '%s'", + zone->journal, strbuf); + } + } + } + + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), + "loaded; checking validity"); + + /* + * Primary / Secondary / Mirror / Stub zones require both NS and SOA + * records at the top of the zone. + */ + + switch (zone->type) { + case dns_zone_dlz: + case dns_zone_primary: + case dns_zone_secondary: + case dns_zone_mirror: + case dns_zone_stub: + case dns_zone_redirect: + if (soacount != 1) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, "has %d SOA records", + soacount); + result = DNS_R_BADZONE; + } + if (nscount == 0) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, "has no NS records"); + result = DNS_R_BADZONE; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (zone->type == dns_zone_primary && errors != 0) { + result = DNS_R_BADZONE; + goto cleanup; + } + if (zone->type != dns_zone_stub && + zone->type != dns_zone_redirect) + { + result = check_nsec3param(zone, db); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + if (zone->type == dns_zone_primary && + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKINTEGRITY) && + !integrity_checks(zone, db)) + { + result = DNS_R_BADZONE; + goto cleanup; + } + if (zone->type == dns_zone_primary && + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRR) && + !zone_check_dup(zone, db)) + { + result = DNS_R_BADZONE; + goto cleanup; + } + + if (zone->type == dns_zone_primary) { + result = dns_zone_cdscheck(zone, db, NULL); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "CDS/CDNSKEY consistency checks " + "failed"); + goto cleanup; + } + } + + result = dns_zone_verifydb(zone, db, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (zone->db != NULL) { + unsigned int oldsoacount; + + /* + * This is checked in zone_replacedb() for + * secondary zones as they don't reload from disk. + */ + result = zone_get_from_db( + zone, zone->db, NULL, &oldsoacount, NULL, + &oldserial, NULL, NULL, NULL, NULL, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + RUNTIME_CHECK(oldsoacount > 0U); + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) && + !isc_serial_gt(serial, oldserial)) + { + uint32_t serialmin, serialmax; + + INSIST(zone->type == dns_zone_primary); + INSIST(zone->raw == NULL); + + if (serial == oldserial && + zone_unchanged(zone->db, db, zone->mctx)) + { + dns_zone_logc(zone, + DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_INFO, + "ixfr-from-differences: " + "unchanged"); + zone->loadtime = loadtime; + goto done; + } + + serialmin = (oldserial + 1) & 0xffffffffU; + serialmax = (oldserial + 0x7fffffffU) & + 0xffffffffU; + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "ixfr-from-differences: " + "new serial (%u) out of range " + "[%u - %u]", + serial, serialmin, serialmax); + result = DNS_R_BADZONE; + goto cleanup; + } else if (!isc_serial_ge(serial, oldserial)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "zone serial (%u/%u) has gone " + "backwards", + serial, oldserial); + } else if (serial == oldserial && !hasinclude && + strcmp(zone->db_argv[0], "_builtin") != 0) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "zone serial (%u) unchanged. " + "zone may fail to transfer " + "to secondaries.", + serial); + } + } + + if (zone->type == dns_zone_primary && + (zone->update_acl != NULL || zone->ssutable != NULL) && + dns_zone_getsigresigninginterval(zone) < (3 * refresh) && + dns_db_issecure(db)) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_WARNING, + "sig-re-signing-interval less than " + "3 * refresh."); + } + + zone->refresh = RANGE(refresh, zone->minrefresh, + zone->maxrefresh); + zone->retry = RANGE(retry, zone->minretry, zone->maxretry); + zone->expire = RANGE(expire, zone->refresh + zone->retry, + DNS_MAX_EXPIRE); + zone->soattl = soattl; + zone->minimum = minimum; + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + + if (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_stub || + (zone->type == dns_zone_redirect && + zone->primaries != NULL)) + { + isc_time_t t; + uint32_t delay; + + result = isc_file_getmodtime(zone->journal, &t); + if (result != ISC_R_SUCCESS) { + result = isc_file_getmodtime(zone->masterfile, + &t); + } + if (result == ISC_R_SUCCESS) { + DNS_ZONE_TIME_ADD(&t, zone->expire, + &zone->expiretime); + } else { + DNS_ZONE_TIME_ADD(&now, zone->retry, + &zone->expiretime); + } + + delay = (zone->retry - + isc_random_uniform((zone->retry * 3) / 4)); + DNS_ZONE_TIME_ADD(&now, delay, &zone->refreshtime); + if (isc_time_compare(&zone->refreshtime, + &zone->expiretime) >= 0) + { + zone->refreshtime = now; + } + } + + break; + + case dns_zone_key: + /* Nothing needs to be done now */ + break; + + default: + UNEXPECTED_ERROR("unexpected zone type %d", zone->type); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* + * Check for weak DNSKEY's. + */ + if (zone->type == dns_zone_primary) { + zone_check_dnskeys(zone, db); + } + + /* + * Schedule DNSSEC key refresh. + */ + if (zone->type == dns_zone_primary && + DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) + { + zone->refreshkeytime = now; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + if (zone->db != NULL) { + had_db = true; + result = zone_replacedb(zone, db, false); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + zone_attachdb(zone, db); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | + DNS_ZONEFLG_NEEDSTARTUPNOTIFY); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SENDSECURE) && + inline_raw(zone)) + { + if (zone->secure->db == NULL) { + zone_send_securedb(zone, db); + } else { + zone_send_secureserial(zone, serial); + } + } + } + + /* + * Finished loading inline-signing zone; need to get status + * from the raw side now. + */ + if (zone->type == dns_zone_primary && inline_secure(zone)) { + maybe_send_secure(zone); + } + + result = ISC_R_SUCCESS; + + if (fixjournal) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FIXJOURNAL); + zone_journal_compact(zone, zone->db, 0); + } + if (needdump) { + if (zone->type == dns_zone_key) { + zone_needdump(zone, 30); + } else { + zone_needdump(zone, DNS_DUMP_DELAY); + } + } + + if (zone->task != NULL) { + if (zone->type == dns_zone_primary) { + set_resigntime(zone); + resume_signingwithkey(zone); + resume_addnsec3chain(zone); + } + + is_dynamic = dns_zone_isdynamic(zone, false); + if (zone->type == dns_zone_primary && + !DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) && + is_dynamic && dns_db_issecure(db)) + { + dns_name_t *name; + dns_fixedname_t fixed; + dns_rdataset_t next; + + dns_rdataset_init(&next); + name = dns_fixedname_initname(&fixed); + + result = dns_db_getsigningtime(db, &next, name); + if (result == ISC_R_SUCCESS) { + isc_stdtime_t timenow; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + isc_stdtime_get(&timenow); + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(next.covers, typebuf, + sizeof(typebuf)); + dnssec_log( + zone, ISC_LOG_DEBUG(3), + "next resign: %s/%s " + "in %d seconds", + namebuf, typebuf, + next.resign - timenow - + dns_zone_getsigresigninginterval( + zone)); + dns_rdataset_disassociate(&next); + } else { + dnssec_log(zone, ISC_LOG_WARNING, + "signed dynamic zone has no " + "resign event scheduled"); + } + } + + zone_settimer(zone, &now); + } + + /* + * Clear old include list. + */ + for (inc = ISC_LIST_HEAD(zone->includes); inc != NULL; + inc = ISC_LIST_HEAD(zone->includes)) + { + ISC_LIST_UNLINK(zone->includes, inc, link); + isc_mem_free(zone->mctx, inc->name); + isc_mem_put(zone->mctx, inc, sizeof(*inc)); + } + zone->nincludes = 0; + + /* + * Transfer new include list. + */ + for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; + inc = ISC_LIST_HEAD(zone->newincludes)) + { + ISC_LIST_UNLINK(zone->newincludes, inc, link); + ISC_LIST_APPEND(zone->includes, inc, link); + zone->nincludes++; + } + + if (!dns_db_ispersistent(db)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, + "loaded serial %u%s", serial, + dns_db_issecure(db) ? " (DNSSEC signed)" : ""); + } + + if (!had_db && zone->type == dns_zone_mirror) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, + "mirror zone is now in use"); + } + + zone->loadtime = loadtime; + goto done; + +cleanup: + if (result != ISC_R_SUCCESS) { + dns_zone_rpz_disable_db(zone, db); + dns_zone_catz_disable_db(zone, db); + } + + for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; + inc = ISC_LIST_HEAD(zone->newincludes)) + { + ISC_LIST_UNLINK(zone->newincludes, inc, link); + isc_mem_free(zone->mctx, inc->name); + isc_mem_put(zone->mctx, inc, sizeof(*inc)); + } + if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || + zone->type == dns_zone_stub || zone->type == dns_zone_key || + (zone->type == dns_zone_redirect && zone->primaries != NULL)) + { + if (result != ISC_R_NOMEMORY) { + if (zone->journal != NULL) { + zone_saveunique(zone, zone->journal, + "jn-XXXXXXXX"); + } + if (zone->masterfile != NULL) { + zone_saveunique(zone, zone->masterfile, + "db-XXXXXXXX"); + } + } + + /* Mark the zone for immediate refresh. */ + zone->refreshtime = now; + if (zone->task != NULL) { + zone_settimer(zone, &now); + } + result = ISC_R_SUCCESS; + } else if (zone->type == dns_zone_primary || + zone->type == dns_zone_redirect) + { + if (!(inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, + "not loaded due to errors."); + } else if (zone->type == dns_zone_primary) { + result = ISC_R_SUCCESS; + } + } + +done: + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); + /* + * If this is an inline-signed zone and we were called for the raw + * zone, we need to clear DNS_ZONEFLG_LOADPENDING for the secure zone + * as well, but only if this is a reload, not an initial zone load: in + * the former case, zone_postload() will not be run for the secure + * zone; in the latter case, it will be. Check which case we are + * dealing with by consulting the DNS_ZONEFLG_LOADED flag for the + * secure zone: if it is set, this must be a reload. + */ + if (inline_raw(zone) && DNS_ZONE_FLAG(zone->secure, DNS_ZONEFLG_LOADED)) + { + DNS_ZONE_CLRFLAG(zone->secure, DNS_ZONEFLG_LOADPENDING); + /* + * Re-start zone maintenance if it had been stalled + * due to DNS_ZONEFLG_LOADPENDING being set when + * zone_maintenance was called. + */ + if (zone->secure->task != NULL) { + zone_settimer(zone->secure, &now); + } + } + + zone_debuglog(zone, "zone_postload", 99, "done"); + + return (result); +} + +static bool +exit_check(dns_zone_t *zone) { + REQUIRE(LOCKED_ZONE(zone)); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN) && + isc_refcount_current(&zone->irefs) == 0) + { + /* + * DNS_ZONEFLG_SHUTDOWN can only be set if erefs == 0. + */ + INSIST(isc_refcount_current(&zone->erefs) == 0); + return (true); + } + return (false); +} + +static bool +zone_check_ns(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_name_t *name, bool logit) { + isc_result_t result; + char namebuf[DNS_NAME_FORMATSIZE]; + char altbuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed; + dns_name_t *foundname; + int level; + + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOCHECKNS)) { + return (true); + } + + if (zone->type == dns_zone_primary) { + level = ISC_LOG_ERROR; + } else { + level = ISC_LOG_WARNING; + } + + foundname = dns_fixedname_initname(&fixed); + + result = dns_db_find(db, name, version, dns_rdatatype_a, 0, 0, NULL, + foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + + if (result == DNS_R_NXRRSET) { + result = dns_db_find(db, name, version, dns_rdatatype_aaaa, 0, + 0, NULL, foundname, NULL, NULL); + if (result == ISC_R_SUCCESS) { + return (true); + } + } + + if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || + result == DNS_R_EMPTYNAME) + { + if (logit) { + dns_name_format(name, namebuf, sizeof namebuf); + dns_zone_log(zone, level, + "NS '%s' has no address " + "records (A or AAAA)", + namebuf); + } + return (false); + } + + if (result == DNS_R_CNAME) { + if (logit) { + dns_name_format(name, namebuf, sizeof namebuf); + dns_zone_log(zone, level, + "NS '%s' is a CNAME " + "(illegal)", + namebuf); + } + return (false); + } + + if (result == DNS_R_DNAME) { + if (logit) { + dns_name_format(name, namebuf, sizeof namebuf); + dns_name_format(foundname, altbuf, sizeof altbuf); + dns_zone_log(zone, level, + "NS '%s' is below a DNAME " + "'%s' (illegal)", + namebuf, altbuf); + } + return (false); + } + + return (true); +} + +static isc_result_t +zone_count_ns_rr(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, unsigned int *nscount, + unsigned int *errors, bool logit) { + isc_result_t result; + unsigned int count = 0; + unsigned int ecount = 0; + dns_rdataset_t rdataset; + dns_rdata_t rdata; + dns_rdata_ns_t ns; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_ns, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto success; + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto invalidate_rdataset; + } + + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + if (errors != NULL && zone->rdclass == dns_rdataclass_in && + (zone->type == dns_zone_primary || + zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (dns_name_issubdomain(&ns.name, &zone->origin) && + !zone_check_ns(zone, db, version, &ns.name, logit)) + { + ecount++; + } + } + count++; + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + +success: + if (nscount != NULL) { + *nscount = count; + } + if (errors != NULL) { + *errors = ecount; + } + + result = ISC_R_SUCCESS; + +invalidate_rdataset: + dns_rdataset_invalidate(&rdataset); + + return (result); +} + +#define SET_IF_NOT_NULL(obj, val) \ + if (obj != NULL) { \ + *obj = val; \ + } + +#define SET_SOA_VALUES(soattl_v, serial_v, refresh_v, retry_v, expire_v, \ + minimum_v) \ + { \ + SET_IF_NOT_NULL(soattl, soattl_v); \ + SET_IF_NOT_NULL(serial, serial_v); \ + SET_IF_NOT_NULL(refresh, refresh_v); \ + SET_IF_NOT_NULL(retry, retry_v); \ + SET_IF_NOT_NULL(expire, expire_v); \ + SET_IF_NOT_NULL(minimum, minimum_v); \ + } + +#define CLR_SOA_VALUES() \ + { \ + SET_SOA_VALUES(0, 0, 0, 0, 0, 0); \ + } + +static isc_result_t +zone_load_soa_rr(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + unsigned int *soacount, uint32_t *soattl, uint32_t *serial, + uint32_t *refresh, uint32_t *retry, uint32_t *expire, + uint32_t *minimum) { + isc_result_t result; + unsigned int count = 0; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + result = ISC_R_SUCCESS; + goto invalidate_rdataset; + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto invalidate_rdataset; + } + + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + count++; + if (count == 1) { + dns_rdata_soa_t soa; + result = dns_rdata_tostruct(&rdata, &soa, NULL); + SET_SOA_VALUES(rdataset.ttl, soa.serial, soa.refresh, + soa.retry, soa.expire, soa.minimum); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + result = dns_rdataset_next(&rdataset); + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&rdataset); + + result = ISC_R_SUCCESS; + +invalidate_rdataset: + SET_IF_NOT_NULL(soacount, count); + if (count == 0) { + CLR_SOA_VALUES(); + } + + dns_rdataset_invalidate(&rdataset); + + return (result); +} + +/* + * zone must be locked. + */ +static isc_result_t +zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount, + unsigned int *soacount, uint32_t *soattl, uint32_t *serial, + uint32_t *refresh, uint32_t *retry, uint32_t *expire, + uint32_t *minimum, unsigned int *errors) { + isc_result_t result; + isc_result_t answer = ISC_R_SUCCESS; + dns_dbversion_t *version = NULL; + dns_dbnode_t *node; + + REQUIRE(db != NULL); + REQUIRE(zone != NULL); + + dns_db_currentversion(db, &version); + + SET_IF_NOT_NULL(nscount, 0); + SET_IF_NOT_NULL(soacount, 0); + SET_IF_NOT_NULL(errors, 0); + CLR_SOA_VALUES(); + + node = NULL; + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + answer = result; + goto closeversion; + } + + if (nscount != NULL || errors != NULL) { + result = zone_count_ns_rr(zone, db, node, version, nscount, + errors, true); + if (result != ISC_R_SUCCESS) { + answer = result; + } + } + + if (soacount != NULL || soattl != NULL || serial != NULL || + refresh != NULL || retry != NULL || expire != NULL || + minimum != NULL) + { + result = zone_load_soa_rr(db, node, version, soacount, soattl, + serial, refresh, retry, expire, + minimum); + if (result != ISC_R_SUCCESS) { + answer = result; + } + } + + dns_db_detachnode(db, &node); +closeversion: + dns_db_closeversion(db, &version, false); + + return (answer); +} + +void +dns_zone_attach(dns_zone_t *source, dns_zone_t **target) { + REQUIRE(DNS_ZONE_VALID(source)); + REQUIRE(target != NULL && *target == NULL); + isc_refcount_increment(&source->erefs); + *target = source; +} + +void +dns_zone_detach(dns_zone_t **zonep) { + REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); + + dns_zone_t *zone = *zonep; + *zonep = NULL; + + if (isc_refcount_decrement(&zone->erefs) == 1) { + isc_event_t *ev = &zone->ctlevent; + + isc_refcount_destroy(&zone->erefs); + + /* + * Stop things being restarted after we cancel them below. + */ + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXITING); + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "final reference detached"); + if (zone->task != NULL) { + /* + * This zone has a task; it can clean + * itself up asynchronously. + */ + isc_task_send(zone->task, &ev); + return; + } + + /* + * This zone is unmanaged; we're probably running in + * named-checkzone or a unit test. There's no task, + * so we need to free it immediately. + * + * Unmanaged zones must not have null views; we have no way + * of detaching from the view here without causing deadlock + * because this code is called with the view already + * locked. + */ + INSIST(zone->view == NULL); + + zone_shutdown(zone->task, ev); + ev = NULL; + } +} + +void +dns_zone_iattach(dns_zone_t *source, dns_zone_t **target) { + REQUIRE(DNS_ZONE_VALID(source)); + + LOCK_ZONE(source); + zone_iattach(source, target); + UNLOCK_ZONE(source); +} + +static void +zone_iattach(dns_zone_t *source, dns_zone_t **target) { + REQUIRE(DNS_ZONE_VALID(source)); + REQUIRE(LOCKED_ZONE(source)); + REQUIRE(target != NULL && *target == NULL); + INSIST(isc_refcount_increment0(&source->irefs) + + isc_refcount_current(&source->erefs) > + 0); + *target = source; +} + +static void +zone_idetach(dns_zone_t **zonep) { + dns_zone_t *zone; + + /* + * 'zone' locked by caller. + */ + REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); + REQUIRE(LOCKED_ZONE(*zonep)); + + zone = *zonep; + *zonep = NULL; + + INSIST(isc_refcount_decrement(&zone->irefs) - 1 + + isc_refcount_current(&zone->erefs) > + 0); +} + +void +dns_zone_idetach(dns_zone_t **zonep) { + dns_zone_t *zone; + + REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); + + zone = *zonep; + *zonep = NULL; + + if (isc_refcount_decrement(&zone->irefs) == 1) { + bool free_needed; + LOCK_ZONE(zone); + free_needed = exit_check(zone); + UNLOCK_ZONE(zone); + if (free_needed) { + zone_free(zone); + } + } +} + +isc_mem_t * +dns_zone_getmctx(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->mctx); +} + +dns_zonemgr_t * +dns_zone_getmgr(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->zmgr); +} + +void +dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->kasp != NULL) { + dns_kasp_detach(&zone->kasp); + } + if (kasp != NULL) { + dns_kasp_attach(kasp, &zone->kasp); + } + UNLOCK_ZONE(zone); +} + +dns_kasp_t * +dns_zone_getkasp(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->kasp); +} + +void +dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (value) { + DNS_ZONE_SETOPTION(zone, option); + } else { + DNS_ZONE_CLROPTION(zone, option); + } +} + +dns_zoneopt_t +dns_zone_getoptions(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (atomic_load_relaxed(&zone->options)); +} + +void +dns_zone_setkeyopt(dns_zone_t *zone, unsigned int keyopt, bool value) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (value) { + DNS_ZONEKEY_SETOPTION(zone, keyopt); + } else { + DNS_ZONEKEY_CLROPTION(zone, keyopt); + } +} + +unsigned int +dns_zone_getkeyopts(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (atomic_load_relaxed(&zone->keyopts)); +} + +isc_result_t +dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->xfrsource4 = *xfrsource; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getxfrsource4(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->xfrsource4); +} + +isc_result_t +dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->xfrsource6 = *xfrsource; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getxfrsource6(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->xfrsource6); +} + +isc_result_t +dns_zone_setaltxfrsource4(dns_zone_t *zone, + const isc_sockaddr_t *altxfrsource) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->altxfrsource4 = *altxfrsource; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getaltxfrsource4(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->altxfrsource4); +} + +isc_result_t +dns_zone_setaltxfrsource6(dns_zone_t *zone, + const isc_sockaddr_t *altxfrsource) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->altxfrsource6 = *altxfrsource; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getaltxfrsource6(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->altxfrsource6); +} + +isc_result_t +dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->parentalsrc4 = *parentalsrc; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getparentalsrc4(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->parentalsrc4); +} + +isc_result_t +dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->parentalsrc6 = *parentalsrc; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getparentalsrc6(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->parentalsrc6); +} + +isc_result_t +dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->notifysrc4 = *notifysrc; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getnotifysrc4(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->notifysrc4); +} + +isc_result_t +dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->notifysrc6 = *notifysrc; + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +isc_sockaddr_t * +dns_zone_getnotifysrc6(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (&zone->notifysrc6); +} + +static bool +same_addrs(isc_sockaddr_t const *oldlist, isc_sockaddr_t const *newlist, + uint32_t count) { + unsigned int i; + + for (i = 0; i < count; i++) { + if (!isc_sockaddr_equal(&oldlist[i], &newlist[i])) { + return (false); + } + } + return (true); +} + +static bool +same_names(dns_name_t *const *oldlist, dns_name_t *const *newlist, + uint32_t count) { + unsigned int i; + + if (oldlist == NULL && newlist == NULL) { + return (true); + } + if (oldlist == NULL || newlist == NULL) { + return (false); + } + + for (i = 0; i < count; i++) { + if (oldlist[i] == NULL && newlist[i] == NULL) { + continue; + } + if (oldlist[i] == NULL || newlist[i] == NULL || + !dns_name_equal(oldlist[i], newlist[i])) + { + return (false); + } + } + return (true); +} + +static void +clear_serverslist(isc_sockaddr_t **addrsp, dns_name_t ***keynamesp, + dns_name_t ***tlsnamesp, unsigned int *countp, + isc_mem_t *mctx) { + unsigned int count; + isc_sockaddr_t *addrs; + dns_name_t **keynames; + dns_name_t **tlsnames; + + REQUIRE(countp != NULL); + REQUIRE(addrsp != NULL); + REQUIRE(keynamesp != NULL); + REQUIRE(tlsnamesp != NULL); + + count = *countp; + *countp = 0; + addrs = *addrsp; + *addrsp = NULL; + keynames = *keynamesp; + *keynamesp = NULL; + tlsnames = *tlsnamesp; + *tlsnamesp = NULL; + + if (addrs != NULL) { + isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t)); + } + + if (keynames != NULL) { + unsigned int i; + for (i = 0; i < count; i++) { + if (keynames[i] != NULL) { + dns_name_free(keynames[i], mctx); + isc_mem_put(mctx, keynames[i], + sizeof(dns_name_t)); + keynames[i] = NULL; + } + } + isc_mem_put(mctx, keynames, count * sizeof(dns_name_t *)); + } + + if (tlsnames != NULL) { + unsigned int i; + for (i = 0; i < count; i++) { + if (tlsnames[i] != NULL) { + dns_name_free(tlsnames[i], mctx); + isc_mem_put(mctx, tlsnames[i], + sizeof(dns_name_t)); + tlsnames[i] = NULL; + } + } + isc_mem_put(mctx, tlsnames, count * sizeof(dns_name_t *)); + } +} + +static void +set_serverslist(unsigned int count, const isc_sockaddr_t *addrs, + isc_sockaddr_t **newaddrsp, dns_name_t **keynames, + dns_name_t ***newkeynamesp, dns_name_t **tlsnames, + dns_name_t ***newtlsnamesp, isc_mem_t *mctx) { + isc_sockaddr_t *newaddrs = NULL; + dns_name_t **newkeynames = NULL; + dns_name_t **newtlsnames = NULL; + unsigned int i; + + REQUIRE(newaddrsp != NULL && *newaddrsp == NULL); + REQUIRE(newkeynamesp != NULL && *newkeynamesp == NULL); + REQUIRE(newtlsnamesp != NULL && *newtlsnamesp == NULL); + + newaddrs = isc_mem_get(mctx, count * sizeof(*newaddrs)); + memmove(newaddrs, addrs, count * sizeof(*newaddrs)); + + if (keynames != NULL) { + newkeynames = isc_mem_get(mctx, count * sizeof(*newkeynames)); + for (i = 0; i < count; i++) { + newkeynames[i] = NULL; + } + for (i = 0; i < count; i++) { + if (keynames[i] != NULL) { + newkeynames[i] = + isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(newkeynames[i], NULL); + dns_name_dup(keynames[i], mctx, newkeynames[i]); + } + } + } + + if (tlsnames != NULL) { + newtlsnames = isc_mem_get(mctx, count * sizeof(*newtlsnames)); + for (i = 0; i < count; i++) { + newtlsnames[i] = NULL; + } + for (i = 0; i < count; i++) { + if (tlsnames[i] != NULL) { + newtlsnames[i] = + isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(newtlsnames[i], NULL); + dns_name_dup(tlsnames[i], mctx, newtlsnames[i]); + } + } + } + + *newaddrsp = newaddrs; + *newkeynamesp = newkeynames; + *newtlsnamesp = newtlsnames; +} + +void +dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count) { + isc_sockaddr_t *newaddrs = NULL; + dns_name_t **newkeynames = NULL; + dns_name_t **newtlsnames = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(count == 0 || notify != NULL); + if (keynames != NULL) { + REQUIRE(count != 0); + } + + LOCK_ZONE(zone); + + if (count == zone->notifycnt && + same_addrs(zone->notify, notify, count) && + same_names(zone->notifykeynames, keynames, count) && + same_names(zone->notifytlsnames, tlsnames, count)) + { + goto unlock; + } + + clear_serverslist(&zone->notify, &zone->notifykeynames, + &zone->notifytlsnames, &zone->notifycnt, zone->mctx); + + if (count == 0) { + goto unlock; + } + + /* + * Set up the notify and notifykey lists + */ + set_serverslist(count, notify, &newaddrs, keynames, &newkeynames, + tlsnames, &newtlsnames, zone->mctx); + + /* + * Everything is ok so attach to the zone. + */ + zone->notify = newaddrs; + zone->notifykeynames = newkeynames; + zone->notifytlsnames = newtlsnames; + zone->notifycnt = count; +unlock: + UNLOCK_ZONE(zone); +} + +void +dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count) { + isc_sockaddr_t *newaddrs = NULL; + dns_name_t **newkeynames = NULL; + dns_name_t **newtlsnames = NULL; + bool *newok; + unsigned int i; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(count == 0 || primaries != NULL); + if (keynames != NULL || tlsnames != NULL) { + REQUIRE(count != 0); + } + + LOCK_ZONE(zone); + /* + * The refresh code assumes that 'primaries' wouldn't change under it. + * If it will change then kill off any current refresh in progress + * and update the primaries info. If it won't change then we can just + * unlock and exit. + */ + if (count != zone->primariescnt || + !same_addrs(zone->primaries, primaries, count) || + !same_names(zone->primarykeynames, keynames, count) || + !same_names(zone->primarytlsnames, tlsnames, count)) + { + if (zone->request != NULL) { + dns_request_cancel(zone->request); + } + } else { + goto unlock; + } + + /* + * This needs to happen before clear_addresskeylist() sets + * zone->primariescnt to 0: + */ + if (zone->primariesok != NULL) { + isc_mem_put(zone->mctx, zone->primariesok, + zone->primariescnt * sizeof(bool)); + zone->primariesok = NULL; + } + clear_serverslist(&zone->primaries, &zone->primarykeynames, + &zone->primarytlsnames, &zone->primariescnt, + zone->mctx); + /* + * If count == 0, don't allocate any space for primaries, primariesok or + * keynames so internally, those pointers are NULL if count == 0 + */ + if (count == 0) { + goto unlock; + } + + /* + * primariesok must contain count elements + */ + newok = isc_mem_get(zone->mctx, count * sizeof(*newok)); + for (i = 0; i < count; i++) { + newok[i] = false; + } + + /* + * Now set up the primaries and primary key lists + */ + set_serverslist(count, primaries, &newaddrs, keynames, &newkeynames, + tlsnames, &newtlsnames, zone->mctx); + + /* + * Everything is ok so attach to the zone. + */ + zone->curprimary = 0; + zone->primariesok = newok; + zone->primaries = newaddrs; + zone->primarykeynames = newkeynames; + zone->primarytlsnames = newtlsnames; + zone->primariescnt = count; + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOPRIMARIES); + +unlock: + UNLOCK_ZONE(zone); +} + +void +dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals, + dns_name_t **keynames, dns_name_t **tlsnames, + uint32_t count) { + isc_sockaddr_t *newaddrs = NULL; + dns_name_t **newkeynames = NULL; + dns_name_t **newtlsnames = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(count == 0 || parentals != NULL); + if (keynames != NULL || tlsnames != NULL) { + REQUIRE(count != 0); + } + + LOCK_ZONE(zone); + + clear_serverslist(&zone->parentals, &zone->parentalkeynames, + &zone->parentaltlsnames, &zone->parentalscnt, + zone->mctx); + /* + * If count == 0, don't allocate any space for parentals, or keynames + * so internally, those pointers are NULL if count == 0 + */ + if (count == 0) { + goto unlock; + } + + /* + * Now set up the parentals and parental key lists + */ + set_serverslist(count, parentals, &newaddrs, keynames, &newkeynames, + tlsnames, &newtlsnames, zone->mctx); + + /* + * Everything is ok so attach to the zone. + */ + zone->parentals = newaddrs; + zone->parentalkeynames = newkeynames; + zone->parentaltlsnames = newtlsnames; + zone->parentalscnt = count; + + dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: set %u parentals", count); + +unlock: + UNLOCK_ZONE(zone); +} + +isc_result_t +dns_zone_getdb(dns_zone_t *zone, dns_db_t **dpb) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db == NULL) { + result = DNS_R_NOTLOADED; + } else { + dns_db_attach(zone->db, dpb); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + return (result); +} + +void +dns_zone_setdb(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(zone->type == dns_zone_staticstub); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + REQUIRE(zone->db == NULL); + dns_db_attach(db, &zone->db); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); +} + +/* + * Coordinates the starting of routine jobs. + */ +void +dns_zone_maintenance(dns_zone_t *zone) { + const char me[] = "dns_zone_maintenance"; + isc_time_t now; + + REQUIRE(DNS_ZONE_VALID(zone)); + ENTER; + + LOCK_ZONE(zone); + TIME_NOW(&now); + zone_settimer(zone, &now); + UNLOCK_ZONE(zone); +} + +static bool +was_dumping(dns_zone_t *zone) { + REQUIRE(LOCKED_ZONE(zone)); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { + return (true); + } + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); + isc_time_settoepoch(&zone->dumptime); + return (false); +} + +/*% + * Find up to 'maxkeys' DNSSEC keys used for signing version 'ver' of database + * 'db' for zone 'zone' in its key directory, then load these keys into 'keys'. + * Only load the public part of a given key if it is not active at timestamp + * 'now'. Store the number of keys found in 'nkeys'. + */ +isc_result_t +dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, + dst_key_t **keys, unsigned int *nkeys) { + isc_result_t result; + dns_dbnode_t *node = NULL; + const char *directory = dns_zone_getkeydirectory(zone); + + CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); + memset(keys, 0, sizeof(*keys) * maxkeys); + + dns_zone_lock_keyfiles(zone); + + result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db), + directory, now, mctx, maxkeys, keys, + nkeys); + + dns_zone_unlock_keyfiles(zone); + + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + +failure: + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/*% + * Find DNSSEC keys used for signing zone with dnssec-policy. Load these keys + * into 'keys'. Requires KASP to be locked. + */ +isc_result_t +dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, dns_dnsseckeylist_t *keys) { + isc_result_t result; + const char *dir = dns_zone_getkeydirectory(zone); + dns_dbnode_t *node = NULL; + dns_dnsseckey_t *key, *key_next; + dns_dnsseckeylist_t dnskeys; + dns_name_t *origin = dns_zone_getorigin(zone); + dns_kasp_t *kasp = dns_zone_getkasp(zone); + dns_rdataset_t keyset; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(kasp != NULL); + + ISC_LIST_INIT(dnskeys); + + dns_rdataset_init(&keyset); + + CHECK(dns_db_findnode(db, origin, false, &node)); + + /* Get keys from private key files. */ + dns_zone_lock_keyfiles(zone); + result = dns_dnssec_findmatchingkeys(origin, dir, now, + dns_zone_getmctx(zone), keys); + dns_zone_unlock_keyfiles(zone); + + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + /* Get public keys (dnskeys). */ + dns_rdataset_init(&keyset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &keyset, NULL); + if (result == ISC_R_SUCCESS) { + CHECK(dns_dnssec_keylistfromrdataset( + origin, dir, dns_zone_getmctx(zone), &keyset, NULL, + NULL, false, false, &dnskeys)); + } else if (result != ISC_R_NOTFOUND) { + CHECK(result); + } + + /* Add new 'dnskeys' to 'keys'. */ + for (dns_dnsseckey_t *k1 = ISC_LIST_HEAD(dnskeys); k1 != NULL; + k1 = key_next) + { + dns_dnsseckey_t *k2 = NULL; + key_next = ISC_LIST_NEXT(k1, link); + + for (k2 = ISC_LIST_HEAD(*keys); k2 != NULL; + k2 = ISC_LIST_NEXT(k2, link)) + { + if (dst_key_compare(k1->key, k2->key)) { + break; + } + } + /* No match found, add the new key. */ + if (k2 == NULL) { + ISC_LIST_UNLINK(dnskeys, k1, link); + ISC_LIST_APPEND(*keys, k1, link); + } + } + +failure: + if (dns_rdataset_isassociated(&keyset)) { + dns_rdataset_disassociate(&keyset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + while (!ISC_LIST_EMPTY(dnskeys)) { + key = ISC_LIST_HEAD(dnskeys); + ISC_LIST_UNLINK(dnskeys, key, link); + dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key); + } + return (result); +} + +static isc_result_t +offline(dns_db_t *db, dns_dbversion_t *ver, dns__zonediff_t *zonediff, + dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { + isc_result_t result; + + if ((rdata->flags & DNS_RDATA_OFFLINE) != 0) { + return (ISC_R_SUCCESS); + } + result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN, + name, ttl, rdata); + if (result != ISC_R_SUCCESS) { + return (result); + } + rdata->flags |= DNS_RDATA_OFFLINE; + result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_ADDRESIGN, + name, ttl, rdata); + zonediff->offline = true; + return (result); +} + +static void +set_key_expiry_warning(dns_zone_t *zone, isc_stdtime_t when, + isc_stdtime_t now) { + unsigned int delta; + char timebuf[80]; + + LOCK_ZONE(zone); + zone->key_expiry = when; + if (when <= now) { + dns_zone_log(zone, ISC_LOG_ERROR, + "DNSKEY RRSIG(s) have expired"); + isc_time_settoepoch(&zone->keywarntime); + } else if (when < now + 7 * 24 * 3600) { + isc_time_t t; + isc_time_set(&t, when, 0); + isc_time_formattimestamp(&t, timebuf, 80); + dns_zone_log(zone, ISC_LOG_WARNING, + "DNSKEY RRSIG(s) will expire within 7 days: %s", + timebuf); + delta = when - now; + delta--; /* loop prevention */ + delta /= 24 * 3600; /* to whole days */ + delta *= 24 * 3600; /* to seconds */ + isc_time_set(&zone->keywarntime, when - delta, 0); + } else { + isc_time_set(&zone->keywarntime, when - 7 * 24 * 3600, 0); + isc_time_formattimestamp(&zone->keywarntime, timebuf, 80); + dns_zone_log(zone, ISC_LOG_NOTICE, "setting keywarntime to %s", + timebuf); + } + UNLOCK_ZONE(zone); +} + +/* + * Helper function to del_sigs(). We don't want to delete RRSIGs that + * have no new key. + */ +static bool +delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, + bool kasp, bool *warn) { + unsigned int i = 0; + isc_result_t ret; + bool have_ksk = false, have_zsk = false; + bool have_pksk = false, have_pzsk = false; + + for (i = 0; i < nkeys; i++) { + bool ksk, zsk; + + if (have_pksk && have_ksk && have_pzsk && have_zsk) { + break; + } + + if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) { + continue; + } + + ret = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(keys[i]); + } + ret = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (ret != ISC_R_SUCCESS) { + zsk = !KSK(keys[i]); + } + + if (ksk) { + have_ksk = true; + if (dst_key_isprivate(keys[i])) { + have_pksk = true; + } + } + if (zsk) { + have_zsk = true; + if (dst_key_isprivate(keys[i])) { + have_pzsk = true; + } + } + } + + if (have_zsk && have_ksk && !have_pzsk) { + *warn = true; + } + + if (have_pksk && have_pzsk) { + return (true); + } + + /* + * Deleting the SOA RRSIG is always okay. + */ + if (rrsig_ptr->covered == dns_rdatatype_soa) { + return (true); + } + + /* + * It's okay to delete a signature if there is an active key with the + * same algorithm to replace it, unless that violates the DNSSEC + * policy. + */ + if (have_pksk || have_pzsk) { + if (kasp && have_pzsk) { + return (true); + } + return (!kasp); + } + + /* + * Failing that, it is *not* okay to delete a signature + * if the associated public key is still in the DNSKEY RRset + */ + for (i = 0; i < nkeys; i++) { + if ((rrsig_ptr->algorithm == dst_key_alg(keys[i])) && + (rrsig_ptr->keyid == dst_key_id(keys[i]))) + { + return (false); + } + } + + /* + * But if the key is gone, then go ahead. + */ + return (true); +} + +/* + * Delete expired RRsigs and any RRsigs we are about to re-sign. + * See also update.c:del_keysigs(). + */ +static isc_result_t +del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + dns_rdatatype_t type, dns__zonediff_t *zonediff, dst_key_t **keys, + unsigned int nkeys, isc_stdtime_t now, bool incremental) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + unsigned int i; + dns_rdata_rrsig_t rrsig; + bool kasp = (dns_zone_getkasp(zone) != NULL); + bool found; + int64_t timewarn = 0, timemaybe = 0; + + dns_rdataset_init(&rdataset); + + if (type == dns_rdatatype_nsec3) { + result = dns_db_findnsec3node(db, name, false, &node); + } else { + result = dns_db_findnode(db, name, false, &node); + } + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig, type, + (isc_stdtime_t)0, &rdataset, NULL); + dns_db_detachnode(db, &node); + + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (type != dns_rdatatype_dnskey && type != dns_rdatatype_cds && + type != dns_rdatatype_cdnskey) + { + bool warn = false, deleted = false; + if (delsig_ok(&rrsig, keys, nkeys, kasp, &warn)) { + result = update_one_rr(db, ver, zonediff->diff, + DNS_DIFFOP_DELRESIGN, + name, rdataset.ttl, + &rdata); + if (result != ISC_R_SUCCESS) { + break; + } + deleted = true; + } + if (warn && !deleted) { + /* + * At this point, we've got an RRSIG, + * which is signed by an inactive key. + * An administrator needs to provide a new + * key/alg, but until that time, we want to + * keep the old RRSIG. Marking the key as + * offline will prevent us spinning waiting + * for the private part. + */ + if (incremental) { + result = offline(db, ver, zonediff, + name, rdataset.ttl, + &rdata); + if (result != ISC_R_SUCCESS) { + break; + } + } + + /* + * Log the key id and algorithm of + * the inactive key with no replacement + */ + if (zone->log_key_expired_timer <= now) { + char origin[DNS_NAME_FORMATSIZE]; + char algbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&zone->origin, origin, + sizeof(origin)); + dns_secalg_format(rrsig.algorithm, + algbuf, + sizeof(algbuf)); + dns_zone_log(zone, ISC_LOG_WARNING, + "Key %s/%s/%d " + "missing or inactive " + "and has no replacement: " + "retaining signatures.", + origin, algbuf, + rrsig.keyid); + zone->log_key_expired_timer = now + + 3600; + } + } + continue; + } + + /* + * KSK RRSIGs requires special processing. + */ + found = false; + for (i = 0; i < nkeys; i++) { + if (rrsig.algorithm == dst_key_alg(keys[i]) && + rrsig.keyid == dst_key_id(keys[i])) + { + found = true; + /* + * Mark offline DNSKEY. + * We want the earliest offline expire time + * iff there is a new offline signature. + */ + if (!dst_key_inactive(keys[i]) && + !dst_key_isprivate(keys[i])) + { + int64_t timeexpire = dns_time64_from32( + rrsig.timeexpire); + if (timewarn != 0 && + timewarn > timeexpire) + { + timewarn = timeexpire; + } + if (rdata.flags & DNS_RDATA_OFFLINE) { + if (timemaybe == 0 || + timemaybe > timeexpire) + { + timemaybe = timeexpire; + } + break; + } + if (timewarn == 0) { + timewarn = timemaybe; + } + if (timewarn == 0 || + timewarn > timeexpire) + { + timewarn = timeexpire; + } + result = offline(db, ver, zonediff, + name, rdataset.ttl, + &rdata); + break; + } + result = update_one_rr(db, ver, zonediff->diff, + DNS_DIFFOP_DELRESIGN, + name, rdataset.ttl, + &rdata); + break; + } + } + + /* + * If there is not a matching DNSKEY then + * delete the RRSIG. + */ + if (!found) { + result = update_one_rr(db, ver, zonediff->diff, + DNS_DIFFOP_DELRESIGN, name, + rdataset.ttl, &rdata); + } + if (result != ISC_R_SUCCESS) { + break; + } + } + + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + if (timewarn > 0) { + isc_stdtime_t stdwarn = (isc_stdtime_t)timewarn; + if (timewarn == stdwarn) { + set_key_expiry_warning(zone, (isc_stdtime_t)timewarn, + now); + } else { + dns_zone_log(zone, ISC_LOG_ERROR, + "key expiry warning time out of range"); + } + } +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static isc_result_t +add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone, + dns_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys, + unsigned int nkeys, isc_mem_t *mctx, isc_stdtime_t inception, + isc_stdtime_t expire, bool check_ksk, bool keyset_kskonly) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_stats_t *dnssecsignstats; + dns_rdataset_t rdataset; + dns_rdata_t sig_rdata = DNS_RDATA_INIT; + unsigned char data[1024]; /* XXX */ + isc_buffer_t buffer; + unsigned int i, j; + bool use_kasp = false; + + if (dns_zone_getkasp(zone) != NULL) { + check_ksk = false; + keyset_kskonly = true; + use_kasp = true; + } + + dns_rdataset_init(&rdataset); + isc_buffer_init(&buffer, data, sizeof(data)); + + if (type == dns_rdatatype_nsec3) { + result = dns_db_findnsec3node(db, name, false, &node); + } else { + result = dns_db_findnode(db, name, false, &node); + } + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dns_db_findrdataset(db, node, ver, type, 0, (isc_stdtime_t)0, + &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto failure; + } + + for (i = 0; i < nkeys; i++) { + bool both = false; + + /* Don't add signatures for offline or inactive keys */ + if (!dst_key_isprivate(keys[i])) { + continue; + } + if (dst_key_inactive(keys[i])) { + continue; + } + + if (check_ksk && !REVOKE(keys[i])) { + bool have_ksk, have_nonksk; + if (KSK(keys[i])) { + have_ksk = true; + have_nonksk = false; + } else { + have_ksk = false; + have_nonksk = true; + } + + for (j = 0; j < nkeys; j++) { + if (j == i || ALG(keys[i]) != ALG(keys[j])) { + continue; + } + + /* + * Don't consider inactive keys, however + * the KSK may be temporary offline, so do + * consider keys which private key files are + * unavailable. + */ + if (dst_key_inactive(keys[j])) { + continue; + } + + if (REVOKE(keys[j])) { + continue; + } + if (KSK(keys[j])) { + have_ksk = true; + } else if (dst_key_isprivate(keys[j])) { + have_nonksk = true; + } + both = have_ksk && have_nonksk; + if (both) { + break; + } + } + } + if (use_kasp) { + /* + * A dnssec-policy is found. Check what RRsets this + * key should sign. + */ + isc_result_t kresult; + isc_stdtime_t when; + bool ksk = false; + bool zsk = false; + bool have_ksk = false; + bool have_zsk = false; + + kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[i])) { + ksk = true; + } + } + kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[i])) { + zsk = true; + } + } + + have_ksk = ksk; + have_zsk = zsk; + both = have_ksk && have_zsk; + + for (j = 0; j < nkeys; j++) { + if (both) { + break; + } + + if (j == i || ALG(keys[i]) != ALG(keys[j])) { + continue; + } + + /* + * Don't consider inactive keys or offline keys. + */ + if (!dst_key_isprivate(keys[j])) { + continue; + } + if (dst_key_inactive(keys[j])) { + continue; + } + + if (REVOKE(keys[j])) { + continue; + } + + if (!have_ksk) { + kresult = dst_key_getbool(keys[j], + DST_BOOL_KSK, + &have_ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(keys[j])) { + have_ksk = true; + } + } + } + if (!have_zsk) { + kresult = dst_key_getbool(keys[j], + DST_BOOL_ZSK, + &have_zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(keys[j])) { + have_zsk = true; + } + } + } + both = have_ksk && have_zsk; + } + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + /* + * DNSKEY RRset is signed with KSK. + * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). + */ + if (!ksk) { + continue; + } + } else if (!zsk) { + /* + * Other RRsets are signed with ZSK. + */ + if (type != dns_rdatatype_soa && + type != zone->privatetype) + { + continue; + } + if (have_zsk) { + continue; + } + } else if (!dst_key_is_signing(keys[i], DST_BOOL_ZSK, + inception, &when)) + { + /* + * This key is not active for zone-signing. + */ + continue; + } + + /* + * If this key is revoked, it may only sign the + * DNSKEY RRset. + */ + if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + } else if (both) { + /* + * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). + */ + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || + type == dns_rdatatype_cds) + { + if (!KSK(keys[i]) && keyset_kskonly) { + continue; + } + } else if (KSK(keys[i])) { + continue; + } + } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { + continue; + } + + /* Calculate the signature, creating a RRSIG RDATA. */ + isc_buffer_clear(&buffer); + CHECK(dns_dnssec_sign(name, &rdataset, keys[i], &inception, + &expire, mctx, &buffer, &sig_rdata)); + + /* Update the database and journal with the RRSIG. */ + /* XXX inefficient - will cause dataset merging */ + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name, + rdataset.ttl, &sig_rdata)); + dns_rdata_reset(&sig_rdata); + isc_buffer_init(&buffer, data, sizeof(data)); + + /* Update DNSSEC sign statistics. */ + dnssecsignstats = dns_zone_getdnssecsignstats(zone); + if (dnssecsignstats != NULL) { + /* Generated a new signature. */ + dns_dnssecsignstats_increment(dnssecsignstats, + ID(keys[i]), + (uint8_t)ALG(keys[i]), + dns_dnssecsignstats_sign); + /* This is a refresh. */ + dns_dnssecsignstats_increment( + dnssecsignstats, ID(keys[i]), + (uint8_t)ALG(keys[i]), + dns_dnssecsignstats_refresh); + } + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static void +zone_resigninc(dns_zone_t *zone) { + const char *me = "zone_resigninc"; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_diff_t _sig_diff; + dns__zonediff_t zonediff; + dns_fixedname_t fixed; + dns_name_t *name; + dns_rdataset_t rdataset; + dns_rdatatype_t covers; + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + bool check_ksk, keyset_kskonly = false; + isc_result_t result; + isc_stdtime_t now, inception, soaexpire, expire, fullexpire, stop; + uint32_t sigvalidityinterval, expiryinterval; + unsigned int i; + unsigned int nkeys = 0; + unsigned int resign; + + ENTER; + + dns_rdataset_init(&rdataset); + dns_diff_init(zone->mctx, &_sig_diff); + zonediff_init(&zonediff, &_sig_diff); + + /* + * Zone is frozen or automatic resigning is disabled. + * Pause for 5 minutes. + */ + if (zone->update_disabled || + DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN)) + { + result = ISC_R_FAILURE; + goto failure; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + result = ISC_R_FAILURE; + goto failure; + } + + result = dns_db_newversion(db, &version); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + isc_stdtime_get(&now); + + result = dns__zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:dns__zone_findkeys -> %s", + isc_result_totext(result)); + goto failure; + } + + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); + inception = now - 3600; /* Allow for clock skew. */ + soaexpire = now + sigvalidityinterval; + expiryinterval = dns_zone_getsigresigninginterval(zone); + if (expiryinterval > sigvalidityinterval) { + expiryinterval = sigvalidityinterval; + } else { + expiryinterval = sigvalidityinterval - expiryinterval; + } + + /* + * Spread out signatures over time if they happen to be + * clumped. We don't do this for each add_sigs() call as + * we still want some clustering to occur. In normal operations + * the records should be re-signed as they fall due and they should + * already be spread out. However if the server is off for a + * period we need to ensure that the clusters don't become + * synchronised by using the full jitter range. + */ + if (sigvalidityinterval >= 3600U) { + uint32_t normaljitter, fulljitter; + if (sigvalidityinterval > 7200U) { + normaljitter = isc_random_uniform(3600); + fulljitter = isc_random_uniform(expiryinterval); + } else { + normaljitter = fulljitter = isc_random_uniform(1200); + } + expire = soaexpire - normaljitter - 1; + fullexpire = soaexpire - fulljitter - 1; + } else { + expire = fullexpire = soaexpire - 1; + } + stop = now + 5; + + check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); + + name = dns_fixedname_initname(&fixed); + result = dns_db_getsigningtime(db, &rdataset, name); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:dns_db_getsigningtime -> %s", + isc_result_totext(result)); + } + + i = 0; + while (result == ISC_R_SUCCESS) { + resign = rdataset.resign - + dns_zone_getsigresigninginterval(zone); + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + + /* + * Stop if we hit the SOA as that means we have walked the + * entire zone. The SOA record should always be the most + * recent signature. + */ + /* XXXMPA increase number of RRsets signed pre call */ + if ((covers == dns_rdatatype_soa && + dns_name_equal(name, &zone->origin)) || + i++ > zone->signatures || resign > stop) + { + break; + } + + result = del_sigs(zone, db, version, name, covers, &zonediff, + zone_keys, nkeys, now, true); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:del_sigs -> %s", + isc_result_totext(result)); + break; + } + + /* + * If re-signing is over 5 minutes late use 'fullexpire' + * to redistribute the signature over the complete + * re-signing window, otherwise only add a small amount + * of jitter. + */ + result = add_sigs(db, version, name, zone, covers, + zonediff.diff, zone_keys, nkeys, zone->mctx, + inception, + resign > (now - 300) ? expire : fullexpire, + check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:add_sigs -> %s", + isc_result_totext(result)); + break; + } + result = dns_db_getsigningtime(db, &rdataset, name); + if (nkeys == 0 && result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + break; + } + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:dns_db_getsigningtime -> " + "%s", + isc_result_totext(result)); + } + } + + if (result != ISC_R_NOMORE && result != ISC_R_SUCCESS) { + goto failure; + } + + result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, + &zonediff, zone_keys, nkeys, now, true); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:del_sigs -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * Did we change anything in the zone? + */ + if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { + /* + * Commit the changes if any key has been marked as offline. + */ + if (zonediff.offline) { + dns_db_closeversion(db, &version, true); + } + goto failure; + } + + /* Increment SOA serial if we have made changes */ + result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, + zone->updatemethod); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:update_soa_serial -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * Generate maximum life time signatures so that the above loop + * termination is sensible. + */ + result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, + zonediff.diff, zone_keys, nkeys, zone->mctx, + inception, soaexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_resigninc:add_sigs -> %s", + isc_result_totext(result)); + goto failure; + } + + /* Write changes to journal file. */ + CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_resigninc")); + + /* Everything has succeeded. Commit the changes. */ + dns_db_closeversion(db, &version, true); + +failure: + dns_diff_clear(&_sig_diff); + for (i = 0; i < nkeys; i++) { + dst_key_free(&zone_keys[i]); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); + } else if (db != NULL) { + dns_db_detach(&db); + } + + LOCK_ZONE(zone); + if (result == ISC_R_SUCCESS) { + set_resigntime(zone); + zone_needdump(zone, DNS_DUMP_DELAY); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + } else { + /* + * Something failed. Retry in 5 minutes. + */ + isc_interval_t ival; + isc_interval_set(&ival, 300, 0); + isc_time_nowplusinterval(&zone->resigntime, &ival); + } + UNLOCK_ZONE(zone); + + INSIST(version == NULL); +} + +static isc_result_t +next_active(dns_db_t *db, dns_dbversion_t *version, dns_name_t *oldname, + dns_name_t *newname, bool bottom) { + isc_result_t result; + dns_dbiterator_t *dbit = NULL; + dns_rdatasetiter_t *rdsit = NULL; + dns_dbnode_t *node = NULL; + + CHECK(dns_db_createiterator(db, DNS_DB_NONSEC3, &dbit)); + CHECK(dns_dbiterator_seek(dbit, oldname)); + do { + result = dns_dbiterator_next(dbit); + if (result == ISC_R_NOMORE) { + CHECK(dns_dbiterator_first(dbit)); + } + CHECK(dns_dbiterator_current(dbit, &node, newname)); + if (bottom && dns_name_issubdomain(newname, oldname) && + !dns_name_equal(newname, oldname)) + { + dns_db_detachnode(db, &node); + continue; + } + /* + * Is this node empty? + */ + CHECK(dns_db_allrdatasets(db, node, version, 0, 0, &rdsit)); + result = dns_rdatasetiter_first(rdsit); + dns_db_detachnode(db, &node); + dns_rdatasetiter_destroy(&rdsit); + if (result != ISC_R_NOMORE) { + break; + } + } while (1); +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (dbit != NULL) { + dns_dbiterator_destroy(&dbit); + } + return (result); +} + +static bool +signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, dns_rdatatype_t type, + dst_key_t *key) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t rrsig; + int count = 0; + dns_kasp_t *kasp = dns_zone_getkasp(zone); + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_rrsig, + type, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + return (false); + } + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + INSIST(result == ISC_R_SUCCESS); + if (rrsig.algorithm == dst_key_alg(key) && + rrsig.keyid == dst_key_id(key)) + { + dns_rdataset_disassociate(&rdataset); + return (true); + } + if (rrsig.algorithm == dst_key_alg(key)) { + count++; + } + dns_rdata_reset(&rdata); + } + + if (dns_zone_getkasp(zone) != NULL) { + dns_kasp_key_t *kkey; + int zsk_count = 0; + bool approved; + + KASP_LOCK(kasp); + for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; + kkey = ISC_LIST_NEXT(kkey, link)) + { + if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { + continue; + } + if (dns_kasp_key_zsk(kkey)) { + zsk_count++; + } + } + KASP_UNLOCK(kasp); + + if (type == dns_rdatatype_dnskey || + type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) + { + /* + * CDS and CDNSKEY are signed with KSK like DNSKEY. + * (RFC 7344, section 4.1 specifies that they must + * be signed with a key in the current DS RRset, + * which would only include KSK's.) + */ + approved = false; + } else { + approved = (zsk_count == count); + } + + dns_rdataset_disassociate(&rdataset); + return (approved); + } + + dns_rdataset_disassociate(&rdataset); + return (false); +} + +static isc_result_t +add_nsec(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, + dns_dbnode_t *node, dns_ttl_t ttl, bool bottom, dns_diff_t *diff) { + dns_fixedname_t fixed; + dns_name_t *next; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + unsigned char nsecbuffer[DNS_NSEC_BUFFERSIZE]; + + next = dns_fixedname_initname(&fixed); + + CHECK(next_active(db, version, name, next, bottom)); + CHECK(dns_nsec_buildrdata(db, version, node, next, nsecbuffer, &rdata)); + CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADD, name, ttl, + &rdata)); +failure: + return (result); +} + +static isc_result_t +check_if_bottom_of_zone(dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, bool *is_bottom_of_zone) { + isc_result_t result; + dns_rdatasetiter_t *iterator = NULL; + dns_rdataset_t rdataset; + bool seen_soa = false, seen_ns = false, seen_dname = false; + + REQUIRE(is_bottom_of_zone != NULL); + + result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + return (result); + } + + dns_rdataset_init(&rdataset); + for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + dns_rdatasetiter_current(iterator, &rdataset); + switch (rdataset.type) { + case dns_rdatatype_soa: + seen_soa = true; + break; + case dns_rdatatype_ns: + seen_ns = true; + break; + case dns_rdatatype_dname: + seen_dname = true; + break; + } + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + if ((seen_ns && !seen_soa) || seen_dname) { + *is_bottom_of_zone = true; + } + result = ISC_R_SUCCESS; + +failure: + dns_rdatasetiter_destroy(&iterator); + + return (result); +} + +static isc_result_t +sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, + dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3, + bool build_nsec, dst_key_t *key, isc_stdtime_t inception, + isc_stdtime_t expire, dns_ttl_t nsecttl, bool is_ksk, bool is_zsk, + bool keyset_kskonly, bool is_bottom_of_zone, dns_diff_t *diff, + int32_t *signatures, isc_mem_t *mctx) { + isc_result_t result; + dns_rdatasetiter_t *iterator = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_stats_t *dnssecsignstats; + + isc_buffer_t buffer; + unsigned char data[1024]; + bool seen_soa, seen_ns, seen_rr, seen_nsec, seen_nsec3, seen_ds; + + result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + return (result); + } + + dns_rdataset_init(&rdataset); + isc_buffer_init(&buffer, data, sizeof(data)); + seen_rr = seen_soa = seen_ns = seen_nsec = seen_nsec3 = seen_ds = false; + for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + dns_rdatasetiter_current(iterator, &rdataset); + if (rdataset.type == dns_rdatatype_soa) { + seen_soa = true; + } else if (rdataset.type == dns_rdatatype_ns) { + seen_ns = true; + } else if (rdataset.type == dns_rdatatype_ds) { + seen_ds = true; + } else if (rdataset.type == dns_rdatatype_nsec) { + seen_nsec = true; + } else if (rdataset.type == dns_rdatatype_nsec3) { + seen_nsec3 = true; + } + if (rdataset.type != dns_rdatatype_rrsig) { + seen_rr = true; + } + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + /* + * Going from insecure to NSEC3. + * Don't generate NSEC3 records for NSEC3 records. + */ + if (build_nsec3 && !seen_nsec3 && seen_rr) { + bool unsecure = !seen_ds && seen_ns && !seen_soa; + CHECK(dns_nsec3_addnsec3s(db, version, name, nsecttl, unsecure, + diff)); + (*signatures)--; + } + /* + * Going from insecure to NSEC. + * Don't generate NSEC records for NSEC3 records. + */ + if (build_nsec && !seen_nsec3 && !seen_nsec && seen_rr) { + /* + * Build a NSEC record except at the origin. + */ + if (!dns_name_equal(name, dns_db_origin(db))) { + CHECK(add_nsec(db, version, name, node, nsecttl, + is_bottom_of_zone, diff)); + /* Count a NSEC generation as a signature generation. */ + (*signatures)--; + } + } + result = dns_rdatasetiter_first(iterator); + while (result == ISC_R_SUCCESS) { + isc_stdtime_t when; + + dns_rdatasetiter_current(iterator, &rdataset); + if (rdataset.type == dns_rdatatype_soa || + rdataset.type == dns_rdatatype_rrsig) + { + goto next_rdataset; + } + if (rdataset.type == dns_rdatatype_dnskey || + rdataset.type == dns_rdatatype_cdnskey || + rdataset.type == dns_rdatatype_cds) + { + /* + * CDS and CDNSKEY are signed with KSK like DNSKEY. + * (RFC 7344, section 4.1 specifies that they must + * be signed with a key in the current DS RRset, + * which would only include KSK's.) + */ + if (!is_ksk && keyset_kskonly) { + goto next_rdataset; + } + } else if (!is_zsk) { + goto next_rdataset; + } else if (is_zsk && !dst_key_is_signing(key, DST_BOOL_ZSK, + inception, &when)) + { + /* Only applies to dnssec-policy. */ + if (dns_zone_getkasp(zone) != NULL) { + goto next_rdataset; + } + } + + if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds && + rdataset.type != dns_rdatatype_nsec) + { + goto next_rdataset; + } + if (signed_with_good_key(zone, db, node, version, rdataset.type, + key)) + { + goto next_rdataset; + } + + /* Calculate the signature, creating a RRSIG RDATA. */ + isc_buffer_clear(&buffer); + CHECK(dns_dnssec_sign(name, &rdataset, key, &inception, &expire, + mctx, &buffer, &rdata)); + /* Update the database and journal with the RRSIG. */ + /* XXX inefficient - will cause dataset merging */ + CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADDRESIGN, + name, rdataset.ttl, &rdata)); + dns_rdata_reset(&rdata); + + /* Update DNSSEC sign statistics. */ + dnssecsignstats = dns_zone_getdnssecsignstats(zone); + if (dnssecsignstats != NULL) { + /* Generated a new signature. */ + dns_dnssecsignstats_increment(dnssecsignstats, ID(key), + ALG(key), + dns_dnssecsignstats_sign); + /* This is a refresh. */ + dns_dnssecsignstats_increment( + dnssecsignstats, ID(key), ALG(key), + dns_dnssecsignstats_refresh); + } + + (*signatures)--; + next_rdataset: + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(iterator); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (iterator != NULL) { + dns_rdatasetiter_destroy(&iterator); + } + return (result); +} + +/* + * If 'update_only' is set then don't create a NSEC RRset if it doesn't exist. + */ +static isc_result_t +updatesecure(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, + dns_ttl_t nsecttl, bool update_only, dns_diff_t *diff) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + + CHECK(dns_db_getoriginnode(db, &node)); + if (update_only) { + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset( + db, node, version, dns_rdatatype_nsec, + dns_rdatatype_none, 0, &rdataset, NULL); + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (result == ISC_R_NOTFOUND) { + goto success; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + CHECK(delete_nsec(db, version, node, name, diff)); + CHECK(add_nsec(db, version, name, node, nsecttl, false, diff)); +success: + result = ISC_R_SUCCESS; +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static isc_result_t +updatesignwithkey(dns_zone_t *zone, dns_signing_t *signing, + dns_dbversion_t *version, bool build_nsec3, dns_ttl_t nsecttl, + dns_diff_t *diff) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char data[5]; + bool seen_done = false; + bool have_rr = false; + + dns_rdataset_init(&rdataset); + result = dns_db_getoriginnode(signing->db, &node); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_db_findrdataset(signing->db, node, version, + zone->privatetype, dns_rdatatype_none, 0, + &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + result = ISC_R_SUCCESS; + goto failure; + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto failure; + } + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + /* + * If we don't match the algorithm or keyid skip the record. + */ + if (rdata.length != 5 || rdata.data[0] != signing->algorithm || + rdata.data[1] != ((signing->keyid >> 8) & 0xff) || + rdata.data[2] != (signing->keyid & 0xff)) + { + have_rr = true; + dns_rdata_reset(&rdata); + continue; + } + /* + * We have a match. If we were signing (!signing->deleteit) + * and we already have a record indicating that we have + * finished signing (rdata.data[4] != 0) then keep it. + * Otherwise it needs to be deleted as we have removed all + * the signatures (signing->deleteit), so any record indicating + * completion is now out of date, or we have finished signing + * with the new record so we no longer need to remember that + * we need to sign the zone with the matching key across a + * nameserver re-start. + */ + if (!signing->deleteit && rdata.data[4] != 0) { + seen_done = true; + have_rr = true; + } else { + CHECK(update_one_rr(signing->db, version, diff, + DNS_DIFFOP_DEL, &zone->origin, + rdataset.ttl, &rdata)); + } + dns_rdata_reset(&rdata); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + if (!signing->deleteit && !seen_done) { + /* + * If we were signing then we need to indicate that we have + * finished signing the zone with this key. If it is already + * there we don't need to add it a second time. + */ + data[0] = signing->algorithm; + data[1] = (signing->keyid >> 8) & 0xff; + data[2] = signing->keyid & 0xff; + data[3] = 0; + data[4] = 1; + rdata.length = sizeof(data); + rdata.data = data; + rdata.type = zone->privatetype; + rdata.rdclass = dns_db_class(signing->db); + CHECK(update_one_rr(signing->db, version, diff, DNS_DIFFOP_ADD, + &zone->origin, rdataset.ttl, &rdata)); + } else if (!have_rr) { + dns_name_t *origin = dns_db_origin(signing->db); + /* + * Rebuild the NSEC/NSEC3 record for the origin as we no + * longer have any private records. + */ + if (build_nsec3) { + CHECK(dns_nsec3_addnsec3s(signing->db, version, origin, + nsecttl, false, diff)); + } + CHECK(updatesecure(signing->db, version, origin, nsecttl, true, + diff)); + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(signing->db, &node); + } + return (result); +} + +/* + * Called from zone_nsec3chain() in order to update zone records indicating + * processing status of given NSEC3 chain: + * + * - If the supplied dns_nsec3chain_t structure has been fully processed + * (which is indicated by "active" being set to false): + * + * - remove all NSEC3PARAM records matching the relevant NSEC3 chain, + * + * - remove all private-type records containing NSEC3PARAM RDATA matching + * the relevant NSEC3 chain. + * + * - If the supplied dns_nsec3chain_t structure has not been fully processed + * (which is indicated by "active" being set to true), only remove the + * NSEC3PARAM record which matches the relevant NSEC3 chain and has the + * "flags" field set to 0. + * + * - If given NSEC3 chain is being added, add an NSEC3PARAM record contained + * in the relevant private-type record, but with the "flags" field set to + * 0, indicating that this NSEC3 chain is now complete for this zone. + * + * Note that this function is called at different processing stages for NSEC3 + * chain additions vs. removals and needs to handle all cases properly. + */ +static isc_result_t +fixup_nsec3param(dns_db_t *db, dns_dbversion_t *ver, dns_nsec3chain_t *chain, + bool active, dns_rdatatype_t privatetype, dns_diff_t *diff) { + dns_dbnode_t *node = NULL; + dns_name_t *name = dns_db_origin(db); + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t rdataset; + dns_rdata_nsec3param_t nsec3param; + isc_result_t result; + isc_buffer_t buffer; + unsigned char parambuf[DNS_NSEC3PARAM_BUFFERSIZE]; + dns_ttl_t ttl = 0; + bool nseconly = false, nsec3ok = false; + + dns_rdataset_init(&rdataset); + + result = dns_db_getoriginnode(db, &node); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, + 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + goto try_private; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * Preserve the existing ttl. + */ + ttl = rdataset.ttl; + + /* + * Delete all NSEC3PARAM records which match that in nsec3chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + if (nsec3param.hash != chain->nsec3param.hash || + (active && nsec3param.flags != 0) || + nsec3param.iterations != chain->nsec3param.iterations || + nsec3param.salt_length != chain->nsec3param.salt_length || + memcmp(nsec3param.salt, chain->nsec3param.salt, + nsec3param.salt_length)) + { + dns_rdata_reset(&rdata); + continue; + } + + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata)); + dns_rdata_reset(&rdata); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + + dns_rdataset_disassociate(&rdataset); + +try_private: + + if (active) { + goto add; + } + + result = dns_nsec_nseconly(db, ver, diff, &nseconly); + nsec3ok = (result == ISC_R_SUCCESS && !nseconly); + + /* + * Delete all private records which match that in nsec3chain. + */ + result = dns_db_findrdataset(db, node, ver, privatetype, 0, 0, + &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + goto add; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t private = DNS_RDATA_INIT; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + dns_rdataset_current(&rdataset, &private); + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + + if ((!nsec3ok && + (nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0) || + nsec3param.hash != chain->nsec3param.hash || + nsec3param.iterations != chain->nsec3param.iterations || + nsec3param.salt_length != chain->nsec3param.salt_length || + memcmp(nsec3param.salt, chain->nsec3param.salt, + nsec3param.salt_length)) + { + dns_rdata_reset(&rdata); + continue; + } + + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, + rdataset.ttl, &private)); + dns_rdata_reset(&rdata); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + +add: + if ((chain->nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { + result = ISC_R_SUCCESS; + goto failure; + } + + /* + * Add a NSEC3PARAM record which matches that in nsec3chain but + * with all flags bits cleared. + * + * Note: we do not clear chain->nsec3param.flags as this change + * may be reversed. + */ + isc_buffer_init(&buffer, ¶mbuf, sizeof(parambuf)); + CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), + dns_rdatatype_nsec3param, &chain->nsec3param, + &buffer)); + rdata.data[1] = 0; /* Clear flag bits. */ + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, name, ttl, &rdata)); + +failure: + dns_db_detachnode(db, &node); + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + return (result); +} + +static isc_result_t +delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, + dns_name_t *name, dns_diff_t *diff) { + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0, + &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata)); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +failure: + dns_rdataset_disassociate(&rdataset); + return (result); +} + +static isc_result_t +deletematchingnsec3(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, + dns_name_t *name, const dns_rdata_nsec3param_t *param, + dns_diff_t *diff) { + dns_rdataset_t rdataset; + dns_rdata_nsec3_t nsec3; + isc_result_t result; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL)); + if (nsec3.hash != param->hash || + nsec3.iterations != param->iterations || + nsec3.salt_length != param->salt_length || + memcmp(nsec3.salt, param->salt, nsec3.salt_length)) + { + continue; + } + CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata)); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } +failure: + dns_rdataset_disassociate(&rdataset); + return (result); +} + +static isc_result_t +need_nsec_chain(dns_db_t *db, dns_dbversion_t *ver, + const dns_rdata_nsec3param_t *param, bool *answer) { + dns_dbnode_t *node = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t myparam; + dns_rdataset_t rdataset; + isc_result_t result; + + *answer = false; + + result = dns_db_getoriginnode(db, &node); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdataset_init(&rdataset); + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0, + &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + return (result); + } + if (result != ISC_R_NOTFOUND) { + dns_db_detachnode(db, &node); + return (result); + } + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, + 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + *answer = true; + dns_db_detachnode(db, &node); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(db, &node); + return (result); + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &myparam, NULL)); + dns_rdata_reset(&rdata); + /* + * Ignore any NSEC3PARAM removals. + */ + if (NSEC3REMOVE(myparam.flags)) { + continue; + } + /* + * Ignore the chain that we are in the process of deleting. + */ + if (myparam.hash == param->hash && + myparam.iterations == param->iterations && + myparam.salt_length == param->salt_length && + !memcmp(myparam.salt, param->salt, myparam.salt_length)) + { + continue; + } + /* + * Found an active NSEC3 chain. + */ + break; + } + if (result == ISC_R_NOMORE) { + *answer = true; + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + dns_db_detachnode(db, &node); + return (result); +} + +/*% + * Given a tuple which is part of a diff, return a pointer to the next tuple in + * that diff which has the same name and type (or NULL if no such tuple is + * found). + */ +static dns_difftuple_t * +find_next_matching_tuple(dns_difftuple_t *cur) { + dns_difftuple_t *next = cur; + + while ((next = ISC_LIST_NEXT(next, link)) != NULL) { + if (cur->rdata.type == next->rdata.type && + dns_name_equal(&cur->name, &next->name)) + { + return (next); + } + } + + return (NULL); +} + +/*% + * Remove all tuples with the same name and type as 'cur' from 'src' and append + * them to 'dst'. + */ +static void +move_matching_tuples(dns_difftuple_t *cur, dns_diff_t *src, dns_diff_t *dst) { + do { + dns_difftuple_t *next = find_next_matching_tuple(cur); + ISC_LIST_UNLINK(src->tuples, cur, link); + dns_diff_appendminimal(dst, &cur); + cur = next; + } while (cur != NULL); +} + +/*% + * Add/remove DNSSEC signatures for the list of "raw" zone changes supplied in + * 'diff'. Gradually remove tuples from 'diff' and append them to 'zonediff' + * along with tuples representing relevant signature changes. + */ +isc_result_t +dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version, + dst_key_t *zone_keys[], unsigned int nkeys, + dns_zone_t *zone, isc_stdtime_t inception, + isc_stdtime_t expire, isc_stdtime_t keyexpire, + isc_stdtime_t now, bool check_ksk, bool keyset_kskonly, + dns__zonediff_t *zonediff) { + dns_difftuple_t *tuple; + isc_result_t result; + + while ((tuple = ISC_LIST_HEAD(diff->tuples)) != NULL) { + isc_stdtime_t exp = expire; + + if (keyexpire != 0 && + (tuple->rdata.type == dns_rdatatype_dnskey || + tuple->rdata.type == dns_rdatatype_cdnskey || + tuple->rdata.type == dns_rdatatype_cds)) + { + exp = keyexpire; + } + + result = del_sigs(zone, db, version, &tuple->name, + tuple->rdata.type, zonediff, zone_keys, nkeys, + now, false); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "dns__zone_updatesigs:del_sigs -> %s", + isc_result_totext(result)); + return (result); + } + result = add_sigs(db, version, &tuple->name, zone, + tuple->rdata.type, zonediff->diff, zone_keys, + nkeys, zone->mctx, inception, exp, check_ksk, + keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "dns__zone_updatesigs:add_sigs -> %s", + isc_result_totext(result)); + return (result); + } + + /* + * Signature changes for all RRs with name tuple->name and type + * tuple->rdata.type were appended to zonediff->diff. Now we + * remove all the "raw" changes with the same name and type + * from diff (so that they are not processed by this loop + * again) and append them to zonediff so that they get applied. + */ + move_matching_tuples(tuple, diff, zonediff->diff); + } + return (ISC_R_SUCCESS); +} + +/* + * Incrementally build and sign a new NSEC3 chain using the parameters + * requested. + */ +static void +zone_nsec3chain(dns_zone_t *zone) { + const char *me = "zone_nsec3chain"; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_diff_t _sig_diff; + dns_diff_t nsec_diff; + dns_diff_t nsec3_diff; + dns_diff_t param_diff; + dns__zonediff_t zonediff; + dns_fixedname_t fixed; + dns_fixedname_t nextfixed; + dns_name_t *name, *nextname; + dns_rdataset_t rdataset; + dns_nsec3chain_t *nsec3chain = NULL, *nextnsec3chain; + dns_nsec3chainlist_t cleanup; + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + int32_t signatures; + bool check_ksk, keyset_kskonly; + bool delegation; + bool first; + isc_result_t result; + isc_stdtime_t now, inception, soaexpire, expire; + uint32_t jitter, sigvalidityinterval, expiryinterval; + unsigned int i; + unsigned int nkeys = 0; + uint32_t nodes; + bool unsecure = false; + bool seen_soa, seen_ns, seen_dname, seen_ds; + bool seen_nsec, seen_nsec3, seen_rr; + dns_rdatasetiter_t *iterator = NULL; + bool buildnsecchain; + bool updatensec = false; + dns_rdatatype_t privatetype = zone->privatetype; + + ENTER; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fixed); + nextname = dns_fixedname_initname(&nextfixed); + dns_diff_init(zone->mctx, ¶m_diff); + dns_diff_init(zone->mctx, &nsec3_diff); + dns_diff_init(zone->mctx, &nsec_diff); + dns_diff_init(zone->mctx, &_sig_diff); + zonediff_init(&zonediff, &_sig_diff); + ISC_LIST_INIT(cleanup); + + /* + * Updates are disabled. Pause for 5 minutes. + */ + if (zone->update_disabled) { + result = ISC_R_FAILURE; + goto failure; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + /* + * This function is called when zone timer fires, after the latter gets + * set by zone_addnsec3chain(). If the action triggering the call to + * zone_addnsec3chain() is closely followed by a zone deletion request, + * it might turn out that the timer thread will not be woken up until + * after the zone is deleted by rmzone(), which calls dns_db_detach() + * for zone->db, causing the latter to become NULL. Return immediately + * if that happens. + */ + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + return; + } + + result = dns_db_newversion(db, &version); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + isc_stdtime_get(&now); + + result = dns__zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns__zone_findkeys -> %s", + isc_result_totext(result)); + goto failure; + } + + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); + inception = now - 3600; /* Allow for clock skew. */ + soaexpire = now + sigvalidityinterval; + expiryinterval = dns_zone_getsigresigninginterval(zone); + if (expiryinterval > sigvalidityinterval) { + expiryinterval = sigvalidityinterval; + } else { + expiryinterval = sigvalidityinterval - expiryinterval; + } + + /* + * Spread out signatures over time if they happen to be + * clumped. We don't do this for each add_sigs() call as + * we still want some clustering to occur. + */ + if (sigvalidityinterval >= 3600U) { + if (sigvalidityinterval > 7200U) { + jitter = isc_random_uniform(expiryinterval); + } else { + jitter = isc_random_uniform(1200); + } + expire = soaexpire - jitter - 1; + } else { + expire = soaexpire - 1; + } + + check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); + + /* + * We keep pulling nodes off each iterator in turn until + * we have no more nodes to pull off or we reach the limits + * for this quantum. + */ + nodes = zone->nodes; + signatures = zone->signatures; + LOCK_ZONE(zone); + nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); + UNLOCK_ZONE(zone); + first = true; + + if (nsec3chain != NULL) { + nsec3chain->save_delete_nsec = nsec3chain->delete_nsec; + } + /* + * Generate new NSEC3 chains first. + * + * The following while loop iterates over nodes in the zone database, + * updating the NSEC3 chain by calling dns_nsec3_addnsec3() for each of + * them. Once all nodes are processed, the "delete_nsec" field is + * consulted to check whether we are supposed to remove NSEC records + * from the zone database; if so, the database iterator is reset to + * point to the first node and the loop traverses all of them again, + * this time removing NSEC records. If we hit a node which is obscured + * by a delegation or a DNAME, nodes are skipped over until we find one + * that is not obscured by the same obscuring name and then normal + * processing is resumed. + * + * The above is repeated until all requested NSEC3 chain changes are + * applied or when we reach the limits for this quantum, whichever + * happens first. + * + * Note that the "signatures" variable is only used here to limit the + * amount of work performed. Actual DNSSEC signatures are only + * generated by dns__zone_updatesigs() calls later in this function. + */ + while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) { + dns_dbiterator_pause(nsec3chain->dbiterator); + + LOCK_ZONE(zone); + nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (nsec3chain->done || nsec3chain->db != zone->db) { + ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); + ISC_LIST_APPEND(cleanup, nsec3chain, link); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + UNLOCK_ZONE(zone); + if (ISC_LIST_TAIL(cleanup) == nsec3chain) { + goto next_addchain; + } + + /* + * Possible future db. + */ + if (nsec3chain->db != db) { + goto next_addchain; + } + + if (NSEC3REMOVE(nsec3chain->nsec3param.flags)) { + goto next_addchain; + } + + dns_dbiterator_current(nsec3chain->dbiterator, &node, name); + + if (nsec3chain->delete_nsec) { + delegation = false; + dns_dbiterator_pause(nsec3chain->dbiterator); + CHECK(delete_nsec(db, version, node, name, &nsec_diff)); + goto next_addnode; + } + /* + * On the first pass we need to check if the current node + * has not been obscured. + */ + delegation = false; + unsecure = false; + if (first) { + dns_fixedname_t ffound; + dns_name_t *found; + found = dns_fixedname_initname(&ffound); + result = dns_db_find( + db, name, version, dns_rdatatype_soa, + DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); + if ((result == DNS_R_DELEGATION || + result == DNS_R_DNAME) && + !dns_name_equal(name, found)) + { + /* + * Remember the obscuring name so that + * we skip all obscured names. + */ + dns_name_copy(found, name); + delegation = true; + goto next_addnode; + } + } + + /* + * Check to see if this is a bottom of zone node. + */ + result = dns_db_allrdatasets(db, node, version, 0, 0, + &iterator); + if (result == ISC_R_NOTFOUND) { + /* Empty node? */ + goto next_addnode; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + seen_soa = seen_ns = seen_dname = seen_ds = seen_nsec = false; + for (result = dns_rdatasetiter_first(iterator); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + dns_rdatasetiter_current(iterator, &rdataset); + INSIST(rdataset.type != dns_rdatatype_nsec3); + if (rdataset.type == dns_rdatatype_soa) { + seen_soa = true; + } else if (rdataset.type == dns_rdatatype_ns) { + seen_ns = true; + } else if (rdataset.type == dns_rdatatype_dname) { + seen_dname = true; + } else if (rdataset.type == dns_rdatatype_ds) { + seen_ds = true; + } else if (rdataset.type == dns_rdatatype_nsec) { + seen_nsec = true; + } + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&iterator); + /* + * Is there a NSEC chain than needs to be cleaned up? + */ + if (seen_nsec) { + nsec3chain->seen_nsec = true; + } + if (seen_ns && !seen_soa && !seen_ds) { + unsecure = true; + } + if ((seen_ns && !seen_soa) || seen_dname) { + delegation = true; + } + + /* + * Process one node. + */ + dns_dbiterator_pause(nsec3chain->dbiterator); + result = dns_nsec3_addnsec3( + db, version, name, &nsec3chain->nsec3param, + zone_nsecttl(zone), unsecure, &nsec3_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "dns_nsec3_addnsec3 -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * Treat each call to dns_nsec3_addnsec3() as if it's cost is + * two signatures. Additionally there will, in general, be + * two signature generated below. + * + * If we are only changing the optout flag the cost is half + * that of the cost of generating a completely new chain. + */ + signatures -= 4; + + /* + * Go onto next node. + */ + next_addnode: + first = false; + dns_db_detachnode(db, &node); + do { + result = dns_dbiterator_next(nsec3chain->dbiterator); + + if (result == ISC_R_NOMORE && nsec3chain->delete_nsec) { + dns_dbiterator_pause(nsec3chain->dbiterator); + CHECK(fixup_nsec3param(db, version, nsec3chain, + false, privatetype, + ¶m_diff)); + LOCK_ZONE(zone); + ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, + link); + UNLOCK_ZONE(zone); + ISC_LIST_APPEND(cleanup, nsec3chain, link); + goto next_addchain; + } + if (result == ISC_R_NOMORE) { + dns_dbiterator_pause(nsec3chain->dbiterator); + if (nsec3chain->seen_nsec) { + CHECK(fixup_nsec3param( + db, version, nsec3chain, true, + privatetype, ¶m_diff)); + nsec3chain->delete_nsec = true; + goto same_addchain; + } + CHECK(fixup_nsec3param(db, version, nsec3chain, + false, privatetype, + ¶m_diff)); + LOCK_ZONE(zone); + ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, + link); + UNLOCK_ZONE(zone); + ISC_LIST_APPEND(cleanup, nsec3chain, link); + goto next_addchain; + } else if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "dns_dbiterator_next -> %s", + isc_result_totext(result)); + goto failure; + } else if (delegation) { + dns_dbiterator_current(nsec3chain->dbiterator, + &node, nextname); + dns_db_detachnode(db, &node); + if (!dns_name_issubdomain(nextname, name)) { + break; + } + } else { + break; + } + } while (1); + continue; + + same_addchain: + CHECK(dns_dbiterator_first(nsec3chain->dbiterator)); + first = true; + continue; + + next_addchain: + dns_dbiterator_pause(nsec3chain->dbiterator); + nsec3chain = nextnsec3chain; + first = true; + if (nsec3chain != NULL) { + nsec3chain->save_delete_nsec = nsec3chain->delete_nsec; + } + } + + if (nsec3chain != NULL) { + goto skip_removals; + } + + /* + * Process removals. + * + * This is a counterpart of the above while loop which takes care of + * removing an NSEC3 chain. It starts with determining whether the + * zone needs to switch from NSEC3 to NSEC; if so, it first builds an + * NSEC chain by iterating over all nodes in the zone database and only + * then goes on to remove NSEC3 records be iterating over all nodes + * again and calling deletematchingnsec3() for each of them; otherwise, + * it starts removing NSEC3 records immediately. Rules for processing + * obscured nodes and interrupting work are the same as for the while + * loop above. + */ + LOCK_ZONE(zone); + nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); + UNLOCK_ZONE(zone); + first = true; + buildnsecchain = false; + while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) { + dns_dbiterator_pause(nsec3chain->dbiterator); + + LOCK_ZONE(zone); + nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link); + UNLOCK_ZONE(zone); + + if (nsec3chain->db != db) { + goto next_removechain; + } + + if (!NSEC3REMOVE(nsec3chain->nsec3param.flags)) { + goto next_removechain; + } + + /* + * Work out if we need to build a NSEC chain as a consequence + * of removing this NSEC3 chain. + */ + if (first && !updatensec && + (nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_NONSEC) == 0) + { + result = need_nsec_chain(db, version, + &nsec3chain->nsec3param, + &buildnsecchain); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "need_nsec_chain -> %s", + isc_result_totext(result)); + goto failure; + } + } + + if (first) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "zone_nsec3chain:buildnsecchain = %u\n", + buildnsecchain); + } + + dns_dbiterator_current(nsec3chain->dbiterator, &node, name); + dns_dbiterator_pause(nsec3chain->dbiterator); + delegation = false; + + if (!buildnsecchain) { + /* + * Delete the NSEC3PARAM record matching this chain. + */ + if (first) { + result = fixup_nsec3param( + db, version, nsec3chain, true, + privatetype, ¶m_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "fixup_nsec3param -> %s", + isc_result_totext(result)); + goto failure; + } + } + + /* + * Delete the NSEC3 records. + */ + result = deletematchingnsec3(db, version, node, name, + &nsec3chain->nsec3param, + &nsec3_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "deletematchingnsec3 -> %s", + isc_result_totext(result)); + goto failure; + } + goto next_removenode; + } + + if (first) { + dns_fixedname_t ffound; + dns_name_t *found; + found = dns_fixedname_initname(&ffound); + result = dns_db_find( + db, name, version, dns_rdatatype_soa, + DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); + if ((result == DNS_R_DELEGATION || + result == DNS_R_DNAME) && + !dns_name_equal(name, found)) + { + /* + * Remember the obscuring name so that + * we skip all obscured names. + */ + dns_name_copy(found, name); + delegation = true; + goto next_removenode; + } + } + + /* + * Check to see if this is a bottom of zone node. + */ + result = dns_db_allrdatasets(db, node, version, 0, 0, + &iterator); + if (result == ISC_R_NOTFOUND) { + /* Empty node? */ + goto next_removenode; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + + seen_soa = seen_ns = seen_dname = seen_nsec3 = seen_nsec = + seen_rr = false; + for (result = dns_rdatasetiter_first(iterator); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + dns_rdatasetiter_current(iterator, &rdataset); + if (rdataset.type == dns_rdatatype_soa) { + seen_soa = true; + } else if (rdataset.type == dns_rdatatype_ns) { + seen_ns = true; + } else if (rdataset.type == dns_rdatatype_dname) { + seen_dname = true; + } else if (rdataset.type == dns_rdatatype_nsec) { + seen_nsec = true; + } else if (rdataset.type == dns_rdatatype_nsec3) { + seen_nsec3 = true; + } else if (rdataset.type != dns_rdatatype_rrsig) { + seen_rr = true; + } + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&iterator); + + if (!seen_rr || seen_nsec3 || seen_nsec) { + goto next_removenode; + } + if ((seen_ns && !seen_soa) || seen_dname) { + delegation = true; + } + + /* + * Add a NSEC record except at the origin. + */ + if (!dns_name_equal(name, dns_db_origin(db))) { + dns_dbiterator_pause(nsec3chain->dbiterator); + CHECK(add_nsec(db, version, name, node, + zone_nsecttl(zone), delegation, + &nsec_diff)); + signatures--; + } + + next_removenode: + first = false; + dns_db_detachnode(db, &node); + do { + result = dns_dbiterator_next(nsec3chain->dbiterator); + if (result == ISC_R_NOMORE && buildnsecchain) { + /* + * The NSEC chain should now be built. + * We can now remove the NSEC3 chain. + */ + updatensec = true; + goto same_removechain; + } + if (result == ISC_R_NOMORE) { + dns_dbiterator_pause(nsec3chain->dbiterator); + LOCK_ZONE(zone); + ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, + link); + UNLOCK_ZONE(zone); + ISC_LIST_APPEND(cleanup, nsec3chain, link); + result = fixup_nsec3param( + db, version, nsec3chain, false, + privatetype, ¶m_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "fixup_nsec3param -> %s", + isc_result_totext(result)); + goto failure; + } + goto next_removechain; + } else if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "dns_dbiterator_next -> %s", + isc_result_totext(result)); + goto failure; + } else if (delegation) { + dns_dbiterator_current(nsec3chain->dbiterator, + &node, nextname); + dns_db_detachnode(db, &node); + if (!dns_name_issubdomain(nextname, name)) { + break; + } + } else { + break; + } + } while (1); + continue; + + same_removechain: + CHECK(dns_dbiterator_first(nsec3chain->dbiterator)); + buildnsecchain = false; + first = true; + continue; + + next_removechain: + dns_dbiterator_pause(nsec3chain->dbiterator); + nsec3chain = nextnsec3chain; + first = true; + } + +skip_removals: + /* + * We may need to update the NSEC/NSEC3 records for the zone apex. + */ + if (!ISC_LIST_EMPTY(param_diff.tuples)) { + bool rebuild_nsec = false, rebuild_nsec3 = false; + result = dns_db_getoriginnode(db, &node); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = dns_db_allrdatasets(db, node, version, 0, 0, + &iterator); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns_db_allrdatasets -> %s", + isc_result_totext(result)); + goto failure; + } + for (result = dns_rdatasetiter_first(iterator); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + dns_rdatasetiter_current(iterator, &rdataset); + if (rdataset.type == dns_rdatatype_nsec) { + rebuild_nsec = true; + } else if (rdataset.type == dns_rdatatype_nsec3param) { + rebuild_nsec3 = true; + } + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&iterator); + dns_db_detachnode(db, &node); + + if (rebuild_nsec) { + if (nsec3chain != NULL) { + dns_dbiterator_pause(nsec3chain->dbiterator); + } + + result = updatesecure(db, version, &zone->origin, + zone_nsecttl(zone), true, + &nsec_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:updatesecure -> %s", + isc_result_totext(result)); + goto failure; + } + } + + if (rebuild_nsec3) { + if (nsec3chain != NULL) { + dns_dbiterator_pause(nsec3chain->dbiterator); + } + + result = dns_nsec3_addnsec3s( + db, version, dns_db_origin(db), + zone_nsecttl(zone), false, &nsec3_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:" + "dns_nsec3_addnsec3s -> %s", + isc_result_totext(result)); + goto failure; + } + } + } + + /* + * Add / update signatures for the NSEC3 records. + */ + if (nsec3chain != NULL) { + dns_dbiterator_pause(nsec3chain->dbiterator); + } + result = dns__zone_updatesigs(&nsec3_diff, db, version, zone_keys, + nkeys, zone, inception, expire, 0, now, + check_ksk, keyset_kskonly, &zonediff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns__zone_updatesigs -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * We have changed the NSEC3PARAM or private RRsets + * above so we need to update the signatures. + */ + result = dns__zone_updatesigs(¶m_diff, db, version, zone_keys, + nkeys, zone, inception, expire, 0, now, + check_ksk, keyset_kskonly, &zonediff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns__zone_updatesigs -> %s", + isc_result_totext(result)); + goto failure; + } + + if (updatensec) { + result = updatesecure(db, version, &zone->origin, + zone_nsecttl(zone), false, &nsec_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:updatesecure -> %s", + isc_result_totext(result)); + goto failure; + } + } + + result = dns__zone_updatesigs(&nsec_diff, db, version, zone_keys, nkeys, + zone, inception, expire, 0, now, + check_ksk, keyset_kskonly, &zonediff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:dns__zone_updatesigs -> %s", + isc_result_totext(result)); + goto failure; + } + + /* + * If we made no effective changes to the zone then we can just + * cleanup otherwise we need to increment the serial. + */ + if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { + /* + * No need to call dns_db_closeversion() here as it is + * called with commit = true below. + */ + goto done; + } + + result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, + &zonediff, zone_keys, nkeys, now, false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:del_sigs -> %s", + isc_result_totext(result)); + goto failure; + } + + result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, + zone->updatemethod); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:update_soa_serial -> %s", + isc_result_totext(result)); + goto failure; + } + + result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, + zonediff.diff, zone_keys, nkeys, zone->mctx, + inception, soaexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_nsec3chain:add_sigs -> %s", + isc_result_totext(result)); + goto failure; + } + + /* Write changes to journal file. */ + CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_nsec3chain")); + + LOCK_ZONE(zone); + zone_needdump(zone, DNS_DUMP_DELAY); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + UNLOCK_ZONE(zone); + +done: + /* + * Pause all iterators so that dns_db_closeversion() can succeed. + */ + LOCK_ZONE(zone); + for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; + nsec3chain = ISC_LIST_NEXT(nsec3chain, link)) + { + dns_dbiterator_pause(nsec3chain->dbiterator); + } + UNLOCK_ZONE(zone); + + /* + * Everything has succeeded. Commit the changes. + * Unconditionally commit as zonediff.offline not checked above. + */ + dns_db_closeversion(db, &version, true); + + /* + * Everything succeeded so we can clean these up now. + */ + nsec3chain = ISC_LIST_HEAD(cleanup); + while (nsec3chain != NULL) { + ISC_LIST_UNLINK(cleanup, nsec3chain, link); + dns_db_detach(&nsec3chain->db); + dns_dbiterator_destroy(&nsec3chain->dbiterator); + isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); + nsec3chain = ISC_LIST_HEAD(cleanup); + } + + LOCK_ZONE(zone); + set_resigntime(zone); + UNLOCK_ZONE(zone); + +failure: + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain: %s", + isc_result_totext(result)); + } + + /* + * On error roll back the current nsec3chain. + */ + if (result != ISC_R_SUCCESS && nsec3chain != NULL) { + if (nsec3chain->done) { + dns_db_detach(&nsec3chain->db); + dns_dbiterator_destroy(&nsec3chain->dbiterator); + isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); + } else { + result = dns_dbiterator_first(nsec3chain->dbiterator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_dbiterator_pause(nsec3chain->dbiterator); + nsec3chain->delete_nsec = nsec3chain->save_delete_nsec; + } + } + + /* + * Rollback the cleanup list. + */ + nsec3chain = ISC_LIST_TAIL(cleanup); + while (nsec3chain != NULL) { + ISC_LIST_UNLINK(cleanup, nsec3chain, link); + if (nsec3chain->done) { + dns_db_detach(&nsec3chain->db); + dns_dbiterator_destroy(&nsec3chain->dbiterator); + isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); + } else { + LOCK_ZONE(zone); + ISC_LIST_PREPEND(zone->nsec3chain, nsec3chain, link); + UNLOCK_ZONE(zone); + result = dns_dbiterator_first(nsec3chain->dbiterator); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_dbiterator_pause(nsec3chain->dbiterator); + nsec3chain->delete_nsec = nsec3chain->save_delete_nsec; + } + nsec3chain = ISC_LIST_TAIL(cleanup); + } + + LOCK_ZONE(zone); + for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; + nsec3chain = ISC_LIST_NEXT(nsec3chain, link)) + { + dns_dbiterator_pause(nsec3chain->dbiterator); + } + UNLOCK_ZONE(zone); + + dns_diff_clear(¶m_diff); + dns_diff_clear(&nsec3_diff); + dns_diff_clear(&nsec_diff); + dns_diff_clear(&_sig_diff); + + if (iterator != NULL) { + dns_rdatasetiter_destroy(&iterator); + } + + for (i = 0; i < nkeys; i++) { + dst_key_free(&zone_keys[i]); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); + } else if (db != NULL) { + dns_db_detach(&db); + } + + LOCK_ZONE(zone); + if (ISC_LIST_HEAD(zone->nsec3chain) != NULL) { + isc_interval_t interval; + if (zone->update_disabled || result != ISC_R_SUCCESS) { + isc_interval_set(&interval, 60, 0); /* 1 minute */ + } else { + isc_interval_set(&interval, 0, 10000000); /* 10 ms */ + } + isc_time_nowplusinterval(&zone->nsec3chaintime, &interval); + } else { + isc_time_settoepoch(&zone->nsec3chaintime); + } + UNLOCK_ZONE(zone); + + INSIST(version == NULL); +} + +/*% + * Delete all RRSIG records with the given algorithm and keyid. + * Remove the NSEC record and RRSIGs if nkeys is zero. + * If all remaining RRsets are signed with the given algorithm + * set *has_algp to true. + */ +static isc_result_t +del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, + dns_dbnode_t *node, unsigned int nkeys, dns_secalg_t algorithm, + uint16_t keyid, bool *has_algp, dns_diff_t *diff) { + dns_rdata_rrsig_t rrsig; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *iterator = NULL; + isc_result_t result; + bool alg_missed = false; + bool alg_found = false; + + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + + result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + return (result); + } + + dns_rdataset_init(&rdataset); + for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iterator)) + { + bool has_alg = false; + dns_rdatasetiter_current(iterator, &rdataset); + if (nkeys == 0 && rdataset.type == dns_rdatatype_nsec) { + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + CHECK(update_one_rr(db, version, diff, + DNS_DIFFOP_DEL, name, + rdataset.ttl, &rdata)); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + dns_rdataset_disassociate(&rdataset); + continue; + } + if (rdataset.type != dns_rdatatype_rrsig) { + dns_rdataset_disassociate(&rdataset); + continue; + } + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &rrsig, NULL)); + if (nkeys != 0 && (rrsig.algorithm != algorithm || + rrsig.keyid != keyid)) + { + if (rrsig.algorithm == algorithm) { + has_alg = true; + } + continue; + } + CHECK(update_one_rr(db, version, diff, + DNS_DIFFOP_DELRESIGN, name, + rdataset.ttl, &rdata)); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + break; + } + + /* + * After deleting, if there's still a signature for + * 'algorithm', set alg_found; if not, set alg_missed. + */ + if (has_alg) { + alg_found = true; + } else { + alg_missed = true; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + /* + * Set `has_algp` if the algorithm was found in every RRset: + * i.e., found in at least one, and not missing from any. + */ + *has_algp = (alg_found && !alg_missed); +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&iterator); + return (result); +} + +/* + * Prevent the zone entering a inconsistent state where + * NSEC only DNSKEYs are present with NSEC3 chains. + */ +bool +dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_diff_t *diff, + dst_key_t **keys, unsigned int numkeys) { + uint8_t alg; + dns_rdatatype_t privatetype; + ; + bool nseconly = false, nsec3 = false; + isc_result_t result; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + privatetype = dns_zone_getprivatetype(zone); + + /* Scan the tuples for an NSEC-only DNSKEY */ + if (diff != NULL) { + for (dns_difftuple_t *tuple = ISC_LIST_HEAD(diff->tuples); + tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) + { + if (nseconly && nsec3) { + break; + } + + if (tuple->op != DNS_DIFFOP_ADD) { + continue; + } + + if (tuple->rdata.type == dns_rdatatype_nsec3param) { + nsec3 = true; + } + + if (tuple->rdata.type != dns_rdatatype_dnskey) { + continue; + } + + alg = tuple->rdata.data[3]; + if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH || + alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1) + { + nseconly = true; + } + } + } + /* Scan the zone keys for an NSEC-only DNSKEY */ + if (keys != NULL && !nseconly) { + for (unsigned int i = 0; i < numkeys; i++) { + alg = dst_key_alg(keys[i]); + if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH || + alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1) + { + nseconly = true; + break; + } + } + } + + /* Check DB for NSEC-only DNSKEY */ + if (!nseconly) { + result = dns_nsec_nseconly(db, ver, diff, &nseconly); + /* + * Adding an NSEC3PARAM record can proceed without a + * DNSKEY (it will trigger a delayed change), so we can + * ignore ISC_R_NOTFOUND here. + */ + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + CHECK(result); + } + + /* Check existing DB for NSEC3 */ + if (!nsec3) { + CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3)); + } + + /* Check kasp for NSEC3PARAM settings */ + if (!nsec3) { + dns_kasp_t *kasp = dns_zone_getkasp(zone); + if (kasp != NULL) { + nsec3 = dns_kasp_nsec3(kasp); + } + } + + /* Refuse to allow NSEC3 with NSEC-only keys */ + if (nseconly && nsec3) { + goto failure; + } + + return (true); + +failure: + return (false); +} + +/* + * Incrementally sign the zone using the keys requested. + * Builds the NSEC chain if required. + */ +static void +zone_sign(dns_zone_t *zone) { + const char *me = "zone_sign"; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_diff_t _sig_diff; + dns_diff_t post_diff; + dns__zonediff_t zonediff; + dns_fixedname_t fixed; + dns_fixedname_t nextfixed; + dns_kasp_t *kasp; + dns_name_t *name, *nextname; + dns_rdataset_t rdataset; + dns_signing_t *signing, *nextsigning; + dns_signinglist_t cleanup; + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + int32_t signatures; + bool check_ksk, keyset_kskonly, is_ksk, is_zsk; + bool with_ksk, with_zsk; + bool commit = false; + bool is_bottom_of_zone; + bool build_nsec = false; + bool build_nsec3 = false; + bool use_kasp = false; + bool first; + isc_result_t result; + isc_stdtime_t now, inception, soaexpire, expire; + uint32_t jitter, sigvalidityinterval, expiryinterval; + unsigned int i, j; + unsigned int nkeys = 0; + uint32_t nodes; + + ENTER; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fixed); + nextname = dns_fixedname_initname(&nextfixed); + dns_diff_init(zone->mctx, &_sig_diff); + dns_diff_init(zone->mctx, &post_diff); + zonediff_init(&zonediff, &_sig_diff); + ISC_LIST_INIT(cleanup); + + /* + * Updates are disabled. Pause for 1 minute. + */ + if (zone->update_disabled) { + result = ISC_R_FAILURE; + goto cleanup; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = dns_db_newversion(db, &version); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_sign:dns_db_newversion -> %s", + isc_result_totext(result)); + goto cleanup; + } + + isc_stdtime_get(&now); + + result = dns__zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_sign:dns__zone_findkeys -> %s", + isc_result_totext(result)); + goto cleanup; + } + + kasp = dns_zone_getkasp(zone); + sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); + inception = now - 3600; /* Allow for clock skew. */ + soaexpire = now + sigvalidityinterval; + expiryinterval = dns_zone_getsigresigninginterval(zone); + if (expiryinterval > sigvalidityinterval) { + expiryinterval = sigvalidityinterval; + } else { + expiryinterval = sigvalidityinterval - expiryinterval; + } + + /* + * Spread out signatures over time if they happen to be + * clumped. We don't do this for each add_sigs() call as + * we still want some clustering to occur. + */ + if (sigvalidityinterval >= 3600U) { + if (sigvalidityinterval > 7200U) { + jitter = isc_random_uniform(expiryinterval); + } else { + jitter = isc_random_uniform(1200); + } + expire = soaexpire - jitter - 1; + } else { + expire = soaexpire - 1; + } + + /* + * We keep pulling nodes off each iterator in turn until + * we have no more nodes to pull off or we reach the limits + * for this quantum. + */ + nodes = zone->nodes; + signatures = zone->signatures; + signing = ISC_LIST_HEAD(zone->signing); + first = true; + + if (dns_zone_getkasp(zone) != NULL) { + check_ksk = false; + keyset_kskonly = true; + use_kasp = true; + } else { + check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = DNS_ZONE_OPTION(zone, + DNS_ZONEOPT_DNSKEYKSKONLY); + } + dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_sign:use kasp -> %s", + use_kasp ? "yes" : "no"); + + /* Determine which type of chain to build */ + CHECK(dns_private_chains(db, version, zone->privatetype, &build_nsec, + &build_nsec3)); + if (!build_nsec && !build_nsec3) { + if (use_kasp) { + build_nsec3 = dns_kasp_nsec3(kasp); + if (!dns_zone_check_dnskey_nsec3( + zone, db, version, NULL, + (dst_key_t **)&zone_keys, nkeys)) + { + dnssec_log(zone, ISC_LOG_INFO, + "wait building NSEC3 chain until " + "NSEC only DNSKEYs are removed"); + build_nsec3 = false; + } + build_nsec = !build_nsec3; + } else { + /* If neither chain is found, default to NSEC */ + build_nsec = true; + } + } + + while (signing != NULL && nodes-- > 0 && signatures > 0) { + bool has_alg = false; + + dns_dbiterator_pause(signing->dbiterator); + nextsigning = ISC_LIST_NEXT(signing, link); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (signing->done || signing->db != zone->db) { + /* + * The zone has been reloaded. We will have to + * created new signings as part of the reload + * process so we can destroy this one. + */ + ISC_LIST_UNLINK(zone->signing, signing, link); + ISC_LIST_APPEND(cleanup, signing, link); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + goto next_signing; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + if (signing->db != db) { + goto next_signing; + } + + is_bottom_of_zone = false; + + if (first && signing->deleteit) { + /* + * Remove the key we are deleting from consideration. + */ + for (i = 0, j = 0; i < nkeys; i++) { + /* + * Find the key we want to remove. + */ + if (ALG(zone_keys[i]) == signing->algorithm && + dst_key_id(zone_keys[i]) == signing->keyid) + { + bool ksk = false; + isc_result_t ret = dst_key_getbool( + zone_keys[i], DST_BOOL_KSK, + &ksk); + if (ret != ISC_R_SUCCESS) { + ksk = KSK(zone_keys[i]); + } + if (ksk) { + dst_key_free(&zone_keys[i]); + } + continue; + } + zone_keys[j] = zone_keys[i]; + j++; + } + for (i = j; i < nkeys; i++) { + zone_keys[i] = NULL; + } + nkeys = j; + } + + dns_dbiterator_current(signing->dbiterator, &node, name); + + if (signing->deleteit) { + dns_dbiterator_pause(signing->dbiterator); + CHECK(del_sig(db, version, name, node, nkeys, + signing->algorithm, signing->keyid, + &has_alg, zonediff.diff)); + } + + /* + * On the first pass we need to check if the current node + * has not been obscured. + */ + if (first) { + dns_fixedname_t ffound; + dns_name_t *found; + found = dns_fixedname_initname(&ffound); + result = dns_db_find( + db, name, version, dns_rdatatype_soa, + DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); + if ((result == DNS_R_DELEGATION || + result == DNS_R_DNAME) && + !dns_name_equal(name, found)) + { + /* + * Remember the obscuring name so that + * we skip all obscured names. + */ + dns_name_copy(found, name); + is_bottom_of_zone = true; + goto next_node; + } + } + + /* + * Process one node. + */ + with_ksk = false; + with_zsk = false; + dns_dbiterator_pause(signing->dbiterator); + + CHECK(check_if_bottom_of_zone(db, node, version, + &is_bottom_of_zone)); + + for (i = 0; !has_alg && i < nkeys; i++) { + bool both = false; + + /* + * Find the keys we want to sign with. + */ + if (!dst_key_isprivate(zone_keys[i])) { + continue; + } + if (dst_key_inactive(zone_keys[i])) { + continue; + } + + /* + * When adding look for the specific key. + */ + if (!signing->deleteit && + (dst_key_alg(zone_keys[i]) != signing->algorithm || + dst_key_id(zone_keys[i]) != signing->keyid)) + { + continue; + } + + /* + * When deleting make sure we are properly signed + * with the algorithm that was being removed. + */ + if (signing->deleteit && + ALG(zone_keys[i]) != signing->algorithm) + { + continue; + } + + /* + * Do we do KSK processing? + */ + if (check_ksk && !REVOKE(zone_keys[i])) { + bool have_ksk, have_nonksk; + if (KSK(zone_keys[i])) { + have_ksk = true; + have_nonksk = false; + } else { + have_ksk = false; + have_nonksk = true; + } + for (j = 0; j < nkeys; j++) { + if (j == i || (ALG(zone_keys[i]) != + ALG(zone_keys[j]))) + { + continue; + } + /* + * Don't consider inactive keys, however + * the key may be temporary offline, so + * do consider KSKs which private key + * files are unavailable. + */ + if (dst_key_inactive(zone_keys[j])) { + continue; + } + if (REVOKE(zone_keys[j])) { + continue; + } + if (KSK(zone_keys[j])) { + have_ksk = true; + } else if (dst_key_isprivate( + zone_keys[j])) + { + have_nonksk = true; + } + both = have_ksk && have_nonksk; + if (both) { + break; + } + } + } + if (use_kasp) { + /* + * A dnssec-policy is found. Check what + * RRsets this key can sign. + */ + isc_result_t kresult; + is_ksk = false; + kresult = dst_key_getbool( + zone_keys[i], DST_BOOL_KSK, &is_ksk); + if (kresult != ISC_R_SUCCESS) { + if (KSK(zone_keys[i])) { + is_ksk = true; + } + } + + is_zsk = false; + kresult = dst_key_getbool( + zone_keys[i], DST_BOOL_ZSK, &is_zsk); + if (kresult != ISC_R_SUCCESS) { + if (!KSK(zone_keys[i])) { + is_zsk = true; + } + } + /* Treat as if we have both KSK and ZSK. */ + both = true; + } else if (both || REVOKE(zone_keys[i])) { + is_ksk = KSK(zone_keys[i]); + is_zsk = !KSK(zone_keys[i]); + } else { + is_ksk = false; + is_zsk = true; + } + + /* + * If deleting signatures, we need to ensure that + * the RRset is still signed at least once by a + * KSK and a ZSK. + */ + if (signing->deleteit && is_zsk && with_zsk) { + continue; + } + + if (signing->deleteit && is_ksk && with_ksk) { + continue; + } + + CHECK(sign_a_node( + db, zone, name, node, version, build_nsec3, + build_nsec, zone_keys[i], inception, expire, + zone_nsecttl(zone), is_ksk, is_zsk, + (both && keyset_kskonly), is_bottom_of_zone, + zonediff.diff, &signatures, zone->mctx)); + /* + * If we are adding we are done. Look for other keys + * of the same algorithm if deleting. + */ + if (!signing->deleteit) { + break; + } + if (is_zsk) { + with_zsk = true; + } + if (is_ksk) { + with_ksk = true; + } + } + + /* + * Go onto next node. + */ + next_node: + first = false; + dns_db_detachnode(db, &node); + do { + result = dns_dbiterator_next(signing->dbiterator); + if (result == ISC_R_NOMORE) { + ISC_LIST_UNLINK(zone->signing, signing, link); + ISC_LIST_APPEND(cleanup, signing, link); + dns_dbiterator_pause(signing->dbiterator); + if (nkeys != 0 && build_nsec) { + /* + * We have finished regenerating the + * zone with a zone signing key. + * The NSEC chain is now complete and + * there is a full set of signatures + * for the zone. We can now clear the + * OPT bit from the NSEC record. + */ + result = updatesecure( + db, version, &zone->origin, + zone_nsecttl(zone), false, + &post_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "updatesecure -> %s", + isc_result_totext( + result)); + goto cleanup; + } + } + result = updatesignwithkey( + zone, signing, version, build_nsec3, + zone_nsecttl(zone), &post_diff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "updatesignwithkey -> %s", + isc_result_totext(result)); + goto cleanup; + } + build_nsec = false; + goto next_signing; + } else if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_sign:" + "dns_dbiterator_next -> %s", + isc_result_totext(result)); + goto cleanup; + } else if (is_bottom_of_zone) { + dns_dbiterator_current(signing->dbiterator, + &node, nextname); + dns_db_detachnode(db, &node); + if (!dns_name_issubdomain(nextname, name)) { + break; + } + } else { + break; + } + } while (1); + continue; + + next_signing: + dns_dbiterator_pause(signing->dbiterator); + signing = nextsigning; + first = true; + } + + if (ISC_LIST_HEAD(post_diff.tuples) != NULL) { + result = dns__zone_updatesigs(&post_diff, db, version, + zone_keys, nkeys, zone, inception, + expire, 0, now, check_ksk, + keyset_kskonly, &zonediff); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_sign:dns__zone_updatesigs -> %s", + isc_result_totext(result)); + goto cleanup; + } + } + + /* + * Have we changed anything? + */ + if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { + if (zonediff.offline) { + commit = true; + } + result = ISC_R_SUCCESS; + goto pauseall; + } + + commit = true; + + result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, + &zonediff, zone_keys, nkeys, now, false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:del_sigs -> %s", + isc_result_totext(result)); + goto cleanup; + } + + result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, + zone->updatemethod); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_sign:update_soa_serial -> %s", + isc_result_totext(result)); + goto cleanup; + } + + /* + * Generate maximum life time signatures so that the above loop + * termination is sensible. + */ + result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, + zonediff.diff, zone_keys, nkeys, zone->mctx, + inception, soaexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:add_sigs -> %s", + isc_result_totext(result)); + goto cleanup; + } + + /* + * Write changes to journal file. + */ + CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_sign")); + +pauseall: + /* + * Pause all iterators so that dns_db_closeversion() can succeed. + */ + for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; + signing = ISC_LIST_NEXT(signing, link)) + { + dns_dbiterator_pause(signing->dbiterator); + } + + for (signing = ISC_LIST_HEAD(cleanup); signing != NULL; + signing = ISC_LIST_NEXT(signing, link)) + { + dns_dbiterator_pause(signing->dbiterator); + } + + /* + * Everything has succeeded. Commit the changes. + */ + dns_db_closeversion(db, &version, commit); + + /* + * Everything succeeded so we can clean these up now. + */ + signing = ISC_LIST_HEAD(cleanup); + while (signing != NULL) { + ISC_LIST_UNLINK(cleanup, signing, link); + dns_db_detach(&signing->db); + dns_dbiterator_destroy(&signing->dbiterator); + isc_mem_put(zone->mctx, signing, sizeof *signing); + signing = ISC_LIST_HEAD(cleanup); + } + + LOCK_ZONE(zone); + set_resigntime(zone); + if (commit) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + zone_needdump(zone, DNS_DUMP_DELAY); + } + UNLOCK_ZONE(zone); + +failure: + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, "zone_sign: failed: %s", + isc_result_totext(result)); + } + +cleanup: + /* + * Pause all dbiterators. + */ + for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; + signing = ISC_LIST_NEXT(signing, link)) + { + dns_dbiterator_pause(signing->dbiterator); + } + + /* + * Rollback the cleanup list. + */ + signing = ISC_LIST_HEAD(cleanup); + while (signing != NULL) { + ISC_LIST_UNLINK(cleanup, signing, link); + ISC_LIST_PREPEND(zone->signing, signing, link); + dns_dbiterator_first(signing->dbiterator); + dns_dbiterator_pause(signing->dbiterator); + signing = ISC_LIST_HEAD(cleanup); + } + + dns_diff_clear(&_sig_diff); + + for (i = 0; i < nkeys; i++) { + dst_key_free(&zone_keys[i]); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + + if (version != NULL) { + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); + } else if (db != NULL) { + dns_db_detach(&db); + } + + LOCK_ZONE(zone); + if (ISC_LIST_HEAD(zone->signing) != NULL) { + isc_interval_t interval; + if (zone->update_disabled || result != ISC_R_SUCCESS) { + isc_interval_set(&interval, 60, 0); /* 1 minute */ + } else { + isc_interval_set(&interval, 0, 10000000); /* 10 ms */ + } + isc_time_nowplusinterval(&zone->signingtime, &interval); + } else { + isc_time_settoepoch(&zone->signingtime); + } + UNLOCK_ZONE(zone); + + INSIST(version == NULL); +} + +static isc_result_t +normalize_key(dns_rdata_t *rr, dns_rdata_t *target, unsigned char *data, + int size) { + dns_rdata_dnskey_t dnskey; + dns_rdata_keydata_t keydata; + isc_buffer_t buf; + isc_result_t result; + + dns_rdata_reset(target); + isc_buffer_init(&buf, data, size); + + switch (rr->type) { + case dns_rdatatype_dnskey: + result = dns_rdata_tostruct(rr, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dnskey.flags &= ~DNS_KEYFLAG_REVOKE; + dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey, + &dnskey, &buf); + break; + case dns_rdatatype_keydata: + result = dns_rdata_tostruct(rr, &keydata, NULL); + if (result == ISC_R_UNEXPECTEDEND) { + return (result); + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_keydata_todnskey(&keydata, &dnskey, NULL); + dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey, + &dnskey, &buf); + break; + default: + UNREACHABLE(); + } + return (ISC_R_SUCCESS); +} + +/* + * 'rdset' contains either a DNSKEY rdataset from the zone apex, or + * a KEYDATA rdataset from the key zone. + * + * 'rr' contains either a DNSKEY record, or a KEYDATA record + * + * After normalizing keys to the same format (DNSKEY, with revoke bit + * cleared), return true if a key that matches 'rr' is found in + * 'rdset', or false if not. + */ + +static bool +matchkey(dns_rdataset_t *rdset, dns_rdata_t *rr) { + unsigned char data1[4096], data2[4096]; + dns_rdata_t rdata, rdata1, rdata2; + isc_result_t result; + + dns_rdata_init(&rdata); + dns_rdata_init(&rdata1); + dns_rdata_init(&rdata2); + + result = normalize_key(rr, &rdata1, data1, sizeof(data1)); + if (result != ISC_R_SUCCESS) { + return (false); + } + + for (result = dns_rdataset_first(rdset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdset, &rdata); + result = normalize_key(&rdata, &rdata2, data2, sizeof(data2)); + if (result != ISC_R_SUCCESS) { + continue; + } + if (dns_rdata_compare(&rdata1, &rdata2) == 0) { + return (true); + } + } + + return (false); +} + +/* + * Calculate the refresh interval for a keydata zone, per + * RFC5011: MAX(1 hr, + * MIN(15 days, + * 1/2 * OrigTTL, + * 1/2 * RRSigExpirationInterval)) + * or for retries: MAX(1 hr, + * MIN(1 day, + * 1/10 * OrigTTL, + * 1/10 * RRSigExpirationInterval)) + */ +static isc_stdtime_t +refresh_time(dns_keyfetch_t *kfetch, bool retry) { + isc_result_t result; + uint32_t t; + dns_rdataset_t *rdset; + dns_rdata_t sigrr = DNS_RDATA_INIT; + dns_rdata_sig_t sig; + isc_stdtime_t now; + + isc_stdtime_get(&now); + + if (dns_rdataset_isassociated(&kfetch->dnskeysigset)) { + rdset = &kfetch->dnskeysigset; + } else { + return (now + dns_zone_mkey_hour); + } + + result = dns_rdataset_first(rdset); + if (result != ISC_R_SUCCESS) { + return (now + dns_zone_mkey_hour); + } + + dns_rdataset_current(rdset, &sigrr); + result = dns_rdata_tostruct(&sigrr, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (!retry) { + t = sig.originalttl / 2; + + if (isc_serial_gt(sig.timeexpire, now)) { + uint32_t exp = (sig.timeexpire - now) / 2; + if (t > exp) { + t = exp; + } + } + + if (t > (15 * dns_zone_mkey_day)) { + t = (15 * dns_zone_mkey_day); + } + + if (t < dns_zone_mkey_hour) { + t = dns_zone_mkey_hour; + } + } else { + t = sig.originalttl / 10; + + if (isc_serial_gt(sig.timeexpire, now)) { + uint32_t exp = (sig.timeexpire - now) / 10; + if (t > exp) { + t = exp; + } + } + + if (t > dns_zone_mkey_day) { + t = dns_zone_mkey_day; + } + + if (t < dns_zone_mkey_hour) { + t = dns_zone_mkey_hour; + } + } + + return (now + t); +} + +/* + * This routine is called when no changes are needed in a KEYDATA + * record except to simply update the refresh timer. Caller should + * hold zone lock. + */ +static isc_result_t +minimal_update(dns_keyfetch_t *kfetch, dns_dbversion_t *ver, dns_diff_t *diff) { + isc_result_t result; + isc_buffer_t keyb; + unsigned char key_buf[4096]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t keydata; + dns_name_t *name; + dns_zone_t *zone = kfetch->zone; + isc_stdtime_t now; + + name = dns_fixedname_name(&kfetch->name); + isc_stdtime_get(&now); + + for (result = dns_rdataset_first(&kfetch->keydataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&kfetch->keydataset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(&kfetch->keydataset, &rdata); + + /* Delete old version */ + CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_DEL, name, + 0, &rdata)); + + /* Update refresh timer */ + result = dns_rdata_tostruct(&rdata, &keydata, NULL); + if (result == ISC_R_UNEXPECTEDEND) { + continue; + } + if (result != ISC_R_SUCCESS) { + goto failure; + } + keydata.refresh = refresh_time(kfetch, true); + set_refreshkeytimer(zone, &keydata, now, false); + + dns_rdata_reset(&rdata); + isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); + CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass, + dns_rdatatype_keydata, &keydata, + &keyb)); + + /* Insert updated version */ + CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_ADD, name, + 0, &rdata)); + } + result = ISC_R_SUCCESS; +failure: + return (result); +} + +/* + * Verify that DNSKEY set is signed by the key specified in 'keydata'. + */ +static bool +revocable(dns_keyfetch_t *kfetch, dns_rdata_keydata_t *keydata) { + isc_result_t result; + dns_name_t *keyname; + isc_mem_t *mctx; + dns_rdata_t sigrr = DNS_RDATA_INIT; + dns_rdata_t rr = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + dns_rdata_dnskey_t dnskey; + dst_key_t *dstkey = NULL; + unsigned char key_buf[4096]; + isc_buffer_t keyb; + bool answer = false; + + REQUIRE(kfetch != NULL && keydata != NULL); + REQUIRE(dns_rdataset_isassociated(&kfetch->dnskeysigset)); + + keyname = dns_fixedname_name(&kfetch->name); + mctx = kfetch->zone->view->mctx; + + /* Generate a key from keydata */ + isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); + dns_keydata_todnskey(keydata, &dnskey, NULL); + dns_rdata_fromstruct(&rr, keydata->common.rdclass, dns_rdatatype_dnskey, + &dnskey, &keyb); + result = dns_dnssec_keyfromrdata(keyname, &rr, mctx, &dstkey); + if (result != ISC_R_SUCCESS) { + return (false); + } + + /* See if that key generated any of the signatures */ + for (result = dns_rdataset_first(&kfetch->dnskeysigset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&kfetch->dnskeysigset)) + { + dns_fixedname_t fixed; + dns_fixedname_init(&fixed); + + dns_rdata_reset(&sigrr); + dns_rdataset_current(&kfetch->dnskeysigset, &sigrr); + result = dns_rdata_tostruct(&sigrr, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (dst_key_alg(dstkey) == sig.algorithm && + dst_key_rid(dstkey) == sig.keyid) + { + result = dns_dnssec_verify( + keyname, &kfetch->dnskeyset, dstkey, false, 0, + mctx, &sigrr, dns_fixedname_name(&fixed)); + + dnssec_log(kfetch->zone, ISC_LOG_DEBUG(3), + "Confirm revoked DNSKEY is self-signed: %s", + isc_result_totext(result)); + + if (result == ISC_R_SUCCESS) { + answer = true; + break; + } + } + } + + dst_key_free(&dstkey); + return (answer); +} + +/* + * A DNSKEY set has been fetched from the zone apex of a zone whose trust + * anchors are being managed; scan the keyset, and update the key zone and the + * local trust anchors according to RFC5011. + */ +static void +keyfetch_done(isc_task_t *task, isc_event_t *event) { + isc_result_t result, eresult; + dns_fetchevent_t *devent; + dns_keyfetch_t *kfetch; + dns_zone_t *zone; + isc_mem_t *mctx = NULL; + dns_keytable_t *secroots = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + bool alldone = false; + bool commit = false; + dns_name_t *keyname = NULL; + dns_rdata_t sigrr = DNS_RDATA_INIT; + dns_rdata_t dnskeyrr = DNS_RDATA_INIT; + dns_rdata_t keydatarr = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + dns_rdata_dnskey_t dnskey; + dns_rdata_keydata_t keydata; + bool initializing; + char namebuf[DNS_NAME_FORMATSIZE]; + unsigned char key_buf[4096]; + isc_buffer_t keyb; + dst_key_t *dstkey = NULL; + isc_stdtime_t now; + int pending = 0; + bool secure = false, initial = false; + bool free_needed; + dns_keynode_t *keynode = NULL; + dns_rdataset_t *dnskeys = NULL, *dnskeysigs = NULL; + dns_rdataset_t *keydataset = NULL, dsset; + + UNUSED(task); + INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE); + INSIST(event->ev_arg != NULL); + + kfetch = event->ev_arg; + zone = kfetch->zone; + mctx = kfetch->mctx; + keyname = dns_fixedname_name(&kfetch->name); + dnskeys = &kfetch->dnskeyset; + dnskeysigs = &kfetch->dnskeysigset; + keydataset = &kfetch->keydataset; + + devent = (dns_fetchevent_t *)event; + eresult = devent->result; + + /* Free resources which are not of interest */ + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + isc_event_free(&event); + dns_resolver_destroyfetch(&kfetch->fetch); + + LOCK_ZONE(zone); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || zone->view == NULL) { + goto cleanup; + } + + isc_stdtime_get(&now); + dns_name_format(keyname, namebuf, sizeof(namebuf)); + + result = dns_view_getsecroots(zone->view, &secroots); + INSIST(result == ISC_R_SUCCESS); + + dns_diff_init(mctx, &diff); + + CHECK(dns_db_newversion(kfetch->db, &ver)); + + zone->refreshkeycount--; + alldone = (zone->refreshkeycount == 0); + + if (alldone) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING); + } + + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Returned from key fetch in keyfetch_done() for '%s': %s", + namebuf, isc_result_totext(eresult)); + + /* Fetch failed */ + if (eresult != ISC_R_SUCCESS || !dns_rdataset_isassociated(dnskeys)) { + dnssec_log(zone, ISC_LOG_WARNING, + "Unable to fetch DNSKEY set '%s': %s", namebuf, + isc_result_totext(eresult)); + CHECK(minimal_update(kfetch, ver, &diff)); + goto done; + } + + /* No RRSIGs found */ + if (!dns_rdataset_isassociated(dnskeysigs)) { + dnssec_log(zone, ISC_LOG_WARNING, + "No DNSKEY RRSIGs found for '%s': %s", namebuf, + isc_result_totext(eresult)); + CHECK(minimal_update(kfetch, ver, &diff)); + goto done; + } + + /* + * Clear any cached trust level, as we need to run validation + * over again; trusted keys might have changed. + */ + dnskeys->trust = dnskeysigs->trust = dns_trust_none; + + /* Look up the trust anchor */ + result = dns_keytable_find(secroots, keyname, &keynode); + if (result != ISC_R_SUCCESS) { + goto anchors_done; + } + + /* + * If the keynode has a DS trust anchor, use it for verification. + */ + dns_rdataset_init(&dsset); + if (dns_keynode_dsset(keynode, &dsset)) { + for (result = dns_rdataset_first(dnskeysigs); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(dnskeysigs)) + { + isc_result_t tresult; + dns_rdata_t keyrdata = DNS_RDATA_INIT; + + dns_rdata_reset(&sigrr); + dns_rdataset_current(dnskeysigs, &sigrr); + result = dns_rdata_tostruct(&sigrr, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + for (tresult = dns_rdataset_first(&dsset); + tresult == ISC_R_SUCCESS; + tresult = dns_rdataset_next(&dsset)) + { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_ds_t ds; + + dns_rdata_reset(&dsrdata); + dns_rdataset_current(&dsset, &dsrdata); + tresult = dns_rdata_tostruct(&dsrdata, &ds, + NULL); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + + if (ds.key_tag != sig.keyid || + ds.algorithm != sig.algorithm) + { + continue; + } + + result = dns_dnssec_matchdskey( + keyname, &dsrdata, dnskeys, &keyrdata); + if (result == ISC_R_SUCCESS) { + break; + } + } + + if (tresult == ISC_R_NOMORE) { + continue; + } + + result = dns_dnssec_keyfromrdata(keyname, &keyrdata, + mctx, &dstkey); + if (result != ISC_R_SUCCESS) { + continue; + } + + result = dns_dnssec_verify(keyname, dnskeys, dstkey, + false, 0, mctx, &sigrr, + NULL); + dst_key_free(&dstkey); + + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Verifying DNSKEY set for zone " + "'%s' using DS %d/%d: %s", + namebuf, sig.keyid, sig.algorithm, + isc_result_totext(result)); + + if (result == ISC_R_SUCCESS) { + dnskeys->trust = dns_trust_secure; + dnskeysigs->trust = dns_trust_secure; + initial = dns_keynode_initial(keynode); + dns_keynode_trust(keynode); + secure = true; + break; + } + } + dns_rdataset_disassociate(&dsset); + } + +anchors_done: + if (keynode != NULL) { + dns_keytable_detachkeynode(secroots, &keynode); + } + + /* + * If we were not able to verify the answer using the current + * trusted keys then all we can do is look at any revoked keys. + */ + if (!secure) { + dnssec_log(zone, ISC_LOG_INFO, + "DNSKEY set for zone '%s' could not be verified " + "with current keys", + namebuf); + } + + /* + * First scan keydataset to find keys that are not in dnskeyset + * - Missing keys which are not scheduled for removal, + * log a warning + * - Missing keys which are scheduled for removal and + * the remove hold-down timer has completed should + * be removed from the key zone + * - Missing keys whose acceptance timers have not yet + * completed, log a warning and reset the acceptance + * timer to 30 days in the future + * - All keys not being removed have their refresh timers + * updated + */ + initializing = true; + for (result = dns_rdataset_first(keydataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(keydataset)) + { + dns_keytag_t keytag; + + dns_rdata_reset(&keydatarr); + dns_rdataset_current(keydataset, &keydatarr); + result = dns_rdata_tostruct(&keydatarr, &keydata, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_keydata_todnskey(&keydata, &dnskey, NULL); + result = compute_tag(keyname, &dnskey, mctx, &keytag); + if (result != ISC_R_SUCCESS) { + /* + * Skip if we cannot compute the key tag. + * This may happen if the algorithm is unsupported + */ + dns_zone_log(zone, ISC_LOG_ERROR, + "Cannot compute tag for key in zone %s: " + "%s " + "(skipping)", + namebuf, isc_result_totext(result)); + continue; + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * If any keydata record has a nonzero add holddown, then + * there was a pre-existing trust anchor for this domain; + * that means we are *not* initializing it and shouldn't + * automatically trust all the keys we find at the zone apex. + */ + initializing = initializing && (keydata.addhd == 0); + + if (!matchkey(dnskeys, &keydatarr)) { + bool deletekey = false; + + if (!secure) { + if (keydata.removehd != 0 && + keydata.removehd <= now) + { + deletekey = true; + } + } else if (keydata.addhd == 0) { + deletekey = true; + } else if (keydata.addhd > now) { + dnssec_log(zone, ISC_LOG_INFO, + "Pending key %d for zone %s " + "unexpectedly missing from DNSKEY " + "RRset: restarting 30-day " + "acceptance timer", + keytag, namebuf); + if (keydata.addhd < now + dns_zone_mkey_month) { + keydata.addhd = now + + dns_zone_mkey_month; + } + keydata.refresh = refresh_time(kfetch, false); + } else if (keydata.removehd == 0) { + dnssec_log(zone, ISC_LOG_INFO, + "Active key %d for zone %s " + "unexpectedly missing from DNSKEY " + "RRset", + keytag, namebuf); + keydata.refresh = now + dns_zone_mkey_hour; + } else if (keydata.removehd <= now) { + deletekey = true; + dnssec_log( + zone, ISC_LOG_INFO, + "Revoked key %d for zone %s no longer " + "present in DNSKEY RRset: deleting " + "from managed keys database", + keytag, namebuf); + } else { + keydata.refresh = refresh_time(kfetch, false); + } + + if (secure || deletekey) { + /* Delete old version */ + CHECK(update_one_rr(kfetch->db, ver, &diff, + DNS_DIFFOP_DEL, keyname, 0, + &keydatarr)); + } + + if (!secure || deletekey) { + continue; + } + + dns_rdata_reset(&keydatarr); + isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); + dns_rdata_fromstruct(&keydatarr, zone->rdclass, + dns_rdatatype_keydata, &keydata, + &keyb); + + /* Insert updated version */ + CHECK(update_one_rr(kfetch->db, ver, &diff, + DNS_DIFFOP_ADD, keyname, 0, + &keydatarr)); + + set_refreshkeytimer(zone, &keydata, now, false); + } + } + + /* + * Next scan dnskeyset: + * - If new keys are found (i.e., lacking a match in keydataset) + * add them to the key zone and set the acceptance timer + * to 30 days in the future (or to immediately if we've + * determined that we're initializing the zone for the + * first time) + * - Previously-known keys that have been revoked + * must be scheduled for removal from the key zone (or, + * if they hadn't been accepted as trust anchors yet + * anyway, removed at once) + * - Previously-known unrevoked keys whose acceptance timers + * have completed are promoted to trust anchors + * - All keys not being removed have their refresh + * timers updated + */ + for (result = dns_rdataset_first(dnskeys); result == ISC_R_SUCCESS; + result = dns_rdataset_next(dnskeys)) + { + bool revoked = false; + bool newkey = false; + bool updatekey = false; + bool deletekey = false; + bool trustkey = false; + dns_keytag_t keytag; + + dns_rdata_reset(&dnskeyrr); + dns_rdataset_current(dnskeys, &dnskeyrr); + result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Skip ZSK's */ + if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) { + continue; + } + + result = compute_tag(keyname, &dnskey, mctx, &keytag); + if (result != ISC_R_SUCCESS) { + /* + * Skip if we cannot compute the key tag. + * This may happen if the algorithm is unsupported + */ + dns_zone_log(zone, ISC_LOG_ERROR, + "Cannot compute tag for key in zone %s: " + "%s " + "(skipping)", + namebuf, isc_result_totext(result)); + continue; + } + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + revoked = ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0); + + if (matchkey(keydataset, &dnskeyrr)) { + dns_rdata_reset(&keydatarr); + dns_rdataset_current(keydataset, &keydatarr); + result = dns_rdata_tostruct(&keydatarr, &keydata, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (revoked && revocable(kfetch, &keydata)) { + if (keydata.addhd > now) { + /* + * Key wasn't trusted yet, and now + * it's been revoked? Just remove it + */ + deletekey = true; + dnssec_log(zone, ISC_LOG_INFO, + "Pending key %d for " + "zone %s is now revoked: " + "deleting from the " + "managed keys database", + keytag, namebuf); + } else if (keydata.removehd == 0) { + /* + * Remove key from secroots. + */ + dns_view_untrust(zone->view, keyname, + &dnskey); + + /* If initializing, delete now */ + if (keydata.addhd == 0) { + deletekey = true; + } else { + keydata.removehd = + now + + dns_zone_mkey_month; + keydata.flags |= + DNS_KEYFLAG_REVOKE; + } + + dnssec_log(zone, ISC_LOG_INFO, + "Trusted key %d for " + "zone %s is now revoked", + keytag, namebuf); + } else if (keydata.removehd < now) { + /* Scheduled for removal */ + deletekey = true; + + dnssec_log(zone, ISC_LOG_INFO, + "Revoked key %d for " + "zone %s removal timer " + "complete: deleting from " + "the managed keys database", + keytag, namebuf); + } + } else if (revoked && keydata.removehd == 0) { + dnssec_log(zone, ISC_LOG_WARNING, + "Active key %d for zone " + "%s is revoked but " + "did not self-sign; " + "ignoring", + keytag, namebuf); + continue; + } else if (secure) { + if (keydata.removehd != 0) { + /* + * Key isn't revoked--but it + * seems it used to be. + * Remove it now and add it + * back as if it were a fresh key, + * with a 30-day acceptance timer. + */ + deletekey = true; + newkey = true; + keydata.removehd = 0; + keydata.addhd = now + + dns_zone_mkey_month; + + dnssec_log(zone, ISC_LOG_INFO, + "Revoked key %d for " + "zone %s has returned: " + "starting 30-day " + "acceptance timer", + keytag, namebuf); + } else if (keydata.addhd > now) { + pending++; + } else if (keydata.addhd == 0) { + keydata.addhd = now; + } + + if (keydata.addhd <= now) { + trustkey = true; + dnssec_log(zone, ISC_LOG_INFO, + "Key %d for zone %s " + "is now trusted (%s)", + keytag, namebuf, + initial ? "initializing key " + "verified" + : "acceptance timer " + "complete"); + } + } else if (keydata.addhd > now) { + /* + * Not secure, and key is pending: + * reset the acceptance timer + */ + pending++; + keydata.addhd = now + dns_zone_mkey_month; + dnssec_log(zone, ISC_LOG_INFO, + "Pending key %d " + "for zone %s was " + "not validated: restarting " + "30-day acceptance timer", + keytag, namebuf); + } + + if (!deletekey && !newkey) { + updatekey = true; + } + } else if (secure) { + /* + * Key wasn't in the key zone but it's + * revoked now anyway, so just skip it + */ + if (revoked) { + continue; + } + + /* Key wasn't in the key zone: add it */ + newkey = true; + + if (initializing) { + dnssec_log(zone, ISC_LOG_WARNING, + "Initializing automatic trust " + "anchor management for zone '%s'; " + "DNSKEY ID %d is now trusted, " + "waiving the normal 30-day " + "waiting period.", + namebuf, keytag); + trustkey = true; + } else { + dnssec_log(zone, ISC_LOG_INFO, + "New key %d observed " + "for zone '%s': " + "starting 30-day " + "acceptance timer", + keytag, namebuf); + } + } else { + /* + * No previously known key, and the key is not + * secure, so skip it. + */ + continue; + } + + /* Delete old version */ + if (deletekey || !newkey) { + CHECK(update_one_rr(kfetch->db, ver, &diff, + DNS_DIFFOP_DEL, keyname, 0, + &keydatarr)); + } + + if (updatekey) { + /* Set refresh timer */ + keydata.refresh = refresh_time(kfetch, false); + dns_rdata_reset(&keydatarr); + isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); + dns_rdata_fromstruct(&keydatarr, zone->rdclass, + dns_rdatatype_keydata, &keydata, + &keyb); + + /* Insert updated version */ + CHECK(update_one_rr(kfetch->db, ver, &diff, + DNS_DIFFOP_ADD, keyname, 0, + &keydatarr)); + } else if (newkey) { + /* Convert DNSKEY to KEYDATA */ + result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_keydata_fromdnskey(&keydata, &dnskey, 0, 0, 0, + NULL); + keydata.addhd = initializing + ? now + : now + dns_zone_mkey_month; + keydata.refresh = refresh_time(kfetch, false); + dns_rdata_reset(&keydatarr); + isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); + dns_rdata_fromstruct(&keydatarr, zone->rdclass, + dns_rdatatype_keydata, &keydata, + &keyb); + + /* Insert into key zone */ + CHECK(update_one_rr(kfetch->db, ver, &diff, + DNS_DIFFOP_ADD, keyname, 0, + &keydatarr)); + } + + if (trustkey) { + /* Trust this key. */ + result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + trust_key(zone, keyname, &dnskey, false); + } + + if (secure && !deletekey) { + INSIST(newkey || updatekey); + set_refreshkeytimer(zone, &keydata, now, false); + } + } + + /* + * RFC5011 says, "A trust point that has all of its trust anchors + * revoked is considered deleted and is treated as if the trust + * point was never configured." But if someone revoked their + * active key before the standby was trusted, that would mean the + * zone would suddenly be nonsecured. We avoid this by checking to + * see if there's pending keydata. If so, we put a null key in + * the security roots; then all queries to the zone will fail. + */ + if (pending != 0) { + fail_secure(zone, keyname); + } + +done: + if (!ISC_LIST_EMPTY(diff.tuples)) { + /* Write changes to journal file. */ + CHECK(update_soa_serial(zone, kfetch->db, ver, &diff, mctx, + zone->updatemethod)); + CHECK(zone_journal(zone, &diff, NULL, "keyfetch_done")); + commit = true; + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + zone_needdump(zone, 30); + } else if (result == ISC_R_NOMORE) { + /* + * If "updatekey" was true for all keys found in the DNSKEY + * response and the previous update of those keys happened + * during the same second (only possible if a key refresh was + * externally triggered), it may happen that all relevant + * update_one_rr() calls will return ISC_R_SUCCESS, but + * diff.tuples will remain empty. Reset result to + * ISC_R_SUCCESS to prevent a bogus warning from being logged. + */ + result = ISC_R_SUCCESS; + } + +failure: + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "error during managed-keys processing (%s): " + "DNSSEC validation may be at risk", + isc_result_totext(result)); + } + dns_diff_clear(&diff); + if (ver != NULL) { + dns_db_closeversion(kfetch->db, &ver, commit); + } + +cleanup: + dns_db_detach(&kfetch->db); + + /* The zone must be managed */ + INSIST(kfetch->zone->task != NULL); + isc_refcount_decrement(&zone->irefs); + + if (dns_rdataset_isassociated(keydataset)) { + dns_rdataset_disassociate(keydataset); + } + if (dns_rdataset_isassociated(dnskeys)) { + dns_rdataset_disassociate(dnskeys); + } + if (dns_rdataset_isassociated(dnskeysigs)) { + dns_rdataset_disassociate(dnskeysigs); + } + + dns_name_free(keyname, mctx); + isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(dns_keyfetch_t)); + + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + + free_needed = exit_check(zone); + UNLOCK_ZONE(zone); + + if (free_needed) { + zone_free(zone); + } + + INSIST(ver == NULL); +} + +static void +retry_keyfetch(dns_keyfetch_t *kfetch, dns_name_t *kname) { + isc_time_t timenow, timethen; + dns_zone_t *zone = kfetch->zone; + bool free_needed; + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(kname, namebuf, sizeof(namebuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "Failed to create fetch for %s DNSKEY update", namebuf); + + /* + * Error during a key fetch; cancel and retry in an hour. + */ + LOCK_ZONE(zone); + zone->refreshkeycount--; + isc_refcount_decrement(&zone->irefs); + dns_db_detach(&kfetch->db); + dns_rdataset_disassociate(&kfetch->keydataset); + dns_name_free(kname, zone->mctx); + isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(*kfetch)); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + /* Don't really retry if we are exiting */ + char timebuf[80]; + + TIME_NOW(&timenow); + DNS_ZONE_TIME_ADD(&timenow, dns_zone_mkey_hour, &timethen); + zone->refreshkeytime = timethen; + zone_settimer(zone, &timenow); + + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dnssec_log(zone, ISC_LOG_DEBUG(1), "retry key refresh: %s", + timebuf); + } + + free_needed = exit_check(zone); + UNLOCK_ZONE(zone); + + if (free_needed) { + zone_free(zone); + } +} + +static void +do_keyfetch(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_keyfetch_t *kfetch = (dns_keyfetch_t *)event->ev_arg; + dns_name_t *kname = dns_fixedname_name(&kfetch->name); + dns_zone_t *zone = kfetch->zone; + + UNUSED(task); + + isc_event_free(&event); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + retry_keyfetch(kfetch, kname); + return; + } + + /* + * Use of DNS_FETCHOPT_NOCACHED is essential here. If it is not + * set and the cache still holds a non-expired, validated version + * of the RRset being queried for by the time the response is + * received, the cached RRset will be passed to keyfetch_done() + * instead of the one received in the response as the latter will + * have a lower trust level due to not being validated until + * keyfetch_done() is called. + */ + result = dns_resolver_createfetch( + zone->view->resolver, kname, dns_rdatatype_dnskey, NULL, NULL, + NULL, NULL, 0, + DNS_FETCHOPT_NOVALIDATE | DNS_FETCHOPT_UNSHARED | + DNS_FETCHOPT_NOCACHED, + 0, NULL, zone->task, keyfetch_done, kfetch, &kfetch->dnskeyset, + &kfetch->dnskeysigset, &kfetch->fetch); + + if (result != ISC_R_SUCCESS) { + retry_keyfetch(kfetch, kname); + } +} + +/* + * Refresh the data in the key zone. Initiate a fetch to look up + * DNSKEY records at the trust anchor name. + */ +static void +zone_refreshkeys(dns_zone_t *zone) { + const char me[] = "zone_refreshkeys"; + isc_result_t result; + dns_rriterator_t rrit; + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t kd; + isc_stdtime_t now; + bool commit = false; + bool fetching = false; + bool timerset = false; + + ENTER; + REQUIRE(zone->db != NULL); + + isc_stdtime_get(&now); + + LOCK_ZONE(zone); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + isc_time_settoepoch(&zone->refreshkeytime); + UNLOCK_ZONE(zone); + return; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + dns_db_attach(zone->db, &db); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + dns_diff_init(zone->mctx, &diff); + + CHECK(dns_db_newversion(db, &ver)); + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESHING); + + dns_rriterator_init(&rrit, db, ver, 0); + for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS; + result = dns_rriterator_nextrrset(&rrit)) + { + isc_stdtime_t timer = 0xffffffff; + dns_name_t *name = NULL, *kname = NULL; + dns_rdataset_t *kdset = NULL; + uint32_t ttl; + + dns_rriterator_current(&rrit, &name, &ttl, &kdset, NULL); + if (kdset == NULL || kdset->type != dns_rdatatype_keydata || + !dns_rdataset_isassociated(kdset)) + { + continue; + } + + /* + * Scan the stored keys looking for ones that need + * removal or refreshing + */ + for (result = dns_rdataset_first(kdset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(kdset)) + { + dns_rdata_reset(&rdata); + dns_rdataset_current(kdset, &rdata); + result = dns_rdata_tostruct(&rdata, &kd, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Removal timer expired? */ + if (kd.removehd != 0 && kd.removehd < now) { + dns_rriterator_pause(&rrit); + CHECK(update_one_rr(db, ver, &diff, + DNS_DIFFOP_DEL, name, ttl, + &rdata)); + continue; + } + + /* Acceptance timer expired? */ + if (kd.addhd <= now) { + timer = kd.addhd; + } + + /* Or do we just need to refresh the keyset? */ + if (timer > kd.refresh) { + timer = kd.refresh; + } + + dns_rriterator_pause(&rrit); + set_refreshkeytimer(zone, &kd, now, false); + timerset = true; + } + + if (timer > now) { + continue; + } + + dns_rriterator_pause(&rrit); + +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) { +#endif /* ifdef ENABLE_AFL */ + dns_keyfetch_t *kfetch = NULL; + isc_event_t *e; + + kfetch = isc_mem_get(zone->mctx, + sizeof(dns_keyfetch_t)); + *kfetch = (dns_keyfetch_t){ .zone = zone }; + isc_mem_attach(zone->mctx, &kfetch->mctx); + + zone->refreshkeycount++; + isc_refcount_increment0(&zone->irefs); + kname = dns_fixedname_initname(&kfetch->name); + dns_name_dup(name, zone->mctx, kname); + dns_rdataset_init(&kfetch->dnskeyset); + dns_rdataset_init(&kfetch->dnskeysigset); + dns_rdataset_init(&kfetch->keydataset); + dns_rdataset_clone(kdset, &kfetch->keydataset); + dns_db_attach(db, &kfetch->db); + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(kname, namebuf, + sizeof(namebuf)); + dnssec_log(zone, ISC_LOG_DEBUG(3), + "Creating key fetch in " + "zone_refreshkeys() for '%s'", + namebuf); + } + + e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE, + do_keyfetch, kfetch, + sizeof(isc_event_t)); + isc_task_send(zone->task, &e); + fetching = true; +#ifdef ENABLE_AFL + } +#endif /* ifdef ENABLE_AFL */ + } + if (!ISC_LIST_EMPTY(diff.tuples)) { + CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx, + zone->updatemethod)); + CHECK(zone_journal(zone, &diff, NULL, "zone_refreshkeys")); + commit = true; + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + zone_needdump(zone, 30); + } + +failure: + if (!timerset) { + isc_time_settoepoch(&zone->refreshkeytime); + } + + if (!fetching) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING); + } + + dns_diff_clear(&diff); + if (ver != NULL) { + dns_rriterator_destroy(&rrit); + dns_db_closeversion(db, &ver, commit); + } + dns_db_detach(&db); + + UNLOCK_ZONE(zone); + + INSIST(ver == NULL); +} + +static void +zone_maintenance(dns_zone_t *zone) { + const char me[] = "zone_maintenance"; + isc_time_t now; + isc_result_t result; + bool load_pending, exiting, dumping, viewok, notify; + bool refreshkeys, sign, resign, rekey, chain, warn_expire; + + REQUIRE(DNS_ZONE_VALID(zone)); + ENTER; + + /* + * Are we pending load/reload, exiting, or unconfigured + * (e.g. because of a syntax failure in the config file)? + * If so, don't attempt maintenance. + */ + LOCK_ZONE(zone); + load_pending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING); + exiting = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING); + viewok = (zone->view != NULL && zone->view->adb != NULL); + UNLOCK_ZONE(zone); + + if (load_pending || exiting || !viewok) { + return; + } + + TIME_NOW(&now); + + /* + * Expire check. + */ + switch (zone->type) { + case dns_zone_redirect: + if (zone->primaries == NULL) { + break; + } + FALLTHROUGH; + case dns_zone_secondary: + case dns_zone_mirror: + case dns_zone_stub: + LOCK_ZONE(zone); + if (isc_time_compare(&now, &zone->expiretime) >= 0 && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + zone_expire(zone); + zone->refreshtime = now; + } + UNLOCK_ZONE(zone); + break; + default: + break; + } + + /* + * Up to date check. + */ + switch (zone->type) { + case dns_zone_redirect: + if (zone->primaries == NULL) { + break; + } + FALLTHROUGH; + case dns_zone_secondary: + case dns_zone_mirror: + case dns_zone_stub: + LOCK_ZONE(zone); + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH) && + isc_time_compare(&now, &zone->refreshtime) >= 0) + { + zone_refresh(zone); + } + UNLOCK_ZONE(zone); + break; + default: + break; + } + + /* + * Secondaries send notifies before backing up to disk, + * primaries after. + */ + LOCK_ZONE(zone); + notify = (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror) && + (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) && + isc_time_compare(&now, &zone->notifytime) >= 0; + UNLOCK_ZONE(zone); + + if (notify) { + zone_notify(zone, &now); + } + + /* + * Do we need to consolidate the backing store? + */ + switch (zone->type) { + case dns_zone_primary: + case dns_zone_secondary: + case dns_zone_mirror: + case dns_zone_key: + case dns_zone_redirect: + case dns_zone_stub: + LOCK_ZONE(zone); + if (zone->masterfile != NULL && + isc_time_compare(&now, &zone->dumptime) >= 0 && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP)) + { + dumping = was_dumping(zone); + } else { + dumping = true; + } + UNLOCK_ZONE(zone); + if (!dumping) { + result = zone_dump(zone, true); /* task locked */ + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_WARNING, + "dump failed: %s", + isc_result_totext(result)); + } + } + break; + default: + break; + } + + /* + * Primary/redirect zones send notifies now, if needed + */ + switch (zone->type) { + case dns_zone_primary: + case dns_zone_redirect: + LOCK_ZONE(zone); + notify = (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) && + isc_time_compare(&now, &zone->notifytime) >= 0; + UNLOCK_ZONE(zone); + if (notify) { + zone_notify(zone, &now); + } + default: + break; + } + + /* + * Do we need to refresh keys? + */ + switch (zone->type) { + case dns_zone_key: + LOCK_ZONE(zone); + refreshkeys = isc_time_compare(&now, &zone->refreshkeytime) >= + 0 && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING); + UNLOCK_ZONE(zone); + if (refreshkeys) { + zone_refreshkeys(zone); + } + break; + case dns_zone_primary: + LOCK_ZONE(zone); + rekey = (!isc_time_isepoch(&zone->refreshkeytime) && + isc_time_compare(&now, &zone->refreshkeytime) >= 0 && + zone->rss_event == NULL); + UNLOCK_ZONE(zone); + if (rekey) { + zone_rekey(zone); + } + default: + break; + } + + switch (zone->type) { + case dns_zone_primary: + case dns_zone_redirect: + case dns_zone_secondary: + /* + * Do we need to sign/resign some RRsets? + */ + LOCK_ZONE(zone); + if (zone->rss_event != NULL) { + UNLOCK_ZONE(zone); + break; + } + sign = !isc_time_isepoch(&zone->signingtime) && + isc_time_compare(&now, &zone->signingtime) >= 0; + resign = !isc_time_isepoch(&zone->resigntime) && + isc_time_compare(&now, &zone->resigntime) >= 0; + chain = !isc_time_isepoch(&zone->nsec3chaintime) && + isc_time_compare(&now, &zone->nsec3chaintime) >= 0; + warn_expire = !isc_time_isepoch(&zone->keywarntime) && + isc_time_compare(&now, &zone->keywarntime) >= 0; + UNLOCK_ZONE(zone); + + if (sign) { + zone_sign(zone); + } else if (resign) { + zone_resigninc(zone); + } else if (chain) { + zone_nsec3chain(zone); + } + + /* + * Do we need to issue a key expiry warning? + */ + if (warn_expire) { + set_key_expiry_warning(zone, zone->key_expiry, + isc_time_seconds(&now)); + } + break; + + default: + break; + } + LOCK_ZONE(zone); + zone_settimer(zone, &now); + UNLOCK_ZONE(zone); +} + +void +dns_zone_markdirty(dns_zone_t *zone) { + uint32_t serial; + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *secure = NULL; + + /* + * Obtaining a lock on the zone->secure (see zone_send_secureserial) + * could result in a deadlock due to a LOR so we will spin if we + * can't obtain the both locks. + */ +again: + LOCK_ZONE(zone); + if (zone->type == dns_zone_primary) { + if (inline_raw(zone)) { + unsigned int soacount; + secure = zone->secure; + INSIST(secure != zone); + TRYLOCK_ZONE(result, secure); + if (result != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + result = zone_get_from_db( + zone, zone->db, NULL, &soacount, NULL, + &serial, NULL, NULL, NULL, NULL, NULL); + } else { + result = DNS_R_NOTLOADED; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (result == ISC_R_SUCCESS && soacount > 0U) { + zone_send_secureserial(zone, serial); + } + } + + /* XXXMPA make separate call back */ + if (result == ISC_R_SUCCESS) { + set_resigntime(zone); + if (zone->task != NULL) { + isc_time_t now; + TIME_NOW(&now); + zone_settimer(zone, &now); + } + } + } + if (secure != NULL) { + UNLOCK_ZONE(secure); + } + zone_needdump(zone, DNS_DUMP_DELAY); + UNLOCK_ZONE(zone); +} + +void +dns_zone_expire(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone_expire(zone); + UNLOCK_ZONE(zone); +} + +static void +zone_expire(dns_zone_t *zone) { + dns_db_t *db = NULL; + + /* + * 'zone' locked by caller. + */ + + REQUIRE(LOCKED_ZONE(zone)); + + dns_zone_log(zone, ISC_LOG_WARNING, "expired"); + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXPIRED); + zone->refresh = DNS_ZONE_DEFAULTREFRESH; + zone->retry = DNS_ZONE_DEFAULTRETRY; + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + + /* + * An RPZ zone has expired; before unloading it, we must + * first remove it from the RPZ summary database. The + * easiest way to do this is "update" it with an empty + * database so that the update callback synchronizes + * the diff automatically. + */ + if (zone->rpzs != NULL && zone->rpz_num != DNS_RPZ_INVALID_NUM) { + isc_result_t result; + dns_rpz_zone_t *rpz = zone->rpzs->zones[zone->rpz_num]; + + CHECK(dns_db_create(zone->mctx, "rbt", &zone->origin, + dns_dbtype_zone, zone->rdclass, 0, NULL, + &db)); + CHECK(dns_rpz_dbupdate_callback(db, rpz)); + dns_zone_log(zone, ISC_LOG_WARNING, + "response-policy zone expired; " + "policies unloaded"); + } + +failure: + if (db != NULL) { + dns_db_detach(&db); + } + + zone_unload(zone); +} + +static void +zone_refresh(dns_zone_t *zone) { + isc_interval_t i; + uint32_t oldflags; + unsigned int j; + isc_result_t result; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + return; + } + + /* + * Set DNS_ZONEFLG_REFRESH so that there is only one refresh operation + * in progress at a time. + */ + + oldflags = atomic_load(&zone->flags); + if (zone->primariescnt == 0) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOPRIMARIES); + if ((oldflags & DNS_ZONEFLG_NOPRIMARIES) == 0) { + dns_zone_log(zone, ISC_LOG_ERROR, + "cannot refresh: no primaries"); + } + return; + } + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); + if ((oldflags & (DNS_ZONEFLG_REFRESH | DNS_ZONEFLG_LOADING)) != 0) { + return; + } + + /* + * Set the next refresh time as if refresh check has failed. + * Setting this to the retry time will do that. XXXMLG + * If we are successful it will be reset using zone->refresh. + */ + isc_interval_set(&i, zone->retry - isc_random_uniform(zone->retry / 4), + 0); + result = isc_time_nowplusinterval(&zone->refreshtime, &i); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_WARNING, + "isc_time_nowplusinterval() failed: %s", + isc_result_totext(result)); + } + + /* + * When lacking user-specified timer values from the SOA, + * do exponential backoff of the retry time up to a + * maximum of six hours. + */ + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) { + zone->retry = ISC_MIN(zone->retry * 2, 6 * 3600); + } + + zone->curprimary = 0; + for (j = 0; j < zone->primariescnt; j++) { + zone->primariesok[j] = false; + } + /* initiate soa query */ + queue_soa_query(zone); +} + +void +dns_zone_refresh(dns_zone_t *zone) { + LOCK_ZONE(zone); + zone_refresh(zone); + UNLOCK_ZONE(zone); +} + +static isc_result_t +zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump, + bool *fixjournal) { + dns_journal_t *journal = NULL; + unsigned int options; + isc_result_t result; + + if (zone->type == dns_zone_primary && + (inline_secure(zone) || + (zone->update_acl != NULL || zone->ssutable != NULL))) + { + options = DNS_JOURNALOPT_RESIGN; + } else { + options = 0; + } + + result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ, + &journal); + if (result == ISC_R_NOTFOUND) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(3), + "no journal file, but that's OK "); + return (ISC_R_SUCCESS); + } else if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, + "journal open failed: %s", + isc_result_totext(result)); + return (result); + } + + if (dns_journal_empty(journal)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), + "journal empty"); + dns_journal_destroy(&journal); + return (ISC_R_SUCCESS); + } + + result = dns_journal_rollforward(journal, db, options); + switch (result) { + case ISC_R_SUCCESS: + *needdump = true; + FALLTHROUGH; + case DNS_R_UPTODATE: + if (dns_journal_recovered(journal)) { + *fixjournal = true; + dns_zone_logc( + zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "journal rollforward completed successfully " + "using old journal format: %s", + isc_result_totext(result)); + } else { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_DEBUG(1), + "journal rollforward completed " + "successfully: %s", + isc_result_totext(result)); + } + + dns_journal_destroy(&journal); + return (ISC_R_SUCCESS); + case ISC_R_NOTFOUND: + case ISC_R_RANGE: + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, + "journal rollforward failed: journal out of sync " + "with zone"); + dns_journal_destroy(&journal); + return (result); + default: + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, + "journal rollforward failed: %s", + isc_result_totext(result)); + dns_journal_destroy(&journal); + return (result); + } +} + +static void +zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial) { + isc_result_t result; + int32_t journalsize; + dns_dbversion_t *ver = NULL; + uint64_t dbsize; + uint32_t options = 0; + + INSIST(LOCKED_ZONE(zone)); + if (inline_raw(zone)) { + INSIST(LOCKED_ZONE(zone->secure)); + } + + journalsize = zone->journalsize; + if (journalsize == -1) { + journalsize = DNS_JOURNAL_SIZE_MAX; + dns_db_currentversion(db, &ver); + result = dns_db_getsize(db, ver, NULL, &dbsize); + dns_db_closeversion(db, &ver, false); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "zone_journal_compact: " + "could not get zone size: %s", + isc_result_totext(result)); + } else if (dbsize < DNS_JOURNAL_SIZE_MAX / 2) { + journalsize = (int32_t)dbsize * 2; + } + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FIXJOURNAL)) { + options |= DNS_JOURNAL_COMPACTALL; + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FIXJOURNAL); + zone_debuglog(zone, "zone_journal_compact", 1, + "repair full journal"); + } else { + zone_debuglog(zone, "zone_journal_compact", 1, + "target journal size %d", journalsize); + } + result = dns_journal_compact(zone->mctx, zone->journal, serial, options, + journalsize); + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_NOSPACE: + case ISC_R_NOTFOUND: + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_journal_compact: %s", + isc_result_totext(result)); + break; + default: + dns_zone_log(zone, ISC_LOG_ERROR, + "dns_journal_compact failed: %s", + isc_result_totext(result)); + break; + } +} + +isc_result_t +dns_zone_flush(dns_zone_t *zone) { + isc_result_t result = ISC_R_SUCCESS; + bool dumping; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FLUSH); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + zone->masterfile != NULL) + { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); + result = ISC_R_ALREADYRUNNING; + dumping = was_dumping(zone); + } else { + dumping = true; + } + UNLOCK_ZONE(zone); + if (!dumping) { + result = zone_dump(zone, true); /* Unknown task. */ + } + return (result); +} + +isc_result_t +dns_zone_dump(dns_zone_t *zone) { + isc_result_t result = ISC_R_ALREADYRUNNING; + bool dumping; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + dumping = was_dumping(zone); + UNLOCK_ZONE(zone); + if (!dumping) { + result = zone_dump(zone, false); /* Unknown task. */ + } + return (result); +} + +static void +zone_needdump(dns_zone_t *zone, unsigned int delay) { + const char me[] = "zone_needdump"; + isc_time_t dumptime; + isc_time_t now; + + /* + * 'zone' locked by caller + */ + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + ENTER; + + /* + * Do we have a place to dump to and are we loaded? + */ + if (zone->masterfile == NULL || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0) + { + return; + } + + TIME_NOW(&now); + /* add some noise */ + DNS_ZONE_JITTER_ADD(&now, delay, &dumptime); + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDDUMP); + if (isc_time_isepoch(&zone->dumptime) || + isc_time_compare(&zone->dumptime, &dumptime) > 0) + { + zone->dumptime = dumptime; + } + if (zone->task != NULL) { + zone_settimer(zone, &now); + } +} + +static void +dump_done(void *arg, isc_result_t result) { + const char me[] = "dump_done"; + dns_zone_t *zone = arg; + dns_zone_t *secure = NULL; + dns_db_t *db; + dns_dbversion_t *version; + bool again = false; + bool compact = false; + uint32_t serial; + isc_result_t tresult; + + REQUIRE(DNS_ZONE_VALID(zone)); + + ENTER; + + if (result == ISC_R_SUCCESS && zone->journal != NULL) { + /* + * We don't own these, zone->dctx must stay valid. + */ + db = dns_dumpctx_db(zone->dctx); + version = dns_dumpctx_version(zone->dctx); + tresult = dns_db_getsoaserial(db, version, &serial); + + /* + * Handle lock order inversion. + */ + again: + LOCK_ZONE(zone); + if (inline_raw(zone)) { + secure = zone->secure; + INSIST(secure != zone); + TRYLOCK_ZONE(result, secure); + if (result != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + } + + /* + * If there is a secure version of this zone + * use its serial if it is less than ours. + */ + if (tresult == ISC_R_SUCCESS && secure != NULL) { + uint32_t sserial; + isc_result_t mresult; + + ZONEDB_LOCK(&secure->dblock, isc_rwlocktype_read); + if (secure->db != NULL) { + mresult = dns_db_getsoaserial(zone->secure->db, + NULL, &sserial); + if (mresult == ISC_R_SUCCESS && + isc_serial_lt(sserial, serial)) + { + serial = sserial; + } + } + ZONEDB_UNLOCK(&secure->dblock, isc_rwlocktype_read); + } + if (tresult == ISC_R_SUCCESS && zone->xfr == NULL) { + dns_db_t *zdb = NULL; + if (dns_zone_getdb(zone, &zdb) == ISC_R_SUCCESS) { + zone_journal_compact(zone, zdb, serial); + dns_db_detach(&zdb); + } + } else if (tresult == ISC_R_SUCCESS) { + compact = true; + zone->compact_serial = serial; + } + if (secure != NULL) { + UNLOCK_ZONE(secure); + } + UNLOCK_ZONE(zone); + } + + LOCK_ZONE(zone); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING); + if (compact) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN)) { + /* + * If DNS_ZONEFLG_SHUTDOWN is set, all external references to + * the zone are gone, which means it is in the process of being + * cleaned up, so do not reschedule dumping. + * + * Detach from the raw version of the zone in case this + * operation has been deferred in zone_shutdown(). + */ + if (zone->raw != NULL) { + dns_zone_detach(&zone->raw); + } + if (result == ISC_R_SUCCESS) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); + } + } else if (result != ISC_R_SUCCESS && result != ISC_R_CANCELED) { + /* + * Try again in a short while. + */ + zone_needdump(zone, DNS_DUMP_DELAY); + } else if (result == ISC_R_SUCCESS && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); + isc_time_settoepoch(&zone->dumptime); + again = true; + } else if (result == ISC_R_SUCCESS) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); + } + + if (zone->dctx != NULL) { + dns_dumpctx_detach(&zone->dctx); + } + zonemgr_putio(&zone->writeio); + UNLOCK_ZONE(zone); + if (again) { + (void)zone_dump(zone, false); + } + dns_zone_idetach(&zone); +} + +static isc_result_t +zone_dump(dns_zone_t *zone, bool compact) { + const char me[] = "zone_dump"; + isc_result_t result; + dns_dbversion_t *version = NULL; + bool again; + dns_db_t *db = NULL; + char *masterfile = NULL; + dns_masterformat_t masterformat = dns_masterformat_none; + + /* + * 'compact' MUST only be set if we are task locked. + */ + + REQUIRE(DNS_ZONE_VALID(zone)); + ENTER; + +redo: + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + LOCK_ZONE(zone); + if (zone->masterfile != NULL) { + masterfile = isc_mem_strdup(zone->mctx, zone->masterfile); + masterformat = zone->masterformat; + } + UNLOCK_ZONE(zone); + if (db == NULL) { + result = DNS_R_NOTLOADED; + goto fail; + } + if (masterfile == NULL) { + result = DNS_R_NOMASTERFILE; + goto fail; + } + + if (compact && zone->type != dns_zone_stub) { + dns_zone_t *dummy = NULL; + LOCK_ZONE(zone); + zone_iattach(zone, &dummy); + result = zonemgr_getio(zone->zmgr, false, zone->task, + zone_gotwritehandle, zone, + &zone->writeio); + if (result != ISC_R_SUCCESS) { + zone_idetach(&dummy); + } else { + result = DNS_R_CONTINUE; + } + UNLOCK_ZONE(zone); + } else { + const dns_master_style_t *output_style; + + dns_masterrawheader_t rawdata; + dns_db_currentversion(db, &version); + dns_master_initrawheader(&rawdata); + if (inline_secure(zone)) { + get_raw_serial(zone->raw, &rawdata); + } + if (zone->type == dns_zone_key) { + output_style = &dns_master_style_keyzone; + } else { + output_style = &dns_master_style_default; + } + result = dns_master_dump(zone->mctx, db, version, output_style, + masterfile, masterformat, &rawdata); + dns_db_closeversion(db, &version, false); + } +fail: + if (db != NULL) { + dns_db_detach(&db); + } + if (masterfile != NULL) { + isc_mem_free(zone->mctx, masterfile); + } + masterfile = NULL; + + if (result == DNS_R_CONTINUE) { + return (ISC_R_SUCCESS); /* XXXMPA */ + } + + again = false; + LOCK_ZONE(zone); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING); + if (result != ISC_R_SUCCESS) { + /* + * Try again in a short while. + */ + zone_needdump(zone, DNS_DUMP_DELAY); + } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); + isc_time_settoepoch(&zone->dumptime); + again = true; + } else { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); + } + UNLOCK_ZONE(zone); + if (again) { + goto redo; + } + + return (result); +} + +static isc_result_t +dumptostream(dns_zone_t *zone, FILE *fd, const dns_master_style_t *style, + dns_masterformat_t format, const uint32_t rawversion) { + isc_result_t result; + dns_dbversion_t *version = NULL; + dns_db_t *db = NULL; + dns_masterrawheader_t rawdata; + + REQUIRE(DNS_ZONE_VALID(zone)); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + return (DNS_R_NOTLOADED); + } + + dns_db_currentversion(db, &version); + dns_master_initrawheader(&rawdata); + if (rawversion == 0) { + rawdata.flags |= DNS_MASTERRAW_COMPAT; + } else if (inline_secure(zone)) { + get_raw_serial(zone->raw, &rawdata); + } else if (zone->sourceserialset) { + rawdata.flags = DNS_MASTERRAW_SOURCESERIALSET; + rawdata.sourceserial = zone->sourceserial; + } + result = dns_master_dumptostream(zone->mctx, db, version, style, format, + &rawdata, fd); + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); + return (result); +} + +isc_result_t +dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format, + const dns_master_style_t *style, + const uint32_t rawversion) { + return (dumptostream(zone, fd, style, format, rawversion)); +} + +void +dns_zone_unload(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone_unload(zone); + UNLOCK_ZONE(zone); +} + +static void +notify_cancel(dns_zone_t *zone) { + dns_notify_t *notify; + + /* + * 'zone' locked by caller. + */ + + REQUIRE(LOCKED_ZONE(zone)); + + for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL; + notify = ISC_LIST_NEXT(notify, link)) + { + if (notify->find != NULL) { + dns_adb_cancelfind(notify->find); + } + if (notify->request != NULL) { + dns_request_cancel(notify->request); + } + } +} + +static void +checkds_cancel(dns_zone_t *zone) { + dns_checkds_t *checkds; + + /* + * 'zone' locked by caller. + */ + + REQUIRE(LOCKED_ZONE(zone)); + + for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL; + checkds = ISC_LIST_NEXT(checkds, link)) + { + if (checkds->request != NULL) { + dns_request_cancel(checkds->request); + } + } +} + +static void +forward_cancel(dns_zone_t *zone) { + dns_forward_t *forward; + + /* + * 'zone' locked by caller. + */ + + REQUIRE(LOCKED_ZONE(zone)); + + for (forward = ISC_LIST_HEAD(zone->forwards); forward != NULL; + forward = ISC_LIST_NEXT(forward, link)) + { + if (forward->request != NULL) { + dns_request_cancel(forward->request); + } + } +} + +static void +zone_unload(dns_zone_t *zone) { + /* + * 'zone' locked by caller. + */ + + REQUIRE(LOCKED_ZONE(zone)); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) || + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + if (zone->writeio != NULL) { + zonemgr_cancelio(zone->writeio); + } + + if (zone->dctx != NULL) { + dns_dumpctx_cancel(zone->dctx); + } + } + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + zone_detachdb(zone); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADED); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); + + if (zone->type == dns_zone_mirror) { + dns_zone_log(zone, ISC_LOG_INFO, + "mirror zone is no longer in use; " + "reverting to normal recursion"); + } +} + +void +dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(val > 0); + + zone->minrefresh = val; +} + +void +dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(val > 0); + + zone->maxrefresh = val; +} + +void +dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(val > 0); + + zone->minretry = val; +} + +void +dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(val > 0); + + zone->maxretry = val; +} + +uint32_t +dns_zone_getmaxrecords(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->maxrecords); +} + +void +dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->maxrecords = val; +} + +static bool +notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name, + isc_sockaddr_t *addr, dns_tsigkey_t *key, + dns_transport_t *transport) { + dns_notify_t *notify; + dns_zonemgr_t *zmgr; + isc_result_t result; + + for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL; + notify = ISC_LIST_NEXT(notify, link)) + { + if (notify->request != NULL) { + continue; + } + if (name != NULL && dns_name_dynamic(¬ify->ns) && + dns_name_equal(name, ¬ify->ns)) + { + goto requeue; + } + if (addr != NULL && isc_sockaddr_equal(addr, ¬ify->dst) && + notify->key == key && notify->transport == transport) + { + goto requeue; + } + } + return (false); + +requeue: + /* + * If we are enqueued on the startup ratelimiter and this is + * not a startup notify, re-enqueue on the normal notify + * ratelimiter. + */ + if (notify->event != NULL && (flags & DNS_NOTIFY_STARTUP) == 0 && + (notify->flags & DNS_NOTIFY_STARTUP) != 0) + { + zmgr = notify->zone->zmgr; + result = isc_ratelimiter_dequeue(zmgr->startupnotifyrl, + notify->event); + if (result != ISC_R_SUCCESS) { + return (true); + } + + notify->flags &= ~DNS_NOTIFY_STARTUP; + result = isc_ratelimiter_enqueue(notify->zone->zmgr->notifyrl, + notify->zone->task, + ¬ify->event); + if (result != ISC_R_SUCCESS) { + isc_event_free(¬ify->event); + return (false); + } + } + + return (true); +} + +static bool +notify_isself(dns_zone_t *zone, isc_sockaddr_t *dst) { + dns_tsigkey_t *key = NULL; + isc_sockaddr_t src; + isc_sockaddr_t any; + bool isself; + isc_netaddr_t dstaddr; + isc_result_t result; + + if (zone->view == NULL || zone->isself == NULL) { + return (false); + } + + switch (isc_sockaddr_pf(dst)) { + case PF_INET: + src = zone->notifysrc4; + isc_sockaddr_any(&any); + break; + case PF_INET6: + src = zone->notifysrc6; + isc_sockaddr_any6(&any); + break; + default: + return (false); + } + + /* + * When sending from any the kernel will assign a source address + * that matches the destination address. + */ + if (isc_sockaddr_eqaddr(&any, &src)) { + src = *dst; + } + + isc_netaddr_fromsockaddr(&dstaddr, dst); + result = dns_view_getpeertsig(zone->view, &dstaddr, &key); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + return (false); + } + isself = (zone->isself)(zone->view, key, &src, dst, zone->rdclass, + zone->isselfarg); + if (key != NULL) { + dns_tsigkey_detach(&key); + } + return (isself); +} + +static void +notify_destroy(dns_notify_t *notify, bool locked) { + isc_mem_t *mctx; + + REQUIRE(DNS_NOTIFY_VALID(notify)); + + if (notify->zone != NULL) { + if (!locked) { + LOCK_ZONE(notify->zone); + } + REQUIRE(LOCKED_ZONE(notify->zone)); + if (ISC_LINK_LINKED(notify, link)) { + ISC_LIST_UNLINK(notify->zone->notifies, notify, link); + } + if (!locked) { + UNLOCK_ZONE(notify->zone); + } + if (locked) { + zone_idetach(¬ify->zone); + } else { + dns_zone_idetach(¬ify->zone); + } + } + if (notify->find != NULL) { + dns_adb_destroyfind(¬ify->find); + } + if (notify->request != NULL) { + dns_request_destroy(¬ify->request); + } + if (dns_name_dynamic(¬ify->ns)) { + dns_name_free(¬ify->ns, notify->mctx); + } + if (notify->key != NULL) { + dns_tsigkey_detach(¬ify->key); + } + if (notify->transport != NULL) { + dns_transport_detach(¬ify->transport); + } + mctx = notify->mctx; + isc_mem_put(notify->mctx, notify, sizeof(*notify)); + isc_mem_detach(&mctx); +} + +static isc_result_t +notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) { + dns_notify_t *notify; + + REQUIRE(notifyp != NULL && *notifyp == NULL); + + notify = isc_mem_get(mctx, sizeof(*notify)); + *notify = (dns_notify_t){ + .flags = flags, + }; + + isc_mem_attach(mctx, ¬ify->mctx); + isc_sockaddr_any(¬ify->dst); + dns_name_init(¬ify->ns, NULL); + ISC_LINK_INIT(notify, link); + notify->magic = NOTIFY_MAGIC; + *notifyp = notify; + return (ISC_R_SUCCESS); +} + +/* + * XXXAG should check for DNS_ZONEFLG_EXITING + */ +static void +process_adb_event(isc_task_t *task, isc_event_t *ev) { + dns_notify_t *notify; + isc_eventtype_t result; + + UNUSED(task); + + notify = ev->ev_arg; + REQUIRE(DNS_NOTIFY_VALID(notify)); + INSIST(task == notify->zone->task); + result = ev->ev_type; + isc_event_free(&ev); + if (result == DNS_EVENT_ADBMOREADDRESSES) { + dns_adb_destroyfind(¬ify->find); + notify_find_address(notify); + return; + } + if (result == DNS_EVENT_ADBNOMOREADDRESSES) { + LOCK_ZONE(notify->zone); + notify_send(notify); + UNLOCK_ZONE(notify->zone); + } + notify_destroy(notify, false); +} + +static void +notify_find_address(dns_notify_t *notify) { + isc_result_t result; + unsigned int options; + + REQUIRE(DNS_NOTIFY_VALID(notify)); + options = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_INET | DNS_ADBFIND_INET6 | + DNS_ADBFIND_RETURNLAME; + + if (notify->zone->view->adb == NULL) { + goto destroy; + } + + result = dns_adb_createfind( + notify->zone->view->adb, notify->zone->task, process_adb_event, + notify, ¬ify->ns, dns_rootname, 0, options, 0, NULL, + notify->zone->view->dstport, 0, NULL, ¬ify->find); + + /* Something failed? */ + if (result != ISC_R_SUCCESS) { + goto destroy; + } + + /* More addresses pending? */ + if ((notify->find->options & DNS_ADBFIND_WANTEVENT) != 0) { + return; + } + + /* We have as many addresses as we can get. */ + LOCK_ZONE(notify->zone); + notify_send(notify); + UNLOCK_ZONE(notify->zone); + +destroy: + notify_destroy(notify, false); +} + +static isc_result_t +notify_send_queue(dns_notify_t *notify, bool startup) { + isc_event_t *e; + isc_result_t result; + + INSIST(notify->event == NULL); + e = isc_event_allocate(notify->mctx, NULL, DNS_EVENT_NOTIFYSENDTOADDR, + notify_send_toaddr, notify, sizeof(isc_event_t)); + if (startup) { + notify->event = e; + } + e->ev_arg = notify; + e->ev_sender = NULL; + result = isc_ratelimiter_enqueue( + startup ? notify->zone->zmgr->startupnotifyrl + : notify->zone->zmgr->notifyrl, + notify->zone->task, &e); + if (result != ISC_R_SUCCESS) { + isc_event_free(&e); + notify->event = NULL; + } + return (result); +} + +static void +notify_send_toaddr(isc_task_t *task, isc_event_t *event) { + dns_notify_t *notify; + isc_result_t result; + dns_message_t *message = NULL; + isc_netaddr_t dstip; + dns_tsigkey_t *key = NULL; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t src; + unsigned int options, timeout; + bool have_notifysource = false; + + notify = event->ev_arg; + REQUIRE(DNS_NOTIFY_VALID(notify)); + + UNUSED(task); + + LOCK_ZONE(notify->zone); + + notify->event = NULL; + + if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_LOADED) == 0) { + result = ISC_R_CANCELED; + goto cleanup; + } + + if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 || + DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING) || + notify->zone->view->requestmgr == NULL || notify->zone->db == NULL) + { + result = ISC_R_CANCELED; + goto cleanup; + } + + /* + * The raw IPv4 address should also exist. Don't send to the + * mapped form. + */ + if (isc_sockaddr_pf(¬ify->dst) == PF_INET6 && + IN6_IS_ADDR_V4MAPPED(¬ify->dst.type.sin6.sin6_addr)) + { + isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); + notify_log(notify->zone, ISC_LOG_DEBUG(3), + "notify: ignoring IPv6 mapped IPV4 address: %s", + addrbuf); + result = ISC_R_CANCELED; + goto cleanup; + } + + result = notify_createmessage(notify->zone, notify->flags, &message); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); + if (notify->key != NULL) { + /* Transfer ownership of key */ + key = notify->key; + notify->key = NULL; + } else { + isc_netaddr_fromsockaddr(&dstip, ¬ify->dst); + result = dns_view_getpeertsig(notify->zone->view, &dstip, &key); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + notify_log(notify->zone, ISC_LOG_ERROR, + "NOTIFY to %s not sent. " + "Peer TSIG key lookup failure.", + addrbuf); + goto cleanup_message; + } + } + + if (key != NULL) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&key->name, namebuf, sizeof(namebuf)); + notify_log(notify->zone, ISC_LOG_DEBUG(3), + "sending notify to %s : TSIG (%s)", addrbuf, + namebuf); + } else { + notify_log(notify->zone, ISC_LOG_DEBUG(3), + "sending notify to %s", addrbuf); + } + options = 0; + if (notify->zone->view->peers != NULL) { + dns_peer_t *peer = NULL; + bool usetcp = false; + result = dns_peerlist_peerbyaddr(notify->zone->view->peers, + &dstip, &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getnotifysource(peer, &src); + if (result == ISC_R_SUCCESS) { + have_notifysource = true; + } + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + options |= DNS_FETCHOPT_TCP; + } + } + } + switch (isc_sockaddr_pf(¬ify->dst)) { + case PF_INET: + if (!have_notifysource) { + src = notify->zone->notifysrc4; + } + break; + case PF_INET6: + if (!have_notifysource) { + src = notify->zone->notifysrc6; + } + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_key; + } + timeout = 15; + if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_DIALNOTIFY)) { + timeout = 30; + } + result = dns_request_create(notify->zone->view->requestmgr, message, + &src, ¬ify->dst, options, key, + timeout * 3, timeout, 2, notify->zone->task, + notify_done, notify, ¬ify->request); + if (result == ISC_R_SUCCESS) { + if (isc_sockaddr_pf(¬ify->dst) == AF_INET) { + inc_stats(notify->zone, + dns_zonestatscounter_notifyoutv4); + } else { + inc_stats(notify->zone, + dns_zonestatscounter_notifyoutv6); + } + } + +cleanup_key: + if (key != NULL) { + dns_tsigkey_detach(&key); + } +cleanup_message: + dns_message_detach(&message); +cleanup: + UNLOCK_ZONE(notify->zone); + isc_event_free(&event); + if (result != ISC_R_SUCCESS) { + notify_destroy(notify, false); + } +} + +static void +notify_send(dns_notify_t *notify) { + dns_adbaddrinfo_t *ai; + isc_sockaddr_t dst; + isc_result_t result; + dns_notify_t *newnotify = NULL; + unsigned int flags; + bool startup; + + /* + * Zone lock held by caller. + */ + REQUIRE(DNS_NOTIFY_VALID(notify)); + REQUIRE(LOCKED_ZONE(notify->zone)); + + if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING)) { + return; + } + + for (ai = ISC_LIST_HEAD(notify->find->list); ai != NULL; + ai = ISC_LIST_NEXT(ai, publink)) + { + dst = ai->sockaddr; + if (notify_isqueued(notify->zone, notify->flags, NULL, &dst, + NULL, NULL)) + { + continue; + } + if (notify_isself(notify->zone, &dst)) { + continue; + } + newnotify = NULL; + flags = notify->flags & DNS_NOTIFY_NOSOA; + result = notify_create(notify->mctx, flags, &newnotify); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + zone_iattach(notify->zone, &newnotify->zone); + ISC_LIST_APPEND(newnotify->zone->notifies, newnotify, link); + newnotify->dst = dst; + startup = ((notify->flags & DNS_NOTIFY_STARTUP) != 0); + result = notify_send_queue(newnotify, startup); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + newnotify = NULL; + } + +cleanup: + if (newnotify != NULL) { + notify_destroy(newnotify, true); + } +} + +void +dns_zone_notify(dns_zone_t *zone) { + isc_time_t now; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + + TIME_NOW(&now); + zone_settimer(zone, &now); + UNLOCK_ZONE(zone); +} + +static void +zone_notify(dns_zone_t *zone, isc_time_t *now) { + dns_dbnode_t *node = NULL; + dns_db_t *zonedb = NULL; + dns_dbversion_t *version = NULL; + dns_name_t *origin = NULL; + dns_name_t primary; + dns_rdata_ns_t ns; + dns_rdata_soa_t soa; + uint32_t serial; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_t nsrdset; + dns_rdataset_t soardset; + isc_result_t result; + unsigned int i; + isc_sockaddr_t dst; + bool isqueued; + dns_notifytype_t notifytype; + unsigned int flags = 0; + bool loggednotify = false; + bool startup; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + startup = !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY); + notifytype = zone->notifytype; + DNS_ZONE_TIME_ADD(now, zone->notifydelay, &zone->notifytime); + UNLOCK_ZONE(zone); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) + { + return; + } + + if (notifytype == dns_notifytype_no) { + return; + } + + if (notifytype == dns_notifytype_masteronly && + zone->type != dns_zone_primary) + { + return; + } + + origin = &zone->origin; + + /* + * If the zone is dialup we are done as we don't want to send + * the current soa so as to force a refresh query. + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) { + flags |= DNS_NOTIFY_NOSOA; + } + + /* + * Record that this was a notify due to starting up. + */ + if (startup) { + flags |= DNS_NOTIFY_STARTUP; + } + + /* + * Get SOA RRset. + */ + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &zonedb); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (zonedb == NULL) { + return; + } + dns_db_currentversion(zonedb, &version); + result = dns_db_findnode(zonedb, origin, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup1; + } + + dns_rdataset_init(&soardset); + result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa, + dns_rdatatype_none, 0, &soardset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup2; + } + + /* + * Find serial and primary server's name. + */ + dns_name_init(&primary, NULL); + result = dns_rdataset_first(&soardset); + if (result != ISC_R_SUCCESS) { + goto cleanup3; + } + dns_rdataset_current(&soardset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + dns_name_dup(&soa.origin, zone->mctx, &primary); + serial = soa.serial; + dns_rdataset_disassociate(&soardset); + + /* + * Enqueue notify requests for 'also-notify' servers. + */ + LOCK_ZONE(zone); + for (i = 0; i < zone->notifycnt; i++) { + dns_tsigkey_t *key = NULL; + dns_transport_t *transport = NULL; + dns_notify_t *notify = NULL; + dns_view_t *view = dns_zone_getview(zone); + + if ((zone->notifykeynames != NULL) && + (zone->notifykeynames[i] != NULL)) + { + dns_name_t *keyname = zone->notifykeynames[i]; + (void)dns_view_gettsig(view, keyname, &key); + } + + if ((zone->notifytlsnames != NULL) && + (zone->notifytlsnames[i] != NULL)) + { + dns_name_t *tlsname = zone->notifytlsnames[i]; + (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS, + tlsname, &transport); + + dns_zone_logc( + zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, + "got TLS configuration for zone transfer"); + } + + /* TODO: glue the transport to the notify */ + + dst = zone->notify[i]; + if (notify_isqueued(zone, flags, NULL, &dst, key, transport)) { + if (key != NULL) { + dns_tsigkey_detach(&key); + } + if (transport != NULL) { + dns_transport_detach(&transport); + } + continue; + } + + result = notify_create(zone->mctx, flags, ¬ify); + if (result != ISC_R_SUCCESS) { + if (key != NULL) { + dns_tsigkey_detach(&key); + } + if (transport != NULL) { + dns_transport_detach(&transport); + } + continue; + } + + zone_iattach(zone, ¬ify->zone); + notify->dst = dst; + + INSIST(notify->key == NULL); + + if (key != NULL) { + notify->key = key; + key = NULL; + } + + INSIST(notify->transport == NULL); + if (transport != NULL) { + notify->transport = transport; + transport = NULL; + } + + ISC_LIST_APPEND(zone->notifies, notify, link); + result = notify_send_queue(notify, startup); + if (result != ISC_R_SUCCESS) { + notify_destroy(notify, true); + } + if (!loggednotify) { + notify_log(zone, ISC_LOG_INFO, + "sending notifies (serial %u)", serial); + loggednotify = true; + } + } + UNLOCK_ZONE(zone); + + if (notifytype == dns_notifytype_explicit) { + goto cleanup3; + } + + /* + * Process NS RRset to generate notifies. + */ + + dns_rdataset_init(&nsrdset); + result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_ns, + dns_rdatatype_none, 0, &nsrdset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup3; + } + + result = dns_rdataset_first(&nsrdset); + while (result == ISC_R_SUCCESS) { + dns_notify_t *notify = NULL; + + dns_rdataset_current(&nsrdset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + /* + * Don't notify the primary server unless explicitly + * configured to do so. + */ + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOTIFYTOSOA) && + dns_name_compare(&primary, &ns.name) == 0) + { + result = dns_rdataset_next(&nsrdset); + continue; + } + + if (!loggednotify) { + notify_log(zone, ISC_LOG_INFO, + "sending notifies (serial %u)", serial); + loggednotify = true; + } + + LOCK_ZONE(zone); + isqueued = notify_isqueued(zone, flags, &ns.name, NULL, NULL, + NULL); + UNLOCK_ZONE(zone); + if (isqueued) { + result = dns_rdataset_next(&nsrdset); + continue; + } + result = notify_create(zone->mctx, flags, ¬ify); + if (result != ISC_R_SUCCESS) { + continue; + } + dns_zone_iattach(zone, ¬ify->zone); + dns_name_dup(&ns.name, zone->mctx, ¬ify->ns); + LOCK_ZONE(zone); + ISC_LIST_APPEND(zone->notifies, notify, link); + UNLOCK_ZONE(zone); + notify_find_address(notify); + result = dns_rdataset_next(&nsrdset); + } + dns_rdataset_disassociate(&nsrdset); + +cleanup3: + if (dns_name_dynamic(&primary)) { + dns_name_free(&primary, zone->mctx); + } +cleanup2: + dns_db_detachnode(zonedb, &node); +cleanup1: + dns_db_closeversion(zonedb, &version, false); + dns_db_detach(&zonedb); +} + +/*** + *** Private + ***/ +static isc_result_t +create_query(dns_zone_t *zone, dns_rdatatype_t rdtype, dns_name_t *name, + dns_message_t **messagep) { + dns_message_t *message = NULL; + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_result_t result; + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); + + message->opcode = dns_opcode_query; + message->rdclass = zone->rdclass; + + result = dns_message_gettempname(message, &qname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_gettemprdataset(message, &qrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Make question. + */ + dns_name_clone(name, qname); + dns_rdataset_makequestion(qrdataset, zone->rdclass, rdtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(message, qname, DNS_SECTION_QUESTION); + + *messagep = message; + return (ISC_R_SUCCESS); + +cleanup: + if (qname != NULL) { + dns_message_puttempname(message, &qname); + } + if (qrdataset != NULL) { + dns_message_puttemprdataset(message, &qrdataset); + } + dns_message_detach(&message); + return (result); +} + +static isc_result_t +add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid, + bool reqexpire) { + isc_result_t result; + dns_rdataset_t *rdataset = NULL; + dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; + int count = 0; + + /* Set EDNS options if applicable. */ + if (reqnsid) { + INSIST(count < DNS_EDNSOPTIONS); + ednsopts[count].code = DNS_OPT_NSID; + ednsopts[count].length = 0; + ednsopts[count].value = NULL; + count++; + } + if (reqexpire) { + INSIST(count < DNS_EDNSOPTIONS); + ednsopts[count].code = DNS_OPT_EXPIRE; + ednsopts[count].length = 0; + ednsopts[count].value = NULL; + count++; + } + result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0, + ednsopts, count); + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (dns_message_setopt(message, rdataset)); +} + +/* + * Called when stub zone update is finished. + * Update zone refresh, retry, expire values accordingly with + * SOA received from primary, sync database to file, restart + * zone management timer. + */ +static void +stub_finish_zone_update(dns_stub_t *stub, isc_time_t now) { + uint32_t refresh, retry, expire; + isc_result_t result; + isc_interval_t i; + unsigned int soacount; + dns_zone_t *zone = stub->zone; + + /* + * Tidy up. + */ + dns_db_closeversion(stub->db, &stub->version, true); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + if (zone->db == NULL) { + zone_attachdb(zone, stub->db); + } + result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, NULL, + &refresh, &retry, &expire, NULL, NULL); + if (result == ISC_R_SUCCESS && soacount > 0U) { + zone->refresh = RANGE(refresh, zone->minrefresh, + zone->maxrefresh); + zone->retry = RANGE(retry, zone->minretry, zone->maxretry); + zone->expire = RANGE(expire, zone->refresh + zone->retry, + DNS_MAX_EXPIRE); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + dns_db_detach(&stub->db); + + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); + isc_interval_set(&i, zone->expire, 0); + DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); + + if (zone->masterfile != NULL) { + zone_needdump(zone, 0); + } + + zone_settimer(zone, &now); +} + +/* + * Process answers for A and AAAA queries when + * resolving nameserver addresses for which glue + * was missing in a previous answer for a NS query. + */ +static void +stub_glue_response_cb(isc_task_t *task, isc_event_t *event) { + const char me[] = "stub_glue_response_cb"; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_stub_t *stub = NULL; + dns_message_t *msg = NULL; + dns_zone_t *zone = NULL; + char primary[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + uint32_t addr_count, cnamecnt; + isc_result_t result; + isc_time_t now; + struct stub_glue_request *request; + struct stub_cb_args *cb_args; + dns_rdataset_t *addr_rdataset = NULL; + dns_dbnode_t *node = NULL; + + UNUSED(task); + + request = revent->ev_arg; + cb_args = request->args; + stub = cb_args->stub; + INSIST(DNS_STUB_VALID(stub)); + + zone = stub->zone; + + ENTER; + + TIME_NOW(&now); + + LOCK_ZONE(zone); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + zone_debuglog(zone, me, 1, "exiting"); + goto cleanup; + } + + isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); + isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); + + if (revent->result != ISC_R_SUCCESS) { + dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr, + &zone->sourceaddr, &now); + dns_zone_log(zone, ISC_LOG_INFO, + "could not refresh stub from primary %s" + " (source %s): %s", + primary, source, + isc_result_totext(revent->result)); + goto cleanup; + } + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + result = dns_request_getresponse(revent->request, msg, 0); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unable to parse response (%s)", + isc_result_totext(result)); + goto cleanup; + } + + /* + * Unexpected opcode. + */ + if (msg->opcode != dns_opcode_query) { + char opcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, opcode, sizeof(opcode)); + (void)dns_opcode_totext(msg->opcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "unexpected opcode (%.*s) from %s (source %s)", + (int)rb.used, opcode, primary, source); + goto cleanup; + } + + /* + * Unexpected rcode. + */ + if (msg->rcode != dns_rcode_noerror) { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "unexpected rcode (%.*s) from %s (source %s)", + (int)rb.used, rcode, primary, source); + goto cleanup; + } + + /* + * We need complete messages. + */ + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + if (dns_request_usedtcp(revent->request)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: truncated TCP " + "response from primary %s (source %s)", + primary, source); + } + goto cleanup; + } + + /* + * If non-auth log. + */ + if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "non-authoritative answer from " + "primary %s (source %s)", + primary, source); + goto cleanup; + } + + /* + * Sanity checks. + */ + cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); + addr_count = message_count(msg, DNS_SECTION_ANSWER, + request->ipv4 ? dns_rdatatype_a + : dns_rdatatype_aaaa); + + if (cnamecnt != 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unexpected CNAME response " + "from primary %s (source %s)", + primary, source); + goto cleanup; + } + + if (addr_count == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: no %s records in response " + "from primary %s (source %s)", + request->ipv4 ? "A" : "AAAA", primary, source); + goto cleanup; + } + /* + * Extract A or AAAA RRset from message. + */ + result = dns_message_findname(msg, DNS_SECTION_ANSWER, &request->name, + request->ipv4 ? dns_rdatatype_a + : dns_rdatatype_aaaa, + dns_rdatatype_none, NULL, &addr_rdataset); + if (result != ISC_R_SUCCESS) { + if (result != DNS_R_NXDOMAIN && result != DNS_R_NXRRSET) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&request->name, namebuf, + sizeof(namebuf)); + dns_zone_log( + zone, ISC_LOG_INFO, + "refreshing stub: dns_message_findname(%s/%s) " + "failed (%s)", + namebuf, request->ipv4 ? "A" : "AAAA", + isc_result_totext(result)); + } + goto cleanup; + } + + result = dns_db_findnode(stub->db, &request->name, true, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_findnode() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + result = dns_db_addrdataset(stub->db, node, stub->version, 0, + addr_rdataset, 0, NULL); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_addrdataset() failed: %s", + isc_result_totext(result)); + } + dns_db_detachnode(stub->db, &node); + +cleanup: + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_name_free(&request->name, zone->mctx); + dns_request_destroy(&request->request); + isc_mem_put(zone->mctx, request, sizeof(*request)); + + /* If last request, release all related resources */ + if (atomic_fetch_sub_release(&stub->pending_requests, 1) == 1) { + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + stub_finish_zone_update(stub, now); + UNLOCK_ZONE(zone); + stub->magic = 0; + dns_zone_idetach(&stub->zone); + INSIST(stub->db == NULL); + INSIST(stub->version == NULL); + isc_mem_put(stub->mctx, stub, sizeof(*stub)); + } else { + UNLOCK_ZONE(zone); + } +} + +/* + * Create and send an A or AAAA query to the primary + * server of the stub zone given. + */ +static isc_result_t +stub_request_nameserver_address(struct stub_cb_args *args, bool ipv4, + const dns_name_t *name) { + dns_message_t *message = NULL; + dns_zone_t *zone; + isc_result_t result; + struct stub_glue_request *request; + + zone = args->stub->zone; + request = isc_mem_get(zone->mctx, sizeof(*request)); + request->request = NULL; + request->args = args; + request->name = (dns_name_t)DNS_NAME_INITEMPTY; + request->ipv4 = ipv4; + dns_name_dup(name, zone->mctx, &request->name); + + result = create_query(zone, ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa, + &request->name, &message); + INSIST(result == ISC_R_SUCCESS); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + result = add_opt(message, args->udpsize, args->reqnsid, false); + if (result != ISC_R_SUCCESS) { + zone_debuglog(zone, "stub_send_query", 1, + "unable to add opt record: %s", + isc_result_totext(result)); + goto fail; + } + } + + atomic_fetch_add_release(&args->stub->pending_requests, 1); + + result = dns_request_create( + zone->view->requestmgr, message, &zone->sourceaddr, + &zone->primaryaddr, DNS_REQUESTOPT_TCP, args->tsig_key, + args->timeout * 3, args->timeout, 2, zone->task, + stub_glue_response_cb, request, &request->request); + + if (result != ISC_R_SUCCESS) { + uint_fast32_t pr; + pr = atomic_fetch_sub_release(&args->stub->pending_requests, 1); + INSIST(pr > 1); + zone_debuglog(zone, "stub_send_query", 1, + "dns_request_create() failed: %s", + isc_result_totext(result)); + goto fail; + } + + dns_message_detach(&message); + + return (ISC_R_SUCCESS); + +fail: + dns_name_free(&request->name, zone->mctx); + isc_mem_put(zone->mctx, request, sizeof(*request)); + + if (message != NULL) { + dns_message_detach(&message); + } + + return (result); +} + +static isc_result_t +save_nsrrset(dns_message_t *message, dns_name_t *name, + struct stub_cb_args *cb_args, dns_db_t *db, + dns_dbversion_t *version) { + dns_rdataset_t *nsrdataset = NULL; + dns_rdataset_t *rdataset = NULL; + dns_dbnode_t *node = NULL; + dns_rdata_ns_t ns; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + bool has_glue = false; + dns_name_t *ns_name; + /* + * List of NS entries in answer, keep names that will be used + * to resolve missing A/AAAA glue for each entry. + */ + dns_namelist_t ns_list; + ISC_LIST_INIT(ns_list); + + /* + * Extract NS RRset from message. + */ + result = dns_message_findname(message, DNS_SECTION_ANSWER, name, + dns_rdatatype_ns, dns_rdatatype_none, + NULL, &nsrdataset); + if (result != ISC_R_SUCCESS) { + goto done; + } + + /* + * Add NS rdataset. + */ + result = dns_db_findnode(db, name, true, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + result = dns_db_addrdataset(db, node, version, 0, nsrdataset, 0, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + /* + * Add glue rdatasets. + */ + for (result = dns_rdataset_first(nsrdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(nsrdataset)) + { + dns_rdataset_current(nsrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + if (!dns_name_issubdomain(&ns.name, name)) { + continue; + } + rdataset = NULL; + result = dns_message_findname(message, DNS_SECTION_ADDITIONAL, + &ns.name, dns_rdatatype_aaaa, + dns_rdatatype_none, NULL, + &rdataset); + if (result == ISC_R_SUCCESS) { + has_glue = true; + result = dns_db_findnode(db, &ns.name, true, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + result = dns_db_addrdataset(db, node, version, 0, + rdataset, 0, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + + rdataset = NULL; + result = dns_message_findname( + message, DNS_SECTION_ADDITIONAL, &ns.name, + dns_rdatatype_a, dns_rdatatype_none, NULL, &rdataset); + if (result == ISC_R_SUCCESS) { + has_glue = true; + result = dns_db_findnode(db, &ns.name, true, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + result = dns_db_addrdataset(db, node, version, 0, + rdataset, 0, NULL); + dns_db_detachnode(db, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + + /* + * If no glue is found so far, we add the name to the list to + * resolve the A/AAAA glue later. If any glue is found in any + * iteration step, this list will be discarded and only the glue + * provided in this message will be used. + */ + if (!has_glue && dns_name_issubdomain(&ns.name, name)) { + dns_name_t *tmp_name; + tmp_name = isc_mem_get(cb_args->stub->mctx, + sizeof(*tmp_name)); + dns_name_init(tmp_name, NULL); + dns_name_dup(&ns.name, cb_args->stub->mctx, tmp_name); + ISC_LIST_APPEND(ns_list, tmp_name, link); + } + } + + if (result != ISC_R_NOMORE) { + goto done; + } + + /* + * If no glue records were found, we attempt to resolve A/AAAA + * for each NS entry found in the answer. + */ + if (!has_glue) { + for (ns_name = ISC_LIST_HEAD(ns_list); ns_name != NULL; + ns_name = ISC_LIST_NEXT(ns_name, link)) + { + /* + * Resolve NS IPv4 address/A. + */ + result = stub_request_nameserver_address(cb_args, true, + ns_name); + if (result != ISC_R_SUCCESS) { + goto done; + } + /* + * Resolve NS IPv6 address/AAAA. + */ + result = stub_request_nameserver_address(cb_args, false, + ns_name); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + } + + result = ISC_R_SUCCESS; + +done: + while ((ns_name = ISC_LIST_HEAD(ns_list)) != NULL) { + ISC_LIST_UNLINK(ns_list, ns_name, link); + dns_name_free(ns_name, cb_args->stub->mctx); + isc_mem_put(cb_args->stub->mctx, ns_name, sizeof(*ns_name)); + } + return (result); +} + +static void +stub_callback(isc_task_t *task, isc_event_t *event) { + const char me[] = "stub_callback"; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_stub_t *stub = NULL; + dns_message_t *msg = NULL; + dns_zone_t *zone = NULL; + char primary[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + uint32_t nscnt, cnamecnt; + isc_result_t result; + isc_time_t now; + bool exiting = false; + unsigned int j; + struct stub_cb_args *cb_args; + + cb_args = revent->ev_arg; + stub = cb_args->stub; + INSIST(DNS_STUB_VALID(stub)); + + UNUSED(task); + + zone = stub->zone; + + ENTER; + + TIME_NOW(&now); + + LOCK_ZONE(zone); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + goto exiting; + } + + isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); + isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); + + switch (revent->result) { + case ISC_R_SUCCESS: + break; + case ISC_R_SHUTTINGDOWN: + goto exiting; + case ISC_R_TIMEDOUT: + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refreshing stub: timeout retrying " + "without EDNS primary %s (source %s)", + primary, source); + goto same_primary; + } + FALLTHROUGH; + default: + dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr, + &zone->sourceaddr, &now); + dns_zone_log(zone, ISC_LOG_INFO, + "could not refresh stub from primary " + "%s (source %s): %s", + primary, source, + isc_result_totext(revent->result)); + goto next_primary; + } + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + + result = dns_request_getresponse(revent->request, msg, 0); + if (result != ISC_R_SUCCESS) { + goto next_primary; + } + + /* + * Unexpected opcode. + */ + if (msg->opcode != dns_opcode_query) { + char opcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, opcode, sizeof(opcode)); + (void)dns_opcode_totext(msg->opcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "unexpected opcode (%.*s) from %s (source %s)", + (int)rb.used, opcode, primary, source); + goto next_primary; + } + + /* + * Unexpected rcode. + */ + if (msg->rcode != dns_rcode_noerror) { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && + (msg->rcode == dns_rcode_servfail || + msg->rcode == dns_rcode_notimp || + (msg->rcode == dns_rcode_formerr && msg->opt == NULL))) + { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refreshing stub: rcode (%.*s) retrying " + "without EDNS primary %s (source %s)", + (int)rb.used, rcode, primary, source); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + goto same_primary; + } + + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "unexpected rcode (%.*s) from %s (source %s)", + (int)rb.used, rcode, primary, source); + goto next_primary; + } + + /* + * We need complete messages. + */ + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + if (dns_request_usedtcp(revent->request)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: truncated TCP " + "response from primary %s (source %s)", + primary, source); + goto next_primary; + } + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC); + goto same_primary; + } + + /* + * If non-auth log and next primary. + */ + if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "non-authoritative answer from " + "primary %s (source %s)", + primary, source); + goto next_primary; + } + + /* + * Sanity checks. + */ + cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); + nscnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_ns); + + if (cnamecnt != 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unexpected CNAME response " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + if (nscnt == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: no NS records in response " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + atomic_fetch_add(&stub->pending_requests, 1); + + /* + * Save answer. + */ + result = save_nsrrset(msg, &zone->origin, cb_args, stub->db, + stub->version); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unable to save NS records " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + dns_message_detach(&msg); + isc_event_free(&event); + dns_request_destroy(&zone->request); + + /* + * Check to see if there are no outstanding requests and + * finish off if that is so. + */ + if (atomic_fetch_sub(&stub->pending_requests, 1) == 1) { + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + stub_finish_zone_update(stub, now); + goto free_stub; + } + + UNLOCK_ZONE(zone); + return; + +exiting: + zone_debuglog(zone, me, 1, "exiting"); + exiting = true; + +next_primary: + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + if (stub->version != NULL) { + dns_db_closeversion(stub->db, &stub->version, false); + } + if (stub->db != NULL) { + dns_db_detach(&stub->db); + } + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_request_destroy(&zone->request); + /* + * Skip to next failed / untried primary. + */ + do { + zone->curprimary++; + } while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); + if (exiting || zone->curprimary >= zone->primariescnt) { + bool done = true; + if (!exiting && + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) + { + /* + * Did we get a good answer from all the primaries? + */ + for (j = 0; j < zone->primariescnt; j++) { + if (!zone->primariesok[j]) { + { + done = false; + break; + } + } + } + } else { + done = true; + } + if (!done) { + zone->curprimary = 0; + /* + * Find the next failed primary. + */ + while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]) + { + zone->curprimary++; + } + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); + } else { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + + zone_settimer(zone, &now); + goto free_stub; + } + } + queue_soa_query(zone); + goto free_stub; + +same_primary: + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_request_destroy(&zone->request); + ns_query(zone, NULL, stub); + UNLOCK_ZONE(zone); + goto done; + +free_stub: + UNLOCK_ZONE(zone); + stub->magic = 0; + dns_zone_idetach(&stub->zone); + INSIST(stub->db == NULL); + INSIST(stub->version == NULL); + isc_mem_put(stub->mctx, stub, sizeof(*stub)); + +done: + INSIST(event == NULL); + return; +} + +/* + * Get the EDNS EXPIRE option from the response and if it exists trim + * expire to be not more than it. + */ +static void +get_edns_expire(dns_zone_t *zone, dns_message_t *message, uint32_t *expirep) { + isc_result_t result; + uint32_t expire; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t optbuf; + uint16_t optcode; + uint16_t optlen; + + REQUIRE(expirep != NULL); + REQUIRE(message != NULL); + + if (message->opt == NULL) { + return; + } + + result = dns_rdataset_first(message->opt); + if (result == ISC_R_SUCCESS) { + dns_rdataset_current(message->opt, &rdata); + isc_buffer_init(&optbuf, rdata.data, rdata.length); + isc_buffer_add(&optbuf, rdata.length); + while (isc_buffer_remaininglength(&optbuf) >= 4) { + optcode = isc_buffer_getuint16(&optbuf); + optlen = isc_buffer_getuint16(&optbuf); + /* + * A EDNS EXPIRE response has a length of 4. + */ + if (optcode != DNS_OPT_EXPIRE || optlen != 4) { + isc_buffer_forward(&optbuf, optlen); + continue; + } + expire = isc_buffer_getuint32(&optbuf); + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "got EDNS EXPIRE of %u", expire); + /* + * Trim *expirep? + */ + if (expire < *expirep) { + *expirep = expire; + } + break; + } + } +} + +/* + * Set the file modification time zone->expire seconds before expiretime. + */ +static void +setmodtime(dns_zone_t *zone, isc_time_t *expiretime) { + isc_result_t result; + isc_time_t when; + isc_interval_t i; + + isc_interval_set(&i, zone->expire, 0); + result = isc_time_subtract(expiretime, &i, &when); + if (result != ISC_R_SUCCESS) { + return; + } + + result = ISC_R_FAILURE; + if (zone->journal != NULL) { + result = isc_file_settime(zone->journal, &when); + } + if (result == ISC_R_SUCCESS && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + result = isc_file_settime(zone->masterfile, &when); + } else if (result != ISC_R_SUCCESS) { + result = isc_file_settime(zone->masterfile, &when); + } + + /* + * Someone removed the file from underneath us! + */ + if (result == ISC_R_FILENOTFOUND) { + zone_needdump(zone, DNS_DUMP_DELAY); + } else if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "refresh: could not set " + "file modification time of '%s': %s", + zone->masterfile, isc_result_totext(result)); + } +} + +/* + * An SOA query has finished (successfully or not). + */ +static void +refresh_callback(isc_task_t *task, isc_event_t *event) { + const char me[] = "refresh_callback"; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_zone_t *zone; + dns_message_t *msg = NULL; + uint32_t soacnt, cnamecnt, soacount, nscount; + isc_time_t now; + char primary[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + isc_result_t result; + uint32_t serial, oldserial = 0; + unsigned int j; + bool do_queue_xfrin = false; + + zone = revent->ev_arg; + INSIST(DNS_ZONE_VALID(zone)); + + UNUSED(task); + + ENTER; + + TIME_NOW(&now); + + LOCK_ZONE(zone); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + goto exiting; + } + + /* + * If timeout, log and try the next primary + */ + isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); + isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); + + switch (revent->result) { + case ISC_R_SUCCESS: + break; + case ISC_R_SHUTTINGDOWN: + goto exiting; + case ISC_R_TIMEDOUT: + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refresh: timeout retrying without EDNS " + "primary %s (source %s)", + primary, source); + goto same_primary; + } else if (!dns_request_usedtcp(revent->request)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: retry limit for " + "primary %s exceeded (source %s)", + primary, source); + /* Try with secondary with TCP. */ + if ((zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_redirect) && + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_TRYTCPREFRESH)) + { + if (!dns_zonemgr_unreachable( + zone->zmgr, &zone->primaryaddr, + &zone->sourceaddr, &now)) + { + DNS_ZONE_SETFLAG( + zone, + DNS_ZONEFLG_SOABEFOREAXFR); + goto tcp_transfer; + } + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refresh: skipped tcp fallback " + "as primary %s (source %s) is " + "unreachable (cached)", + primary, source); + } + goto next_primary; + } + FALLTHROUGH; + default: + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: failure trying primary " + "%s (source %s): %s", + primary, source, + isc_result_totext(revent->result)); + goto next_primary; + } + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + result = dns_request_getresponse(revent->request, msg, 0); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: failure trying primary " + "%s (source %s): %s", + primary, source, isc_result_totext(result)); + goto next_primary; + } + + /* + * Unexpected opcode. + */ + if (msg->opcode != dns_opcode_query) { + char opcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, opcode, sizeof(opcode)); + (void)dns_opcode_totext(msg->opcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: " + "unexpected opcode (%.*s) from %s (source %s)", + (int)rb.used, opcode, primary, source); + goto next_primary; + } + + /* + * Unexpected rcode. + */ + if (msg->rcode != dns_rcode_noerror) { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && + (msg->rcode == dns_rcode_servfail || + msg->rcode == dns_rcode_notimp || + (msg->rcode == dns_rcode_formerr && msg->opt == NULL))) + { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refresh: rcode (%.*s) retrying without " + "EDNS primary %s (source %s)", + (int)rb.used, rcode, primary, source); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + goto same_primary; + } + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && + msg->rcode == dns_rcode_badvers) + { + dns_zone_log(zone, ISC_LOG_DEBUG(1), + "refresh: rcode (%.*s) retrying without " + "EDNS EXPIRE OPTION primary %s " + "(source %s)", + (int)rb.used, rcode, primary, source); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + goto same_primary; + } + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: unexpected rcode (%.*s) from " + "primary %s (source %s)", + (int)rb.used, rcode, primary, source); + /* + * Perhaps AXFR/IXFR is allowed even if SOA queries aren't. + */ + if (msg->rcode == dns_rcode_refused && + (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_redirect)) + { + goto tcp_transfer; + } + goto next_primary; + } + + /* + * If truncated punt to zone transfer which will query again. + */ + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + if (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_redirect) + { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: truncated UDP answer, " + "initiating TCP zone xfer " + "for primary %s (source %s)", + primary, source); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); + goto tcp_transfer; + } else { + INSIST(zone->type == dns_zone_stub); + if (dns_request_usedtcp(revent->request)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: truncated TCP response " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC); + goto same_primary; + } + } + + /* + * If non-auth, log and try the next primary + */ + if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: non-authoritative answer from " + "primary %s (source %s)", + primary, source); + goto next_primary; + } + + cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); + soacnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_soa); + nscount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_ns); + soacount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_soa); + + /* + * There should not be a CNAME record at top of zone. + */ + if (cnamecnt != 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: CNAME at top of zone " + "in primary %s (source %s)", + primary, source); + goto next_primary; + } + + /* + * If referral, log and try the next primary; + */ + if (soacnt == 0 && soacount == 0 && nscount != 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: referral response " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + /* + * If nodata, log and try the next primary; + */ + if (soacnt == 0 && (nscount == 0 || soacount != 0)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: NODATA response " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + /* + * Only one soa at top of zone. + */ + if (soacnt != 1) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: answer SOA count (%d) != 1 " + "from primary %s (source %s)", + soacnt, primary, source); + goto next_primary; + } + + /* + * Extract serial + */ + rdataset = NULL; + result = dns_message_findname(msg, DNS_SECTION_ANSWER, &zone->origin, + dns_rdatatype_soa, dns_rdatatype_none, + NULL, &rdataset); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: unable to get SOA record " + "from primary %s (source %s)", + primary, source); + goto next_primary; + } + + result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: dns_rdataset_first() failed"); + goto next_primary; + } + + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + serial = soa.serial; + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { + unsigned int dbsoacount; + result = zone_get_from_db(zone, zone->db, NULL, &dbsoacount, + NULL, &oldserial, NULL, NULL, NULL, + NULL, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + RUNTIME_CHECK(dbsoacount > 0U); + zone_debuglog(zone, me, 1, "serial: new %u, old %u", serial, + oldserial); + } else { + zone_debuglog(zone, me, 1, "serial: new %u, old not loaded", + serial); + } + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) || + isc_serial_gt(serial, oldserial)) + { + if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr, + &zone->sourceaddr, &now)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "refresh: skipping %s as primary %s " + "(source %s) is unreachable (cached)", + (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_redirect) + ? "zone transfer" + : "NS query", + primary, source); + goto next_primary; + } + tcp_transfer: + isc_event_free(&event); + dns_request_destroy(&zone->request); + if (zone->type == dns_zone_secondary || + zone->type == dns_zone_mirror || + zone->type == dns_zone_redirect) + { + do_queue_xfrin = true; + } else { + INSIST(zone->type == dns_zone_stub); + ns_query(zone, rdataset, NULL); + } + if (msg != NULL) { + dns_message_detach(&msg); + } + } else if (isc_serial_eq(soa.serial, oldserial)) { + isc_time_t expiretime; + uint32_t expire; + + /* + * Compute the new expire time based on this response. + */ + expire = zone->expire; + get_edns_expire(zone, msg, &expire); + DNS_ZONE_TIME_ADD(&now, expire, &expiretime); + + /* + * Has the expire time improved? + */ + if (isc_time_compare(&expiretime, &zone->expiretime) > 0) { + zone->expiretime = expiretime; + if (zone->masterfile != NULL) { + setmodtime(zone, &expiretime); + } + } + + DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); + zone->primariesok[zone->curprimary] = true; + goto next_primary; + } else { + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MULTIMASTER)) { + dns_zone_log(zone, ISC_LOG_INFO, + "serial number (%u) " + "received from primary %s < ours (%u)", + soa.serial, primary, oldserial); + } else { + zone_debuglog(zone, me, 1, "ahead"); + } + zone->primariesok[zone->curprimary] = true; + goto next_primary; + } + if (msg != NULL) { + dns_message_detach(&msg); + } + goto detach; + +next_primary: + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_request_destroy(&zone->request); + /* + * Skip to next failed / untried primary. + */ + do { + zone->curprimary++; + } while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); + if (zone->curprimary >= zone->primariescnt) { + bool done = true; + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) + { + /* + * Did we get a good answer from all the primaries? + */ + for (j = 0; j < zone->primariescnt; j++) { + if (!zone->primariesok[j]) { + { + done = false; + break; + } + } + } + } else { + done = true; + } + if (!done) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); + zone->curprimary = 0; + /* + * Find the next failed primary. + */ + while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]) + { + zone->curprimary++; + } + goto requeue; + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); + zone->refreshtime = now; + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); + zone_settimer(zone, &now); + goto detach; + } + +requeue: + queue_soa_query(zone); + goto detach; + +exiting: + isc_event_free(&event); + dns_request_destroy(&zone->request); + goto detach; + +same_primary: + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_request_destroy(&zone->request); + queue_soa_query(zone); + +detach: + UNLOCK_ZONE(zone); + if (do_queue_xfrin) { + queue_xfrin(zone); + } + dns_zone_idetach(&zone); + return; +} + +static void +queue_soa_query(dns_zone_t *zone) { + const char me[] = "queue_soa_query"; + isc_event_t *e; + dns_zone_t *dummy = NULL; + isc_result_t result; + + ENTER; + /* + * Locked by caller + */ + REQUIRE(LOCKED_ZONE(zone)); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + cancel_refresh(zone); + return; + } + + e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE, soa_query, + zone, sizeof(isc_event_t)); + + /* + * Attach so that we won't clean up + * until the event is delivered. + */ + zone_iattach(zone, &dummy); + + e->ev_arg = zone; + e->ev_sender = NULL; + result = isc_ratelimiter_enqueue(zone->zmgr->refreshrl, zone->task, &e); + if (result != ISC_R_SUCCESS) { + zone_idetach(&dummy); + isc_event_free(&e); + cancel_refresh(zone); + } +} + +static void +soa_query(isc_task_t *task, isc_event_t *event) { + const char me[] = "soa_query"; + isc_result_t result = ISC_R_FAILURE; + dns_message_t *message = NULL; + dns_zone_t *zone = event->ev_arg; + dns_zone_t *dummy = NULL; + isc_netaddr_t primaryip; + dns_tsigkey_t *key = NULL; + dns_transport_t *transport = NULL; + uint32_t options; + bool cancel = true; + int timeout; + bool have_xfrsource = false, reqnsid, reqexpire; + uint16_t udpsize = SEND_BUFFER_SIZE; + bool do_queue_xfrin = false; + + REQUIRE(DNS_ZONE_VALID(zone)); + + UNUSED(task); + + ENTER; + + LOCK_ZONE(zone); + if (((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || + zone->view->requestmgr == NULL) + { + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + cancel = false; + } + goto cleanup; + } + +again: + INSIST(zone->primariescnt > 0); + INSIST(zone->curprimary < zone->primariescnt); + + zone->primaryaddr = zone->primaries[zone->curprimary]; + + isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); + /* + * First, look for a tsig key in the primaries statement, then + * try for a server key. + */ + if ((zone->primarykeynames != NULL) && + (zone->primarykeynames[zone->curprimary] != NULL)) + { + dns_view_t *view = dns_zone_getview(zone); + dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; + result = dns_view_gettsig(view, keyname, &key); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(keyname, namebuf, sizeof(namebuf)); + dns_zone_log(zone, ISC_LOG_ERROR, + "unable to find key: %s", namebuf); + goto skip_primary; + } + } + if (key == NULL) { + result = dns_view_getpeertsig(zone->view, &primaryip, &key); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + char addrbuf[ISC_NETADDR_FORMATSIZE]; + isc_netaddr_format(&primaryip, addrbuf, + sizeof(addrbuf)); + dns_zone_log(zone, ISC_LOG_ERROR, + "unable to find TSIG key for %s", addrbuf); + goto skip_primary; + } + } + + if ((zone->primarytlsnames != NULL) && + (zone->primarytlsnames[zone->curprimary] != NULL)) + { + dns_view_t *view = dns_zone_getview(zone); + dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary]; + result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, + &transport); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(tlsname, namebuf, sizeof(namebuf)); + dns_zone_log(zone, ISC_LOG_ERROR, + "unable to find TLS configuration: %s", + namebuf); + goto skip_primary; + } + } + + options = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEVC) ? DNS_REQUESTOPT_TCP + : 0; + reqnsid = zone->view->requestnsid; + reqexpire = zone->requestexpire; + if (zone->view->peers != NULL) { + dns_peer_t *peer = NULL; + bool edns, usetcp; + result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, + &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getsupportedns(peer, &edns); + if (result == ISC_R_SUCCESS && !edns) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + } + result = dns_peer_gettransfersource(peer, + &zone->sourceaddr); + if (result == ISC_R_SUCCESS) { + have_xfrsource = true; + } + if (zone->view->resolver != NULL) { + udpsize = dns_resolver_getudpsize( + zone->view->resolver); + } + (void)dns_peer_getudpsize(peer, &udpsize); + (void)dns_peer_getrequestnsid(peer, &reqnsid); + (void)dns_peer_getrequestexpire(peer, &reqexpire); + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + options |= DNS_REQUESTOPT_TCP; + } + } + } + + switch (isc_sockaddr_pf(&zone->primaryaddr)) { + case PF_INET: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { + if (isc_sockaddr_equal(&zone->altxfrsource4, + &zone->xfrsource4)) + { + goto skip_primary; + } + zone->sourceaddr = zone->altxfrsource4; + } else if (!have_xfrsource) { + zone->sourceaddr = zone->xfrsource4; + } + break; + case PF_INET6: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { + if (isc_sockaddr_equal(&zone->altxfrsource6, + &zone->xfrsource6)) + { + goto skip_primary; + } + zone->sourceaddr = zone->altxfrsource6; + } else if (!have_xfrsource) { + zone->sourceaddr = zone->xfrsource6; + } + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + + /* + * FIXME(OS): This is a bit hackish, but it enforces the SOA query to go + * through the XFR channel instead of doing dns_request that doesn't + * have DoT support yet. + */ + if (transport != NULL) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); + do_queue_xfrin = true; + cancel = false; + result = ISC_R_SUCCESS; + goto cleanup; + } + + result = create_query(zone, dns_rdatatype_soa, &zone->origin, &message); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + result = add_opt(message, udpsize, reqnsid, reqexpire); + if (result != ISC_R_SUCCESS) { + zone_debuglog(zone, me, 1, + "unable to add opt record: %s", + isc_result_totext(result)); + } + } + + zone_iattach(zone, &dummy); + timeout = 15; + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { + timeout = 30; + } + result = dns_request_create( + zone->view->requestmgr, message, &zone->sourceaddr, + &zone->primaryaddr, options, key, timeout * 3, timeout, 2, + zone->task, refresh_callback, zone, &zone->request); + if (result != ISC_R_SUCCESS) { + zone_idetach(&dummy); + zone_debuglog(zone, me, 1, "dns_request_create() failed: %s", + isc_result_totext(result)); + goto skip_primary; + } else { + if (isc_sockaddr_pf(&zone->primaryaddr) == PF_INET) { + inc_stats(zone, dns_zonestatscounter_soaoutv4); + } else { + inc_stats(zone, dns_zonestatscounter_soaoutv6); + } + } + cancel = false; +cleanup: + if (transport != NULL) { + dns_transport_detach(&transport); + } + if (key != NULL) { + dns_tsigkey_detach(&key); + } + if (result != ISC_R_SUCCESS) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + } + if (message != NULL) { + dns_message_detach(&message); + } + if (cancel) { + cancel_refresh(zone); + } + isc_event_free(&event); + UNLOCK_ZONE(zone); + if (do_queue_xfrin) { + queue_xfrin(zone); + } + dns_zone_idetach(&zone); + return; + +skip_primary: + if (transport != NULL) { + dns_transport_detach(&transport); + } + if (key != NULL) { + dns_tsigkey_detach(&key); + } + if (message != NULL) { + dns_message_detach(&message); + } + /* + * Skip to next failed / untried primary. + */ + do { + zone->curprimary++; + } while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]); + if (zone->curprimary < zone->primariescnt) { + goto again; + } + zone->curprimary = 0; + goto cleanup; +} + +static void +ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { + const char me[] = "ns_query"; + isc_result_t result; + dns_message_t *message = NULL; + isc_netaddr_t primaryip; + dns_tsigkey_t *key = NULL; + dns_dbnode_t *node = NULL; + int timeout; + bool have_xfrsource = false; + bool reqnsid; + uint16_t udpsize = SEND_BUFFER_SIZE; + struct stub_cb_args *cb_args; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + REQUIRE((soardataset != NULL && stub == NULL) || + (soardataset == NULL && stub != NULL)); + REQUIRE(stub == NULL || DNS_STUB_VALID(stub)); + + ENTER; + + if (stub == NULL) { + stub = isc_mem_get(zone->mctx, sizeof(*stub)); + stub->magic = STUB_MAGIC; + stub->mctx = zone->mctx; + stub->zone = NULL; + stub->db = NULL; + stub->version = NULL; + atomic_init(&stub->pending_requests, 0); + + /* + * Attach so that the zone won't disappear from under us. + */ + zone_iattach(zone, &stub->zone); + + /* + * If a db exists we will update it, otherwise we create a + * new one and attach it to the zone once we have the NS + * RRset and glue. + */ + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &stub->db); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + } else { + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + INSIST(zone->db_argc >= 1); + result = dns_db_create(zone->mctx, zone->db_argv[0], + &zone->origin, dns_dbtype_stub, + zone->rdclass, zone->db_argc - 1, + zone->db_argv + 1, &stub->db); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "refreshing stub: " + "could not create " + "database: %s", + isc_result_totext(result)); + goto cleanup; + } + dns_db_settask(stub->db, zone->task); + } + + result = dns_db_newversion(stub->db, &stub->version); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_newversion() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + /* + * Update SOA record. + */ + result = dns_db_findnode(stub->db, &zone->origin, true, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_findnode() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + result = dns_db_addrdataset(stub->db, node, stub->version, 0, + soardataset, 0, NULL); + dns_db_detachnode(stub->db, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_addrdataset() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + } + + /* + * XXX Optimisation: Create message when zone is setup and reuse. + */ + result = create_query(zone, dns_rdatatype_ns, &zone->origin, &message); + INSIST(result == ISC_R_SUCCESS); + + INSIST(zone->primariescnt > 0); + INSIST(zone->curprimary < zone->primariescnt); + zone->primaryaddr = zone->primaries[zone->curprimary]; + + isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); + /* + * First, look for a tsig key in the primaries statement, then + * try for a server key. + */ + if ((zone->primarykeynames != NULL) && + (zone->primarykeynames[zone->curprimary] != NULL)) + { + dns_view_t *view = dns_zone_getview(zone); + dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; + result = dns_view_gettsig(view, keyname, &key); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(keyname, namebuf, sizeof(namebuf)); + dns_zone_log(zone, ISC_LOG_ERROR, + "unable to find key: %s", namebuf); + } + } + if (key == NULL) { + (void)dns_view_getpeertsig(zone->view, &primaryip, &key); + } + + /* FIXME(OS): Do we need the transport here too? Most probably yes */ + + reqnsid = zone->view->requestnsid; + if (zone->view->peers != NULL) { + dns_peer_t *peer = NULL; + bool edns; + result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, + &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getsupportedns(peer, &edns); + if (result == ISC_R_SUCCESS && !edns) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); + } + result = dns_peer_gettransfersource(peer, + &zone->sourceaddr); + if (result == ISC_R_SUCCESS) { + have_xfrsource = true; + } + if (zone->view->resolver != NULL) { + udpsize = dns_resolver_getudpsize( + zone->view->resolver); + } + (void)dns_peer_getudpsize(peer, &udpsize); + (void)dns_peer_getrequestnsid(peer, &reqnsid); + } + } + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + result = add_opt(message, udpsize, reqnsid, false); + if (result != ISC_R_SUCCESS) { + zone_debuglog(zone, me, 1, + "unable to add opt record: %s", + isc_result_totext(result)); + } + } + + /* + * Always use TCP so that we shouldn't truncate in additional section. + */ + switch (isc_sockaddr_pf(&zone->primaryaddr)) { + case PF_INET: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { + zone->sourceaddr = zone->altxfrsource4; + } else if (!have_xfrsource) { + zone->sourceaddr = zone->xfrsource4; + } + break; + case PF_INET6: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { + zone->sourceaddr = zone->altxfrsource6; + } else if (!have_xfrsource) { + zone->sourceaddr = zone->xfrsource6; + } + break; + default: + result = ISC_R_NOTIMPLEMENTED; + POST(result); + goto cleanup; + } + timeout = 15; + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { + timeout = 30; + } + + /* + * Save request parameters so we can reuse them later on + * for resolving missing glue A/AAAA records. + */ + cb_args = isc_mem_get(zone->mctx, sizeof(*cb_args)); + cb_args->stub = stub; + cb_args->tsig_key = key; + cb_args->udpsize = udpsize; + cb_args->timeout = timeout; + cb_args->reqnsid = reqnsid; + + result = dns_request_create( + zone->view->requestmgr, message, &zone->sourceaddr, + &zone->primaryaddr, DNS_REQUESTOPT_TCP, key, timeout * 3, + timeout, 2, zone->task, stub_callback, cb_args, &zone->request); + if (result != ISC_R_SUCCESS) { + zone_debuglog(zone, me, 1, "dns_request_create() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + dns_message_detach(&message); + goto unlock; + +cleanup: + cancel_refresh(zone); + stub->magic = 0; + if (stub->version != NULL) { + dns_db_closeversion(stub->db, &stub->version, false); + } + if (stub->db != NULL) { + dns_db_detach(&stub->db); + } + if (stub->zone != NULL) { + zone_idetach(&stub->zone); + } + isc_mem_put(stub->mctx, stub, sizeof(*stub)); + if (message != NULL) { + dns_message_detach(&message); + } +unlock: + if (key != NULL) { + dns_tsigkey_detach(&key); + } + return; +} + +/* + * Shut the zone down. + */ +static void +zone_shutdown(isc_task_t *task, isc_event_t *event) { + dns_zone_t *zone = (dns_zone_t *)event->ev_arg; + bool free_needed, linked = false; + dns_zone_t *raw = NULL, *secure = NULL; + dns_view_t *view = NULL, *prev_view = NULL; + + UNUSED(task); + REQUIRE(DNS_ZONE_VALID(zone)); + INSIST(event->ev_type == DNS_EVENT_ZONECONTROL); + INSIST(isc_refcount_current(&zone->erefs) == 0); + + zone_debuglog(zone, "zone_shutdown", 3, "shutting down"); + + /* + * If we were waiting for xfrin quota, step out of + * the queue. + * If there's no zone manager, we can't be waiting for the + * xfrin quota + */ + if (zone->zmgr != NULL) { + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + if (zone->statelist == &zone->zmgr->waiting_for_xfrin) { + ISC_LIST_UNLINK(zone->zmgr->waiting_for_xfrin, zone, + statelink); + linked = true; + zone->statelist = NULL; + } + if (zone->statelist == &zone->zmgr->xfrin_in_progress) { + ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, + statelink); + zone->statelist = NULL; + zmgr_resume_xfrs(zone->zmgr, false); + } + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + } + + /* + * In task context, no locking required. See zone_xfrdone(). + */ + if (zone->xfr != NULL) { + /* The final detach will happen in zone_xfrdone() */ + dns_xfrin_shutdown(zone->xfr); + } + + /* Safe to release the zone now */ + if (zone->zmgr != NULL) { + dns_zonemgr_releasezone(zone->zmgr, zone); + } + + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + + /* + * Detach the views early, we don't need them anymore. However, we need + * to detach them outside of the zone lock to break the lock loop + * between view, adb and zone locks. + */ + view = zone->view; + zone->view = NULL; + prev_view = zone->prev_view; + zone->prev_view = NULL; + + if (linked) { + isc_refcount_decrement(&zone->irefs); + } + if (zone->request != NULL) { + dns_request_cancel(zone->request); + } + + if (zone->readio != NULL) { + zonemgr_cancelio(zone->readio); + } + + if (zone->lctx != NULL) { + dns_loadctx_cancel(zone->lctx); + } + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) || + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + if (zone->writeio != NULL) { + zonemgr_cancelio(zone->writeio); + } + + if (zone->dctx != NULL) { + dns_dumpctx_cancel(zone->dctx); + } + } + + checkds_cancel(zone); + + notify_cancel(zone); + + forward_cancel(zone); + + if (zone->timer != NULL) { + isc_timer_destroy(&zone->timer); + isc_refcount_decrement(&zone->irefs); + } + + /* + * We have now canceled everything set the flag to allow exit_check() + * to succeed. We must not unlock between setting this flag and + * calling exit_check(). + */ + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SHUTDOWN); + free_needed = exit_check(zone); + /* + * If a dump is in progress for the secure zone, defer detaching from + * the raw zone as it may prevent the unsigned serial number from being + * stored in the raw-format dump of the secure zone. In this scenario, + * dump_done() takes care of cleaning up the zone->raw reference. + */ + if (inline_secure(zone) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { + raw = zone->raw; + zone->raw = NULL; + } + if (inline_raw(zone)) { + secure = zone->secure; + zone->secure = NULL; + } + UNLOCK_ZONE(zone); + + if (view != NULL) { + dns_view_weakdetach(&view); + } + if (prev_view != NULL) { + dns_view_weakdetach(&prev_view); + } + + if (raw != NULL) { + dns_zone_detach(&raw); + } + if (secure != NULL) { + dns_zone_idetach(&secure); + } + if (free_needed) { + zone_free(zone); + } +} + +static void +zone_timer(isc_task_t *task, isc_event_t *event) { + const char me[] = "zone_timer"; + dns_zone_t *zone = (dns_zone_t *)event->ev_arg; + + UNUSED(task); + REQUIRE(DNS_ZONE_VALID(zone)); + + ENTER; + + zone_maintenance(zone); + + isc_event_free(&event); +} + +static void +zone_settimer(dns_zone_t *zone, isc_time_t *now) { + const char me[] = "zone_settimer"; + isc_time_t next; + isc_result_t result; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + ENTER; + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + return; + } + + isc_time_settoepoch(&next); + + switch (zone->type) { + case dns_zone_redirect: + if (zone->primaries != NULL) { + goto treat_as_secondary; + } + FALLTHROUGH; + case dns_zone_primary: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) + { + next = zone->notifytime; + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + INSIST(!isc_time_isepoch(&zone->dumptime)); + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->dumptime, &next) < 0) + { + next = zone->dumptime; + } + } + if (zone->type == dns_zone_redirect) { + break; + } + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING) && + !isc_time_isepoch(&zone->refreshkeytime)) + { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->refreshkeytime, &next) < 0) + { + next = zone->refreshkeytime; + } + } + if (!isc_time_isepoch(&zone->resigntime)) { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->resigntime, &next) < 0) + { + next = zone->resigntime; + } + } + if (!isc_time_isepoch(&zone->keywarntime)) { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->keywarntime, &next) < 0) + { + next = zone->keywarntime; + } + } + if (!isc_time_isepoch(&zone->signingtime)) { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->signingtime, &next) < 0) + { + next = zone->signingtime; + } + } + if (!isc_time_isepoch(&zone->nsec3chaintime)) { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->nsec3chaintime, &next) < 0) + { + next = zone->nsec3chaintime; + } + } + break; + + case dns_zone_secondary: + case dns_zone_mirror: + treat_as_secondary: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) + { + next = zone->notifytime; + } + FALLTHROUGH; + case dns_zone_stub: + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOPRIMARIES) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING) && + !isc_time_isepoch(&zone->refreshtime) && + (isc_time_isepoch(&next) || + isc_time_compare(&zone->refreshtime, &next) < 0)) + { + next = zone->refreshtime; + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && + !isc_time_isepoch(&zone->expiretime)) + { + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->expiretime, &next) < 0) + { + next = zone->expiretime; + } + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + INSIST(!isc_time_isepoch(&zone->dumptime)); + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->dumptime, &next) < 0) + { + next = zone->dumptime; + } + } + break; + + case dns_zone_key: + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) + { + INSIST(!isc_time_isepoch(&zone->dumptime)); + if (isc_time_isepoch(&next) || + isc_time_compare(&zone->dumptime, &next) < 0) + { + next = zone->dumptime; + } + } + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING)) { + if (isc_time_isepoch(&next) || + (!isc_time_isepoch(&zone->refreshkeytime) && + isc_time_compare(&zone->refreshkeytime, &next) < + 0)) + { + next = zone->refreshkeytime; + } + } + break; + + default: + break; + } + + if (isc_time_isepoch(&next)) { + zone_debuglog(zone, me, 10, "settimer inactive"); + result = isc_timer_reset(zone->timer, isc_timertype_inactive, + NULL, NULL, true); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "could not deactivate zone timer: %s", + isc_result_totext(result)); + } + } else { + if (isc_time_compare(&next, now) <= 0) { + next = *now; + } + result = isc_timer_reset(zone->timer, isc_timertype_once, &next, + NULL, true); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "could not reset zone timer: %s", + isc_result_totext(result)); + } + } +} + +static void +cancel_refresh(dns_zone_t *zone) { + const char me[] = "cancel_refresh"; + isc_time_t now; + + /* + * 'zone' locked by caller. + */ + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + + ENTER; + + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + TIME_NOW(&now); + zone_settimer(zone, &now); +} + +static isc_result_t +notify_createmessage(dns_zone_t *zone, unsigned int flags, + dns_message_t **messagep) { + dns_db_t *zonedb = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_message_t *message = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_name_t *tempname = NULL; + dns_rdata_t *temprdata = NULL; + dns_rdatalist_t *temprdatalist = NULL; + dns_rdataset_t *temprdataset = NULL; + + isc_result_t result; + isc_region_t r; + isc_buffer_t *b = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(messagep != NULL && *messagep == NULL); + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); + + message->opcode = dns_opcode_notify; + message->flags |= DNS_MESSAGEFLAG_AA; + message->rdclass = zone->rdclass; + + result = dns_message_gettempname(message, &tempname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_gettemprdataset(message, &temprdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Make question. + */ + dns_name_clone(&zone->origin, tempname); + dns_rdataset_makequestion(temprdataset, zone->rdclass, + dns_rdatatype_soa); + ISC_LIST_APPEND(tempname->list, temprdataset, link); + dns_message_addname(message, tempname, DNS_SECTION_QUESTION); + tempname = NULL; + temprdataset = NULL; + + if ((flags & DNS_NOTIFY_NOSOA) != 0) { + goto done; + } + + result = dns_message_gettempname(message, &tempname); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + result = dns_message_gettemprdata(message, &temprdata); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + result = dns_message_gettemprdataset(message, &temprdataset); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + result = dns_message_gettemprdatalist(message, &temprdatalist); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + INSIST(zone->db != NULL); /* XXXJT: is this assumption correct? */ + dns_db_attach(zone->db, &zonedb); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + dns_name_clone(&zone->origin, tempname); + dns_db_currentversion(zonedb, &version); + result = dns_db_findnode(zonedb, tempname, false, &node); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + dns_rdataset_current(&rdataset, &rdata); + dns_rdata_toregion(&rdata, &r); + isc_buffer_allocate(zone->mctx, &b, r.length); + isc_buffer_putmem(b, r.base, r.length); + isc_buffer_usedregion(b, &r); + dns_rdata_init(temprdata); + dns_rdata_fromregion(temprdata, rdata.rdclass, rdata.type, &r); + dns_message_takebuffer(message, &b); + result = dns_rdataset_next(&rdataset); + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + goto soa_cleanup; + } + temprdatalist->rdclass = rdata.rdclass; + temprdatalist->type = rdata.type; + temprdatalist->ttl = rdataset.ttl; + ISC_LIST_APPEND(temprdatalist->rdata, temprdata, link); + + result = dns_rdatalist_tordataset(temprdatalist, temprdataset); + if (result != ISC_R_SUCCESS) { + goto soa_cleanup; + } + + ISC_LIST_APPEND(tempname->list, temprdataset, link); + dns_message_addname(message, tempname, DNS_SECTION_ANSWER); + temprdatalist = NULL; + temprdataset = NULL; + temprdata = NULL; + tempname = NULL; + +soa_cleanup: + if (node != NULL) { + dns_db_detachnode(zonedb, &node); + } + if (version != NULL) { + dns_db_closeversion(zonedb, &version, false); + } + if (zonedb != NULL) { + dns_db_detach(&zonedb); + } + if (tempname != NULL) { + dns_message_puttempname(message, &tempname); + } + if (temprdata != NULL) { + dns_message_puttemprdata(message, &temprdata); + } + if (temprdataset != NULL) { + dns_message_puttemprdataset(message, &temprdataset); + } + if (temprdatalist != NULL) { + dns_message_puttemprdatalist(message, &temprdatalist); + } + +done: + *messagep = message; + return (ISC_R_SUCCESS); + +cleanup: + if (tempname != NULL) { + dns_message_puttempname(message, &tempname); + } + if (temprdataset != NULL) { + dns_message_puttemprdataset(message, &temprdataset); + } + dns_message_detach(&message); + return (result); +} + +isc_result_t +dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, + isc_sockaddr_t *to, dns_message_t *msg) { + unsigned int i; + dns_rdata_soa_t soa; + dns_rdataset_t *rdataset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + char fromtext[ISC_SOCKADDR_FORMATSIZE]; + int match = 0; + isc_netaddr_t netaddr; + uint32_t serial = 0; + bool have_serial = false; + dns_tsigkey_t *tsigkey; + const dns_name_t *tsig; + + REQUIRE(DNS_ZONE_VALID(zone)); + + /* + * If type != T_SOA return DNS_R_NOTIMP. We don't yet support + * ROLLOVER. + * + * SOA: RFC1996 + * Check that 'from' is a valid notify source, (zone->primaries). + * Return DNS_R_REFUSED if not. + * + * If the notify message contains a serial number check it + * against the zones serial and return if <= current serial + * + * If a refresh check is progress, if so just record the + * fact we received a NOTIFY and from where and return. + * We will perform a new refresh check when the current one + * completes. Return ISC_R_SUCCESS. + * + * Otherwise initiate a refresh check using 'from' as the + * first address to check. Return ISC_R_SUCCESS. + */ + + isc_sockaddr_format(from, fromtext, sizeof(fromtext)); + + /* + * Notify messages are processed by the raw zone. + */ + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + if (inline_secure(zone)) { + result = dns_zone_notifyreceive(zone->raw, from, to, msg); + UNLOCK_ZONE(zone); + return (result); + } + /* + * We only handle NOTIFY (SOA) at the present. + */ + if (isc_sockaddr_pf(from) == PF_INET) { + inc_stats(zone, dns_zonestatscounter_notifyinv4); + } else { + inc_stats(zone, dns_zonestatscounter_notifyinv6); + } + if (msg->counts[DNS_SECTION_QUESTION] == 0 || + dns_message_findname(msg, DNS_SECTION_QUESTION, &zone->origin, + dns_rdatatype_soa, dns_rdatatype_none, NULL, + NULL) != ISC_R_SUCCESS) + { + UNLOCK_ZONE(zone); + if (msg->counts[DNS_SECTION_QUESTION] == 0) { + dns_zone_log(zone, ISC_LOG_NOTICE, + "NOTIFY with no " + "question section from: %s", + fromtext); + return (DNS_R_FORMERR); + } + dns_zone_log(zone, ISC_LOG_NOTICE, + "NOTIFY zone does not match"); + return (DNS_R_NOTIMP); + } + + /* + * If we are a primary zone just succeed. + */ + if (zone->type == dns_zone_primary) { + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); + } + + isc_netaddr_fromsockaddr(&netaddr, from); + for (i = 0; i < zone->primariescnt; i++) { + if (isc_sockaddr_eqaddr(from, &zone->primaries[i])) { + break; + } + if (zone->view->aclenv->match_mapped && + IN6_IS_ADDR_V4MAPPED(&from->type.sin6.sin6_addr) && + isc_sockaddr_pf(&zone->primaries[i]) == AF_INET) + { + isc_netaddr_t na1, na2; + isc_netaddr_fromv4mapped(&na1, &netaddr); + isc_netaddr_fromsockaddr(&na2, &zone->primaries[i]); + if (isc_netaddr_equal(&na1, &na2)) { + break; + } + } + } + + /* + * Accept notify requests from non primaries if they are on + * 'zone->notify_acl'. + */ + tsigkey = dns_message_gettsigkey(msg); + tsig = dns_tsigkey_identity(tsigkey); + if (i >= zone->primariescnt && zone->notify_acl != NULL && + (dns_acl_match(&netaddr, tsig, zone->notify_acl, zone->view->aclenv, + &match, NULL) == ISC_R_SUCCESS) && + match > 0) + { + /* Accept notify. */ + } else if (i >= zone->primariescnt) { + UNLOCK_ZONE(zone); + dns_zone_log(zone, ISC_LOG_INFO, + "refused notify from non-primary: %s", fromtext); + inc_stats(zone, dns_zonestatscounter_notifyrej); + return (DNS_R_REFUSED); + } + + /* + * If the zone is loaded and there are answers check the serial + * to see if we need to do a refresh. Do not worry about this + * check if we are a dialup zone as we use the notify request + * to trigger a refresh check. + */ + if (msg->counts[DNS_SECTION_ANSWER] > 0 && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH)) + { + result = dns_message_findname( + msg, DNS_SECTION_ANSWER, &zone->origin, + dns_rdatatype_soa, dns_rdatatype_none, NULL, &rdataset); + if (result == ISC_R_SUCCESS) { + result = dns_rdataset_first(rdataset); + } + if (result == ISC_R_SUCCESS) { + uint32_t oldserial; + unsigned int soacount; + + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + serial = soa.serial; + have_serial = true; + /* + * The following should safely be performed without DB + * lock and succeed in this context. + */ + result = zone_get_from_db(zone, zone->db, NULL, + &soacount, NULL, &oldserial, + NULL, NULL, NULL, NULL, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + RUNTIME_CHECK(soacount > 0U); + if (isc_serial_le(serial, oldserial)) { + dns_zone_log(zone, ISC_LOG_INFO, + "notify from %s: " + "zone is up to date", + fromtext); + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); + } + } + } + + /* + * If we got this far and there was a refresh in progress just + * let it complete. Record where we got the notify from so we + * can perform a refresh check when the current one completes + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); + zone->notifyfrom = *from; + UNLOCK_ZONE(zone); + if (have_serial) { + dns_zone_log(zone, ISC_LOG_INFO, + "notify from %s: serial %u: refresh in " + "progress, refresh check queued", + fromtext, serial); + } else { + dns_zone_log(zone, ISC_LOG_INFO, + "notify from %s: refresh in progress, " + "refresh check queued", + fromtext); + } + return (ISC_R_SUCCESS); + } + if (have_serial) { + dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: serial %u", + fromtext, serial); + } else { + dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: no serial", + fromtext); + } + zone->notifyfrom = *from; + UNLOCK_ZONE(zone); + + if (to != NULL) { + dns_zonemgr_unreachabledel(zone->zmgr, from, to); + } + dns_zone_refresh(zone); + return (ISC_R_SUCCESS); +} + +void +dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->notify_acl != NULL) { + dns_acl_detach(&zone->notify_acl); + } + dns_acl_attach(acl, &zone->notify_acl); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->query_acl != NULL) { + dns_acl_detach(&zone->query_acl); + } + dns_acl_attach(acl, &zone->query_acl); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->queryon_acl != NULL) { + dns_acl_detach(&zone->queryon_acl); + } + dns_acl_attach(acl, &zone->queryon_acl); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->update_acl != NULL) { + dns_acl_detach(&zone->update_acl); + } + dns_acl_attach(acl, &zone->update_acl); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->forward_acl != NULL) { + dns_acl_detach(&zone->forward_acl); + } + dns_acl_attach(acl, &zone->forward_acl); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->xfr_acl != NULL) { + dns_acl_detach(&zone->xfr_acl); + } + dns_acl_attach(acl, &zone->xfr_acl); + UNLOCK_ZONE(zone); +} + +dns_acl_t * +dns_zone_getnotifyacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->notify_acl); +} + +dns_acl_t * +dns_zone_getqueryacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->query_acl); +} + +dns_acl_t * +dns_zone_getqueryonacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->queryon_acl); +} + +dns_acl_t * +dns_zone_getupdateacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->update_acl); +} + +dns_acl_t * +dns_zone_getforwardacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->forward_acl); +} + +dns_acl_t * +dns_zone_getxfracl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->xfr_acl); +} + +void +dns_zone_clearupdateacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->update_acl != NULL) { + dns_acl_detach(&zone->update_acl); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_clearforwardacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->forward_acl != NULL) { + dns_acl_detach(&zone->forward_acl); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_clearnotifyacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->notify_acl != NULL) { + dns_acl_detach(&zone->notify_acl); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_clearqueryacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->query_acl != NULL) { + dns_acl_detach(&zone->query_acl); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_clearqueryonacl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->queryon_acl != NULL) { + dns_acl_detach(&zone->queryon_acl); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_clearxfracl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->xfr_acl != NULL) { + dns_acl_detach(&zone->xfr_acl); + } + UNLOCK_ZONE(zone); +} + +bool +dns_zone_getupdatedisabled(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->update_disabled); +} + +void +dns_zone_setupdatedisabled(dns_zone_t *zone, bool state) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->update_disabled = state; +} + +bool +dns_zone_getzeronosoattl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->zero_no_soa_ttl); +} + +void +dns_zone_setzeronosoattl(dns_zone_t *zone, bool state) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->zero_no_soa_ttl = state; +} + +void +dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->check_names = severity; +} + +dns_severity_t +dns_zone_getchecknames(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->check_names); +} + +void +dns_zone_setjournalsize(dns_zone_t *zone, int32_t size) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->journalsize = size; +} + +int32_t +dns_zone_getjournalsize(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->journalsize); +} + +static void +zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length) { + isc_result_t result = ISC_R_FAILURE; + isc_buffer_t buffer; + + REQUIRE(buf != NULL); + REQUIRE(length > 1U); + + /* + * Leave space for terminating '\0'. + */ + isc_buffer_init(&buffer, buf, (unsigned int)length - 1); + if (zone->type != dns_zone_redirect && zone->type != dns_zone_key) { + if (dns_name_dynamic(&zone->origin)) { + result = dns_name_totext(&zone->origin, true, &buffer); + } + if (result != ISC_R_SUCCESS && + isc_buffer_availablelength(&buffer) >= + (sizeof("") - 1)) + { + isc_buffer_putstr(&buffer, ""); + } + + if (isc_buffer_availablelength(&buffer) > 0) { + isc_buffer_putstr(&buffer, "/"); + } + (void)dns_rdataclass_totext(zone->rdclass, &buffer); + } + + if (zone->view != NULL && strcmp(zone->view->name, "_bind") != 0 && + strcmp(zone->view->name, "_default") != 0 && + strlen(zone->view->name) < isc_buffer_availablelength(&buffer)) + { + isc_buffer_putstr(&buffer, "/"); + isc_buffer_putstr(&buffer, zone->view->name); + } + if (inline_secure(zone) && 9U < isc_buffer_availablelength(&buffer)) { + isc_buffer_putstr(&buffer, " (signed)"); + } + if (inline_raw(zone) && 11U < isc_buffer_availablelength(&buffer)) { + isc_buffer_putstr(&buffer, " (unsigned)"); + } + + buf[isc_buffer_usedlength(&buffer)] = '\0'; +} + +static void +zone_name_tostr(dns_zone_t *zone, char *buf, size_t length) { + isc_result_t result = ISC_R_FAILURE; + isc_buffer_t buffer; + + REQUIRE(buf != NULL); + REQUIRE(length > 1U); + + /* + * Leave space for terminating '\0'. + */ + isc_buffer_init(&buffer, buf, (unsigned int)length - 1); + if (dns_name_dynamic(&zone->origin)) { + result = dns_name_totext(&zone->origin, true, &buffer); + } + if (result != ISC_R_SUCCESS && + isc_buffer_availablelength(&buffer) >= (sizeof("") - 1)) + { + isc_buffer_putstr(&buffer, ""); + } + + buf[isc_buffer_usedlength(&buffer)] = '\0'; +} + +static void +zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length) { + isc_buffer_t buffer; + + REQUIRE(buf != NULL); + REQUIRE(length > 1U); + + /* + * Leave space for terminating '\0'. + */ + isc_buffer_init(&buffer, buf, (unsigned int)length - 1); + (void)dns_rdataclass_totext(zone->rdclass, &buffer); + + buf[isc_buffer_usedlength(&buffer)] = '\0'; +} + +static void +zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length) { + isc_buffer_t buffer; + + REQUIRE(buf != NULL); + REQUIRE(length > 1U); + + /* + * Leave space for terminating '\0'. + */ + isc_buffer_init(&buffer, buf, (unsigned int)length - 1); + + if (zone->view == NULL) { + isc_buffer_putstr(&buffer, "_none"); + } else if (strlen(zone->view->name) < + isc_buffer_availablelength(&buffer)) + { + isc_buffer_putstr(&buffer, zone->view->name); + } else { + isc_buffer_putstr(&buffer, "_toolong"); + } + + buf[isc_buffer_usedlength(&buffer)] = '\0'; +} + +void +dns_zone_name(dns_zone_t *zone, char *buf, size_t length) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(buf != NULL); + + LOCK_ZONE(zone); + zone_namerd_tostr(zone, buf, length); + UNLOCK_ZONE(zone); +} + +void +dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t length) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(buf != NULL); + zone_name_tostr(zone, buf, length); +} + +void +dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level, + const char *prefix, const char *fmt, va_list ap) { + char message[4096]; + const char *zstr; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + vsnprintf(message, sizeof(message), fmt, ap); + + switch (zone->type) { + case dns_zone_key: + zstr = "managed-keys-zone"; + break; + case dns_zone_redirect: + zstr = "redirect-zone"; + break; + default: + zstr = "zone "; + } + + isc_log_write(dns_lctx, category, DNS_LOGMODULE_ZONE, level, + "%s%s%s%s: %s", (prefix != NULL ? prefix : ""), + (prefix != NULL ? ": " : ""), zstr, zone->strnamerd, + message); +} + +static void +notify_log(dns_zone_t *zone, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + dns_zone_logv(zone, DNS_LOGCATEGORY_NOTIFY, level, NULL, fmt, ap); + va_end(ap); +} + +void +dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level, + const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + dns_zone_logv(zone, category, level, NULL, fmt, ap); + va_end(ap); +} + +void +dns_zone_log(dns_zone_t *zone, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, NULL, fmt, ap); + va_end(ap); +} + +static void +zone_debuglog(dns_zone_t *zone, const char *me, int debuglevel, const char *fmt, + ...) { + int level = ISC_LOG_DEBUG(debuglevel); + va_list ap; + + va_start(ap, fmt); + dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, me, fmt, ap); + va_end(ap); +} + +static void +dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + dns_zone_logv(zone, DNS_LOGCATEGORY_DNSSEC, level, NULL, fmt, ap); + va_end(ap); +} + +static int +message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *curr; + int count = 0; + + result = dns_message_firstname(msg, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(msg, section, &name); + + for (curr = ISC_LIST_TAIL(name->list); curr != NULL; + curr = ISC_LIST_PREV(curr, link)) + { + if (curr->type == type) { + count++; + } + } + result = dns_message_nextname(msg, section); + } + + return (count); +} + +void +dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->maxxfrin = maxxfrin; +} + +uint32_t +dns_zone_getmaxxfrin(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->maxxfrin); +} + +void +dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->maxxfrout = maxxfrout; +} + +uint32_t +dns_zone_getmaxxfrout(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->maxxfrout); +} + +dns_zonetype_t +dns_zone_gettype(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->type); +} + +const char * +dns_zonetype_name(dns_zonetype_t type) { + switch (type) { + case dns_zone_none: + return ("none"); + case dns_zone_primary: + return ("primary"); + case dns_zone_secondary: + return ("secondary"); + case dns_zone_mirror: + return ("mirror"); + case dns_zone_stub: + return ("stub"); + case dns_zone_staticstub: + return ("static-stub"); + case dns_zone_key: + return ("key"); + case dns_zone_dlz: + return ("dlz"); + case dns_zone_redirect: + return ("redirect"); + default: + return ("unknown"); + } +} + +dns_zonetype_t +dns_zone_getredirecttype(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(zone->type == dns_zone_redirect); + + return (zone->primaries == NULL ? dns_zone_primary + : dns_zone_secondary); +} + +dns_name_t * +dns_zone_getorigin(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (&zone->origin); +} + +void +dns_zone_settask(dns_zone_t *zone, isc_task_t *task) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->task != NULL) { + isc_task_detach(&zone->task); + } + isc_task_attach(task, &zone->task); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_settask(zone->db, zone->task); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + UNLOCK_ZONE(zone); +} + +void +dns_zone_gettask(dns_zone_t *zone, isc_task_t **target) { + REQUIRE(DNS_ZONE_VALID(zone)); + isc_task_attach(zone->task, target); +} + +void +dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (idlein == 0) { + idlein = DNS_DEFAULT_IDLEIN; + } + zone->idlein = idlein; +} + +uint32_t +dns_zone_getidlein(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->idlein); +} + +void +dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->idleout = idleout; +} + +uint32_t +dns_zone_getidleout(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->idleout); +} + +static void +notify_done(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_notify_t *notify; + isc_result_t result; + dns_message_t *message = NULL; + isc_buffer_t buf; + char rcode[128]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + UNUSED(task); + + notify = event->ev_arg; + REQUIRE(DNS_NOTIFY_VALID(notify)); + INSIST(task == notify->zone->task); + + isc_buffer_init(&buf, rcode, sizeof(rcode)); + isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); + dns_message_create(notify->zone->mctx, DNS_MESSAGE_INTENTPARSE, + &message); + + if (revent->result != ISC_R_SUCCESS) { + result = revent->result; + goto fail; + } + + result = dns_request_getresponse(revent->request, message, + DNS_MESSAGEPARSE_PRESERVEORDER); + if (result != ISC_R_SUCCESS) { + goto fail; + } + + result = dns_rcode_totext(message->rcode, &buf); + if (result == ISC_R_SUCCESS) { + notify_log(notify->zone, ISC_LOG_DEBUG(3), + "notify response from %s: %.*s", addrbuf, + (int)buf.used, rcode); + } + + goto done; + +fail: + notify_log(notify->zone, ISC_LOG_DEBUG(2), "notify to %s failed: %s", + addrbuf, isc_result_totext(result)); + if (result == ISC_R_TIMEDOUT) { + notify_log(notify->zone, ISC_LOG_DEBUG(1), + "notify to %s: retries exceeded", addrbuf); + } +done: + notify_destroy(notify, false); + isc_event_free(&event); + dns_message_detach(&message); +} + +struct secure_event { + isc_event_t e; + dns_db_t *db; + uint32_t serial; +}; + +static void +update_log_cb(void *arg, dns_zone_t *zone, int level, const char *message) { + UNUSED(arg); + dns_zone_log(zone, level, "%s", message); +} + +static isc_result_t +sync_secure_journal(dns_zone_t *zone, dns_zone_t *raw, dns_journal_t *journal, + uint32_t start, uint32_t end, dns_difftuple_t **soatuplep, + dns_diff_t *diff) { + isc_result_t result; + dns_difftuple_t *tuple = NULL; + dns_diffop_t op = DNS_DIFFOP_ADD; + int n_soa = 0; + + REQUIRE(soatuplep != NULL); + + if (start == end) { + return (DNS_R_UNCHANGED); + } + + CHECK(dns_journal_iter_init(journal, start, end, NULL)); + for (result = dns_journal_first_rr(journal); result == ISC_R_SUCCESS; + result = dns_journal_next_rr(journal)) + { + dns_name_t *name = NULL; + uint32_t ttl; + dns_rdata_t *rdata = NULL; + dns_journal_current_rr(journal, &name, &ttl, &rdata); + + if (rdata->type == dns_rdatatype_soa) { + n_soa++; + if (n_soa == 2) { + /* + * Save the latest raw SOA record. + */ + if (*soatuplep != NULL) { + dns_difftuple_free(soatuplep); + } + CHECK(dns_difftuple_create( + diff->mctx, DNS_DIFFOP_ADD, name, ttl, + rdata, soatuplep)); + } + if (n_soa == 3) { + n_soa = 1; + } + continue; + } + + /* Sanity. */ + if (n_soa == 0) { + dns_zone_log(raw, ISC_LOG_ERROR, + "corrupt journal file: '%s'\n", + raw->journal); + return (ISC_R_FAILURE); + } + + if (zone->privatetype != 0 && rdata->type == zone->privatetype) + { + continue; + } + + if (rdata->type == dns_rdatatype_nsec || + rdata->type == dns_rdatatype_rrsig || + rdata->type == dns_rdatatype_nsec3 || + rdata->type == dns_rdatatype_dnskey || + rdata->type == dns_rdatatype_nsec3param) + { + continue; + } + + op = (n_soa == 1) ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD; + + CHECK(dns_difftuple_create(diff->mctx, op, name, ttl, rdata, + &tuple)); + dns_diff_appendminimal(diff, &tuple); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +failure: + return (result); +} + +static isc_result_t +sync_secure_db(dns_zone_t *seczone, dns_zone_t *raw, dns_db_t *secdb, + dns_dbversion_t *secver, dns_difftuple_t **soatuple, + dns_diff_t *diff) { + isc_result_t result; + dns_db_t *rawdb = NULL; + dns_dbversion_t *rawver = NULL; + dns_difftuple_t *tuple = NULL, *next; + dns_difftuple_t *oldtuple = NULL, *newtuple = NULL; + dns_rdata_soa_t oldsoa, newsoa; + + REQUIRE(DNS_ZONE_VALID(seczone)); + REQUIRE(soatuple != NULL && *soatuple == NULL); + + if (!seczone->sourceserialset) { + return (DNS_R_UNCHANGED); + } + + dns_db_attach(raw->db, &rawdb); + dns_db_currentversion(rawdb, &rawver); + result = dns_db_diffx(diff, rawdb, rawver, secdb, secver, NULL); + dns_db_closeversion(rawdb, &rawver, false); + dns_db_detach(&rawdb); + + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) { + next = ISC_LIST_NEXT(tuple, link); + if (tuple->rdata.type == dns_rdatatype_nsec || + tuple->rdata.type == dns_rdatatype_rrsig || + tuple->rdata.type == dns_rdatatype_dnskey || + tuple->rdata.type == dns_rdatatype_nsec3 || + tuple->rdata.type == dns_rdatatype_nsec3param) + { + ISC_LIST_UNLINK(diff->tuples, tuple, link); + dns_difftuple_free(&tuple); + continue; + } + if (tuple->rdata.type == dns_rdatatype_soa) { + if (tuple->op == DNS_DIFFOP_DEL) { + INSIST(oldtuple == NULL); + oldtuple = tuple; + } + if (tuple->op == DNS_DIFFOP_ADD) { + INSIST(newtuple == NULL); + newtuple = tuple; + } + } + } + + if (oldtuple != NULL && newtuple != NULL) { + result = dns_rdata_tostruct(&oldtuple->rdata, &oldsoa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = dns_rdata_tostruct(&newtuple->rdata, &newsoa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * If the SOA records are the same except for the serial + * remove them from the diff. + */ + if (oldtuple->ttl == newtuple->ttl && + oldsoa.refresh == newsoa.refresh && + oldsoa.retry == newsoa.retry && + oldsoa.minimum == newsoa.minimum && + oldsoa.expire == newsoa.expire && + dns_name_equal(&oldsoa.origin, &newsoa.origin) && + dns_name_equal(&oldsoa.contact, &newsoa.contact)) + { + ISC_LIST_UNLINK(diff->tuples, oldtuple, link); + dns_difftuple_free(&oldtuple); + ISC_LIST_UNLINK(diff->tuples, newtuple, link); + dns_difftuple_free(&newtuple); + } + } + + if (ISC_LIST_EMPTY(diff->tuples)) { + return (DNS_R_UNCHANGED); + } + + /* + * If there are still SOA records in the diff they can now be removed + * saving the new SOA record. + */ + if (oldtuple != NULL) { + ISC_LIST_UNLINK(diff->tuples, oldtuple, link); + dns_difftuple_free(&oldtuple); + } + + if (newtuple != NULL) { + ISC_LIST_UNLINK(diff->tuples, newtuple, link); + *soatuple = newtuple; + } + + return (ISC_R_SUCCESS); +} + +static void +receive_secure_serial(isc_task_t *task, isc_event_t *event) { + static char me[] = "receive_secure_serial"; + isc_result_t result = ISC_R_SUCCESS; + dns_journal_t *rjournal = NULL; + dns_journal_t *sjournal = NULL; + uint32_t start, end; + dns_zone_t *zone; + dns_difftuple_t *tuple = NULL, *soatuple = NULL; + dns_update_log_t log = { update_log_cb, NULL }; + uint32_t newserial = 0, desired = 0; + isc_time_t timenow; + int level = ISC_LOG_ERROR; + + UNUSED(task); + + zone = event->ev_arg; + end = ((struct secure_event *)event)->serial; + + ENTER; + + LOCK_ZONE(zone); + + /* + * If we are already processing a receive secure serial event + * for the zone, just queue the new one and exit. + */ + if (zone->rss_event != NULL && zone->rss_event != event) { + ISC_LIST_APPEND(zone->rss_events, event, ev_link); + UNLOCK_ZONE(zone); + return; + } + +nextevent: + if (zone->rss_event != NULL) { + INSIST(zone->rss_event == event); + UNLOCK_ZONE(zone); + } else { + zone->rss_event = event; + dns_diff_init(zone->mctx, &zone->rss_diff); + + /* + * zone->db may be NULL, if the load from disk failed. + */ + result = ISC_R_SUCCESS; + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &zone->rss_db); + } else { + result = ISC_R_FAILURE; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + if (result == ISC_R_SUCCESS && zone->raw != NULL) { + dns_zone_attach(zone->raw, &zone->rss_raw); + } else { + result = ISC_R_FAILURE; + } + + UNLOCK_ZONE(zone); + + CHECK(result); + + /* + * We first attempt to sync the raw zone to the secure zone + * by using the raw zone's journal, applying all the deltas + * from the latest source-serial of the secure zone up to + * the current serial number of the raw zone. + * + * If that fails, then we'll fall back to a direct comparison + * between raw and secure zones. + */ + CHECK(dns_journal_open(zone->rss_raw->mctx, + zone->rss_raw->journal, + DNS_JOURNAL_WRITE, &rjournal)); + + result = dns_journal_open(zone->mctx, zone->journal, + DNS_JOURNAL_READ, &sjournal); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + if (!dns_journal_get_sourceserial(rjournal, &start)) { + start = dns_journal_first_serial(rjournal); + dns_journal_set_sourceserial(rjournal, start); + } + if (sjournal != NULL) { + uint32_t serial; + /* + * We read the secure journal first, if that + * exists use its value provided it is greater + * that from the raw journal. + */ + if (dns_journal_get_sourceserial(sjournal, &serial)) { + if (isc_serial_gt(serial, start)) { + start = serial; + } + } + dns_journal_destroy(&sjournal); + } + + dns_db_currentversion(zone->rss_db, &zone->rss_oldver); + CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver)); + + /* + * Try to apply diffs from the raw zone's journal to the secure + * zone. If that fails, we recover by syncing up the databases + * directly. + */ + result = sync_secure_journal(zone, zone->rss_raw, rjournal, + start, end, &soatuple, + &zone->rss_diff); + if (result == DNS_R_UNCHANGED) { + goto failure; + } else if (result != ISC_R_SUCCESS) { + CHECK(sync_secure_db(zone, zone->rss_raw, zone->rss_db, + zone->rss_oldver, &soatuple, + &zone->rss_diff)); + } + + CHECK(dns_diff_apply(&zone->rss_diff, zone->rss_db, + zone->rss_newver)); + + if (soatuple != NULL) { + uint32_t oldserial; + + CHECK(dns_db_createsoatuple( + zone->rss_db, zone->rss_oldver, + zone->rss_diff.mctx, DNS_DIFFOP_DEL, &tuple)); + oldserial = dns_soa_getserial(&tuple->rdata); + newserial = desired = + dns_soa_getserial(&soatuple->rdata); + if (!isc_serial_gt(newserial, oldserial)) { + newserial = oldserial + 1; + if (newserial == 0) { + newserial++; + } + dns_soa_setserial(newserial, &soatuple->rdata); + } + CHECK(do_one_tuple(&tuple, zone->rss_db, + zone->rss_newver, &zone->rss_diff)); + CHECK(do_one_tuple(&soatuple, zone->rss_db, + zone->rss_newver, &zone->rss_diff)); + } else { + CHECK(update_soa_serial(zone, zone->rss_db, + zone->rss_newver, + &zone->rss_diff, zone->mctx, + zone->updatemethod)); + } + } + result = dns_update_signaturesinc( + &log, zone, zone->rss_db, zone->rss_oldver, zone->rss_newver, + &zone->rss_diff, zone->sigvalidityinterval, &zone->rss_state); + if (result == DNS_R_CONTINUE) { + if (rjournal != NULL) { + dns_journal_destroy(&rjournal); + } + isc_task_send(task, &event); + return; + } + /* + * If something went wrong while trying to update the secure zone and + * the latter was already signed before, do not apply raw zone deltas + * to it as that would break existing DNSSEC signatures. However, if + * the secure zone was not yet signed (e.g. because no signing keys + * were created for it), commence applying raw zone deltas to it so + * that contents of the raw zone and the secure zone are kept in sync. + */ + if (result != ISC_R_SUCCESS && dns_db_issecure(zone->rss_db)) { + goto failure; + } + + if (rjournal == NULL) { + CHECK(dns_journal_open(zone->rss_raw->mctx, + zone->rss_raw->journal, + DNS_JOURNAL_WRITE, &rjournal)); + } + CHECK(zone_journal(zone, &zone->rss_diff, &end, + "receive_secure_serial")); + + dns_journal_set_sourceserial(rjournal, end); + dns_journal_commit(rjournal); + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + + zone->sourceserial = end; + zone->sourceserialset = true; + zone_needdump(zone, DNS_DUMP_DELAY); + + /* + * Set resign time to make sure it is set to the earliest + * signature expiration. + */ + set_resigntime(zone); + TIME_NOW(&timenow); + zone_settimer(zone, &timenow); + UNLOCK_ZONE(zone); + + dns_db_closeversion(zone->rss_db, &zone->rss_oldver, false); + dns_db_closeversion(zone->rss_db, &zone->rss_newver, true); + + if (newserial != 0) { + dns_zone_log(zone, ISC_LOG_INFO, "serial %u (unsigned %u)", + newserial, desired); + } + +failure: + isc_event_free(&zone->rss_event); + event = ISC_LIST_HEAD(zone->rss_events); + + if (zone->rss_raw != NULL) { + dns_zone_detach(&zone->rss_raw); + } + if (result != ISC_R_SUCCESS) { + LOCK_ZONE(zone); + set_resigntime(zone); + TIME_NOW(&timenow); + zone_settimer(zone, &timenow); + UNLOCK_ZONE(zone); + if (result == DNS_R_UNCHANGED) { + level = ISC_LOG_INFO; + } + dns_zone_log(zone, level, "receive_secure_serial: %s", + isc_result_totext(result)); + } + if (tuple != NULL) { + dns_difftuple_free(&tuple); + } + if (soatuple != NULL) { + dns_difftuple_free(&soatuple); + } + if (zone->rss_db != NULL) { + if (zone->rss_oldver != NULL) { + dns_db_closeversion(zone->rss_db, &zone->rss_oldver, + false); + } + if (zone->rss_newver != NULL) { + dns_db_closeversion(zone->rss_db, &zone->rss_newver, + false); + } + dns_db_detach(&zone->rss_db); + } + INSIST(zone->rss_oldver == NULL); + INSIST(zone->rss_newver == NULL); + if (rjournal != NULL) { + dns_journal_destroy(&rjournal); + } + dns_diff_clear(&zone->rss_diff); + + if (event != NULL) { + LOCK_ZONE(zone); + isc_refcount_decrement(&zone->irefs); + ISC_LIST_UNLINK(zone->rss_events, event, ev_link); + goto nextevent; + } + + event = ISC_LIST_HEAD(zone->rss_post); + while (event != NULL) { + ISC_LIST_UNLINK(zone->rss_post, event, ev_link); + rss_post(zone, event); + event = ISC_LIST_HEAD(zone->rss_post); + } + + dns_zone_idetach(&zone); +} + +static isc_result_t +zone_send_secureserial(dns_zone_t *zone, uint32_t serial) { + isc_event_t *e; + dns_zone_t *dummy = NULL; + + e = isc_event_allocate(zone->secure->mctx, zone, + DNS_EVENT_ZONESECURESERIAL, + receive_secure_serial, zone->secure, + sizeof(struct secure_event)); + ((struct secure_event *)e)->serial = serial; + INSIST(LOCKED_ZONE(zone->secure)); + zone_iattach(zone->secure, &dummy); + isc_task_send(zone->secure->task, &e); + + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE); + return (ISC_R_SUCCESS); +} + +static isc_result_t +checkandaddsoa(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + dns_rdataset_t *rdataset, uint32_t oldserial) { + dns_rdata_soa_t soa; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t temprdatalist; + dns_rdataset_t temprdataset; + isc_buffer_t b; + isc_result_t result; + unsigned char buf[DNS_SOA_BUFFERSIZE]; + dns_fixedname_t fixed; + dns_name_t *name; + + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &soa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (isc_serial_gt(soa.serial, oldserial)) { + return (dns_db_addrdataset(db, node, version, 0, rdataset, 0, + NULL)); + } + /* + * Always bump the serial. + */ + oldserial++; + if (oldserial == 0) { + oldserial++; + } + soa.serial = oldserial; + + /* + * Construct a replacement rdataset. + */ + dns_rdata_reset(&rdata); + isc_buffer_init(&b, buf, sizeof(buf)); + result = dns_rdata_fromstruct(&rdata, rdataset->rdclass, + dns_rdatatype_soa, &soa, &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdatalist_init(&temprdatalist); + temprdatalist.rdclass = rdata.rdclass; + temprdatalist.type = rdata.type; + temprdatalist.ttl = rdataset->ttl; + ISC_LIST_APPEND(temprdatalist.rdata, &rdata, link); + + dns_rdataset_init(&temprdataset); + result = dns_rdatalist_tordataset(&temprdatalist, &temprdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + name = dns_fixedname_initname(&fixed); + result = dns_db_nodefullname(db, node, name); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_getownercase(rdataset, name); + dns_rdataset_setownercase(&temprdataset, name); + return (dns_db_addrdataset(db, node, version, 0, &temprdataset, 0, + NULL)); +} + +/* + * This function should populate an nsec3paramlist_t with the + * nsecparam_t data from a zone. + */ +static isc_result_t +save_nsec3param(dns_zone_t *zone, nsec3paramlist_t *nsec3list) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset, prdataset; + dns_dbversion_t *version = NULL; + nsec3param_t *nsec3param = NULL; + nsec3param_t *nsec3p = NULL; + nsec3param_t *next; + dns_db_t *db = NULL; + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(nsec3list != NULL); + REQUIRE(ISC_LIST_EMPTY(*nsec3list)); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&prdataset); + + dns_db_attach(zone->db, &db); + CHECK(dns_db_getoriginnode(db, &node)); + + dns_db_currentversion(db, &version); + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, + dns_rdatatype_none, 0, &rdataset, NULL); + + if (result != ISC_R_SUCCESS) { + goto getprivate; + } + + /* + * Walk nsec3param rdataset making a list of parameters (note that + * multiple simultaneous nsec3 chains are annoyingly legal -- this + * is why we use an nsec3list, even though we will usually only + * have one). + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_t private = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), + "looping through nsec3param data"); + nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t)); + ISC_LINK_INIT(nsec3param, link); + + /* + * now transfer the data from the rdata to + * the nsec3param + */ + dns_nsec3param_toprivate(&rdata, &private, zone->privatetype, + nsec3param->data, + sizeof(nsec3param->data)); + nsec3param->length = private.length; + ISC_LIST_APPEND(*nsec3list, nsec3param, link); + } + +getprivate: + result = dns_db_findrdataset(db, node, version, zone->privatetype, + dns_rdatatype_none, 0, &prdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto done; + } + + /* + * walk private type records, converting them to nsec3 parameters + * using dns_nsec3param_fromprivate(), do the right thing based on + * CREATE and REMOVE flags + */ + for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&prdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_t private = DNS_RDATA_INIT; + + dns_rdataset_current(&prdataset, &private); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), + "looping through nsec3param private data"); + + /* + * Do we have a valid private record? + */ + if (!dns_nsec3param_fromprivate(&private, &rdata, buf, + sizeof(buf))) + { + continue; + } + + /* + * Remove any NSEC3PARAM records scheduled to be removed. + */ + if (NSEC3REMOVE(rdata.data[1])) { + /* + * Zero out the flags. + */ + rdata.data[1] = 0; + + for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL; + nsec3p = next) + { + next = ISC_LIST_NEXT(nsec3p, link); + + if (nsec3p->length == rdata.length + 1 && + memcmp(rdata.data, nsec3p->data + 1, + nsec3p->length - 1) == 0) + { + ISC_LIST_UNLINK(*nsec3list, nsec3p, + link); + isc_mem_put(zone->mctx, nsec3p, + sizeof(nsec3param_t)); + } + } + continue; + } + + nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t)); + ISC_LINK_INIT(nsec3param, link); + + /* + * Copy the remaining private records so the nsec/nsec3 + * chain gets created. + */ + INSIST(private.length <= sizeof(nsec3param->data)); + memmove(nsec3param->data, private.data, private.length); + nsec3param->length = private.length; + ISC_LIST_APPEND(*nsec3list, nsec3param, link); + } + +done: + if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&prdataset)) { + dns_rdataset_disassociate(&prdataset); + } + return (result); +} + +/* + * Populate new zone db with private type records found by save_nsec3param(). + */ +static isc_result_t +restore_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + nsec3paramlist_t *nsec3list) { + isc_result_t result = ISC_R_SUCCESS; + dns_diff_t diff; + dns_rdata_t rdata; + nsec3param_t *nsec3p = NULL; + nsec3param_t *next; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(!ISC_LIST_EMPTY(*nsec3list)); + + dns_diff_init(zone->mctx, &diff); + + /* + * Loop through the list of private-type records, set the INITIAL + * and CREATE flags, and the add the record to the apex of the tree + * in db. + */ + for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL; nsec3p = next) + { + next = ISC_LIST_NEXT(nsec3p, link); + dns_rdata_init(&rdata); + nsec3p->data[2] = DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL; + rdata.length = nsec3p->length; + rdata.data = nsec3p->data; + rdata.type = zone->privatetype; + rdata.rdclass = zone->rdclass; + result = update_one_rr(db, version, &diff, DNS_DIFFOP_ADD, + &zone->origin, 0, &rdata); + if (result != ISC_R_SUCCESS) { + break; + } + } + + dns_diff_clear(&diff); + return (result); +} + +static isc_result_t +copy_non_dnssec_records(dns_db_t *db, dns_db_t *version, dns_db_t *rawdb, + dns_dbiterator_t *dbiterator, unsigned int *oldserial) { + dns_dbnode_t *rawnode = NULL, *node = NULL; + dns_fixedname_t fixed; + dns_name_t *name = dns_fixedname_initname(&fixed); + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsit = NULL; + isc_result_t result; + + result = dns_dbiterator_current(dbiterator, &rawnode, name); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + dns_dbiterator_pause(dbiterator); + + result = dns_db_findnode(db, name, true, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_db_allrdatasets(rawdb, rawnode, NULL, 0, 0, &rdsit); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_rdataset_init(&rdataset); + + for (result = dns_rdatasetiter_first(rdsit); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsit)) + { + dns_rdatasetiter_current(rdsit, &rdataset); + if (rdataset.type == dns_rdatatype_nsec || + rdataset.type == dns_rdatatype_rrsig || + rdataset.type == dns_rdatatype_nsec3 || + rdataset.type == dns_rdatatype_dnskey || + rdataset.type == dns_rdatatype_nsec3param) + { + dns_rdataset_disassociate(&rdataset); + continue; + } + if (rdataset.type == dns_rdatatype_soa && oldserial != NULL) { + result = checkandaddsoa(db, node, version, &rdataset, + *oldserial); + } else { + result = dns_db_addrdataset(db, node, version, 0, + &rdataset, 0, NULL); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + if (rdsit != NULL) { + dns_rdatasetiter_destroy(&rdsit); + } + if (rawnode) { + dns_db_detachnode(rawdb, &rawnode); + } + if (node) { + dns_db_detachnode(db, &node); + } + return (result); +} + +static void +receive_secure_db(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_zone_t *zone; + dns_db_t *rawdb, *db = NULL; + dns_dbiterator_t *dbiterator = NULL; + dns_dbversion_t *version = NULL; + isc_time_t loadtime; + unsigned int oldserial = 0, *oldserialp = NULL; + nsec3paramlist_t nsec3list; + isc_event_t *setnsec3param_event; + dns_zone_t *dummy; + + UNUSED(task); + + ISC_LIST_INIT(nsec3list); + + zone = event->ev_arg; + rawdb = ((struct secure_event *)event)->db; + isc_event_free(&event); + + LOCK_ZONE(zone); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || !inline_secure(zone)) { + result = ISC_R_SHUTTINGDOWN; + goto failure; + } + + TIME_NOW(&loadtime); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + result = dns_db_getsoaserial(zone->db, NULL, &oldserial); + if (result == ISC_R_SUCCESS) { + oldserialp = &oldserial; + } + + /* + * assemble nsec3parameters from the old zone, and set a flag + * if any are found + */ + result = save_nsec3param(zone, &nsec3list); + if (result != ISC_R_SUCCESS) { + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + goto failure; + } + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin, + dns_dbtype_zone, zone->rdclass, + zone->db_argc - 1, zone->db_argv + 1, &db); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_db_setgluecachestats(db, zone->gluecachestats); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + goto failure; + } + + result = dns_db_newversion(db, &version); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_db_createiterator(rawdb, 0, &dbiterator); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiterator)) + { + result = copy_non_dnssec_records(db, version, rawdb, dbiterator, + oldserialp); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + dns_dbiterator_destroy(&dbiterator); + if (result != ISC_R_NOMORE) { + goto failure; + } + + /* + * Call restore_nsec3param() to create private-type records from + * the old nsec3 parameters and insert them into db + */ + if (!ISC_LIST_EMPTY(nsec3list)) { + result = restore_nsec3param(zone, db, version, &nsec3list); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + + dns_db_closeversion(db, &version, true); + + /* + * Lock hierarchy: zmgr, zone, raw. + */ + INSIST(zone != zone->raw); + LOCK_ZONE(zone->raw); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS); + zone_needdump(zone, 0); /* XXXMPA */ + UNLOCK_ZONE(zone->raw); + + /* + * Process any queued NSEC3PARAM change requests. + */ + while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { + setnsec3param_event = ISC_LIST_HEAD(zone->setnsec3param_queue); + ISC_LIST_UNLINK(zone->setnsec3param_queue, setnsec3param_event, + ev_link); + dummy = NULL; + zone_iattach(zone, &dummy); + isc_task_send(zone->task, &setnsec3param_event); + } + +failure: + UNLOCK_ZONE(zone); + if (dbiterator != NULL) { + dns_dbiterator_destroy(&dbiterator); + } + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_db: %s", + isc_result_totext(result)); + } + + while (!ISC_LIST_EMPTY(nsec3list)) { + nsec3param_t *nsec3p; + nsec3p = ISC_LIST_HEAD(nsec3list); + ISC_LIST_UNLINK(nsec3list, nsec3p, link); + isc_mem_put(zone->mctx, nsec3p, sizeof(nsec3param_t)); + } + if (db != NULL) { + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + dns_db_detach(&db); + } + dns_db_detach(&rawdb); + dns_zone_idetach(&zone); + + INSIST(version == NULL); +} + +static isc_result_t +zone_send_securedb(dns_zone_t *zone, dns_db_t *db) { + isc_event_t *e; + dns_db_t *dummy = NULL; + dns_zone_t *secure = NULL; + + e = isc_event_allocate(zone->secure->mctx, zone, DNS_EVENT_ZONESECUREDB, + receive_secure_db, zone->secure, + sizeof(struct secure_event)); + dns_db_attach(db, &dummy); + ((struct secure_event *)e)->db = dummy; + INSIST(LOCKED_ZONE(zone->secure)); + zone_iattach(zone->secure, &secure); + isc_task_send(zone->secure->task, &e); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) { + isc_result_t result; + dns_zone_t *secure = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); +again: + LOCK_ZONE(zone); + if (inline_raw(zone)) { + secure = zone->secure; + INSIST(secure != zone); + TRYLOCK_ZONE(result, secure); + if (result != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + } + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + result = zone_replacedb(zone, db, dump); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + if (secure != NULL) { + UNLOCK_ZONE(secure); + } + UNLOCK_ZONE(zone); + return (result); +} + +static isc_result_t +zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) { + dns_dbversion_t *ver; + isc_result_t result; + unsigned int soacount = 0; + unsigned int nscount = 0; + + /* + * 'zone' and 'zone->db' locked by caller. + */ + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(LOCKED_ZONE(zone)); + if (inline_raw(zone)) { + REQUIRE(LOCKED_ZONE(zone->secure)); + } + + result = zone_get_from_db(zone, db, &nscount, &soacount, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (soacount != 1) { + dns_zone_log(zone, ISC_LOG_ERROR, "has %d SOA records", + soacount); + result = DNS_R_BADZONE; + } + if (nscount == 0 && zone->type != dns_zone_key) { + dns_zone_log(zone, ISC_LOG_ERROR, "has no NS records"); + result = DNS_R_BADZONE; + } + if (result != ISC_R_SUCCESS) { + return (result); + } + } else { + dns_zone_log(zone, ISC_LOG_ERROR, + "retrieving SOA and NS records failed: %s", + isc_result_totext(result)); + return (result); + } + + result = check_nsec3param(zone, db); + if (result != ISC_R_SUCCESS) { + return (result); + } + + ver = NULL; + dns_db_currentversion(db, &ver); + + /* + * The initial version of a secondary zone is always dumped; + * subsequent versions may be journaled instead if this + * is enabled in the configuration. + */ + if (zone->db != NULL && zone->journal != NULL && + DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) + { + uint32_t serial, oldserial; + + dns_zone_log(zone, ISC_LOG_DEBUG(3), "generating diffs"); + + result = dns_db_getsoaserial(db, ver, &serial); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "ixfr-from-differences: unable to get " + "new serial"); + goto fail; + } + + /* + * This is checked in zone_postload() for primary zones. + */ + result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, + &oldserial, NULL, NULL, NULL, NULL, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + RUNTIME_CHECK(soacount > 0U); + if ((zone->type == dns_zone_secondary || + (zone->type == dns_zone_redirect && + zone->primaries != NULL)) && + !isc_serial_gt(serial, oldserial)) + { + uint32_t serialmin, serialmax; + serialmin = (oldserial + 1) & 0xffffffffU; + serialmax = (oldserial + 0x7fffffffU) & 0xffffffffU; + dns_zone_log(zone, ISC_LOG_ERROR, + "ixfr-from-differences: failed: " + "new serial (%u) out of range [%u - %u]", + serial, serialmin, serialmax); + result = ISC_R_RANGE; + goto fail; + } + + result = dns_db_diff(zone->mctx, db, ver, zone->db, NULL, + zone->journal); + if (result != ISC_R_SUCCESS) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + dns_zone_log(zone, ISC_LOG_ERROR, + "ixfr-from-differences: failed: " + "%s", + strbuf); + goto fallback; + } + if (dump) { + zone_needdump(zone, DNS_DUMP_DELAY); + } else { + zone_journal_compact(zone, zone->db, serial); + } + if (zone->type == dns_zone_primary && inline_raw(zone)) { + zone_send_secureserial(zone, serial); + } + } else { + fallback: + if (dump && zone->masterfile != NULL) { + /* + * If DNS_ZONEFLG_FORCEXFER was set we don't want + * to keep the old masterfile. + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) && + remove(zone->masterfile) < 0 && errno != ENOENT) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, + ISC_LOG_WARNING, + "unable to remove masterfile " + "'%s': '%s'", + zone->masterfile, strbuf); + } + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NODELAY); + } else { + zone_needdump(zone, 0); + } + } + if (dump && zone->journal != NULL) { + /* + * The in-memory database just changed, and + * because 'dump' is set, it didn't change by + * being loaded from disk. Also, we have not + * journaled diffs for this change. + * Therefore, the on-disk journal is missing + * the deltas for this change. Since it can + * no longer be used to bring the zone + * up-to-date, it is useless and should be + * removed. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), + "removing journal file"); + if (remove(zone->journal) < 0 && errno != ENOENT) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_ZONE, + ISC_LOG_WARNING, + "unable to remove journal " + "'%s': '%s'", + zone->journal, strbuf); + } + } + + if (inline_raw(zone)) { + zone_send_securedb(zone, db); + } + } + + dns_db_closeversion(db, &ver, false); + + dns_zone_log(zone, ISC_LOG_DEBUG(3), "replacing zone database"); + + if (zone->db != NULL) { + zone_detachdb(zone); + } + zone_attachdb(zone, db); + dns_db_settask(zone->db, zone->task); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); + return (ISC_R_SUCCESS); + +fail: + dns_db_closeversion(db, &ver, false); + return (result); +} + +/* The caller must hold the dblock as a writer. */ +static void +zone_attachdb(dns_zone_t *zone, dns_db_t *db) { + REQUIRE(zone->db == NULL && db != NULL); + + dns_db_attach(db, &zone->db); +} + +/* The caller must hold the dblock as a writer. */ +static void +zone_detachdb(dns_zone_t *zone) { + REQUIRE(zone->db != NULL); + + dns_zone_rpz_disable_db(zone, zone->db); + dns_zone_catz_disable_db(zone, zone->db); + dns_db_detach(&zone->db); +} + +static void +zone_xfrdone(dns_zone_t *zone, isc_result_t result) { + isc_time_t now; + bool again = false; + unsigned int soacount; + unsigned int nscount; + uint32_t serial, refresh, retry, expire, minimum, soattl; + isc_result_t xfrresult = result; + bool free_needed; + dns_zone_t *secure = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), + "zone transfer finished: %s", isc_result_totext(result)); + + /* + * Obtaining a lock on the zone->secure (see zone_send_secureserial) + * could result in a deadlock due to a LOR so we will spin if we + * can't obtain both locks. + */ +again: + LOCK_ZONE(zone); + if (inline_raw(zone)) { + secure = zone->secure; + INSIST(secure != zone); + TRYLOCK_ZONE(result, secure); + if (result != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + } + + INSIST(DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); + + TIME_NOW(&now); + switch (xfrresult) { + case ISC_R_SUCCESS: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + FALLTHROUGH; + case DNS_R_UPTODATE: + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FORCEXFER); + /* + * Has the zone expired underneath us? + */ + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db == NULL) { + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + goto same_primary; + } + + /* + * Update the zone structure's data from the actual + * SOA received. + */ + nscount = 0; + soacount = 0; + INSIST(zone->db != NULL); + result = zone_get_from_db(zone, zone->db, &nscount, &soacount, + &soattl, &serial, &refresh, &retry, + &expire, &minimum, NULL); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (result == ISC_R_SUCCESS) { + if (soacount != 1) { + dns_zone_log(zone, ISC_LOG_ERROR, + "transferred zone " + "has %d SOA records", + soacount); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) + { + zone->refresh = DNS_ZONE_DEFAULTREFRESH; + zone->retry = DNS_ZONE_DEFAULTRETRY; + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + zone_unload(zone); + goto next_primary; + } + if (nscount == 0) { + dns_zone_log(zone, ISC_LOG_ERROR, + "transferred zone " + "has no NS records"); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) + { + zone->refresh = DNS_ZONE_DEFAULTREFRESH; + zone->retry = DNS_ZONE_DEFAULTRETRY; + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + zone_unload(zone); + goto next_primary; + } + zone->refresh = RANGE(refresh, zone->minrefresh, + zone->maxrefresh); + zone->retry = RANGE(retry, zone->minretry, + zone->maxretry); + zone->expire = RANGE(expire, + zone->refresh + zone->retry, + DNS_MAX_EXPIRE); + zone->soattl = soattl; + zone->minimum = minimum; + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + } + + /* + * Set our next update/expire times. + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); + zone->refreshtime = now; + DNS_ZONE_TIME_ADD(&now, zone->expire, + &zone->expiretime); + } else { + DNS_ZONE_JITTER_ADD(&now, zone->refresh, + &zone->refreshtime); + DNS_ZONE_TIME_ADD(&now, zone->expire, + &zone->expiretime); + } + if (result == ISC_R_SUCCESS && xfrresult == ISC_R_SUCCESS) { + char buf[DNS_NAME_FORMATSIZE + sizeof(": TSIG ''")]; + if (zone->tsigkey != NULL) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&zone->tsigkey->name, namebuf, + sizeof(namebuf)); + snprintf(buf, sizeof(buf), ": TSIG '%s'", + namebuf); + } else { + buf[0] = '\0'; + } + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_INFO, "transferred serial %u%s", + serial, buf); + if (inline_raw(zone)) { + zone_send_secureserial(zone, serial); + } + } + + /* + * This is not necessary if we just performed a AXFR + * however it is necessary for an IXFR / UPTODATE and + * won't hurt with an AXFR. + */ + if (zone->masterfile != NULL || zone->journal != NULL) { + unsigned int delay = DNS_DUMP_DELAY; + + result = ISC_R_FAILURE; + if (zone->journal != NULL) { + result = isc_file_settime(zone->journal, &now); + } + if (result != ISC_R_SUCCESS && zone->masterfile != NULL) + { + result = isc_file_settime(zone->masterfile, + &now); + } + + if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NODELAY) != 0) || + result == ISC_R_FILENOTFOUND) + { + delay = 0; + } + + if ((result == ISC_R_SUCCESS || + result == ISC_R_FILENOTFOUND) && + zone->masterfile != NULL) + { + zone_needdump(zone, delay); + } else if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_ERROR, + "transfer: could not set file " + "modification time of '%s': %s", + zone->masterfile, + isc_result_totext(result)); + } + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NODELAY); + inc_stats(zone, dns_zonestatscounter_xfrsuccess); + break; + + case DNS_R_BADIXFR: + /* Force retry with AXFR. */ + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOIXFR); + goto same_primary; + + case DNS_R_TOOMANYRECORDS: + case DNS_R_VERIFYFAILURE: + DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); + inc_stats(zone, dns_zonestatscounter_xfrfail); + break; + + default: + next_primary: + /* + * Skip to next failed / untried primary. + */ + do { + zone->curprimary++; + } while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]); + same_primary: + if (zone->curprimary >= zone->primariescnt) { + zone->curprimary = 0; + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && + !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) + { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); + DNS_ZONE_SETFLAG(zone, + DNS_ZONEFLG_USEALTXFRSRC); + while (zone->curprimary < zone->primariescnt && + zone->primariesok[zone->curprimary]) + { + zone->curprimary++; + } + again = true; + } else { + DNS_ZONE_CLRFLAG(zone, + DNS_ZONEFLG_USEALTXFRSRC); + } + } else { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); + again = true; + } + inc_stats(zone, dns_zonestatscounter_xfrfail); + break; + } + zone_settimer(zone, &now); + + /* + * If creating the transfer object failed, zone->xfr is NULL. + * Otherwise, we are called as the done callback of a zone + * transfer object that just entered its shutting-down + * state. Since we are no longer responsible for shutting + * it down, we can detach our reference. + */ + if (zone->xfr != NULL) { + dns_xfrin_detach(&zone->xfr); + } + + if (zone->tsigkey != NULL) { + dns_tsigkey_detach(&zone->tsigkey); + } + + if (zone->transport != NULL) { + dns_transport_detach(&zone->transport); + } + + /* + * Handle any deferred journal compaction. + */ + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDCOMPACT)) { + dns_db_t *db = NULL; + if (dns_zone_getdb(zone, &db) == ISC_R_SUCCESS) { + zone_journal_compact(zone, db, zone->compact_serial); + dns_db_detach(&db); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); + } + } + + if (secure != NULL) { + UNLOCK_ZONE(secure); + } + /* + * This transfer finishing freed up a transfer quota slot. + * Let any other zones waiting for quota have it. + */ + if (zone->zmgr != NULL && + zone->statelist == &zone->zmgr->xfrin_in_progress) + { + UNLOCK_ZONE(zone); + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, statelink); + zone->statelist = NULL; + zmgr_resume_xfrs(zone->zmgr, false); + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + LOCK_ZONE(zone); + } + + /* + * Retry with a different server if necessary. + */ + if (again && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + queue_soa_query(zone); + } + + isc_refcount_decrement(&zone->irefs); + free_needed = exit_check(zone); + UNLOCK_ZONE(zone); + if (free_needed) { + zone_free(zone); + } +} + +static void +zone_loaddone(void *arg, isc_result_t result) { + static char me[] = "zone_loaddone"; + dns_load_t *load = arg; + dns_zone_t *zone; + isc_result_t tresult; + dns_zone_t *secure = NULL; + + REQUIRE(DNS_LOAD_VALID(load)); + zone = load->zone; + + ENTER; + + /* + * If zone loading failed, remove the update db callbacks prior + * to calling the list of callbacks in the zone load structure. + */ + if (result != ISC_R_SUCCESS) { + dns_zone_rpz_disable_db(zone, load->db); + dns_zone_catz_disable_db(zone, load->db); + } + + tresult = dns_db_endload(load->db, &load->callbacks); + if (tresult != ISC_R_SUCCESS && + (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE)) + { + result = tresult; + } + + /* + * Lock hierarchy: zmgr, zone, raw. + */ +again: + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + if (inline_secure(zone)) { + LOCK_ZONE(zone->raw); + } else if (inline_raw(zone)) { + secure = zone->secure; + TRYLOCK_ZONE(tresult, secure); + if (tresult != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + } + (void)zone_postload(zone, load->db, load->loadtime, result); + zonemgr_putio(&zone->readio); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADING); + zone_idetach(&load->callbacks.zone); + /* + * Leave the zone frozen if the reload fails. + */ + if ((result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE) && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_THAW)) + { + zone->update_disabled = false; + } + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_THAW); + if (inline_secure(zone)) { + UNLOCK_ZONE(zone->raw); + } else if (secure != NULL) { + UNLOCK_ZONE(secure); + } + UNLOCK_ZONE(zone); + + load->magic = 0; + dns_db_detach(&load->db); + if (load->zone->lctx != NULL) { + dns_loadctx_detach(&load->zone->lctx); + } + dns_zone_idetach(&load->zone); + isc_mem_putanddetach(&load->mctx, load, sizeof(*load)); +} + +void +dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(table != NULL); + REQUIRE(*table == NULL); + + LOCK_ZONE(zone); + if (zone->ssutable != NULL) { + dns_ssutable_attach(zone->ssutable, table); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->ssutable != NULL) { + dns_ssutable_detach(&zone->ssutable); + } + if (table != NULL) { + dns_ssutable_attach(table, &zone->ssutable); + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->sigvalidityinterval = interval; +} + +uint32_t +dns_zone_getsigvalidityinterval(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->sigvalidityinterval); +} + +void +dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->keyvalidityinterval = interval; +} + +uint32_t +dns_zone_getkeyvalidityinterval(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->keyvalidityinterval); +} + +void +dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval) { + isc_time_t now; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->sigresigninginterval = interval; + set_resigntime(zone); + if (zone->task != NULL) { + TIME_NOW(&now); + zone_settimer(zone, &now); + } + UNLOCK_ZONE(zone); +} + +uint32_t +dns_zone_getsigresigninginterval(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->sigresigninginterval); +} + +static void +queue_xfrin(dns_zone_t *zone) { + const char me[] = "queue_xfrin"; + isc_result_t result; + dns_zonemgr_t *zmgr = zone->zmgr; + + ENTER; + + INSIST(zone->statelist == NULL); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + ISC_LIST_APPEND(zmgr->waiting_for_xfrin, zone, statelink); + isc_refcount_increment0(&zone->irefs); + zone->statelist = &zmgr->waiting_for_xfrin; + result = zmgr_start_xfrin_ifquota(zmgr, zone); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + + if (result == ISC_R_QUOTA) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, + "zone transfer deferred due to quota"); + } else if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, + "starting zone transfer: %s", + isc_result_totext(result)); + } +} + +/* + * This event callback is called when a zone has received + * any necessary zone transfer quota. This is the time + * to go ahead and start the transfer. + */ +static void +got_transfer_quota(isc_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + dns_peer_t *peer = NULL; + char primary[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + dns_rdatatype_t xfrtype; + dns_zone_t *zone = event->ev_arg; + isc_netaddr_t primaryip; + isc_sockaddr_t sourceaddr; + isc_sockaddr_t primaryaddr; + isc_time_t now; + const char *soa_before = ""; + bool loaded; + isc_tlsctx_cache_t *zmgr_tlsctx_cache = NULL; + + UNUSED(task); + + INSIST(task == zone->task); + + isc_event_free(&event); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + zone_xfrdone(zone, ISC_R_CANCELED); + return; + } + + TIME_NOW(&now); + + isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); + if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr, + &zone->sourceaddr, &now)) + { + isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, + "got_transfer_quota: skipping zone transfer as " + "primary %s (source %s) is unreachable (cached)", + primary, source); + zone_xfrdone(zone, ISC_R_CANCELED); + return; + } + + isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); + (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) { + soa_before = "SOA before "; + } + /* + * Decide whether we should request IXFR or AXFR. + */ + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + loaded = (zone->db != NULL); + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + if (!loaded) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), + "no database exists yet, requesting AXFR of " + "initial version from %s", + primary); + xfrtype = dns_rdatatype_axfr; + } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), + "forced reload, requesting AXFR of " + "initial version from %s", + primary); + xfrtype = dns_rdatatype_axfr; + } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOIXFR)) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), + "retrying with AXFR from %s due to " + "previous IXFR failure", + primary); + xfrtype = dns_rdatatype_axfr; + LOCK_ZONE(zone); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOIXFR); + UNLOCK_ZONE(zone); + } else { + bool use_ixfr = true; + if (peer != NULL) { + result = dns_peer_getrequestixfr(peer, &use_ixfr); + } + if (peer == NULL || result != ISC_R_SUCCESS) { + use_ixfr = zone->requestixfr; + } + if (!use_ixfr) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_DEBUG(1), + "IXFR disabled, " + "requesting %sAXFR from %s", + soa_before, primary); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) { + xfrtype = dns_rdatatype_soa; + } else { + xfrtype = dns_rdatatype_axfr; + } + } else { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_DEBUG(1), + "requesting IXFR from %s", primary); + xfrtype = dns_rdatatype_ixfr; + } + } + + /* + * Determine if we should attempt to sign the request with TSIG. + */ + result = ISC_R_NOTFOUND; + + /* + * First, look for a tsig key in the primaries statement, then + * try for a server key. + */ + if ((zone->primarykeynames != NULL) && + (zone->primarykeynames[zone->curprimary] != NULL)) + { + dns_view_t *view = dns_zone_getview(zone); + dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; + result = dns_view_gettsig(view, keyname, &zone->tsigkey); + } + if (result != ISC_R_SUCCESS) { + INSIST(zone->tsigkey == NULL); + result = dns_view_getpeertsig(zone->view, &primaryip, + &zone->tsigkey); + } + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, + "could not get TSIG key for zone transfer: %s", + isc_result_totext(result)); + } + + /* + * Get the TLS transport for the primary, if configured. + */ + if ((zone->primarytlsnames != NULL) && + (zone->primarytlsnames[zone->curprimary] != NULL)) + { + dns_view_t *view = dns_zone_getview(zone); + dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary]; + result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, + &zone->transport); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_ERROR, + "could not get TLS configuration for " + "zone transfer: %s", + isc_result_totext(result)); + } + } + + LOCK_ZONE(zone); + primaryaddr = zone->primaryaddr; + sourceaddr = zone->sourceaddr; + UNLOCK_ZONE(zone); + INSIST(isc_sockaddr_pf(&primaryaddr) == isc_sockaddr_pf(&sourceaddr)); + + if (zone->xfr != NULL) { + dns_xfrin_detach(&zone->xfr); + } + + zmgr_tlsctx_attach(zone->zmgr, &zmgr_tlsctx_cache); + + result = dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr, + zone->tsigkey, zone->transport, + zmgr_tlsctx_cache, zone->mctx, + zone->zmgr->netmgr, zone_xfrdone, &zone->xfr); + + isc_tlsctx_cache_detach(&zmgr_tlsctx_cache); + + /* + * Any failure in this function is handled like a failed + * zone transfer. This ensures that we get removed from + * zmgr->xfrin_in_progress. + */ + if (result != ISC_R_SUCCESS) { + zone_xfrdone(zone, result); + return; + } + + LOCK_ZONE(zone); + if (xfrtype == dns_rdatatype_axfr) { + if (isc_sockaddr_pf(&primaryaddr) == PF_INET) { + inc_stats(zone, dns_zonestatscounter_axfrreqv4); + } else { + inc_stats(zone, dns_zonestatscounter_axfrreqv6); + } + } else if (xfrtype == dns_rdatatype_ixfr) { + if (isc_sockaddr_pf(&primaryaddr) == PF_INET) { + inc_stats(zone, dns_zonestatscounter_ixfrreqv4); + } else { + inc_stats(zone, dns_zonestatscounter_ixfrreqv6); + } + } + UNLOCK_ZONE(zone); +} + +/* + * Update forwarding support. + */ + +static void +forward_destroy(dns_forward_t *forward) { + forward->magic = 0; + if (forward->request != NULL) { + dns_request_destroy(&forward->request); + } + if (forward->msgbuf != NULL) { + isc_buffer_free(&forward->msgbuf); + } + if (forward->zone != NULL) { + LOCK(&forward->zone->lock); + if (ISC_LINK_LINKED(forward, link)) { + ISC_LIST_UNLINK(forward->zone->forwards, forward, link); + } + UNLOCK(&forward->zone->lock); + dns_zone_idetach(&forward->zone); + } + isc_mem_putanddetach(&forward->mctx, forward, sizeof(*forward)); +} + +static isc_result_t +sendtoprimary(dns_forward_t *forward) { + isc_result_t result; + isc_sockaddr_t src; + + LOCK_ZONE(forward->zone); + + if (DNS_ZONE_FLAG(forward->zone, DNS_ZONEFLG_EXITING)) { + UNLOCK_ZONE(forward->zone); + return (ISC_R_CANCELED); + } + + if (forward->which >= forward->zone->primariescnt) { + UNLOCK_ZONE(forward->zone); + return (ISC_R_NOMORE); + } + + forward->addr = forward->zone->primaries[forward->which]; + /* + * Always use TCP regardless of whether the original update + * used TCP. + * XXX The timeout may but a bit small if we are far down a + * transfer graph and have to try several primaries. + */ + switch (isc_sockaddr_pf(&forward->addr)) { + case PF_INET: + src = forward->zone->xfrsource4; + break; + case PF_INET6: + src = forward->zone->xfrsource6; + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto unlock; + } + result = dns_request_createraw(forward->zone->view->requestmgr, + forward->msgbuf, &src, &forward->addr, + forward->options, 15 /* XXX */, 0, 0, + forward->zone->task, forward_callback, + forward, &forward->request); + if (result == ISC_R_SUCCESS) { + if (!ISC_LINK_LINKED(forward, link)) { + ISC_LIST_APPEND(forward->zone->forwards, forward, link); + } + } + +unlock: + UNLOCK_ZONE(forward->zone); + return (result); +} + +static void +forward_callback(isc_task_t *task, isc_event_t *event) { + const char me[] = "forward_callback"; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_message_t *msg = NULL; + char primary[ISC_SOCKADDR_FORMATSIZE]; + isc_result_t result; + dns_forward_t *forward; + dns_zone_t *zone; + + UNUSED(task); + + forward = revent->ev_arg; + INSIST(DNS_FORWARD_VALID(forward)); + zone = forward->zone; + INSIST(DNS_ZONE_VALID(zone)); + + ENTER; + + isc_sockaddr_format(&forward->addr, primary, sizeof(primary)); + + if (revent->result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "could not forward dynamic update to %s: %s", + primary, isc_result_totext(revent->result)); + goto next_primary; + } + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + + result = dns_request_getresponse(revent->request, msg, + DNS_MESSAGEPARSE_PRESERVEORDER | + DNS_MESSAGEPARSE_CLONEBUFFER); + if (result != ISC_R_SUCCESS) { + goto next_primary; + } + + /* + * Unexpected opcode. + */ + if (msg->opcode != dns_opcode_update) { + char opcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, opcode, sizeof(opcode)); + (void)dns_opcode_totext(msg->opcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "forwarding dynamic update: " + "unexpected opcode (%.*s) from %s", + (int)rb.used, opcode, primary); + goto next_primary; + } + + switch (msg->rcode) { + /* + * Pass these rcodes back to client. + */ + case dns_rcode_noerror: + case dns_rcode_yxdomain: + case dns_rcode_yxrrset: + case dns_rcode_nxrrset: + case dns_rcode_refused: + case dns_rcode_nxdomain: { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + dns_zone_log(zone, ISC_LOG_INFO, + "forwarded dynamic update: " + "primary %s returned: %.*s", + primary, (int)rb.used, rcode); + break; + } + + /* These should not occur if the primaries/zone are valid. */ + case dns_rcode_notzone: + case dns_rcode_notauth: { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + dns_zone_log(zone, ISC_LOG_WARNING, + "forwarding dynamic update: " + "unexpected response: primary %s returned: %.*s", + primary, (int)rb.used, rcode); + goto next_primary; + } + + /* Try another server for these rcodes. */ + case dns_rcode_formerr: + case dns_rcode_servfail: + case dns_rcode_notimp: + case dns_rcode_badvers: + default: + goto next_primary; + } + + /* call callback */ + (forward->callback)(forward->callback_arg, ISC_R_SUCCESS, msg); + msg = NULL; + dns_request_destroy(&forward->request); + forward_destroy(forward); + isc_event_free(&event); + return; + +next_primary: + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + forward->which++; + dns_request_destroy(&forward->request); + result = sendtoprimary(forward); + if (result != ISC_R_SUCCESS) { + /* call callback */ + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "exhausted dynamic update forwarder list"); + (forward->callback)(forward->callback_arg, result, NULL); + forward_destroy(forward); + } +} + +isc_result_t +dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg, + dns_updatecallback_t callback, void *callback_arg) { + dns_forward_t *forward; + isc_result_t result; + isc_region_t *mr; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(msg != NULL); + REQUIRE(callback != NULL); + + forward = isc_mem_get(zone->mctx, sizeof(*forward)); + + forward->request = NULL; + forward->zone = NULL; + forward->msgbuf = NULL; + forward->which = 0; + forward->mctx = 0; + forward->callback = callback; + forward->callback_arg = callback_arg; + ISC_LINK_INIT(forward, link); + forward->magic = FORWARD_MAGIC; + forward->options = DNS_REQUESTOPT_TCP; + /* + * If we have a SIG(0) signed message we need to preserve the + * query id as that is included in the SIG(0) computation. + */ + if (msg->sig0 != NULL) { + forward->options |= DNS_REQUESTOPT_FIXEDID; + } + + mr = dns_message_getrawmessage(msg); + if (mr == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + isc_buffer_allocate(zone->mctx, &forward->msgbuf, mr->length); + result = isc_buffer_copyregion(forward->msgbuf, mr); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_mem_attach(zone->mctx, &forward->mctx); + dns_zone_iattach(zone, &forward->zone); + result = sendtoprimary(forward); + +cleanup: + if (result != ISC_R_SUCCESS) { + forward_destroy(forward); + } + return (result); +} + +isc_result_t +dns_zone_next(dns_zone_t *zone, dns_zone_t **next) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(next != NULL && *next == NULL); + + *next = ISC_LIST_NEXT(zone, link); + if (*next == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +isc_result_t +dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(first != NULL && *first == NULL); + + *first = ISC_LIST_HEAD(zmgr->zones); + if (*first == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +/*** + *** Zone manager. + ***/ + +#define KEYMGMT_OVERCOMMIT 3 +#define KEYMGMT_BITS_MIN 2U +#define KEYMGMT_BITS_MAX 32U + +/* + * WMM: Static hash functions copied from lib/dns/rbtdb.c. Should be moved to + * lib/isc/hash.c when we refactor the hash table code. + */ +#define GOLDEN_RATIO_32 0x61C88647 +#define HASHSIZE(bits) (UINT64_C(1) << (bits)) + +static uint32_t +hash_index(uint32_t val, uint32_t bits) { + return (val * GOLDEN_RATIO_32 >> (32 - bits)); +} + +static uint32_t +hash_bits_grow(uint32_t bits, uint32_t count) { + uint32_t newbits = bits; + while (count >= HASHSIZE(newbits) && newbits < KEYMGMT_BITS_MAX) { + newbits++; + } + return (newbits); +} + +static uint32_t +hash_bits_shrink(uint32_t bits, uint32_t count) { + uint32_t newbits = bits; + while (count <= HASHSIZE(newbits) && newbits > KEYMGMT_BITS_MIN) { + newbits--; + } + return (newbits); +} + +static void +zonemgr_keymgmt_init(dns_zonemgr_t *zmgr) { + dns_keymgmt_t *mgmt = isc_mem_get(zmgr->mctx, sizeof(*mgmt)); + uint32_t size; + + *mgmt = (dns_keymgmt_t){ + .bits = KEYMGMT_BITS_MIN, + }; + isc_mem_attach(zmgr->mctx, &mgmt->mctx); + isc_rwlock_init(&mgmt->lock, 0, 0); + + size = HASHSIZE(mgmt->bits); + mgmt->table = isc_mem_get(mgmt->mctx, sizeof(*mgmt->table) * size); + memset(mgmt->table, 0, size * sizeof(mgmt->table[0])); + + atomic_init(&mgmt->count, 0); + mgmt->magic = KEYMGMT_MAGIC; + + zmgr->keymgmt = mgmt; +} + +static void +zonemgr_keymgmt_destroy(dns_zonemgr_t *zmgr) { + dns_keymgmt_t *mgmt = zmgr->keymgmt; + uint32_t size; + + REQUIRE(DNS_KEYMGMT_VALID(mgmt)); + + size = HASHSIZE(mgmt->bits); + + RWLOCK(&mgmt->lock, isc_rwlocktype_write); + INSIST(mgmt->count == 0); + RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); + + mgmt->magic = 0; + isc_rwlock_destroy(&mgmt->lock); + isc_mem_put(mgmt->mctx, mgmt->table, size * sizeof(mgmt->table[0])); + isc_mem_putanddetach(&mgmt->mctx, mgmt, sizeof(dns_keymgmt_t)); +} + +static void +zonemgr_keymgmt_resize(dns_zonemgr_t *zmgr) { + dns_keyfileio_t **newtable; + dns_keymgmt_t *mgmt = zmgr->keymgmt; + uint32_t bits, newbits, count, size, newsize; + bool grow; + + REQUIRE(DNS_KEYMGMT_VALID(mgmt)); + + RWLOCK(&mgmt->lock, isc_rwlocktype_read); + count = atomic_load_relaxed(&mgmt->count); + bits = mgmt->bits; + RWUNLOCK(&mgmt->lock, isc_rwlocktype_read); + + size = HASHSIZE(bits); + INSIST(size > 0); + + if (count >= (size * KEYMGMT_OVERCOMMIT)) { + grow = true; + } else if (count < (size / 2)) { + grow = false; + } else { + /* No need to resize. */ + return; + } + + if (grow) { + newbits = hash_bits_grow(bits, count); + } else { + newbits = hash_bits_shrink(bits, count); + } + + if (newbits == bits) { + /* + * Bit values may stay the same if maximum or minimum is + * reached. + */ + return; + } + + newsize = HASHSIZE(newbits); + INSIST(newsize > 0); + + RWLOCK(&mgmt->lock, isc_rwlocktype_write); + + newtable = isc_mem_get(mgmt->mctx, sizeof(dns_keyfileio_t *) * newsize); + memset(newtable, 0, sizeof(dns_keyfileio_t *) * newsize); + + for (unsigned int i = 0; i < size; i++) { + dns_keyfileio_t *kfio, *next; + for (kfio = mgmt->table[i]; kfio != NULL; kfio = next) { + uint32_t hash = hash_index(kfio->hashval, newbits); + next = kfio->next; + kfio->next = newtable[hash]; + newtable[hash] = kfio; + } + mgmt->table[i] = NULL; + } + + isc_mem_put(mgmt->mctx, mgmt->table, sizeof(*mgmt->table) * size); + mgmt->bits = newbits; + mgmt->table = newtable; + + RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); +} + +static void +zonemgr_keymgmt_add(dns_zonemgr_t *zmgr, dns_zone_t *zone, + dns_keyfileio_t **added) { + dns_keymgmt_t *mgmt = zmgr->keymgmt; + uint32_t hashval, hash; + dns_keyfileio_t *kfio, *next; + + REQUIRE(DNS_KEYMGMT_VALID(mgmt)); + REQUIRE(added != NULL && *added == NULL); + + RWLOCK(&mgmt->lock, isc_rwlocktype_write); + + hashval = dns_name_hash(&zone->origin, false); + hash = hash_index(hashval, mgmt->bits); + + for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) { + next = kfio->next; + if (dns_name_equal(kfio->name, &zone->origin)) { + /* Already in table, increment the counter. */ + isc_refcount_increment(&kfio->references); + break; + } + } + + if (kfio == NULL) { + /* No entry found, add it. */ + kfio = isc_mem_get(mgmt->mctx, sizeof(*kfio)); + *kfio = (dns_keyfileio_t){ + .hashval = hashval, + .next = mgmt->table[hash], + .magic = KEYFILEIO_MAGIC, + }; + + isc_refcount_init(&kfio->references, 1); + + kfio->name = dns_fixedname_initname(&kfio->fname); + dns_name_copy(&zone->origin, kfio->name); + + isc_mutex_init(&kfio->lock); + + mgmt->table[hash] = kfio; + + atomic_fetch_add_relaxed(&mgmt->count, 1); + } + + RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); + + *added = kfio; + + /* + * Call resize, that function will also check if resize is necessary. + */ + zonemgr_keymgmt_resize(zmgr); +} + +static void +zonemgr_keymgmt_delete(dns_zonemgr_t *zmgr, dns_zone_t *zone, + dns_keyfileio_t **deleted) { + dns_keymgmt_t *mgmt = zmgr->keymgmt; + uint32_t hashval, hash; + dns_keyfileio_t *kfio, *prev, *next; + + REQUIRE(DNS_KEYMGMT_VALID(mgmt)); + REQUIRE(deleted != NULL && DNS_KEYFILEIO_VALID(*deleted)); + + RWLOCK(&mgmt->lock, isc_rwlocktype_write); + + hashval = dns_name_hash(&zone->origin, false); + hash = hash_index(hashval, mgmt->bits); + + prev = NULL; + for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) { + next = kfio->next; + if (dns_name_equal(kfio->name, &zone->origin)) { + INSIST(kfio == *deleted); + *deleted = NULL; + + if (isc_refcount_decrement(&kfio->references) == 1) { + if (prev == NULL) { + mgmt->table[hash] = kfio->next; + } else { + prev->next = kfio->next; + } + + isc_refcount_destroy(&kfio->references); + isc_mutex_destroy(&kfio->lock); + isc_mem_put(mgmt->mctx, kfio, sizeof(*kfio)); + + atomic_fetch_sub_relaxed(&mgmt->count, 1); + } + break; + } + + prev = kfio; + } + + RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); + + /* + * Call resize, that function will also check if resize is necessary. + */ + zonemgr_keymgmt_resize(zmgr); +} + +isc_result_t +dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, isc_nm_t *netmgr, + dns_zonemgr_t **zmgrp) { + dns_zonemgr_t *zmgr; + isc_result_t result; + + zmgr = isc_mem_get(mctx, sizeof(*zmgr)); + zmgr->mctx = NULL; + isc_refcount_init(&zmgr->refs, 1); + isc_mem_attach(mctx, &zmgr->mctx); + zmgr->taskmgr = taskmgr; + zmgr->timermgr = timermgr; + zmgr->netmgr = netmgr; + zmgr->zonetasks = NULL; + zmgr->loadtasks = NULL; + zmgr->mctxpool = NULL; + zmgr->task = NULL; + zmgr->checkdsrl = NULL; + zmgr->notifyrl = NULL; + zmgr->refreshrl = NULL; + zmgr->startupnotifyrl = NULL; + zmgr->startuprefreshrl = NULL; + ISC_LIST_INIT(zmgr->zones); + ISC_LIST_INIT(zmgr->waiting_for_xfrin); + ISC_LIST_INIT(zmgr->xfrin_in_progress); + memset(zmgr->unreachable, 0, sizeof(zmgr->unreachable)); + for (size_t i = 0; i < UNREACH_CACHE_SIZE; i++) { + atomic_init(&zmgr->unreachable[i].expire, 0); + } + isc_rwlock_init(&zmgr->rwlock, 0, 0); + + zmgr->transfersin = 10; + zmgr->transfersperns = 2; + + /* Unreachable lock. */ + isc_rwlock_init(&zmgr->urlock, 0, 0); + + /* Create a single task for queueing of SOA queries. */ + result = isc_task_create(taskmgr, 1, &zmgr->task); + if (result != ISC_R_SUCCESS) { + goto free_urlock; + } + + isc_task_setname(zmgr->task, "zmgr", zmgr); + result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, + &zmgr->checkdsrl); + if (result != ISC_R_SUCCESS) { + goto free_task; + } + + result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, + &zmgr->notifyrl); + if (result != ISC_R_SUCCESS) { + goto free_checkdsrl; + } + + result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, + &zmgr->refreshrl); + if (result != ISC_R_SUCCESS) { + goto free_notifyrl; + } + + result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, + &zmgr->startupnotifyrl); + if (result != ISC_R_SUCCESS) { + goto free_refreshrl; + } + + result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, + &zmgr->startuprefreshrl); + if (result != ISC_R_SUCCESS) { + goto free_startupnotifyrl; + } + + /* Key file I/O locks. */ + zonemgr_keymgmt_init(zmgr); + + /* Default to 20 refresh queries / notifies / checkds per second. */ + setrl(zmgr->checkdsrl, &zmgr->checkdsrate, 20); + setrl(zmgr->notifyrl, &zmgr->notifyrate, 20); + setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, 20); + setrl(zmgr->refreshrl, &zmgr->serialqueryrate, 20); + setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, 20); + isc_ratelimiter_setpushpop(zmgr->startupnotifyrl, true); + isc_ratelimiter_setpushpop(zmgr->startuprefreshrl, true); + + zmgr->iolimit = 1; + zmgr->ioactive = 0; + ISC_LIST_INIT(zmgr->high); + ISC_LIST_INIT(zmgr->low); + + isc_mutex_init(&zmgr->iolock); + + zmgr->tlsctx_cache = NULL; + isc_rwlock_init(&zmgr->tlsctx_cache_rwlock, 0, 0); + + zmgr->magic = ZONEMGR_MAGIC; + + *zmgrp = zmgr; + return (ISC_R_SUCCESS); + +#if 0 + free_iolock: + isc_mutex_destroy(&zmgr->iolock); +#endif /* if 0 */ +free_startupnotifyrl: + isc_ratelimiter_detach(&zmgr->startupnotifyrl); +free_refreshrl: + isc_ratelimiter_detach(&zmgr->refreshrl); +free_notifyrl: + isc_ratelimiter_detach(&zmgr->notifyrl); +free_checkdsrl: + isc_ratelimiter_detach(&zmgr->checkdsrl); +free_task: + isc_task_detach(&zmgr->task); +free_urlock: + isc_rwlock_destroy(&zmgr->urlock); + isc_rwlock_destroy(&zmgr->rwlock); + isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr)); + isc_mem_detach(&mctx); + return (result); +} + +isc_result_t +dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep) { + isc_result_t result; + isc_mem_t *mctx = NULL; + dns_zone_t *zone = NULL; + void *item; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(zonep != NULL && *zonep == NULL); + + if (zmgr->mctxpool == NULL) { + return (ISC_R_FAILURE); + } + + item = isc_pool_get(zmgr->mctxpool); + if (item == NULL) { + return (ISC_R_FAILURE); + } + + isc_mem_attach((isc_mem_t *)item, &mctx); + result = dns_zone_create(&zone, mctx); + isc_mem_detach(&mctx); + + if (result == ISC_R_SUCCESS) { + *zonep = zone; + } + + return (result); +} + +isc_result_t +dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { + isc_result_t result; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + if (zmgr->zonetasks == NULL) { + return (ISC_R_FAILURE); + } + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + LOCK_ZONE(zone); + REQUIRE(zone->task == NULL); + REQUIRE(zone->timer == NULL); + REQUIRE(zone->zmgr == NULL); + + isc_taskpool_gettask(zmgr->zonetasks, &zone->task); + isc_taskpool_gettask(zmgr->loadtasks, &zone->loadtask); + + /* + * Set the task name. The tag will arbitrarily point to one + * of the zones sharing the task (in practice, the one + * to be managed last). + */ + isc_task_setname(zone->task, "zone", zone); + isc_task_setname(zone->loadtask, "loadzone", zone); + + result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL, + NULL, zone->task, zone_timer, zone, + &zone->timer); + + if (result != ISC_R_SUCCESS) { + goto cleanup_tasks; + } + + /* + * The timer "holds" a iref. + */ + isc_refcount_increment0(&zone->irefs); + + zonemgr_keymgmt_add(zmgr, zone, &zone->kfio); + INSIST(zone->kfio != NULL); + + ISC_LIST_APPEND(zmgr->zones, zone, link); + zone->zmgr = zmgr; + isc_refcount_increment(&zmgr->refs); + + goto unlock; + +cleanup_tasks: + isc_task_detach(&zone->loadtask); + isc_task_detach(&zone->task); + +unlock: + UNLOCK_ZONE(zone); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + return (result); +} + +void +dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(zone->zmgr == zmgr); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + LOCK_ZONE(zone); + + ISC_LIST_UNLINK(zmgr->zones, zone, link); + + if (zone->kfio != NULL) { + zonemgr_keymgmt_delete(zmgr, zone, &zone->kfio); + ENSURE(zone->kfio == NULL); + } + + /* Detach below, outside of the write lock. */ + zone->zmgr = NULL; + + UNLOCK_ZONE(zone); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + + dns_zonemgr_detach(&zmgr); +} + +void +dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target) { + REQUIRE(DNS_ZONEMGR_VALID(source)); + REQUIRE(target != NULL && *target == NULL); + + isc_refcount_increment(&source->refs); + + *target = source; +} + +void +dns_zonemgr_detach(dns_zonemgr_t **zmgrp) { + dns_zonemgr_t *zmgr; + + REQUIRE(zmgrp != NULL); + zmgr = *zmgrp; + *zmgrp = NULL; + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + if (isc_refcount_decrement(&zmgr->refs) == 1) { + zonemgr_free(zmgr); + } +} + +isc_result_t +dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr) { + dns_zone_t *p; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); + for (p = ISC_LIST_HEAD(zmgr->zones); p != NULL; + p = ISC_LIST_NEXT(p, link)) + { + dns_zone_maintenance(p); + } + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); + + /* + * Recent configuration changes may have increased the + * amount of available transfers quota. Make sure any + * transfers currently blocked on quota get started if + * possible. + */ + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + zmgr_resume_xfrs(zmgr, true); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + return (ISC_R_SUCCESS); +} + +void +dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + zmgr_resume_xfrs(zmgr, true); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); +} + +void +dns_zonemgr_shutdown(dns_zonemgr_t *zmgr) { + dns_zone_t *zone; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + isc_ratelimiter_shutdown(zmgr->checkdsrl); + isc_ratelimiter_shutdown(zmgr->notifyrl); + isc_ratelimiter_shutdown(zmgr->refreshrl); + isc_ratelimiter_shutdown(zmgr->startupnotifyrl); + isc_ratelimiter_shutdown(zmgr->startuprefreshrl); + + if (zmgr->task != NULL) { + isc_task_destroy(&zmgr->task); + } + if (zmgr->zonetasks != NULL) { + isc_taskpool_destroy(&zmgr->zonetasks); + } + if (zmgr->loadtasks != NULL) { + isc_taskpool_destroy(&zmgr->loadtasks); + } + if (zmgr->mctxpool != NULL) { + isc_pool_destroy(&zmgr->mctxpool); + } + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); + for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; + zone = ISC_LIST_NEXT(zone, link)) + { + LOCK_ZONE(zone); + forward_cancel(zone); + UNLOCK_ZONE(zone); + } + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); +} + +static isc_result_t +mctxinit(void **target, void *arg) { + isc_mem_t *mctx = NULL; + + UNUSED(arg); + + REQUIRE(target != NULL && *target == NULL); + + isc_mem_create(&mctx); + isc_mem_setname(mctx, "zonemgr-pool"); + + *target = mctx; + return (ISC_R_SUCCESS); +} + +static void +mctxfree(void **target) { + isc_mem_t *mctx = *(isc_mem_t **)target; + isc_mem_detach(&mctx); + *target = NULL; +} + +#define ZONES_PER_TASK 100 +#define ZONES_PER_MCTX 1000 + +isc_result_t +dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones) { + isc_result_t result; + int ntasks = num_zones / ZONES_PER_TASK; + int nmctx = num_zones / ZONES_PER_MCTX; + isc_taskpool_t *pool = NULL; + isc_pool_t *mctxpool = NULL; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + /* + * For anything fewer than 1000 zones we use 10 tasks in + * the task pools. More than that, and we'll scale at one + * task per 100 zones. Similarly, for anything smaller than + * 2000 zones we use 2 memory contexts, then scale at 1:1000. + */ + if (ntasks < 10) { + ntasks = 10; + } + if (nmctx < 2) { + nmctx = 2; + } + + /* Create or resize the zone task pools. */ + if (zmgr->zonetasks == NULL) { + result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks, + 2, false, &pool); + } else { + result = isc_taskpool_expand(&zmgr->zonetasks, ntasks, false, + &pool); + } + + if (result == ISC_R_SUCCESS) { + zmgr->zonetasks = pool; + } + + /* + * We always set all tasks in the zone-load task pool to + * privileged. This prevents other tasks in the system from + * running while the server task manager is in privileged + * mode. + */ + pool = NULL; + if (zmgr->loadtasks == NULL) { + result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks, + UINT_MAX, true, &pool); + } else { + result = isc_taskpool_expand(&zmgr->loadtasks, ntasks, true, + &pool); + } + + if (result == ISC_R_SUCCESS) { + zmgr->loadtasks = pool; + } + + /* Create or resize the zone memory context pool. */ + if (zmgr->mctxpool == NULL) { + result = isc_pool_create(zmgr->mctx, nmctx, mctxfree, mctxinit, + NULL, &mctxpool); + } else { + result = isc_pool_expand(&zmgr->mctxpool, nmctx, &mctxpool); + } + + if (result == ISC_R_SUCCESS) { + zmgr->mctxpool = mctxpool; + } + + return (result); +} + +static void +zonemgr_free(dns_zonemgr_t *zmgr) { + isc_mem_t *mctx; + + INSIST(ISC_LIST_EMPTY(zmgr->zones)); + + zmgr->magic = 0; + + isc_refcount_destroy(&zmgr->refs); + isc_mutex_destroy(&zmgr->iolock); + isc_ratelimiter_detach(&zmgr->checkdsrl); + isc_ratelimiter_detach(&zmgr->notifyrl); + isc_ratelimiter_detach(&zmgr->refreshrl); + isc_ratelimiter_detach(&zmgr->startupnotifyrl); + isc_ratelimiter_detach(&zmgr->startuprefreshrl); + + isc_rwlock_destroy(&zmgr->urlock); + isc_rwlock_destroy(&zmgr->rwlock); + isc_rwlock_destroy(&zmgr->tlsctx_cache_rwlock); + + zonemgr_keymgmt_destroy(zmgr); + + mctx = zmgr->mctx; + if (zmgr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); + } + isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr)); + isc_mem_detach(&mctx); +} + +void +dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + zmgr->transfersin = value; +} + +uint32_t +dns_zonemgr_gettransfersin(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->transfersin); +} + +void +dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + zmgr->transfersperns = value; +} + +uint32_t +dns_zonemgr_gettransfersperns(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->transfersperns); +} + +isc_taskmgr_t * +dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->taskmgr); +} + +isc_timermgr_t * +dns_zonemgr_gettimermgr(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->timermgr); +} + +/* + * Try to start a new incoming zone transfer to fill a quota + * slot that was just vacated. + * + * Requires: + * The zone manager is locked by the caller. + */ +static void +zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi) { + dns_zone_t *zone; + dns_zone_t *next; + + for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); zone != NULL; + zone = next) + { + isc_result_t result; + next = ISC_LIST_NEXT(zone, statelink); + result = zmgr_start_xfrin_ifquota(zmgr, zone); + if (result == ISC_R_SUCCESS) { + if (multi) { + continue; + } + /* + * We successfully filled the slot. We're done. + */ + break; + } else if (result == ISC_R_QUOTA) { + /* + * Not enough quota. This is probably the per-server + * quota, because we usually get called when a unit of + * global quota has just been freed. Try the next + * zone, it may succeed if it uses another primary. + */ + continue; + } else { + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, + ISC_LOG_DEBUG(1), + "starting zone transfer: %s", + isc_result_totext(result)); + break; + } + } +} + +/* + * Try to start an incoming zone transfer for 'zone', quota permitting. + * + * Requires: + * The zone manager is locked by the caller. + * + * Returns: + * ISC_R_SUCCESS There was enough quota and we attempted to + * start a transfer. zone_xfrdone() has been or will + * be called. + * ISC_R_QUOTA Not enough quota. + * Others Failure. + */ +static isc_result_t +zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone) { + dns_peer_t *peer = NULL; + isc_netaddr_t primaryip; + uint32_t nxfrsin, nxfrsperns; + dns_zone_t *x; + uint32_t maxtransfersin, maxtransfersperns; + isc_event_t *e; + + /* + * If we are exiting just pretend we got quota so the zone will + * be cleaned up in the zone's task context. + */ + LOCK_ZONE(zone); + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + UNLOCK_ZONE(zone); + goto gotquota; + } + + /* + * Find any configured information about the server we'd + * like to transfer this zone from. + */ + isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); + (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); + UNLOCK_ZONE(zone); + + /* + * Determine the total maximum number of simultaneous + * transfers allowed, and the maximum for this specific + * primary. + */ + maxtransfersin = zmgr->transfersin; + maxtransfersperns = zmgr->transfersperns; + if (peer != NULL) { + (void)dns_peer_gettransfers(peer, &maxtransfersperns); + } + + /* + * Count the total number of transfers that are in progress, + * and the number of transfers in progress from this primary. + * We linearly scan a list of all transfers; if this turns + * out to be too slow, we could hash on the primary address. + */ + nxfrsin = nxfrsperns = 0; + for (x = ISC_LIST_HEAD(zmgr->xfrin_in_progress); x != NULL; + x = ISC_LIST_NEXT(x, statelink)) + { + isc_netaddr_t xip; + + LOCK_ZONE(x); + isc_netaddr_fromsockaddr(&xip, &x->primaryaddr); + UNLOCK_ZONE(x); + + nxfrsin++; + if (isc_netaddr_equal(&xip, &primaryip)) { + nxfrsperns++; + } + } + + /* Enforce quota. */ + if (nxfrsin >= maxtransfersin) { + return (ISC_R_QUOTA); + } + + if (nxfrsperns >= maxtransfersperns) { + return (ISC_R_QUOTA); + } + +gotquota: + /* + * We have sufficient quota. Move the zone to the "xfrin_in_progress" + * list and send it an event to let it start the actual transfer in the + * context of its own task. + */ + e = isc_event_allocate(zmgr->mctx, zmgr, DNS_EVENT_ZONESTARTXFRIN, + got_transfer_quota, zone, sizeof(isc_event_t)); + + LOCK_ZONE(zone); + INSIST(zone->statelist == &zmgr->waiting_for_xfrin); + ISC_LIST_UNLINK(zmgr->waiting_for_xfrin, zone, statelink); + ISC_LIST_APPEND(zmgr->xfrin_in_progress, zone, statelink); + zone->statelist = &zmgr->xfrin_in_progress; + isc_task_send(zone->task, &e); + dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, + "Transfer started."); + UNLOCK_ZONE(zone); + + return (ISC_R_SUCCESS); +} + +void +dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(iolimit > 0); + + zmgr->iolimit = iolimit; +} + +uint32_t +dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->iolimit); +} + +/* + * Get permission to request a file handle from the OS. + * An event will be sent to action when one is available. + * There are two queues available (high and low), the high + * queue will be serviced before the low one. + * + * zonemgr_putio() must be called after the event is delivered to + * 'action'. + */ + +static isc_result_t +zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_io_t **iop) { + dns_io_t *io; + bool queue; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(iop != NULL && *iop == NULL); + + io = isc_mem_get(zmgr->mctx, sizeof(*io)); + + io->event = isc_event_allocate(zmgr->mctx, task, DNS_EVENT_IOREADY, + action, arg, sizeof(*io->event)); + + io->zmgr = zmgr; + io->high = high; + io->task = NULL; + isc_task_attach(task, &io->task); + ISC_LINK_INIT(io, link); + io->magic = IO_MAGIC; + + LOCK(&zmgr->iolock); + zmgr->ioactive++; + queue = (zmgr->ioactive > zmgr->iolimit); + if (queue) { + if (io->high) { + ISC_LIST_APPEND(zmgr->high, io, link); + } else { + ISC_LIST_APPEND(zmgr->low, io, link); + } + } + UNLOCK(&zmgr->iolock); + *iop = io; + + if (!queue) { + isc_task_send(io->task, &io->event); + } + return (ISC_R_SUCCESS); +} + +static void +zonemgr_putio(dns_io_t **iop) { + dns_io_t *io; + dns_io_t *next; + dns_zonemgr_t *zmgr; + + REQUIRE(iop != NULL); + io = *iop; + *iop = NULL; + REQUIRE(DNS_IO_VALID(io)); + + INSIST(!ISC_LINK_LINKED(io, link)); + INSIST(io->event == NULL); + + zmgr = io->zmgr; + isc_task_detach(&io->task); + io->magic = 0; + isc_mem_put(zmgr->mctx, io, sizeof(*io)); + + LOCK(&zmgr->iolock); + INSIST(zmgr->ioactive > 0); + zmgr->ioactive--; + next = HEAD(zmgr->high); + if (next == NULL) { + next = HEAD(zmgr->low); + } + if (next != NULL) { + if (next->high) { + ISC_LIST_UNLINK(zmgr->high, next, link); + } else { + ISC_LIST_UNLINK(zmgr->low, next, link); + } + INSIST(next->event != NULL); + } + UNLOCK(&zmgr->iolock); + if (next != NULL) { + isc_task_send(next->task, &next->event); + } +} + +static void +zonemgr_cancelio(dns_io_t *io) { + bool send_event = false; + + REQUIRE(DNS_IO_VALID(io)); + + /* + * If we are queued to be run then dequeue. + */ + LOCK(&io->zmgr->iolock); + if (ISC_LINK_LINKED(io, link)) { + if (io->high) { + ISC_LIST_UNLINK(io->zmgr->high, io, link); + } else { + ISC_LIST_UNLINK(io->zmgr->low, io, link); + } + + send_event = true; + INSIST(io->event != NULL); + } + UNLOCK(&io->zmgr->iolock); + if (send_event) { + io->event->ev_attributes |= ISC_EVENTATTR_CANCELED; + isc_task_send(io->task, &io->event); + } +} + +static void +zone_saveunique(dns_zone_t *zone, const char *path, const char *templat) { + char *buf; + int buflen; + isc_result_t result; + + buflen = strlen(path) + strlen(templat) + 2; + + buf = isc_mem_get(zone->mctx, buflen); + + result = isc_file_template(path, templat, buf, buflen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = isc_file_renameunique(path, buf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + dns_zone_log(zone, ISC_LOG_WARNING, + "unable to load from '%s'; " + "renaming file to '%s' for failure analysis and " + "retransferring.", + path, buf); + +cleanup: + isc_mem_put(zone->mctx, buf, buflen); +} + +static void +setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value) { + isc_interval_t interval; + uint32_t s, ns; + uint32_t pertic; + isc_result_t result; + + if (value == 0) { + value = 1; + } + + if (value == 1) { + s = 1; + ns = 0; + pertic = 1; + } else if (value <= 10) { + s = 0; + ns = 1000000000 / value; + pertic = 1; + } else { + s = 0; + ns = (1000000000 / value) * 10; + pertic = 10; + } + + isc_interval_set(&interval, s, ns); + + result = isc_ratelimiter_setinterval(rl, &interval); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_ratelimiter_setpertic(rl, pertic); + + *rate = value; +} + +void +dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + setrl(zmgr->checkdsrl, &zmgr->checkdsrate, value); +} + +void +dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + setrl(zmgr->notifyrl, &zmgr->notifyrate, value); +} + +void +dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, value); +} + +void +dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + setrl(zmgr->refreshrl, &zmgr->serialqueryrate, value); + /* XXXMPA separate out once we have the code to support this. */ + setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, value); +} + +unsigned int +dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->notifyrate); +} + +unsigned int +dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->startupnotifyrate); +} + +unsigned int +dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + return (zmgr->serialqueryrate); +} + +bool +dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local, isc_time_t *now) { + unsigned int i; + uint32_t seconds = isc_time_seconds(now); + uint32_t count = 0; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->urlock, isc_rwlocktype_read); + for (i = 0; i < UNREACH_CACHE_SIZE; i++) { + if (atomic_load(&zmgr->unreachable[i].expire) >= seconds && + isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && + isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) + { + atomic_store_relaxed(&zmgr->unreachable[i].last, + seconds); + count = zmgr->unreachable[i].count; + break; + } + } + RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); + return (i < UNREACH_CACHE_SIZE && count > 1U); +} + +void +dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local) { + unsigned int i; + char primary[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(remote, primary, sizeof(primary)); + isc_sockaddr_format(local, source, sizeof(source)); + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->urlock, isc_rwlocktype_read); + for (i = 0; i < UNREACH_CACHE_SIZE; i++) { + if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && + isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) + { + atomic_store_relaxed(&zmgr->unreachable[i].expire, 0); + break; + } + } + RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); +} + +void +dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, + isc_sockaddr_t *local, isc_time_t *now) { + uint32_t seconds = isc_time_seconds(now); + uint32_t expire = 0, last = seconds; + unsigned int slot = UNREACH_CACHE_SIZE, oldest = 0; + bool update_entry = true; + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->urlock, isc_rwlocktype_write); + for (unsigned int i = 0; i < UNREACH_CACHE_SIZE; i++) { + /* Existing entry? */ + if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && + isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) + { + update_entry = false; + slot = i; + expire = atomic_load_relaxed( + &zmgr->unreachable[i].expire); + break; + } + /* Pick first empty slot? */ + if (atomic_load_relaxed(&zmgr->unreachable[i].expire) < seconds) + { + slot = i; + break; + } + /* The worst case, least recently used slot? */ + if (atomic_load_relaxed(&zmgr->unreachable[i].last) < last) { + last = atomic_load_relaxed(&zmgr->unreachable[i].last); + oldest = i; + } + } + + /* We haven't found any existing or free slots, use the oldest */ + if (slot == UNREACH_CACHE_SIZE) { + slot = oldest; + } + + if (expire < seconds) { + /* Expired or new entry, reset count to 1 */ + zmgr->unreachable[slot].count = 1; + } else { + zmgr->unreachable[slot].count++; + } + atomic_store_relaxed(&zmgr->unreachable[slot].expire, + seconds + UNREACH_HOLD_TIME); + atomic_store_relaxed(&zmgr->unreachable[slot].last, seconds); + if (update_entry) { + zmgr->unreachable[slot].remote = *remote; + zmgr->unreachable[slot].local = *local; + } + + RWUNLOCK(&zmgr->urlock, isc_rwlocktype_write); +} + +void +dns_zone_forcereload(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->type == dns_zone_primary || + (zone->type == dns_zone_redirect && zone->primaries == NULL)) + { + return; + } + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FORCEXFER); + UNLOCK_ZONE(zone); + dns_zone_refresh(zone); +} + +bool +dns_zone_isforced(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)); +} + +isc_result_t +dns_zone_setstatistics(dns_zone_t *zone, bool on) { + /* + * This function is obsoleted. + */ + UNUSED(zone); + UNUSED(on); + return (ISC_R_NOTIMPLEMENTED); +} + +uint64_t * +dns_zone_getstatscounters(dns_zone_t *zone) { + /* + * This function is obsoleted. + */ + UNUSED(zone); + return (NULL); +} + +void +dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(zone->stats == NULL); + + LOCK_ZONE(zone); + zone->stats = NULL; + isc_stats_attach(stats, &zone->stats); + UNLOCK_ZONE(zone); +} + +void +dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->requeststats_on && stats == NULL) { + zone->requeststats_on = false; + } else if (!zone->requeststats_on && stats != NULL) { + if (zone->requeststats == NULL) { + isc_stats_attach(stats, &zone->requeststats); + } + zone->requeststats_on = true; + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (zone->requeststats_on && stats != NULL) { + if (zone->rcvquerystats == NULL) { + dns_stats_attach(stats, &zone->rcvquerystats); + zone->requeststats_on = true; + } + } + UNLOCK_ZONE(zone); +} + +void +dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (stats != NULL && zone->dnssecsignstats == NULL) { + dns_stats_attach(stats, &zone->dnssecsignstats); + } + UNLOCK_ZONE(zone); +} + +dns_stats_t * +dns_zone_getdnssecsignstats(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->dnssecsignstats); +} + +isc_stats_t * +dns_zone_getrequeststats(dns_zone_t *zone) { + /* + * We don't lock zone for efficiency reason. This is not catastrophic + * because requeststats must always be valid when requeststats_on is + * true. + * Some counters may be incremented while requeststats_on is becoming + * false, or some cannot be incremented just after the statistics are + * installed, but it shouldn't matter much in practice. + */ + if (zone->requeststats_on) { + return (zone->requeststats); + } else { + return (NULL); + } +} + +/* + * Return the received query stats bucket + * see note from dns_zone_getrequeststats() + */ +dns_stats_t * +dns_zone_getrcvquerystats(dns_zone_t *zone) { + if (zone->requeststats_on) { + return (zone->rcvquerystats); + } else { + return (NULL); + } +} + +void +dns_zone_dialup(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone_debuglog(zone, "dns_zone_dialup", 3, "notify = %d, refresh = %d", + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY), + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) { + dns_zone_notify(zone); + } + if (zone->type != dns_zone_primary && zone->primaries != NULL && + DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) + { + dns_zone_refresh(zone); + } +} + +void +dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DIALNOTIFY | + DNS_ZONEFLG_DIALREFRESH | + DNS_ZONEFLG_NOREFRESH); + switch (dialup) { + case dns_dialuptype_no: + break; + case dns_dialuptype_yes: + DNS_ZONE_SETFLAG(zone, (DNS_ZONEFLG_DIALNOTIFY | + DNS_ZONEFLG_DIALREFRESH | + DNS_ZONEFLG_NOREFRESH)); + break; + case dns_dialuptype_notify: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY); + break; + case dns_dialuptype_notifypassive: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); + break; + case dns_dialuptype_refresh: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALREFRESH); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); + break; + case dns_dialuptype_passive: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); + break; + default: + UNREACHABLE(); + } + UNLOCK_ZONE(zone); +} + +isc_result_t +dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + result = dns_zone_setstring(zone, &zone->keydirectory, directory); + UNLOCK_ZONE(zone); + + return (result); +} + +const char * +dns_zone_getkeydirectory(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->keydirectory); +} + +unsigned int +dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state) { + dns_zone_t *zone; + unsigned int count = 0; + + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); + switch (state) { + case DNS_ZONESTATE_XFERRUNNING: + for (zone = ISC_LIST_HEAD(zmgr->xfrin_in_progress); + zone != NULL; zone = ISC_LIST_NEXT(zone, statelink)) + { + count++; + } + break; + case DNS_ZONESTATE_XFERDEFERRED: + for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); + zone != NULL; zone = ISC_LIST_NEXT(zone, statelink)) + { + count++; + } + break; + case DNS_ZONESTATE_SOAQUERY: + for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; + zone = ISC_LIST_NEXT(zone, link)) + { + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { + count++; + } + } + break; + case DNS_ZONESTATE_ANY: + for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; + zone = ISC_LIST_NEXT(zone, link)) + { + dns_view_t *view = zone->view; + if (view != NULL && strcmp(view->name, "_bind") == 0) { + continue; + } + count++; + } + break; + case DNS_ZONESTATE_AUTOMATIC: + for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; + zone = ISC_LIST_NEXT(zone, link)) + { + dns_view_t *view = zone->view; + if (view != NULL && strcmp(view->name, "_bind") == 0) { + continue; + } + if (zone->automatic) { + count++; + } + } + break; + default: + UNREACHABLE(); + } + + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); + + return (count); +} + +void +dns_zone_lock_keyfiles(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->kasp == NULL) { + /* No need to lock, nothing is writing key files. */ + return; + } + + REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio)); + isc_mutex_lock(&zone->kfio->lock); +} + +void +dns_zone_unlock_keyfiles(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (zone->kasp == NULL) { + /* No need to lock, nothing is writing key files. */ + return; + } + + REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio)); + isc_mutex_unlock(&zone->kfio->lock); +} + +isc_result_t +dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name, + dns_rdata_t *rdata) { + bool ok = true; + bool fail = false; + char namebuf[DNS_NAME_FORMATSIZE]; + char namebuf2[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + int level = ISC_LOG_WARNING; + dns_name_t bad; + + REQUIRE(DNS_ZONE_VALID(zone)); + + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES) && + rdata->type != dns_rdatatype_nsec3) + { + return (ISC_R_SUCCESS); + } + + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL) || + rdata->type == dns_rdatatype_nsec3) + { + level = ISC_LOG_ERROR; + fail = true; + } + + ok = dns_rdata_checkowner(name, rdata->rdclass, rdata->type, true); + if (!ok) { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); + dns_zone_log(zone, level, "%s/%s: %s", namebuf, typebuf, + isc_result_totext(DNS_R_BADOWNERNAME)); + if (fail) { + return (DNS_R_BADOWNERNAME); + } + } + + dns_name_init(&bad, NULL); + ok = dns_rdata_checknames(rdata, name, &bad); + if (!ok) { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_name_format(&bad, namebuf2, sizeof(namebuf2)); + dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); + dns_zone_log(zone, level, "%s/%s: %s: %s ", namebuf, typebuf, + namebuf2, isc_result_totext(DNS_R_BADNAME)); + if (fail) { + return (DNS_R_BADNAME); + } + } + + return (ISC_R_SUCCESS); +} + +void +dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->checkmx = checkmx; +} + +void +dns_zone_setchecksrv(dns_zone_t *zone, dns_checksrvfunc_t checksrv) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->checksrv = checksrv; +} + +void +dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->checkns = checkns; +} + +void +dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->isself = isself; + zone->isselfarg = arg; + UNLOCK_ZONE(zone); +} + +void +dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->notifydelay = delay; + UNLOCK_ZONE(zone); +} + +uint32_t +dns_zone_getnotifydelay(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->notifydelay); +} + +isc_result_t +dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, + bool deleteit) { + isc_result_t result; + REQUIRE(DNS_ZONE_VALID(zone)); + + dnssec_log(zone, ISC_LOG_NOTICE, + "dns_zone_signwithkey(algorithm=%u, keyid=%u)", algorithm, + keyid); + LOCK_ZONE(zone); + result = zone_signwithkey(zone, algorithm, keyid, deleteit); + UNLOCK_ZONE(zone); + + return (result); +} + +/* + * Called when a dynamic update for an NSEC3PARAM record is received. + * + * If set, transform the NSEC3 salt into human-readable form so that it can be + * logged. Then call zone_addnsec3chain(), passing NSEC3PARAM RDATA to it. + */ +isc_result_t +dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) { + isc_result_t result; + char salt[255 * 2 + 1]; + + REQUIRE(DNS_ZONE_VALID(zone)); + + result = dns_nsec3param_salttotext(nsec3param, salt, sizeof(salt)); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dnssec_log(zone, ISC_LOG_NOTICE, + "dns_zone_addnsec3chain(hash=%u, iterations=%u, salt=%s)", + nsec3param->hash, nsec3param->iterations, salt); + LOCK_ZONE(zone); + result = zone_addnsec3chain(zone, nsec3param); + UNLOCK_ZONE(zone); + + return (result); +} + +void +dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes) { + REQUIRE(DNS_ZONE_VALID(zone)); + + if (nodes == 0) { + nodes = 1; + } + zone->nodes = nodes; +} + +void +dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures) { + REQUIRE(DNS_ZONE_VALID(zone)); + + /* + * We treat signatures as a signed value so explicitly + * limit its range here. + */ + if (signatures > INT32_MAX) { + signatures = INT32_MAX; + } else if (signatures == 0) { + signatures = 1; + } + zone->signatures = signatures; +} + +uint32_t +dns_zone_getsignatures(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->signatures); +} + +void +dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->privatetype = type; +} + +dns_rdatatype_t +dns_zone_getprivatetype(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->privatetype); +} + +static isc_result_t +zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, + bool deleteit) { + dns_signing_t *signing; + dns_signing_t *current; + isc_result_t result = ISC_R_SUCCESS; + isc_time_t now; + dns_db_t *db = NULL; + + signing = isc_mem_get(zone->mctx, sizeof *signing); + + signing->magic = 0; + signing->db = NULL; + signing->dbiterator = NULL; + signing->algorithm = algorithm; + signing->keyid = keyid; + signing->deleteit = deleteit; + signing->done = false; + + TIME_NOW(&now); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + if (db == NULL) { + result = ISC_R_NOTFOUND; + goto cleanup; + } + + dns_db_attach(db, &signing->db); + + for (current = ISC_LIST_HEAD(zone->signing); current != NULL; + current = ISC_LIST_NEXT(current, link)) + { + if (current->db == signing->db && + current->algorithm == signing->algorithm && + current->keyid == signing->keyid) + { + if (current->deleteit != signing->deleteit) { + current->done = true; + } else { + goto cleanup; + } + } + } + + result = dns_db_createiterator(signing->db, 0, &signing->dbiterator); + + if (result == ISC_R_SUCCESS) { + result = dns_dbiterator_first(signing->dbiterator); + } + if (result == ISC_R_SUCCESS) { + dns_dbiterator_pause(signing->dbiterator); + ISC_LIST_INITANDAPPEND(zone->signing, signing, link); + signing = NULL; + if (isc_time_isepoch(&zone->signingtime)) { + zone->signingtime = now; + if (zone->task != NULL) { + zone_settimer(zone, &now); + } + } + } + +cleanup: + if (signing != NULL) { + if (signing->db != NULL) { + dns_db_detach(&signing->db); + } + if (signing->dbiterator != NULL) { + dns_dbiterator_destroy(&signing->dbiterator); + } + isc_mem_put(zone->mctx, signing, sizeof *signing); + } + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +/* Called once; *timep should be set to the current time. */ +static isc_result_t +next_keyevent(dst_key_t *key, isc_stdtime_t *timep) { + isc_result_t result; + isc_stdtime_t now, then = 0, event; + int i; + + now = *timep; + + for (i = 0; i <= DST_MAX_TIMES; i++) { + result = dst_key_gettime(key, i, &event); + if (result == ISC_R_SUCCESS && event > now && + (then == 0 || event < then)) + { + then = event; + } + } + + if (then != 0) { + *timep = then; + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +static isc_result_t +rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, + const dns_rdata_t *rdata, bool *flag) { + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + isc_result_t result; + + dns_rdataset_init(&rdataset); + if (rdata->type == dns_rdatatype_nsec3) { + CHECK(dns_db_findnsec3node(db, name, false, &node)); + } else { + CHECK(dns_db_findnode(db, name, false, &node)); + } + result = dns_db_findrdataset(db, node, ver, rdata->type, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + *flag = false; + result = ISC_R_SUCCESS; + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t myrdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &myrdata); + if (!dns_rdata_compare(&myrdata, rdata)) { + break; + } + } + dns_rdataset_disassociate(&rdataset); + if (result == ISC_R_SUCCESS) { + *flag = true; + } else if (result == ISC_R_NOMORE) { + *flag = false; + result = ISC_R_SUCCESS; + } + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/* + * Add records to signal the state of signing or of key removal. + */ +static isc_result_t +add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, + dns_dbversion_t *ver, dns_diff_t *diff, bool sign_all) { + dns_difftuple_t *tuple, *newtuple = NULL; + dns_rdata_dnskey_t dnskey; + dns_rdata_t rdata = DNS_RDATA_INIT; + bool flag; + isc_region_t r; + isc_result_t result = ISC_R_SUCCESS; + uint16_t keyid; + unsigned char buf[5]; + dns_name_t *name = dns_db_origin(db); + + for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + if (tuple->rdata.type != dns_rdatatype_dnskey) { + continue; + } + + result = dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if ((dnskey.flags & (DNS_KEYFLAG_OWNERMASK | + DNS_KEYTYPE_NOAUTH)) != DNS_KEYOWNER_ZONE) + { + continue; + } + + dns_rdata_toregion(&tuple->rdata, &r); + + keyid = dst_region_computeid(&r); + + buf[0] = dnskey.algorithm; + buf[1] = (keyid & 0xff00) >> 8; + buf[2] = (keyid & 0xff); + buf[3] = (tuple->op == DNS_DIFFOP_ADD) ? 0 : 1; + buf[4] = 0; + rdata.data = buf; + rdata.length = sizeof(buf); + rdata.type = privatetype; + rdata.rdclass = tuple->rdata.rdclass; + + if (sign_all || tuple->op == DNS_DIFFOP_DEL) { + CHECK(rr_exists(db, ver, name, &rdata, &flag)); + if (flag) { + continue; + } + + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, + name, 0, &rdata, &newtuple)); + CHECK(do_one_tuple(&newtuple, db, ver, diff)); + INSIST(newtuple == NULL); + } + + /* + * Remove any record which says this operation has already + * completed. + */ + buf[4] = 1; + CHECK(rr_exists(db, ver, name, &rdata, &flag)); + if (flag) { + CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, + name, 0, &rdata, &newtuple)); + CHECK(do_one_tuple(&newtuple, db, ver, diff)); + INSIST(newtuple == NULL); + } + } +failure: + return (result); +} + +/* + * See if dns__zone_updatesigs() will update signature for RRset 'rrtype' at + * the apex, and if not tickle them and cause to sign so that newly activated + * keys are used. + */ +static isc_result_t +tickle_apex_rrset(dns_rdatatype_t rrtype, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, + dns__zonediff_t *zonediff, dst_key_t **keys, + unsigned int nkeys, isc_stdtime_t inception, + isc_stdtime_t keyexpire, bool check_ksk, + bool keyset_kskonly) { + dns_difftuple_t *tuple; + isc_result_t result; + + for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + if (tuple->rdata.type == rrtype && + dns_name_equal(&tuple->name, &zone->origin)) + { + break; + } + } + + if (tuple == NULL) { + result = del_sigs(zone, db, ver, &zone->origin, rrtype, + zonediff, keys, nkeys, now, false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:del_sigs -> %s", + isc_result_totext(result)); + return (result); + } + result = add_sigs(db, ver, &zone->origin, zone, rrtype, + zonediff->diff, keys, nkeys, zone->mctx, + inception, keyexpire, check_ksk, + keyset_kskonly); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:add_sigs -> %s", + isc_result_totext(result)); + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff) { + isc_result_t result; + isc_stdtime_t inception, soaexpire, keyexpire; + bool check_ksk, keyset_kskonly; + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + unsigned int nkeys = 0, i; + + result = dns__zone_findkeys(zone, db, ver, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:dns__zone_findkeys -> %s", + isc_result_totext(result)); + return (result); + } + + inception = now - 3600; /* Allow for clock skew. */ + soaexpire = now + dns_zone_getsigvalidityinterval(zone); + + keyexpire = dns_zone_getkeyvalidityinterval(zone); + if (keyexpire == 0) { + keyexpire = soaexpire - 1; + } else { + keyexpire += now; + } + + check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); + keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); + + /* + * See if dns__zone_updatesigs() will update DNSKEY/CDS/CDNSKEY + * signature and if not cause them to sign so that newly activated + * keys are used. + */ + result = tickle_apex_rrset(dns_rdatatype_dnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = tickle_apex_rrset(dns_rdatatype_cds, zone, db, ver, now, diff, + zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = tickle_apex_rrset(dns_rdatatype_cdnskey, zone, db, ver, now, + diff, zonediff, zone_keys, nkeys, inception, + keyexpire, check_ksk, keyset_kskonly); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns__zone_updatesigs(diff, db, ver, zone_keys, nkeys, zone, + inception, soaexpire, keyexpire, now, + check_ksk, keyset_kskonly, zonediff); + + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "sign_apex:dns__zone_updatesigs -> %s", + isc_result_totext(result)); + goto failure; + } + +failure: + for (i = 0; i < nkeys; i++) { + dst_key_free(&zone_keys[i]); + } + return (result); +} + +static isc_result_t +clean_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + CHECK(dns_db_getoriginnode(db, &node)); + + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &rdataset, NULL); + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_nsec3param_deletechains(db, ver, zone, true, diff); + +failure: + if (node != NULL) { + dns_db_detachnode(db, &node); + } + return (result); +} + +/* + * Given an RRSIG rdataset and an algorithm, determine whether there + * are any signatures using that algorithm. + */ +static bool +signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t rrsig; + isc_result_t result; + + REQUIRE(rdataset == NULL || rdataset->type == dns_rdatatype_rrsig); + if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) { + return (false); + } + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rrsig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + if (rrsig.algorithm == alg) { + return (true); + } + } + + return (false); +} + +static isc_result_t +add_chains(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_diff_t *diff) { + dns_name_t *origin; + bool build_nsec3; + isc_result_t result; + + origin = dns_db_origin(db); + CHECK(dns_private_chains(db, ver, zone->privatetype, NULL, + &build_nsec3)); + if (build_nsec3) { + CHECK(dns_nsec3_addnsec3sx(db, ver, origin, zone_nsecttl(zone), + false, zone->privatetype, diff)); + } + CHECK(updatesecure(db, ver, origin, zone_nsecttl(zone), true, diff)); + +failure: + return (result); +} + +static void +dnssec_report(const char *format, ...) { + va_list args; + va_start(args, format); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_ZONE, + ISC_LOG_INFO, format, args); + va_end(args); +} + +static void +checkds_destroy(dns_checkds_t *checkds, bool locked) { + isc_mem_t *mctx; + + REQUIRE(DNS_CHECKDS_VALID(checkds)); + + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: destroy DS query"); + + if (checkds->zone != NULL) { + if (!locked) { + LOCK_ZONE(checkds->zone); + } + REQUIRE(LOCKED_ZONE(checkds->zone)); + if (ISC_LINK_LINKED(checkds, link)) { + ISC_LIST_UNLINK(checkds->zone->checkds_requests, + checkds, link); + } + if (!locked) { + UNLOCK_ZONE(checkds->zone); + } + if (locked) { + zone_idetach(&checkds->zone); + } else { + dns_zone_idetach(&checkds->zone); + } + } + if (checkds->request != NULL) { + dns_request_destroy(&checkds->request); + } + if (checkds->key != NULL) { + dns_tsigkey_detach(&checkds->key); + } + if (checkds->transport != NULL) { + dns_transport_detach(&checkds->transport); + } + mctx = checkds->mctx; + isc_mem_put(checkds->mctx, checkds, sizeof(*checkds)); + isc_mem_detach(&mctx); +} + +static isc_result_t +make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize, + dns_rdata_t *target) { + isc_result_t result; + isc_buffer_t b; + isc_region_t r; + + isc_buffer_init(&b, buf, bufsize); + result = dst_key_todns(key, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_reset(target); + isc_buffer_usedregion(&b, &r); + dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey, + &r); + return (ISC_R_SUCCESS); +} + +static bool +do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now, + bool dspublish) { + dns_kasp_t *kasp = dns_zone_getkasp(zone); + const char *dir = dns_zone_getkeydirectory(zone); + isc_result_t result; + uint32_t count = 0; + + if (dspublish) { + (void)dst_key_getnum(key, DST_NUM_DSPUBCOUNT, &count); + count += 1; + dst_key_setnum(key, DST_NUM_DSPUBCOUNT, count); + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: %u DS published " + "for key %u", + count, dst_key_id(key)); + + if (count != zone->parentalscnt) { + return false; + } + } else { + (void)dst_key_getnum(key, DST_NUM_DSDELCOUNT, &count); + count += 1; + dst_key_setnum(key, DST_NUM_DSDELCOUNT, count); + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: %u DS withdrawn " + "for key %u", + count, dst_key_id(key)); + + if (count != zone->parentalscnt) { + return false; + } + } + + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: checkds %s for key " + "%u", + dspublish ? "published" : "withdrawn", dst_key_id(key)); + + dns_zone_lock_keyfiles(zone); + result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, dir, now, now, + dspublish, dst_key_id(key), + dst_key_alg(key)); + dns_zone_unlock_keyfiles(zone); + + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_WARNING, + "checkds: checkds for key %u failed: %s", + dst_key_id(key), isc_result_totext(result)); + return false; + } + + return true; +} + +static isc_result_t +validate_ds(dns_zone_t *zone, dns_message_t *message) { + UNUSED(zone); + UNUSED(message); + + /* Get closest trust anchor */ + + /* Check that trust anchor is (grand)parent of zone. */ + + /* Find the DNSKEY signing the message. */ + + /* Check that DNSKEY is in chain of trust. */ + + /* Validate DS RRset. */ + + return (ISC_R_SUCCESS); +} + +static void +checkds_done(isc_task_t *task, isc_event_t *event) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char rcode[128]; + dns_checkds_t *checkds; + dns_zone_t *zone; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_dnsseckey_t *key; + dns_dnsseckeylist_t keys; + dns_kasp_t *kasp = NULL; + dns_message_t *message = NULL; + dns_rdataset_t *ds_rrset = NULL; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + isc_buffer_t buf; + isc_result_t result; + isc_stdtime_t now; + isc_time_t timenow; + bool rekey = false; + bool empty = false; + + UNUSED(task); + + checkds = event->ev_arg; + REQUIRE(DNS_CHECKDS_VALID(checkds)); + + zone = checkds->zone; + INSIST(task == zone->task); + + ISC_LIST_INIT(keys); + + kasp = zone->kasp; + INSIST(kasp != NULL); + + isc_buffer_init(&buf, rcode, sizeof(rcode)); + isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); + + dns_zone_log(zone, ISC_LOG_DEBUG(1), "checkds: DS query to %s: done", + addrbuf); + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &message); + INSIST(message != NULL); + + CHECK(revent->result); + CHECK(dns_request_getresponse(revent->request, message, + DNS_MESSAGEPARSE_PRESERVEORDER)); + CHECK(dns_rcode_totext(message->rcode, &buf)); + + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: DS response from %s: %.*s", addrbuf, + (int)buf.used, rcode); + + /* Validate response. */ + CHECK(validate_ds(zone, message)); + + /* Check RCODE. */ + if (message->rcode != dns_rcode_noerror) { + dns_zone_log(zone, ISC_LOG_NOTICE, + "checkds: bad DS response from %s: %.*s", addrbuf, + (int)buf.used, rcode); + goto failure; + } + + /* Make sure that either AA or RA bit is set. */ + if ((message->flags & DNS_MESSAGEFLAG_AA) == 0 && + (message->flags & DNS_MESSAGEFLAG_RA) == 0) + { + dns_zone_log(zone, ISC_LOG_NOTICE, + "checkds: bad DS response from %s: expected AA or " + "RA bit set", + addrbuf); + goto failure; + } + + /* Lookup DS RRset. */ + result = dns_message_firstname(message, DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + + dns_message_currentname(message, DNS_SECTION_ANSWER, &name); + if (dns_name_compare(&zone->origin, name) != 0) { + goto next; + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type != dns_rdatatype_ds) { + goto next; + } + + ds_rrset = rdataset; + break; + } + + if (ds_rrset != NULL) { + break; + } + + next: + result = dns_message_nextname(message, DNS_SECTION_ANSWER); + } + + if (ds_rrset == NULL) { + empty = true; + dns_zone_log(zone, ISC_LOG_NOTICE, + "checkds: empty DS response from %s", addrbuf); + } + + TIME_NOW(&timenow); + now = isc_time_seconds(&timenow); + + CHECK(dns_zone_getdb(zone, &db)); + dns_db_currentversion(db, &version); + + KASP_LOCK(kasp); + LOCK_ZONE(zone); + for (key = ISC_LIST_HEAD(zone->checkds_ok); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + bool alldone = false, found = false; + bool checkdspub = false, checkdsdel = false, ksk = false; + dst_key_state_t ds_state = DST_KEY_STATE_NA; + isc_stdtime_t published = 0, withdrawn = 0; + isc_result_t ret = ISC_R_SUCCESS; + + /* Is this key have the KSK role? */ + (void)dst_key_role(key->key, &ksk, NULL); + if (!ksk) { + continue; + } + + /* Do we need to check the DS RRset for this key? */ + (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state); + (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published); + (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn); + + if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) { + checkdspub = true; + } else if (ds_state == DST_KEY_STATE_UNRETENTIVE && + withdrawn == 0) + { + checkdsdel = true; + } + if (!checkdspub && !checkdsdel) { + continue; + } + + if (empty) { + goto dswithdrawn; + } + + /* Find the appropriate DS record. */ + ret = dns_rdataset_first(ds_rrset); + while (ret == ISC_R_SUCCESS) { + dns_rdata_ds_t ds; + dns_rdata_t dnskey = DNS_RDATA_INIT; + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t r; + unsigned char dsbuf[DNS_DS_BUFFERSIZE]; + unsigned char keybuf[DST_KEY_MAXSIZE]; + + dns_rdataset_current(ds_rrset, &rdata); + r = dns_rdata_tostruct(&rdata, &ds, NULL); + if (r != ISC_R_SUCCESS) { + goto nextds; + } + /* Check key tag and algorithm. */ + if (dst_key_id(key->key) != ds.key_tag) { + goto nextds; + } + if (dst_key_alg(key->key) != ds.algorithm) { + goto nextds; + } + /* Derive DS from DNSKEY, see if the rdata is equal. */ + make_dnskey(key->key, keybuf, sizeof(keybuf), &dnskey); + r = dns_ds_buildrdata(&zone->origin, &dnskey, + ds.digest_type, dsbuf, &dsrdata); + if (r != ISC_R_SUCCESS) { + goto nextds; + } + if (dns_rdata_compare(&rdata, &dsrdata) == 0) { + found = true; + if (checkdspub) { + /* DS Published. */ + alldone = do_checkds(zone, key->key, + now, true); + if (alldone) { + rekey = true; + } + } + } + + nextds: + ret = dns_rdataset_next(ds_rrset); + } + + dswithdrawn: + /* DS withdrawn. */ + if (checkdsdel && !found) { + alldone = do_checkds(zone, key->key, now, false); + if (alldone) { + rekey = true; + } + } + } + UNLOCK_ZONE(zone); + KASP_UNLOCK(kasp); + + /* Rekey after checkds. */ + if (rekey) { + dns_zone_rekey(zone, false); + } + +failure: + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: DS request failed: %s", + isc_result_totext(result)); + } + + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + + while (!ISC_LIST_EMPTY(keys)) { + key = ISC_LIST_HEAD(keys); + ISC_LIST_UNLINK(keys, key, link); + dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key); + } + + isc_event_free(&event); + checkds_destroy(checkds, false); + dns_message_detach(&message); +} + +static bool +checkds_isqueued(dns_zone_t *zone, isc_sockaddr_t *addr, dns_tsigkey_t *key, + dns_transport_t *transport) { + dns_checkds_t *checkds; + + for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL; + checkds = ISC_LIST_NEXT(checkds, link)) + { + if (checkds->request != NULL) { + continue; + } + if (addr != NULL && isc_sockaddr_equal(addr, &checkds->dst) && + checkds->key == key && checkds->transport == transport) + { + return (true); + } + } + return (false); +} + +static isc_result_t +checkds_create(isc_mem_t *mctx, unsigned int flags, dns_checkds_t **checkdsp) { + dns_checkds_t *checkds; + + REQUIRE(checkdsp != NULL && *checkdsp == NULL); + + checkds = isc_mem_get(mctx, sizeof(*checkds)); + *checkds = (dns_checkds_t){ + .flags = flags, + }; + + isc_mem_attach(mctx, &checkds->mctx); + isc_sockaddr_any(&checkds->dst); + ISC_LINK_INIT(checkds, link); + checkds->magic = CHECKDS_MAGIC; + *checkdsp = checkds; + return (ISC_R_SUCCESS); +} + +static isc_result_t +checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep) { + dns_message_t *message = NULL; + + dns_name_t *tempname = NULL; + dns_rdataset_t *temprdataset = NULL; + + isc_result_t result; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(messagep != NULL && *messagep == NULL); + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); + + message->opcode = dns_opcode_query; + message->rdclass = zone->rdclass; + message->flags |= DNS_MESSAGEFLAG_RD; + + result = dns_message_gettempname(message, &tempname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_gettemprdataset(message, &temprdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Make question. + */ + dns_name_init(tempname, NULL); + dns_name_clone(&zone->origin, tempname); + dns_rdataset_makequestion(temprdataset, zone->rdclass, + dns_rdatatype_ds); + ISC_LIST_APPEND(tempname->list, temprdataset, link); + dns_message_addname(message, tempname, DNS_SECTION_QUESTION); + tempname = NULL; + temprdataset = NULL; + + *messagep = message; + return (ISC_R_SUCCESS); + +cleanup: + if (tempname != NULL) { + dns_message_puttempname(message, &tempname); + } + if (temprdataset != NULL) { + dns_message_puttemprdataset(message, &temprdataset); + } + dns_message_detach(&message); + return (result); +} + +static void +checkds_send_toaddr(isc_task_t *task, isc_event_t *event) { + dns_checkds_t *checkds; + isc_result_t result; + dns_message_t *message = NULL; + isc_netaddr_t dstip; + dns_tsigkey_t *key = NULL; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t src; + unsigned int options, timeout; + bool have_checkdssource = false; + + checkds = event->ev_arg; + REQUIRE(DNS_CHECKDS_VALID(checkds)); + + UNUSED(task); + + LOCK_ZONE(checkds->zone); + + checkds->event = NULL; + + if (DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_LOADED) == 0) { + result = ISC_R_CANCELED; + goto cleanup; + } + + if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 || + DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_EXITING) || + checkds->zone->view->requestmgr == NULL || + checkds->zone->db == NULL) + { + result = ISC_R_CANCELED; + goto cleanup; + } + + /* + * The raw IPv4 address should also exist. Don't send to the + * mapped form. + */ + if (isc_sockaddr_pf(&checkds->dst) == PF_INET6 && + IN6_IS_ADDR_V4MAPPED(&checkds->dst.type.sin6.sin6_addr)) + { + isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: ignoring IPv6 mapped IPV4 address: %s", + addrbuf); + result = ISC_R_CANCELED; + goto cleanup; + } + + result = checkds_createmessage(checkds->zone, &message); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); + if (checkds->key != NULL) { + /* Transfer ownership of key */ + key = checkds->key; + checkds->key = NULL; + } else { + isc_netaddr_fromsockaddr(&dstip, &checkds->dst); + result = dns_view_getpeertsig(checkds->zone->view, &dstip, + &key); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + dns_zone_log(checkds->zone, ISC_LOG_ERROR, + "checkds: DS query to %s not sent. " + "Peer TSIG key lookup failure.", + addrbuf); + goto cleanup_message; + } + } + + if (key != NULL) { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(&key->name, namebuf, sizeof(namebuf)); + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: sending DS query to %s : TSIG (%s)", + addrbuf, namebuf); + } else { + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: sending DS query to %s", addrbuf); + } + options = 0; + if (checkds->zone->view->peers != NULL) { + dns_peer_t *peer = NULL; + bool usetcp = false; + result = dns_peerlist_peerbyaddr(checkds->zone->view->peers, + &dstip, &peer); + if (result == ISC_R_SUCCESS) { + result = dns_peer_getquerysource(peer, &src); + if (result == ISC_R_SUCCESS) { + have_checkdssource = true; + } + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + options |= DNS_FETCHOPT_TCP; + } + } + } + switch (isc_sockaddr_pf(&checkds->dst)) { + case PF_INET: + if (!have_checkdssource) { + src = checkds->zone->parentalsrc4; + } + break; + case PF_INET6: + if (!have_checkdssource) { + src = checkds->zone->parentalsrc6; + } + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_key; + } + + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: create request for DS query to %s", addrbuf); + + timeout = 15; + options |= DNS_REQUESTOPT_TCP; + result = dns_request_create( + checkds->zone->view->requestmgr, message, &src, &checkds->dst, + options, key, timeout * 3, timeout, 2, checkds->zone->task, + checkds_done, checkds, &checkds->request); + if (result != ISC_R_SUCCESS) { + dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), + "checkds: dns_request_create() to %s failed: %s", + addrbuf, isc_result_totext(result)); + } + +cleanup_key: + if (key != NULL) { + dns_tsigkey_detach(&key); + } +cleanup_message: + dns_message_detach(&message); +cleanup: + UNLOCK_ZONE(checkds->zone); + isc_event_free(&event); + if (result != ISC_R_SUCCESS) { + checkds_destroy(checkds, false); + } +} + +static isc_result_t +checkds_send_queue(dns_checkds_t *checkds) { + isc_event_t *e; + isc_result_t result; + + INSIST(checkds->event == NULL); + e = isc_event_allocate(checkds->mctx, NULL, DNS_EVENT_CHECKDSSENDTOADDR, + checkds_send_toaddr, checkds, + sizeof(isc_event_t)); + e->ev_arg = checkds; + e->ev_sender = NULL; + result = isc_ratelimiter_enqueue(checkds->zone->zmgr->checkdsrl, + checkds->zone->task, &e); + if (result != ISC_R_SUCCESS) { + isc_event_free(&e); + checkds->event = NULL; + } + return (result); +} + +static void +checkds_send(dns_zone_t *zone) { + dns_view_t *view = dns_zone_getview(zone); + isc_result_t result; + unsigned int flags = 0; + + /* + * Zone lock held by caller. + */ + REQUIRE(LOCKED_ZONE(zone)); + + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: start sending DS queries to %u parentals", + zone->parentalscnt); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: abort, named exiting"); + return; + } + + for (unsigned int i = 0; i < zone->parentalscnt; i++) { + dns_tsigkey_t *key = NULL; + dns_transport_t *transport = NULL; + isc_sockaddr_t dst; + dns_checkds_t *checkds = NULL; + + if ((zone->parentalkeynames != NULL) && + (zone->parentalkeynames[i] != NULL)) + { + dns_name_t *keyname = zone->parentalkeynames[i]; + (void)dns_view_gettsig(view, keyname, &key); + } + + if ((zone->parentaltlsnames != NULL) && + (zone->parentaltlsnames[i] != NULL)) + { + dns_name_t *tlsname = zone->parentaltlsnames[i]; + (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS, + tlsname, &transport); + dns_zone_logc( + zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, + "got TLS configuration for zone transfer"); + } + + dst = zone->parentals[i]; + + /* TODO: glue the transport to the checkds request */ + + if (checkds_isqueued(zone, &dst, key, transport)) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: DS query to parent " + "%d is queued", + i); + if (key != NULL) { + dns_tsigkey_detach(&key); + } + if (transport != NULL) { + dns_transport_detach(&transport); + } + continue; + } + + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: create DS query for " + "parent %d", + i); + + result = checkds_create(zone->mctx, flags, &checkds); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: create DS query for " + "parent %d failed", + i); + continue; + } + zone_iattach(zone, &checkds->zone); + checkds->dst = dst; + + INSIST(checkds->key == NULL); + if (key != NULL) { + checkds->key = key; + key = NULL; + } + + INSIST(checkds->transport == NULL); + if (transport != NULL) { + checkds->transport = transport; + transport = NULL; + } + + ISC_LIST_APPEND(zone->checkds_requests, checkds, link); + result = checkds_send_queue(checkds); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "checkds: send DS query to " + "parent %d failed", + i); + checkds_destroy(checkds, true); + } + } +} + +static void +zone_checkds(dns_zone_t *zone) { + bool cdscheck = false; + + for (dns_dnsseckey_t *key = ISC_LIST_HEAD(zone->checkds_ok); + key != NULL; key = ISC_LIST_NEXT(key, link)) + { + dst_key_state_t ds_state = DST_KEY_STATE_NA; + bool ksk = false; + isc_stdtime_t published = 0, withdrawn = 0; + + /* Is this key have the KSK role? */ + (void)dst_key_role(key->key, &ksk, NULL); + if (!ksk) { + continue; + } + + /* Do we need to check the DS RRset? */ + (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state); + (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published); + (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn); + + if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) { + dst_key_setnum(key->key, DST_NUM_DSPUBCOUNT, 0); + cdscheck = true; + } else if (ds_state == DST_KEY_STATE_UNRETENTIVE && + withdrawn == 0) + { + dst_key_setnum(key->key, DST_NUM_DSDELCOUNT, 0); + cdscheck = true; + } + } + + if (cdscheck) { + /* Request the DS RRset. */ + LOCK_ZONE(zone); + checkds_send(zone); + UNLOCK_ZONE(zone); + } +} + +static void +zone_rekey(dns_zone_t *zone) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *ver = NULL; + dns_rdataset_t cdsset, soaset, soasigs, keyset, keysigs, cdnskeyset; + dns_dnsseckeylist_t dnskeys, keys, rmkeys; + dns_dnsseckey_t *key = NULL; + dns_diff_t diff, _sig_diff; + dns_kasp_t *kasp; + dns__zonediff_t zonediff; + bool commit = false, newactive = false; + bool newalg = false; + bool fullsign; + dns_ttl_t ttl = 3600; + const char *dir = NULL; + isc_mem_t *mctx = NULL; + isc_stdtime_t now, nexttime = 0; + isc_time_t timenow; + isc_interval_t ival; + char timebuf[80]; + + REQUIRE(DNS_ZONE_VALID(zone)); + + ISC_LIST_INIT(dnskeys); + ISC_LIST_INIT(keys); + ISC_LIST_INIT(rmkeys); + dns_rdataset_init(&soaset); + dns_rdataset_init(&soasigs); + dns_rdataset_init(&keyset); + dns_rdataset_init(&keysigs); + dns_rdataset_init(&cdsset); + dns_rdataset_init(&cdnskeyset); + dir = dns_zone_getkeydirectory(zone); + mctx = zone->mctx; + dns_diff_init(mctx, &diff); + dns_diff_init(mctx, &_sig_diff); + zonediff_init(&zonediff, &_sig_diff); + + CHECK(dns_zone_getdb(zone, &db)); + CHECK(dns_db_newversion(db, &ver)); + CHECK(dns_db_getoriginnode(db, &node)); + + TIME_NOW(&timenow); + now = isc_time_seconds(&timenow); + + kasp = dns_zone_getkasp(zone); + + dnssec_log(zone, ISC_LOG_INFO, "reconfiguring zone keys"); + + /* Get the SOA record's TTL */ + CHECK(dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, + dns_rdatatype_none, 0, &soaset, &soasigs)); + ttl = soaset.ttl; + dns_rdataset_disassociate(&soaset); + + /* Get the DNSKEY rdataset */ + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &keyset, &keysigs); + if (result == ISC_R_SUCCESS) { + ttl = keyset.ttl; + + dns_zone_lock_keyfiles(zone); + + result = dns_dnssec_keylistfromrdataset( + &zone->origin, dir, mctx, &keyset, &keysigs, &soasigs, + false, false, &dnskeys); + + dns_zone_unlock_keyfiles(zone); + + if (result != ISC_R_SUCCESS) { + goto failure; + } + } else if (result != ISC_R_NOTFOUND) { + goto failure; + } + + /* Get the CDS rdataset */ + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cds, + dns_rdatatype_none, 0, &cdsset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + + /* Get the CDNSKEY rdataset */ + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cdnskey, + dns_rdatatype_none, 0, &cdnskeyset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + /* + * True when called from "rndc sign". Indicates the zone should be + * fully signed now. + */ + fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN); + + KASP_LOCK(kasp); + + dns_zone_lock_keyfiles(zone); + result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx, + &keys); + dns_zone_unlock_keyfiles(zone); + + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_DEBUG(1), + "zone_rekey:dns_dnssec_findmatchingkeys failed: %s", + isc_result_totext(result)); + } + + if (kasp != NULL) { + /* + * Check DS at parental agents. Clear ongoing checks. + */ + LOCK_ZONE(zone); + checkds_cancel(zone); + clear_keylist(&zone->checkds_ok, zone->mctx); + ISC_LIST_INIT(zone->checkds_ok); + UNLOCK_ZONE(zone); + + result = dns_zone_getdnsseckeys(zone, db, ver, now, + &zone->checkds_ok); + + if (result == ISC_R_SUCCESS) { + zone_checkds(zone); + } else { + dnssec_log(zone, + (result == ISC_R_NOTFOUND) ? ISC_LOG_DEBUG(1) + : ISC_LOG_ERROR, + "zone_rekey:dns_zone_getdnsseckeys failed: " + "%s", + isc_result_totext(result)); + } + + if (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND) { + dns_zone_lock_keyfiles(zone); + result = dns_keymgr_run(&zone->origin, zone->rdclass, + dir, mctx, &keys, &dnskeys, + kasp, now, &nexttime); + dns_zone_unlock_keyfiles(zone); + + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:dns_dnssec_keymgr " + "failed: %s", + isc_result_totext(result)); + KASP_UNLOCK(kasp); + goto failure; + } + } + } + + KASP_UNLOCK(kasp); + + if (result == ISC_R_SUCCESS) { + bool cdsdel = false; + bool cdnskeydel = false; + bool sane_diff, sane_dnskey; + isc_stdtime_t when; + + /* + * Publish CDS/CDNSKEY DELETE records if the zone is + * transitioning from secure to insecure. + */ + if (kasp != NULL) { + if (strcmp(dns_kasp_getname(kasp), "insecure") == 0) { + cdsdel = true; + cdnskeydel = true; + } + } else { + /* Check if there is a CDS DELETE record. */ + if (dns_rdataset_isassociated(&cdsset)) { + for (result = dns_rdataset_first(&cdsset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cdsset)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdataset_current(&cdsset, &crdata); + /* + * CDS deletion record has this form + * "0 0 0 00" which is 5 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 0, + 0, 0 }, + 5) == 0) + { + cdsdel = true; + break; + } + } + } + + /* Check if there is a CDNSKEY DELETE record. */ + if (dns_rdataset_isassociated(&cdnskeyset)) { + for (result = dns_rdataset_first(&cdnskeyset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cdnskeyset)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdataset_current(&cdnskeyset, + &crdata); + /* + * CDNSKEY deletion record has this form + * "0 3 0 AA==" which is 2 zero octets, + * a 3, and 2 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 3, + 0, 0 }, + 5) == 0) + { + cdnskeydel = true; + break; + } + } + } + } + + /* + * Only update DNSKEY TTL if we have a policy. + */ + if (kasp != NULL) { + ttl = dns_kasp_dnskeyttl(kasp); + } + + result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys, + &zone->origin, ttl, &diff, mctx, + dnssec_report); + /* + * Keys couldn't be updated for some reason; + * try again later. + */ + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:couldn't update zone keys: %s", + isc_result_totext(result)); + goto failure; + } + + /* + * Update CDS / CDNSKEY records. + */ + result = dns_dnssec_syncupdate(&dnskeys, &rmkeys, &cdsset, + &cdnskeyset, now, ttl, &diff, + mctx); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:couldn't update CDS/CDNSKEY: %s", + isc_result_totext(result)); + goto failure; + } + + if (cdsdel || cdnskeydel) { + /* + * Only publish CDS/CDNSKEY DELETE records if there is + * a KSK that can be used to verify the RRset. This + * means there must be a key with the KSK role that is + * published and is used for signing. + */ + bool allow = false; + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + dst_key_t *dstk = key->key; + + if (dst_key_is_published(dstk, now, &when) && + dst_key_is_signing(dstk, DST_BOOL_KSK, now, + &when)) + { + allow = true; + break; + } + } + if (cdsdel) { + cdsdel = allow; + } + if (cdnskeydel) { + cdnskeydel = allow; + } + } + result = dns_dnssec_syncdelete( + &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, + &diff, mctx, cdsdel, cdnskeydel); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_rekey:couldn't update CDS/CDNSKEY " + "DELETE records: %s", + isc_result_totext(result)); + goto failure; + } + + /* + * See if any pre-existing keys have newly become active; + * also, see if any new key is for a new algorithm, as in that + * event, we need to sign the zone fully. (If there's a new + * key, but it's for an already-existing algorithm, then + * the zone signing can be handled incrementally.) + */ + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (!key->first_sign) { + continue; + } + + newactive = true; + + if (!dns_rdataset_isassociated(&keysigs)) { + newalg = true; + break; + } + + if (signed_with_alg(&keysigs, dst_key_alg(key->key))) { + /* + * This isn't a new algorithm; clear + * first_sign so we won't sign the + * whole zone with this key later. + */ + key->first_sign = false; + } else { + newalg = true; + break; + } + } + + /* + * A sane diff is one that is not empty, and that does not + * introduce a zone with NSEC only DNSKEYs along with NSEC3 + * chains. + */ + sane_dnskey = dns_zone_check_dnskey_nsec3(zone, db, ver, &diff, + NULL, 0); + sane_diff = !ISC_LIST_EMPTY(diff.tuples) && sane_dnskey; + if (!sane_dnskey) { + dnssec_log(zone, ISC_LOG_ERROR, + "NSEC only DNSKEYs and NSEC3 chains not " + "allowed"); + } + + if (newactive || fullsign || sane_diff) { + CHECK(dns_diff_apply(&diff, db, ver)); + CHECK(clean_nsec3param(zone, db, ver, &diff)); + CHECK(add_signing_records(db, zone->privatetype, ver, + &diff, (newalg || fullsign))); + CHECK(update_soa_serial(zone, db, ver, &diff, mctx, + zone->updatemethod)); + CHECK(add_chains(zone, db, ver, &diff)); + CHECK(sign_apex(zone, db, ver, now, &diff, &zonediff)); + CHECK(zone_journal(zone, zonediff.diff, NULL, + "zone_rekey")); + commit = true; + } + } + + dns_db_closeversion(db, &ver, true); + + LOCK_ZONE(zone); + + if (commit) { + dns_difftuple_t *tuple; + dns_stats_t *dnssecsignstats = + dns_zone_getdnssecsignstats(zone); + + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + + zone_needdump(zone, DNS_DUMP_DELAY); + + zone_settimer(zone, &timenow); + + /* Remove any signatures from removed keys. */ + if (!ISC_LIST_EMPTY(rmkeys)) { + for (key = ISC_LIST_HEAD(rmkeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + result = zone_signwithkey( + zone, dst_key_alg(key->key), + dst_key_id(key->key), true); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: " + "%s", + isc_result_totext(result)); + } + + /* Clear DNSSEC sign statistics. */ + if (dnssecsignstats != NULL) { + dns_dnssecsignstats_clear( + dnssecsignstats, + dst_key_id(key->key), + dst_key_alg(key->key)); + /* + * Also clear the dnssec-sign + * statistics of the revoked key id. + */ + dns_dnssecsignstats_clear( + dnssecsignstats, + dst_key_rid(key->key), + dst_key_alg(key->key)); + } + } + } + + if (fullsign) { + /* + * "rndc sign" was called, so we now sign the zone + * with all active keys, whether they're new or not. + */ + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (!key->force_sign && !key->hint_sign) { + continue; + } + + result = zone_signwithkey( + zone, dst_key_alg(key->key), + dst_key_id(key->key), false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: " + "%s", + isc_result_totext(result)); + } + } + } else if (newalg) { + /* + * We haven't been told to sign fully, but a new + * algorithm was added to the DNSKEY. We sign + * the full zone, but only with newly active + * keys. + */ + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (!key->first_sign) { + continue; + } + + result = zone_signwithkey( + zone, dst_key_alg(key->key), + dst_key_id(key->key), false); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_signwithkey failed: " + "%s", + isc_result_totext(result)); + } + } + } + + /* + * Clear fullsign flag, if it was set, so we don't do + * another full signing next time. + */ + DNS_ZONEKEY_CLROPTION(zone, DNS_ZONEKEY_FULLSIGN); + + /* + * Cause the zone to add/delete NSEC3 chains for the + * deferred NSEC3PARAM changes. + */ + for (tuple = ISC_LIST_HEAD(zonediff.diff->tuples); + tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) + { + unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t nsec3param; + + if (tuple->rdata.type != zone->privatetype || + tuple->op != DNS_DIFFOP_ADD) + { + continue; + } + + if (!dns_nsec3param_fromprivate(&tuple->rdata, &rdata, + buf, sizeof(buf))) + { + continue; + } + + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3param.flags == 0) { + continue; + } + + result = zone_addnsec3chain(zone, &nsec3param); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "zone_addnsec3chain failed: %s", + isc_result_totext(result)); + } + } + + /* + * Activate any NSEC3 chain updates that may have + * been scheduled before this rekey. + */ + if (fullsign || newalg) { + resume_addnsec3chain(zone); + } + + /* + * Schedule the next resigning event + */ + set_resigntime(zone); + } + + isc_time_settoepoch(&zone->refreshkeytime); + + /* + * If keymgr provided a next time, use the calculated next rekey time. + */ + if (kasp != NULL) { + isc_time_t timenext; + uint32_t nexttime_seconds; + + /* + * Set the key refresh timer to the next scheduled key event + * or to 'dnssec-loadkeys-interval' seconds in the future + * if no next key event is scheduled (nexttime == 0). + */ + if (nexttime > 0) { + nexttime_seconds = nexttime - now; + } else { + nexttime_seconds = zone->refreshkeyinterval; + } + + DNS_ZONE_TIME_ADD(&timenow, nexttime_seconds, &timenext); + zone->refreshkeytime = timenext; + zone_settimer(zone, &timenow); + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + + dnssec_log(zone, ISC_LOG_DEBUG(3), + "next key event in %u seconds", nexttime_seconds); + dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); + } + /* + * If we're doing key maintenance, set the key refresh timer to + * the next scheduled key event or to 'dnssec-loadkeys-interval' + * seconds in the future, whichever is sooner. + */ + else if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) + { + isc_time_t timethen; + isc_stdtime_t then; + + DNS_ZONE_TIME_ADD(&timenow, zone->refreshkeyinterval, + &timethen); + zone->refreshkeytime = timethen; + + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + then = now; + result = next_keyevent(key->key, &then); + if (result != ISC_R_SUCCESS) { + continue; + } + + DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen); + if (isc_time_compare(&timethen, &zone->refreshkeytime) < + 0) + { + zone->refreshkeytime = timethen; + } + } + + zone_settimer(zone, &timenow); + + isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); + dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); + } + UNLOCK_ZONE(zone); + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + for (key = ISC_LIST_HEAD(dnskeys); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + /* This debug log is used in the kasp system test */ + char algbuf[DNS_SECALG_FORMATSIZE]; + dns_secalg_format(dst_key_alg(key->key), algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_DEBUG(3), + "zone_rekey done: key %d/%s", + dst_key_id(key->key), algbuf); + } + } + + result = ISC_R_SUCCESS; + +failure: + LOCK_ZONE(zone); + if (result != ISC_R_SUCCESS) { + /* + * Something went wrong; try again in ten minutes or + * after a key refresh interval, whichever is shorter. + */ + dnssec_log(zone, ISC_LOG_DEBUG(3), + "zone_rekey failure: %s (retry in %u seconds)", + isc_result_totext(result), + ISC_MIN(zone->refreshkeyinterval, 600)); + isc_interval_set(&ival, ISC_MIN(zone->refreshkeyinterval, 600), + 0); + isc_time_nowplusinterval(&zone->refreshkeytime, &ival); + } + UNLOCK_ZONE(zone); + + dns_diff_clear(&diff); + dns_diff_clear(&_sig_diff); + + clear_keylist(&dnskeys, mctx); + clear_keylist(&keys, mctx); + clear_keylist(&rmkeys, mctx); + + if (ver != NULL) { + dns_db_closeversion(db, &ver, false); + } + if (dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + if (dns_rdataset_isassociated(&keyset)) { + dns_rdataset_disassociate(&keyset); + } + if (dns_rdataset_isassociated(&keysigs)) { + dns_rdataset_disassociate(&keysigs); + } + if (dns_rdataset_isassociated(&soasigs)) { + dns_rdataset_disassociate(&soasigs); + } + if (dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + + INSIST(ver == NULL); +} + +void +dns_zone_rekey(dns_zone_t *zone, bool fullsign) { + isc_time_t now; + + if (zone->type == dns_zone_primary && zone->task != NULL) { + LOCK_ZONE(zone); + + if (fullsign) { + DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN); + } + + TIME_NOW(&now); + zone->refreshkeytime = now; + zone_settimer(zone, &now); + + UNLOCK_ZONE(zone); + } +} + +isc_result_t +dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + unsigned int *errors) { + isc_result_t result; + dns_dbnode_t *node = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(errors != NULL); + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = zone_count_ns_rr(zone, db, node, version, NULL, errors, false); + dns_db_detachnode(db, &node); + return (result); +} + +isc_result_t +dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t dnskey, cds, cdnskey; + unsigned char algorithms[256]; + unsigned int i; + bool empty = false; + + enum { notexpected = 0, expected = 1, found = 2 }; + + REQUIRE(DNS_ZONE_VALID(zone)); + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&cds); + dns_rdataset_init(&dnskey); + dns_rdataset_init(&cdnskey); + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_cds, + dns_rdatatype_none, 0, &cds, NULL); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_cdnskey, + dns_rdatatype_none, 0, &cdnskey, NULL); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + if (!dns_rdataset_isassociated(&cds) && + !dns_rdataset_isassociated(&cdnskey)) + { + result = ISC_R_SUCCESS; + goto failure; + } + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &dnskey, NULL); + if (result == ISC_R_NOTFOUND) { + empty = true; + } else if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * For each DNSSEC algorithm in the CDS RRset there must be + * a matching DNSKEY record with the exception of a CDS deletion + * record which must be by itself. + */ + if (dns_rdataset_isassociated(&cds)) { + bool delete = false; + memset(algorithms, notexpected, sizeof(algorithms)); + for (result = dns_rdataset_first(&cds); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cds)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdata_cds_t structcds; + + dns_rdataset_current(&cds, &crdata); + /* + * CDS deletion record has this form "0 0 0 00" which + * is 5 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 0, 0, 0 }, 5) == 0) + { + delete = true; + continue; + } + + if (empty) { + result = DNS_R_BADCDS; + goto failure; + } + + CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL)); + if (algorithms[structcds.algorithm] == 0) { + algorithms[structcds.algorithm] = expected; + } + for (result = dns_rdataset_first(&dnskey); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&dnskey)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t structdnskey; + + dns_rdataset_current(&dnskey, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &structdnskey, + NULL)); + + if (structdnskey.algorithm == + structcds.algorithm) + { + algorithms[structcds.algorithm] = found; + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } + } + for (i = 0; i < sizeof(algorithms); i++) { + if (delete) { + if (algorithms[i] != notexpected) { + result = DNS_R_BADCDS; + goto failure; + } + } else if (algorithms[i] == expected) { + result = DNS_R_BADCDS; + goto failure; + } + } + } + + /* + * For each DNSSEC algorithm in the CDNSKEY RRset there must be + * a matching DNSKEY record with the exception of a CDNSKEY deletion + * record which must be by itself. + */ + if (dns_rdataset_isassociated(&cdnskey)) { + bool delete = false; + memset(algorithms, notexpected, sizeof(algorithms)); + for (result = dns_rdataset_first(&cdnskey); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cdnskey)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdata_cdnskey_t structcdnskey; + + dns_rdataset_current(&cdnskey, &crdata); + /* + * CDNSKEY deletion record has this form + * "0 3 0 AA==" which is 2 zero octets, a 3, + * and 2 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 3, 0, 0 }, 5) == 0) + { + delete = true; + continue; + } + + if (empty) { + result = DNS_R_BADCDNSKEY; + goto failure; + } + + CHECK(dns_rdata_tostruct(&crdata, &structcdnskey, + NULL)); + if (algorithms[structcdnskey.algorithm] == 0) { + algorithms[structcdnskey.algorithm] = expected; + } + for (result = dns_rdataset_first(&dnskey); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&dnskey)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t structdnskey; + + dns_rdataset_current(&dnskey, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &structdnskey, + NULL)); + + if (structdnskey.algorithm == + structcdnskey.algorithm) + { + algorithms[structcdnskey.algorithm] = + found; + } + } + if (result != ISC_R_NOMORE) { + goto failure; + } + } + for (i = 0; i < sizeof(algorithms); i++) { + if (delete) { + if (algorithms[i] != notexpected) { + result = DNS_R_BADCDNSKEY; + goto failure; + } + } else if (algorithms[i] == expected) { + result = DNS_R_BADCDNSKEY; + goto failure; + } + } + } + result = ISC_R_SUCCESS; + +failure: + if (dns_rdataset_isassociated(&cds)) { + dns_rdataset_disassociate(&cds); + } + if (dns_rdataset_isassociated(&dnskey)) { + dns_rdataset_disassociate(&dnskey); + } + if (dns_rdataset_isassociated(&cdnskey)) { + dns_rdataset_disassociate(&cdnskey); + } + dns_db_detachnode(db, &node); + return (result); +} + +void +dns_zone_setautomatic(dns_zone_t *zone, bool automatic) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->automatic = automatic; + UNLOCK_ZONE(zone); +} + +bool +dns_zone_getautomatic(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->automatic); +} + +void +dns_zone_setadded(dns_zone_t *zone, bool added) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->added = added; + UNLOCK_ZONE(zone); +} + +bool +dns_zone_getadded(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->added); +} + +isc_result_t +dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db) { + isc_time_t loadtime; + isc_result_t result; + dns_zone_t *secure = NULL; + + TIME_NOW(&loadtime); + + /* + * Lock hierarchy: zmgr, zone, raw. + */ +again: + LOCK_ZONE(zone); + INSIST(zone != zone->raw); + if (inline_secure(zone)) { + LOCK_ZONE(zone->raw); + } else if (inline_raw(zone)) { + secure = zone->secure; + TRYLOCK_ZONE(result, secure); + if (result != ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + secure = NULL; + isc_thread_yield(); + goto again; + } + } + result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS); + if (inline_secure(zone)) { + UNLOCK_ZONE(zone->raw); + } else if (secure != NULL) { + UNLOCK_ZONE(secure); + } + UNLOCK_ZONE(zone); + return (result); +} + +isc_result_t +dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval) { + REQUIRE(DNS_ZONE_VALID(zone)); + if (interval == 0) { + return (ISC_R_RANGE); + } + /* Maximum value: 24 hours (3600 minutes) */ + if (interval > (24 * 60)) { + interval = (24 * 60); + } + /* Multiply by 60 for seconds */ + zone->refreshkeyinterval = interval * 60; + return (ISC_R_SUCCESS); +} + +void +dns_zone_setrequestixfr(dns_zone_t *zone, bool flag) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->requestixfr = flag; +} + +bool +dns_zone_getrequestixfr(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->requestixfr); +} + +void +dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->ixfr_ratio = ratio; +} + +uint32_t +dns_zone_getixfrratio(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->ixfr_ratio); +} + +void +dns_zone_setrequestexpire(dns_zone_t *zone, bool flag) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->requestexpire = flag; +} + +bool +dns_zone_getrequestexpire(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->requestexpire); +} + +void +dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->updatemethod = method; +} + +dns_updatemethod_t +dns_zone_getserialupdatemethod(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + return (zone->updatemethod); +} + +/* + * Lock hierarchy: zmgr, zone, raw. + */ +isc_result_t +dns_zone_link(dns_zone_t *zone, dns_zone_t *raw) { + isc_result_t result; + dns_zonemgr_t *zmgr; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(zone->zmgr != NULL); + REQUIRE(zone->task != NULL); + REQUIRE(zone->loadtask != NULL); + REQUIRE(zone->raw == NULL); + + REQUIRE(DNS_ZONE_VALID(raw)); + REQUIRE(raw->zmgr == NULL); + REQUIRE(raw->task == NULL); + REQUIRE(raw->loadtask == NULL); + REQUIRE(raw->secure == NULL); + + REQUIRE(zone != raw); + + /* + * Lock hierarchy: zmgr, zone, raw. + */ + zmgr = zone->zmgr; + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + LOCK_ZONE(zone); + LOCK_ZONE(raw); + + result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL, + NULL, zone->task, zone_timer, raw, + &raw->timer); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + /* + * The timer "holds" a iref. + */ + isc_refcount_increment0(&raw->irefs); + + /* dns_zone_attach(raw, &zone->raw); */ + isc_refcount_increment(&raw->erefs); + zone->raw = raw; + + /* dns_zone_iattach(zone, &raw->secure); */ + zone_iattach(zone, &raw->secure); + + isc_task_attach(zone->task, &raw->task); + isc_task_attach(zone->loadtask, &raw->loadtask); + + ISC_LIST_APPEND(zmgr->zones, raw, link); + raw->zmgr = zmgr; + isc_refcount_increment(&zmgr->refs); + +unlock: + UNLOCK_ZONE(raw); + UNLOCK_ZONE(zone); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + return (result); +} + +void +dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(raw != NULL && *raw == NULL); + + LOCK(&zone->lock); + INSIST(zone != zone->raw); + if (zone->raw != NULL) { + dns_zone_attach(zone->raw, raw); + } + UNLOCK(&zone->lock); +} + +struct keydone { + isc_event_t event; + bool all; + unsigned char data[5]; +}; + +#define PENDINGFLAGS (DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL) + +static void +keydone(isc_task_t *task, isc_event_t *event) { + const char *me = "keydone"; + bool commit = false; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_dbversion_t *oldver = NULL, *newver = NULL; + dns_zone_t *zone; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_diff_t diff; + struct keydone *kd = (struct keydone *)event; + dns_update_log_t log = { update_log_cb, NULL }; + bool clear_pending = false; + + UNUSED(task); + + zone = event->ev_arg; + INSIST(DNS_ZONE_VALID(zone)); + + ENTER; + + dns_rdataset_init(&rdataset); + dns_diff_init(zone->mctx, &diff); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + goto failure; + } + + dns_db_currentversion(db, &oldver); + result = dns_db_newversion(db, &newver); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "keydone:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + result = dns_db_findrdataset(db, node, newver, zone->privatetype, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result == ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto failure; + } + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + goto failure; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + bool found = false; + + dns_rdataset_current(&rdataset, &rdata); + + if (kd->all) { + if (rdata.length == 5 && rdata.data[0] != 0 && + rdata.data[3] == 0 && rdata.data[4] == 1) + { + found = true; + } else if (rdata.data[0] == 0 && + (rdata.data[2] & PENDINGFLAGS) != 0) + { + found = true; + clear_pending = true; + } + } else if (rdata.length == 5 && + memcmp(rdata.data, kd->data, 5) == 0) + { + found = true; + } + + if (found) { + CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_DEL, + &zone->origin, rdataset.ttl, + &rdata)); + } + dns_rdata_reset(&rdata); + } + + if (!ISC_LIST_EMPTY(diff.tuples)) { + /* Write changes to journal file. */ + CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx, + zone->updatemethod)); + + result = dns_update_signatures(&log, zone, db, oldver, newver, + &diff, + zone->sigvalidityinterval); + if (!clear_pending) { + CHECK(result); + } + + CHECK(zone_journal(zone, &diff, NULL, "keydone")); + commit = true; + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, + DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); + zone_needdump(zone, 30); + UNLOCK_ZONE(zone); + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (db != NULL) { + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (oldver != NULL) { + dns_db_closeversion(db, &oldver, false); + } + if (newver != NULL) { + dns_db_closeversion(db, &newver, commit); + } + dns_db_detach(&db); + } + dns_diff_clear(&diff); + isc_event_free(&event); + dns_zone_idetach(&zone); + + INSIST(oldver == NULL); + INSIST(newver == NULL); +} + +isc_result_t +dns_zone_keydone(dns_zone_t *zone, const char *keystr) { + isc_result_t result = ISC_R_SUCCESS; + isc_event_t *e; + isc_buffer_t b; + dns_zone_t *dummy = NULL; + struct keydone *kd; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + + e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_KEYDONE, keydone, + zone, sizeof(struct keydone)); + + kd = (struct keydone *)e; + if (strcasecmp(keystr, "all") == 0) { + kd->all = true; + } else { + isc_textregion_t r; + const char *algstr; + dns_keytag_t keyid; + dns_secalg_t alg; + size_t n; + + kd->all = false; + + n = sscanf(keystr, "%hu/", &keyid); + if (n == 0U) { + CHECK(ISC_R_FAILURE); + } + + algstr = strchr(keystr, '/'); + if (algstr != NULL) { + algstr++; + } else { + CHECK(ISC_R_FAILURE); + } + + n = sscanf(algstr, "%hhu", &alg); + if (n == 0U) { + DE_CONST(algstr, r.base); + r.length = strlen(algstr); + CHECK(dns_secalg_fromtext(&alg, &r)); + } + + /* construct a private-type rdata */ + isc_buffer_init(&b, kd->data, sizeof(kd->data)); + isc_buffer_putuint8(&b, alg); + isc_buffer_putuint8(&b, (keyid & 0xff00) >> 8); + isc_buffer_putuint8(&b, (keyid & 0xff)); + isc_buffer_putuint8(&b, 0); + isc_buffer_putuint8(&b, 1); + } + + zone_iattach(zone, &dummy); + isc_task_send(zone->task, &e); + +failure: + if (e != NULL) { + isc_event_free(&e); + } + UNLOCK_ZONE(zone); + return (result); +} + +/* + * Called from the zone task's queue after the relevant event is posted by + * dns_zone_setnsec3param(). + */ +static void +setnsec3param(isc_task_t *task, isc_event_t *event) { + const char *me = "setnsec3param"; + dns_zone_t *zone = event->ev_arg; + bool loadpending; + + INSIST(DNS_ZONE_VALID(zone)); + + UNUSED(task); + + ENTER; + + LOCK_ZONE(zone); + loadpending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING); + UNLOCK_ZONE(zone); + + /* + * If receive_secure_serial is still processing or we have a + * queued event append rss_post queue. + */ + if (zone->rss_newver != NULL || ISC_LIST_HEAD(zone->rss_post) != NULL) { + /* + * Wait for receive_secure_serial() to finish processing. + */ + ISC_LIST_APPEND(zone->rss_post, event, ev_link); + } else { + bool rescheduled = false; + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + /* + * The zone is not yet fully loaded. Reschedule the event to + * be picked up later. This turns this function into a busy + * wait, but it only happens at startup. + */ + if (zone->db == NULL && loadpending) { + rescheduled = true; + isc_task_send(task, &event); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (rescheduled) { + return; + } + + rss_post(zone, event); + } + dns_zone_idetach(&zone); +} + +static void +salt2text(unsigned char *salt, uint8_t saltlen, unsigned char *text, + unsigned int textlen) { + isc_region_t r; + isc_buffer_t buf; + isc_result_t result; + + r.base = salt; + r.length = (unsigned int)saltlen; + + isc_buffer_init(&buf, text, textlen); + result = isc_hex_totext(&r, 2, "", &buf); + if (result == ISC_R_SUCCESS) { + text[saltlen * 2] = 0; + } else { + text[0] = 0; + } +} + +/* + * Check whether NSEC3 chain addition or removal specified by the private-type + * record passed with the event was already queued (or even fully performed). + * If not, modify the relevant private-type records at the zone apex and call + * resume_addnsec3chain(). + */ +static void +rss_post(dns_zone_t *zone, isc_event_t *event) { + const char *me = "rss_post"; + bool commit = false; + isc_result_t result; + dns_dbversion_t *oldver = NULL, *newver = NULL; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t prdataset, nrdataset; + dns_diff_t diff; + struct np3event *npe = (struct np3event *)event; + nsec3param_t *np; + dns_update_log_t log = { update_log_cb, NULL }; + dns_rdata_t rdata; + bool nseconly; + bool exists = false; + + ENTER; + + np = &npe->params; + + dns_rdataset_init(&prdataset); + dns_rdataset_init(&nrdataset); + dns_diff_init(zone->mctx, &diff); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + goto failure; + } + + dns_db_currentversion(db, &oldver); + result = dns_db_newversion(db, &newver); + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, + "setnsec3param:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + CHECK(dns_db_getoriginnode(db, &node)); + + /* + * Do we need to look up the NSEC3 parameters? + */ + if (np->lookup) { + dns_rdata_nsec3param_t param; + dns_rdata_t nrdata = DNS_RDATA_INIT; + dns_rdata_t prdata = DNS_RDATA_INIT; + unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE]; + unsigned char saltbuf[255]; + isc_buffer_t b; + + param.salt = NULL; + result = dns__zone_lookup_nsec3param(zone, &np->rdata, ¶m, + saltbuf, np->resalt); + if (result == ISC_R_SUCCESS) { + /* + * Success because the NSEC3PARAM already exists, but + * function returns void, so goto failure to clean up. + */ + goto failure; + } + if (result != DNS_R_NSEC3RESALT && result != ISC_R_NOTFOUND) { + dnssec_log(zone, ISC_LOG_DEBUG(3), + "setnsec3param:lookup nsec3param -> %s", + isc_result_totext(result)); + goto failure; + } + + INSIST(param.salt != NULL); + + /* Update NSEC3 parameters. */ + np->rdata.hash = param.hash; + np->rdata.flags = param.flags; + np->rdata.iterations = param.iterations; + np->rdata.salt_length = param.salt_length; + np->rdata.salt = param.salt; + + isc_buffer_init(&b, nbuf, sizeof(nbuf)); + CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass, + dns_rdatatype_nsec3param, &np->rdata, + &b)); + dns_nsec3param_toprivate(&nrdata, &prdata, zone->privatetype, + np->data, sizeof(np->data)); + np->length = prdata.length; + np->nsec = false; + } + + /* + * Does a private-type record already exist for this chain? + */ + result = dns_db_findrdataset(db, node, newver, zone->privatetype, + dns_rdatatype_none, 0, &prdataset, NULL); + if (result == ISC_R_SUCCESS) { + for (result = dns_rdataset_first(&prdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&prdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&prdataset, &rdata); + + if (np->length == rdata.length && + memcmp(rdata.data, np->data, np->length) == 0) + { + exists = true; + break; + } + } + } else if (result != ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&prdataset)); + goto failure; + } + + /* + * Does the chain already exist? + */ + result = dns_db_findrdataset(db, node, newver, dns_rdatatype_nsec3param, + dns_rdatatype_none, 0, &nrdataset, NULL); + if (result == ISC_R_SUCCESS) { + for (result = dns_rdataset_first(&nrdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&nrdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&nrdataset, &rdata); + + if (np->length == (rdata.length + 1) && + memcmp(rdata.data, np->data + 1, np->length - 1) == + 0) + { + exists = true; + break; + } + } + } else if (result != ISC_R_NOTFOUND) { + INSIST(!dns_rdataset_isassociated(&nrdataset)); + goto failure; + } + + /* + * We need to remove any existing NSEC3 chains if the supplied NSEC3 + * parameters are supposed to replace the current ones or if we are + * switching to NSEC. + */ + if (!exists && np->replace && (np->length != 0 || np->nsec)) { + CHECK(dns_nsec3param_deletechains(db, newver, zone, !np->nsec, + &diff)); + } + + if (!exists && np->length != 0) { + /* + * We're creating an NSEC3 chain. Add the private-type record + * passed in the event handler's argument to the zone apex. + * + * If the zone is not currently capable of supporting an NSEC3 + * chain (due to the DNSKEY RRset at the zone apex not existing + * or containing at least one key using an NSEC-only + * algorithm), add the INITIAL flag, so these parameters can be + * used later when NSEC3 becomes available. + */ + dns_rdata_init(&rdata); + + np->data[2] |= DNS_NSEC3FLAG_CREATE; + result = dns_nsec_nseconly(db, newver, NULL, &nseconly); + if (result == ISC_R_NOTFOUND || nseconly) { + np->data[2] |= DNS_NSEC3FLAG_INITIAL; + } + + rdata.length = np->length; + rdata.data = np->data; + rdata.type = zone->privatetype; + rdata.rdclass = zone->rdclass; + CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_ADD, + &zone->origin, 0, &rdata)); + } + + /* + * If we changed anything in the zone, write changes to journal file + * and set commit to true so that resume_addnsec3chain() will be + * called below in order to kick off adding/removing relevant NSEC3 + * records. + */ + if (!ISC_LIST_EMPTY(diff.tuples)) { + CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx, + zone->updatemethod)); + result = dns_update_signatures(&log, zone, db, oldver, newver, + &diff, + zone->sigvalidityinterval); + if (result != ISC_R_NOTFOUND) { + CHECK(result); + } + CHECK(zone_journal(zone, &diff, NULL, "setnsec3param")); + commit = true; + + LOCK_ZONE(zone); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + zone_needdump(zone, 30); + UNLOCK_ZONE(zone); + } + +failure: + if (dns_rdataset_isassociated(&prdataset)) { + dns_rdataset_disassociate(&prdataset); + } + if (dns_rdataset_isassociated(&nrdataset)) { + dns_rdataset_disassociate(&nrdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (oldver != NULL) { + dns_db_closeversion(db, &oldver, false); + } + if (newver != NULL) { + dns_db_closeversion(db, &newver, commit); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (commit) { + LOCK_ZONE(zone); + resume_addnsec3chain(zone); + UNLOCK_ZONE(zone); + } + dns_diff_clear(&diff); + isc_event_free(&event); + + INSIST(oldver == NULL); + INSIST(newver == NULL); +} + +/* + * Check if zone has NSEC3PARAM (and thus a chain) with the right parameters. + * + * If 'salt' is NULL, a match is found if the salt has the requested length, + * otherwise the NSEC3 salt must match the requested salt value too. + * + * Returns ISC_R_SUCCESS, if a match is found, or an error if no match is + * found, or if the db lookup failed. + */ +isc_result_t +dns__zone_lookup_nsec3param(dns_zone_t *zone, dns_rdata_nsec3param_t *lookup, + dns_rdata_nsec3param_t *param, + unsigned char saltbuf[255], bool resalt) { + isc_result_t result = ISC_R_UNEXPECTED; + dns_dbnode_t *node = NULL; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_rdataset_t rdataset; + dns_rdata_nsec3param_t nsec3param; + dns_rdata_t rdata = DNS_RDATA_INIT; + + REQUIRE(DNS_ZONE_VALID(zone)); + + dns_rdataset_init(&rdataset); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + result = ISC_R_FAILURE; + goto setparam; + } + + result = dns_db_findnode(db, &zone->origin, false, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "dns__zone_lookup_nsec3param:" + "dns_db_findnode -> %s", + isc_result_totext(result)); + result = ISC_R_FAILURE; + goto setparam; + } + dns_db_currentversion(db, &version); + + result = dns_db_findrdataset(db, node, version, + dns_rdatatype_nsec3param, + dns_rdatatype_none, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + INSIST(!dns_rdataset_isassociated(&rdataset)); + if (result != ISC_R_NOTFOUND) { + dns_zone_log(zone, ISC_LOG_ERROR, + "dns__zone_lookup_nsec3param:" + "dns_db_findrdataset -> %s", + isc_result_totext(result)); + } + goto setparam; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + INSIST(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + + /* Check parameters. */ + if (nsec3param.hash != lookup->hash) { + continue; + } + if (nsec3param.iterations != lookup->iterations) { + continue; + } + if (nsec3param.salt_length != lookup->salt_length) { + continue; + } + if (lookup->salt != NULL) { + if (memcmp(nsec3param.salt, lookup->salt, + lookup->salt_length) != 0) + { + continue; + } + } + /* Found a match. */ + result = ISC_R_SUCCESS; + param->hash = nsec3param.hash; + param->flags = nsec3param.flags; + param->iterations = nsec3param.iterations; + param->salt_length = nsec3param.salt_length; + param->salt = nsec3param.salt; + break; + } + + if (result == ISC_R_NOMORE) { + result = ISC_R_NOTFOUND; + } + +setparam: + if (result != ISC_R_SUCCESS) { + /* Found no match. */ + param->hash = lookup->hash; + param->flags = lookup->flags; + param->iterations = lookup->iterations; + param->salt_length = lookup->salt_length; + param->salt = lookup->salt; + } + + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { + goto failure; + } + + if (param->salt_length == 0) { + DE_CONST("-", param->salt); + } else if (resalt || param->salt == NULL) { + unsigned char *newsalt; + unsigned char salttext[255 * 2 + 1]; + do { + /* Generate a new salt. */ + result = dns_nsec3_generate_salt(saltbuf, + param->salt_length); + if (result != ISC_R_SUCCESS) { + break; + } + newsalt = saltbuf; + salt2text(newsalt, param->salt_length, salttext, + sizeof(salttext)); + dnssec_log(zone, ISC_LOG_INFO, "generated salt: %s", + salttext); + /* Check for salt conflict. */ + if (param->salt != NULL && + memcmp(newsalt, param->salt, param->salt_length) == + 0) + { + result = ISC_R_SUCCESS; + } else { + param->salt = newsalt; + result = DNS_R_NSEC3RESALT; + } + } while (result == ISC_R_SUCCESS); + + INSIST(result != ISC_R_SUCCESS); + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (version != NULL) { + dns_db_closeversion(db, &version, false); + } + if (db != NULL) { + dns_db_detach(&db); + } + + return (result); +} + +/* + * Called when an "rndc signing -nsec3param ..." command is received, or the + * 'dnssec-policy' has changed. + * + * Allocate and prepare an nsec3param_t structure which holds information about + * the NSEC3 changes requested for the zone: + * + * - if NSEC3 is to be disabled ("-nsec3param none"), only set the "nsec" + * field of the structure to true and the "replace" field to the value + * of the "replace" argument, leaving other fields initialized to zeros, to + * signal that the zone should be signed using NSEC instead of NSEC3, + * + * - otherwise, prepare NSEC3PARAM RDATA that will eventually be inserted at + * the zone apex, convert it to a private-type record and store the latter + * in the "data" field of the nsec3param_t structure. + * + * Once the nsec3param_t structure is prepared, post an event to the zone's + * task which will cause setnsec3param() to be called with the prepared + * structure passed as an argument. + */ +isc_result_t +dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags, + uint16_t iter, uint8_t saltlen, unsigned char *salt, + bool replace, bool resalt) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_nsec3param_t param, lookup; + dns_rdata_t nrdata = DNS_RDATA_INIT; + dns_rdata_t prdata = DNS_RDATA_INIT; + unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE]; + unsigned char saltbuf[255]; + struct np3event *npe; + nsec3param_t *np; + dns_zone_t *dummy = NULL; + isc_buffer_t b; + isc_event_t *e = NULL; + bool do_lookup = false; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + + /* + * First check if the requested NSEC3 parameters are already set, + * if so, no need to set again. + */ + if (hash != 0) { + lookup.hash = hash; + lookup.flags = flags; + lookup.iterations = iter; + lookup.salt_length = saltlen; + lookup.salt = salt; + param.salt = NULL; + result = dns__zone_lookup_nsec3param(zone, &lookup, ¶m, + saltbuf, resalt); + if (result == ISC_R_SUCCESS) { + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); + } + /* + * Schedule lookup if lookup above failed (may happen if zone + * db is NULL for example). + */ + do_lookup = (param.salt == NULL) ? true : false; + } + + e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETNSEC3PARAM, + setnsec3param, zone, sizeof(struct np3event)); + + npe = (struct np3event *)e; + np = &npe->params; + np->replace = replace; + np->resalt = resalt; + np->lookup = do_lookup; + if (hash == 0) { + np->length = 0; + np->nsec = true; + dnssec_log(zone, ISC_LOG_DEBUG(3), "setnsec3param:nsec"); + } else { + param.common.rdclass = zone->rdclass; + param.common.rdtype = dns_rdatatype_nsec3param; + ISC_LINK_INIT(¶m.common, link); + param.mctx = NULL; + /* nsec3 specific param set in dns__zone_lookup_nsec3param() */ + isc_buffer_init(&b, nbuf, sizeof(nbuf)); + + if (param.salt != NULL) { + CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass, + dns_rdatatype_nsec3param, + ¶m, &b)); + dns_nsec3param_toprivate(&nrdata, &prdata, + zone->privatetype, np->data, + sizeof(np->data)); + np->length = prdata.length; + } + + np->rdata = param; + np->nsec = false; + + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + unsigned char salttext[255 * 2 + 1]; + if (param.salt != NULL) { + salt2text(param.salt, param.salt_length, + salttext, sizeof(salttext)); + } + dnssec_log(zone, ISC_LOG_DEBUG(3), + "setnsec3param:nsec3 %u %u %u %u:%s", + param.hash, param.flags, param.iterations, + param.salt_length, + param.salt == NULL ? "unknown" + : (char *)salttext); + } + } + + /* + * setnsec3param() will silently return early if the zone does not yet + * have a database. Prevent that by queueing the event up if zone->db + * is NULL. All events queued here are subsequently processed by + * receive_secure_db() if it ever gets called or simply freed by + * zone_free() otherwise. + */ + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + zone_iattach(zone, &dummy); + isc_task_send(zone->task, &e); + } else { + ISC_LIST_APPEND(zone->setnsec3param_queue, e, ev_link); + e = NULL; + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + + result = ISC_R_SUCCESS; + +failure: + if (e != NULL) { + isc_event_free(&e); + } + UNLOCK_ZONE(zone); + return (result); +} + +isc_result_t +dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(loadtime != NULL); + + LOCK_ZONE(zone); + *loadtime = zone->loadtime; + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(expiretime != NULL); + + LOCK_ZONE(zone); + *expiretime = zone->expiretime; + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(refreshtime != NULL); + + LOCK_ZONE(zone); + *refreshtime = zone->refreshtime; + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(refreshkeytime != NULL); + + LOCK_ZONE(zone); + *refreshkeytime = zone->refreshkeytime; + UNLOCK_ZONE(zone); + return (ISC_R_SUCCESS); +} + +unsigned int +dns_zone_getincludes(dns_zone_t *zone, char ***includesp) { + dns_include_t *include; + char **array = NULL; + unsigned int n = 0; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(includesp != NULL && *includesp == NULL); + + LOCK_ZONE(zone); + if (zone->nincludes == 0) { + goto done; + } + + array = isc_mem_allocate(zone->mctx, sizeof(char *) * zone->nincludes); + for (include = ISC_LIST_HEAD(zone->includes); include != NULL; + include = ISC_LIST_NEXT(include, link)) + { + INSIST(n < zone->nincludes); + array[n++] = isc_mem_strdup(zone->mctx, include->name); + } + INSIST(n == zone->nincludes); + *includesp = array; + +done: + UNLOCK_ZONE(zone); + return (n); +} + +void +dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->statlevel = level; +} + +dns_zonestat_level_t +dns_zone_getstatlevel(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->statlevel); +} + +static void +setserial(isc_task_t *task, isc_event_t *event) { + uint32_t oldserial, desired; + const char *me = "setserial"; + bool commit = false; + isc_result_t result; + dns_dbversion_t *oldver = NULL, *newver = NULL; + dns_zone_t *zone; + dns_db_t *db = NULL; + dns_diff_t diff; + struct ssevent *sse = (struct ssevent *)event; + dns_update_log_t log = { update_log_cb, NULL }; + dns_difftuple_t *oldtuple = NULL, *newtuple = NULL; + + UNUSED(task); + + zone = event->ev_arg; + INSIST(DNS_ZONE_VALID(zone)); + + ENTER; + + if (zone->update_disabled) { + goto disabled; + } + + desired = sse->serial; + + dns_diff_init(zone->mctx, &diff); + + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); + if (zone->db != NULL) { + dns_db_attach(zone->db, &db); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); + if (db == NULL) { + goto failure; + } + + dns_db_currentversion(db, &oldver); + result = dns_db_newversion(db, &newver); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_ERROR, + "setserial:dns_db_newversion -> %s", + isc_result_totext(result)); + goto failure; + } + + CHECK(dns_db_createsoatuple(db, oldver, diff.mctx, DNS_DIFFOP_DEL, + &oldtuple)); + CHECK(dns_difftuple_copy(oldtuple, &newtuple)); + newtuple->op = DNS_DIFFOP_ADD; + + oldserial = dns_soa_getserial(&oldtuple->rdata); + if (desired == 0U) { + desired = 1; + } + if (!isc_serial_gt(desired, oldserial)) { + if (desired != oldserial) { + dns_zone_log(zone, ISC_LOG_INFO, + "setserial: desired serial (%u) " + "out of range (%u-%u)", + desired, oldserial + 1, + (oldserial + 0x7fffffff)); + } + goto failure; + } + + dns_soa_setserial(desired, &newtuple->rdata); + CHECK(do_one_tuple(&oldtuple, db, newver, &diff)); + CHECK(do_one_tuple(&newtuple, db, newver, &diff)); + result = dns_update_signatures(&log, zone, db, oldver, newver, &diff, + zone->sigvalidityinterval); + if (result != ISC_R_NOTFOUND) { + CHECK(result); + } + + /* Write changes to journal file. */ + CHECK(zone_journal(zone, &diff, NULL, "setserial")); + commit = true; + + LOCK_ZONE(zone); + zone_needdump(zone, 30); + UNLOCK_ZONE(zone); + +failure: + if (oldtuple != NULL) { + dns_difftuple_free(&oldtuple); + } + if (newtuple != NULL) { + dns_difftuple_free(&newtuple); + } + if (oldver != NULL) { + dns_db_closeversion(db, &oldver, false); + } + if (newver != NULL) { + dns_db_closeversion(db, &newver, commit); + } + if (db != NULL) { + dns_db_detach(&db); + } + dns_diff_clear(&diff); + +disabled: + isc_event_free(&event); + dns_zone_idetach(&zone); + + INSIST(oldver == NULL); + INSIST(newver == NULL); +} + +isc_result_t +dns_zone_setserial(dns_zone_t *zone, uint32_t serial) { + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *dummy = NULL; + isc_event_t *e = NULL; + struct ssevent *sse; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + + if (!inline_secure(zone)) { + if (!dns_zone_isdynamic(zone, true)) { + result = DNS_R_NOTDYNAMIC; + goto failure; + } + } + + if (zone->update_disabled) { + result = DNS_R_FROZEN; + goto failure; + } + + e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETSERIAL, setserial, + zone, sizeof(struct ssevent)); + + sse = (struct ssevent *)e; + sse->serial = serial; + + zone_iattach(zone, &dummy); + isc_task_send(zone->task, &e); + +failure: + if (e != NULL) { + isc_event_free(&e); + } + UNLOCK_ZONE(zone); + return (result); +} + +isc_stats_t * +dns_zone_getgluecachestats(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (zone->gluecachestats); +} + +bool +dns_zone_isloaded(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)); +} + +isc_result_t +dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) { + dns_dbversion_t *version = NULL; + dns_keytable_t *secroots = NULL; + isc_result_t result; + dns_name_t *origin; + + const char me[] = "dns_zone_verifydb"; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + ENTER; + + if (dns_zone_gettype(zone) != dns_zone_mirror) { + return (ISC_R_SUCCESS); + } + + if (ver == NULL) { + dns_db_currentversion(db, &version); + } else { + version = ver; + } + + if (zone->view != NULL) { + result = dns_view_getsecroots(zone->view, &secroots); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + + origin = dns_db_origin(db); + result = dns_zoneverify_dnssec(zone, db, version, origin, secroots, + zone->mctx, true, false, dnssec_report); + +done: + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + + if (ver == NULL) { + dns_db_closeversion(db, &version, false); + } + + if (result != ISC_R_SUCCESS) { + dnssec_log(zone, ISC_LOG_ERROR, "zone verification failed: %s", + isc_result_totext(result)); + result = DNS_R_VERIFYFAILURE; + } + + return (result); +} + +static dns_ttl_t +zone_nsecttl(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return (ISC_MIN(zone->minimum, zone->soattl)); +} + +void +dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, + isc_tlsctx_cache_t *tlsctx_cache) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(tlsctx_cache != NULL); + + RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write); + + if (zmgr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); + } + + isc_tlsctx_cache_attach(tlsctx_cache, &zmgr->tlsctx_cache); + + RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write); +} + +static void +zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(ptlsctx_cache != NULL && *ptlsctx_cache == NULL); + + RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read); + + INSIST(zmgr->tlsctx_cache != NULL); + isc_tlsctx_cache_attach(zmgr->tlsctx_cache, ptlsctx_cache); + + RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read); +} diff --git a/lib/dns/zone_p.h b/lib/dns/zone_p.h new file mode 100644 index 0000000..8eeeab4 --- /dev/null +++ b/lib/dns/zone_p.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include + +/*! \file */ + +/*% + * Types and functions below not be used outside this module and its + * associated unit tests. + */ + +ISC_LANG_BEGINDECLS + +typedef struct { + dns_diff_t *diff; + bool offline; +} dns__zonediff_t; + +isc_result_t +dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, + dst_key_t **keys, unsigned int *nkeys); + +isc_result_t +dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version, + dst_key_t *zone_keys[], unsigned int nkeys, + dns_zone_t *zone, isc_stdtime_t inception, + isc_stdtime_t expire, isc_stdtime_t keyxpire, + isc_stdtime_t now, bool check_ksk, bool keyset_kskonly, + dns__zonediff_t *zonediff); + +isc_result_t +dns__zone_lookup_nsec3param(dns_zone_t *zone, dns_rdata_nsec3param_t *lookup, + dns_rdata_nsec3param_t *param, + unsigned char saltbuf[255], bool resalt); + +ISC_LANG_ENDDECLS diff --git a/lib/dns/zonekey.c b/lib/dns/zonekey.c new file mode 100644 index 0000000..f86c2ca --- /dev/null +++ b/lib/dns/zonekey.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +bool +dns_zonekey_iszonekey(dns_rdata_t *keyrdata) { + isc_result_t result; + dns_rdata_dnskey_t key; + bool iszonekey = true; + + REQUIRE(keyrdata != NULL); + + result = dns_rdata_tostruct(keyrdata, &key, NULL); + if (result != ISC_R_SUCCESS) { + return (false); + } + + if ((key.flags & DNS_KEYTYPE_NOAUTH) != 0) { + iszonekey = false; + } + if ((key.flags & DNS_KEYFLAG_OWNERMASK) != DNS_KEYOWNER_ZONE) { + iszonekey = false; + } + if (key.protocol != DNS_KEYPROTO_DNSSEC && + key.protocol != DNS_KEYPROTO_ANY) + { + iszonekey = false; + } + + return (iszonekey); +} diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c new file mode 100644 index 0000000..d5c1537 --- /dev/null +++ b/lib/dns/zoneverify.c @@ -0,0 +1,2037 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct vctx { + isc_mem_t *mctx; + dns_zone_t *zone; + dns_db_t *db; + dns_dbversion_t *ver; + dns_name_t *origin; + dns_keytable_t *secroots; + bool goodksk; + bool goodzsk; + dns_rdataset_t keyset; + dns_rdataset_t keysigs; + dns_rdataset_t soaset; + dns_rdataset_t soasigs; + dns_rdataset_t nsecset; + dns_rdataset_t nsecsigs; + dns_rdataset_t nsec3paramset; + dns_rdataset_t nsec3paramsigs; + unsigned char revoked_ksk[256]; + unsigned char revoked_zsk[256]; + unsigned char standby_ksk[256]; + unsigned char standby_zsk[256]; + unsigned char ksk_algorithms[256]; + unsigned char zsk_algorithms[256]; + unsigned char bad_algorithms[256]; + unsigned char act_algorithms[256]; + isc_heap_t *expected_chains; + isc_heap_t *found_chains; +} vctx_t; + +struct nsec3_chain_fixed { + uint8_t hash; + uint8_t salt_length; + uint8_t next_length; + uint16_t iterations; + /* + * The following non-fixed-length data is stored in memory after the + * fields declared above for each NSEC3 chain element: + * + * unsigned char salt[salt_length]; + * unsigned char owner[next_length]; + * unsigned char next[next_length]; + */ +}; + +/* + * Helper function used to calculate length of variable-length + * data section in object pointed to by 'chain'. + */ +static size_t +chain_length(struct nsec3_chain_fixed *chain) { + return (chain->salt_length + 2 * chain->next_length); +} + +/*% + * Log a zone verification error described by 'fmt' and the variable arguments + * following it. Either use dns_zone_logv() or print to stderr, depending on + * whether the function was invoked from within named or by a standalone tool, + * respectively. + */ +static void +zoneverify_log_error(const vctx_t *vctx, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (vctx->zone != NULL) { + dns_zone_logv(vctx->zone, DNS_LOGCATEGORY_GENERAL, + ISC_LOG_ERROR, NULL, fmt, ap); + } else { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + va_end(ap); +} + +static bool +is_delegation(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, + uint32_t *ttlp) { + dns_rdataset_t nsset; + isc_result_t result; + + if (dns_name_equal(name, vctx->origin)) { + return (false); + } + + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_ns, 0, 0, &nsset, NULL); + if (dns_rdataset_isassociated(&nsset)) { + if (ttlp != NULL) { + *ttlp = nsset.ttl; + } + dns_rdataset_disassociate(&nsset); + } + + return ((result == ISC_R_SUCCESS)); +} + +/*% + * Return true if version 'ver' of database 'db' contains a DNAME RRset at + * 'node'; return false otherwise. + */ +static bool +has_dname(const vctx_t *vctx, dns_dbnode_t *node) { + dns_rdataset_t dnameset; + isc_result_t result; + + dns_rdataset_init(&dnameset); + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_dname, 0, 0, &dnameset, + NULL); + if (dns_rdataset_isassociated(&dnameset)) { + dns_rdataset_disassociate(&dnameset); + } + + return ((result == ISC_R_SUCCESS)); +} + +static bool +goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name, + dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) { + dns_rdata_rrsig_t sig; + isc_result_t result; + + result = dns_rdata_tostruct(sigrdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + for (size_t key = 0; key < nkeys; key++) { + if (sig.algorithm != dst_key_alg(dstkeys[key]) || + sig.keyid != dst_key_id(dstkeys[key]) || + !dns_name_equal(&sig.signer, vctx->origin)) + { + continue; + } + result = dns_dnssec_verify(name, rdataset, dstkeys[key], false, + 0, vctx->mctx, sigrdata, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + return (true); + } + } + return (false); +} + +static bool +nsec_bitmap_equal(dns_rdata_nsec_t *nsec, dns_rdata_t *rdata) { + isc_result_t result; + dns_rdata_nsec_t tmpnsec; + + result = dns_rdata_tostruct(rdata, &tmpnsec, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (nsec->len != tmpnsec.len || + memcmp(nsec->typebits, tmpnsec.typebits, nsec->len) != 0) + { + return (false); + } + return (true); +} + +static isc_result_t +verifynsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, + const dns_name_t *nextname, isc_result_t *vresult) { + unsigned char buffer[DNS_NSEC_BUFFERSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + char nextbuf[DNS_NAME_FORMATSIZE]; + char found[DNS_NAME_FORMATSIZE]; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_t tmprdata = DNS_RDATA_INIT; + dns_rdata_nsec_t nsec; + isc_result_t result; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec, 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, "Missing NSEC record for %s", + namebuf); + *vresult = ISC_R_FAILURE; + result = ISC_R_SUCCESS; + goto done; + } + + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_rdataset_first(): %s", + isc_result_totext(result)); + goto done; + } + + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Check next name is consistent */ + if (!dns_name_equal(&nsec.next, nextname)) { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_name_format(nextname, nextbuf, sizeof(nextbuf)); + dns_name_format(&nsec.next, found, sizeof(found)); + zoneverify_log_error(vctx, + "Bad NSEC record for %s, next name " + "mismatch (expected:%s, found:%s)", + namebuf, nextbuf, found); + *vresult = ISC_R_FAILURE; + goto done; + } + + /* Check bit map is consistent */ + result = dns_nsec_buildrdata(vctx->db, vctx->ver, node, nextname, + buffer, &tmprdata); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_nsec_buildrdata(): %s", + isc_result_totext(result)); + goto done; + } + if (!nsec_bitmap_equal(&nsec, &tmprdata)) { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, + "Bad NSEC record for %s, bit map " + "mismatch", + namebuf); + *vresult = ISC_R_FAILURE; + goto done; + } + + result = dns_rdataset_next(&rdataset); + if (result != ISC_R_NOMORE) { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, "Multiple NSEC records for %s", + namebuf); + *vresult = ISC_R_FAILURE; + goto done; + } + + *vresult = ISC_R_SUCCESS; + result = ISC_R_SUCCESS; + +done: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + return (result); +} + +static isc_result_t +check_no_rrsig(const vctx_t *vctx, const dns_rdataset_t *rdataset, + const dns_name_t *name, dns_dbnode_t *node) { + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdataset_t sigrdataset; + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result; + + dns_rdataset_init(&sigrdataset); + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", + isc_result_totext(result)); + return (result); + } + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &sigrdataset); + if (sigrdataset.type == dns_rdatatype_rrsig && + sigrdataset.covers == rdataset->type) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + zoneverify_log_error( + vctx, + "Warning: Found unexpected signatures " + "for %s/%s", + namebuf, typebuf); + break; + } + dns_rdataset_disassociate(&sigrdataset); + } + if (dns_rdataset_isassociated(&sigrdataset)) { + dns_rdataset_disassociate(&sigrdataset); + } + dns_rdatasetiter_destroy(&rdsiter); + + return (ISC_R_SUCCESS); +} + +static bool +chain_compare(void *arg1, void *arg2) { + struct nsec3_chain_fixed *e1 = arg1, *e2 = arg2; + /* + * Do each element in turn to get a stable sort. + */ + if (e1->hash < e2->hash) { + return (true); + } + if (e1->hash > e2->hash) { + return (false); + } + if (e1->iterations < e2->iterations) { + return (true); + } + if (e1->iterations > e2->iterations) { + return (false); + } + if (e1->salt_length < e2->salt_length) { + return (true); + } + if (e1->salt_length > e2->salt_length) { + return (false); + } + if (e1->next_length < e2->next_length) { + return (true); + } + if (e1->next_length > e2->next_length) { + return (false); + } + if (memcmp(e1 + 1, e2 + 1, chain_length(e1)) < 0) { + return (true); + } + return (false); +} + +static bool +chain_equal(const struct nsec3_chain_fixed *e1, + const struct nsec3_chain_fixed *e2, size_t data_length) { + if (e1->hash != e2->hash) { + return (false); + } + if (e1->iterations != e2->iterations) { + return (false); + } + if (e1->salt_length != e2->salt_length) { + return (false); + } + if (e1->next_length != e2->next_length) { + return (false); + } + + return (memcmp(e1 + 1, e2 + 1, data_length) == 0); +} + +static void +record_nsec3(const vctx_t *vctx, const unsigned char *rawhash, + const dns_rdata_nsec3_t *nsec3, isc_heap_t *chains) { + struct nsec3_chain_fixed *element = NULL; + unsigned char *cp = NULL; + size_t len; + + len = sizeof(*element) + nsec3->next_length * 2 + nsec3->salt_length; + + element = isc_mem_get(vctx->mctx, len); + memset(element, 0, len); + element->hash = nsec3->hash; + element->salt_length = nsec3->salt_length; + element->next_length = nsec3->next_length; + element->iterations = nsec3->iterations; + cp = (unsigned char *)(element + 1); + memmove(cp, nsec3->salt, nsec3->salt_length); + cp += nsec3->salt_length; + memmove(cp, rawhash, nsec3->next_length); + cp += nsec3->next_length; + memmove(cp, nsec3->next, nsec3->next_length); + isc_heap_insert(chains, element); +} + +/* + * Check whether any NSEC3 within 'rdataset' matches the parameters in + * 'nsec3param'. + */ +static isc_result_t +find_nsec3_match(const dns_rdata_nsec3param_t *nsec3param, + dns_rdataset_t *rdataset, size_t rhsize, + dns_rdata_nsec3_t *nsec3_match) { + isc_result_t result; + + /* + * Find matching NSEC3 record. + */ + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, nsec3_match, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3_match->hash == nsec3param->hash && + nsec3_match->next_length == rhsize && + nsec3_match->iterations == nsec3param->iterations && + nsec3_match->salt_length == nsec3param->salt_length && + memcmp(nsec3_match->salt, nsec3param->salt, + nsec3param->salt_length) == 0) + { + return (ISC_R_SUCCESS); + } + } + + return (result); +} + +static isc_result_t +match_nsec3(const vctx_t *vctx, const dns_name_t *name, + const dns_rdata_nsec3param_t *nsec3param, dns_rdataset_t *rdataset, + const unsigned char types[8192], unsigned int maxtype, + const unsigned char *rawhash, size_t rhsize, + isc_result_t *vresult) { + unsigned char cbm[8244]; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rdata_nsec3_t nsec3; + isc_result_t result; + unsigned int len; + + result = find_nsec3_match(nsec3param, rdataset, rhsize, &nsec3); + if (result != ISC_R_SUCCESS) { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, "Missing NSEC3 record for %s", + namebuf); + *vresult = result; + return (ISC_R_SUCCESS); + } + + /* + * Check the type list. + */ + len = dns_nsec_compressbitmap(cbm, types, maxtype); + if (nsec3.len != len || memcmp(cbm, nsec3.typebits, len) != 0) { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, + "Bad NSEC3 record for %s, bit map " + "mismatch", + namebuf); + *vresult = ISC_R_FAILURE; + return (ISC_R_SUCCESS); + } + + /* + * Record chain. + */ + record_nsec3(vctx, rawhash, &nsec3, vctx->expected_chains); + + /* + * Make sure there is only one NSEC3 record with this set of + * parameters. + */ + for (result = dns_rdataset_next(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3.hash == nsec3param->hash && + nsec3.iterations == nsec3param->iterations && + nsec3.salt_length == nsec3param->salt_length && + memcmp(nsec3.salt, nsec3param->salt, nsec3.salt_length) == + 0) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, + "Multiple NSEC3 records with the " + "same parameter set for %s", + namebuf); + *vresult = DNS_R_DUPLICATE; + return (ISC_R_SUCCESS); + } + } + if (result != ISC_R_NOMORE) { + return (result); + } + + *vresult = ISC_R_SUCCESS; + + return (ISC_R_SUCCESS); +} + +static bool +innsec3params(const dns_rdata_nsec3_t *nsec3, dns_rdataset_t *nsec3paramset) { + dns_rdata_nsec3param_t nsec3param; + isc_result_t result; + + for (result = dns_rdataset_first(nsec3paramset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(nsec3paramset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3param.flags == 0 && nsec3param.hash == nsec3->hash && + nsec3param.iterations == nsec3->iterations && + nsec3param.salt_length == nsec3->salt_length && + memcmp(nsec3param.salt, nsec3->salt, nsec3->salt_length) == + 0) + { + return (true); + } + } + return (false); +} + +static isc_result_t +record_found(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, + dns_rdataset_t *nsec3paramset) { + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + dns_rdata_nsec3_t nsec3; + dns_rdataset_t rdataset; + dns_label_t hashlabel; + isc_buffer_t b; + isc_result_t result; + + if (nsec3paramset == NULL || !dns_rdataset_isassociated(nsec3paramset)) + { + return (ISC_R_SUCCESS); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec3, 0, 0, &rdataset, + NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + dns_name_getlabel(name, 0, &hashlabel); + isc_region_consume(&hashlabel, 1); + isc_buffer_init(&b, owner, sizeof(owner)); + result = isc_base32hex_decoderegion(&hashlabel, &b); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (nsec3.next_length != isc_buffer_usedlength(&b)) { + continue; + } + + /* + * We only care about NSEC3 records that match a NSEC3PARAM + * record. + */ + if (!innsec3params(&nsec3, nsec3paramset)) { + continue; + } + + /* + * Record chain. + */ + record_nsec3(vctx, owner, &nsec3, vctx->found_chains); + } + result = ISC_R_SUCCESS; + +cleanup: + dns_rdataset_disassociate(&rdataset); + return (result); +} + +static isc_result_t +isoptout(const vctx_t *vctx, const dns_rdata_nsec3param_t *nsec3param, + bool *optout) { + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + dns_fixedname_t fixed; + dns_name_t *hashname; + isc_result_t result; + dns_dbnode_t *node = NULL; + unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; + size_t rhsize = sizeof(rawhash); + + dns_fixedname_init(&fixed); + result = dns_nsec3_hashname(&fixed, rawhash, &rhsize, vctx->origin, + vctx->origin, nsec3param->hash, + nsec3param->iterations, nsec3param->salt, + nsec3param->salt_length); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s", + isc_result_totext(result)); + return (result); + } + + dns_rdataset_init(&rdataset); + hashname = dns_fixedname_name(&fixed); + result = dns_db_findnsec3node(vctx->db, hashname, false, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + } + if (result != ISC_R_SUCCESS) { + *optout = false; + result = ISC_R_SUCCESS; + goto done; + } + + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_rdataset_first(): %s", + isc_result_totext(result)); + goto done; + } + + dns_rdataset_current(&rdataset, &rdata); + + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + *optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); + +done: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(vctx->db, &node); + } + + return (result); +} + +static isc_result_t +verifynsec3(const vctx_t *vctx, const dns_name_t *name, + const dns_rdata_t *rdata, bool delegation, bool empty, + const unsigned char types[8192], unsigned int maxtype, + isc_result_t *vresult) { + char namebuf[DNS_NAME_FORMATSIZE]; + char hashbuf[DNS_NAME_FORMATSIZE]; + dns_rdataset_t rdataset; + dns_rdata_nsec3param_t nsec3param; + dns_fixedname_t fixed; + dns_name_t *hashname; + isc_result_t result, tvresult = ISC_R_UNSET; + dns_dbnode_t *node = NULL; + unsigned char rawhash[NSEC3_MAX_HASH_LENGTH]; + size_t rhsize = sizeof(rawhash); + bool optout = false; + + result = dns_rdata_tostruct(rdata, &nsec3param, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (nsec3param.flags != 0) { + return (ISC_R_SUCCESS); + } + + if (!dns_nsec3_supportedhash(nsec3param.hash)) { + return (ISC_R_SUCCESS); + } + + if (nsec3param.iterations > DNS_NSEC3_MAXITERATIONS) { + result = DNS_R_NSEC3ITERRANGE; + zoneverify_log_error(vctx, "verifynsec3: %s", + isc_result_totext(result)); + return (result); + } + + result = isoptout(vctx, &nsec3param, &optout); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_fixedname_init(&fixed); + result = dns_nsec3_hashname( + &fixed, rawhash, &rhsize, name, vctx->origin, nsec3param.hash, + nsec3param.iterations, nsec3param.salt, nsec3param.salt_length); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_nsec3_hashname(): %s", + isc_result_totext(result)); + return (result); + } + + /* + * We don't use dns_db_find() here as it works with the chosen + * nsec3 chain and we may also be called with uncommitted data + * from dnssec-signzone so the secure status of the zone may not + * be up to date. + */ + dns_rdataset_init(&rdataset); + hashname = dns_fixedname_name(&fixed); + result = dns_db_findnsec3node(vctx->db, hashname, false, &node); + if (result == ISC_R_SUCCESS) { + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + } + if (result != ISC_R_SUCCESS && + (!delegation || (empty && !optout) || + (!empty && dns_nsec_isset(types, dns_rdatatype_ds)))) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_name_format(hashname, hashbuf, sizeof(hashbuf)); + zoneverify_log_error(vctx, "Missing NSEC3 record for %s (%s)", + namebuf, hashbuf); + } else if (result == ISC_R_NOTFOUND && delegation && (!empty || optout)) + { + result = ISC_R_SUCCESS; + } else if (result == ISC_R_SUCCESS) { + result = match_nsec3(vctx, name, &nsec3param, &rdataset, types, + maxtype, rawhash, rhsize, &tvresult); + if (result != ISC_R_SUCCESS) { + goto done; + } + result = tvresult; + } + + *vresult = result; + result = ISC_R_SUCCESS; + +done: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(vctx->db, &node); + } + + return (result); +} + +static isc_result_t +verifynsec3s(const vctx_t *vctx, const dns_name_t *name, + dns_rdataset_t *nsec3paramset, bool delegation, bool empty, + const unsigned char types[8192], unsigned int maxtype, + isc_result_t *vresult) { + isc_result_t result; + + for (result = dns_rdataset_first(nsec3paramset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(nsec3paramset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(nsec3paramset, &rdata); + result = verifynsec3(vctx, name, &rdata, delegation, empty, + types, maxtype, vresult); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (*vresult != ISC_R_SUCCESS) { + break; + } + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + return (result); +} + +static isc_result_t +verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, + dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) { + unsigned char set_algorithms[256] = { 0 }; + char namebuf[DNS_NAME_FORMATSIZE]; + char algbuf[DNS_SECALG_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdataset_t sigrdataset; + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result; + + dns_rdataset_init(&sigrdataset); + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", + isc_result_totext(result)); + return (result); + } + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &sigrdataset); + if (sigrdataset.type == dns_rdatatype_rrsig && + sigrdataset.covers == rdataset->type) + { + break; + } + dns_rdataset_disassociate(&sigrdataset); + } + if (result != ISC_R_SUCCESS) { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + zoneverify_log_error(vctx, "No signatures for %s/%s", namebuf, + typebuf); + for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) { + if (vctx->act_algorithms[i] != 0) { + vctx->bad_algorithms[i] = 1; + } + } + result = ISC_R_SUCCESS; + goto done; + } + + for (result = dns_rdataset_first(&sigrdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&sigrdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + + dns_rdataset_current(&sigrdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &sig, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (rdataset->ttl != sig.originalttl) { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, + sizeof(typebuf)); + zoneverify_log_error(vctx, + "TTL mismatch for " + "%s %s keytag %u", + namebuf, typebuf, sig.keyid); + continue; + } + if ((set_algorithms[sig.algorithm] != 0) || + (vctx->act_algorithms[sig.algorithm] == 0)) + { + continue; + } + if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) { + dns_rdataset_settrust(rdataset, dns_trust_secure); + dns_rdataset_settrust(&sigrdataset, dns_trust_secure); + set_algorithms[sig.algorithm] = 1; + } + } + result = ISC_R_SUCCESS; + + if (memcmp(set_algorithms, vctx->act_algorithms, + sizeof(set_algorithms)) != 0) + { + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); + for (size_t i = 0; i < ARRAY_SIZE(set_algorithms); i++) { + if ((vctx->act_algorithms[i] != 0) && + (set_algorithms[i] == 0)) + { + dns_secalg_format(i, algbuf, sizeof(algbuf)); + zoneverify_log_error(vctx, + "No correct %s signature " + "for %s %s", + algbuf, namebuf, typebuf); + vctx->bad_algorithms[i] = 1; + } + } + } + +done: + if (dns_rdataset_isassociated(&sigrdataset)) { + dns_rdataset_disassociate(&sigrdataset); + } + dns_rdatasetiter_destroy(&rdsiter); + + return (result); +} + +static isc_result_t +verifynode(vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node, + bool delegation, dst_key_t **dstkeys, size_t nkeys, + dns_rdataset_t *nsecset, dns_rdataset_t *nsec3paramset, + const dns_name_t *nextname, isc_result_t *vresult) { + unsigned char types[8192] = { 0 }; + unsigned int maxtype = 0; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result, tvresult = ISC_R_UNSET; + + REQUIRE(vresult != NULL || (nsecset == NULL && nsec3paramset == NULL)); + + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", + isc_result_totext(result)); + return (result); + } + + result = dns_rdatasetiter_first(rdsiter); + dns_rdataset_init(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + /* + * If we are not at a delegation then everything should be + * signed. If we are at a delegation then only the DS set + * is signed. The NS set is not signed at a delegation but + * its existence is recorded in the bit map. Anything else + * other than NSEC and DS is not signed at a delegation. + */ + if (rdataset.type != dns_rdatatype_rrsig && + rdataset.type != dns_rdatatype_dnskey && + (!delegation || rdataset.type == dns_rdatatype_ds || + rdataset.type == dns_rdatatype_nsec)) + { + result = verifyset(vctx, &rdataset, name, node, dstkeys, + nkeys); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + dns_rdatasetiter_destroy(&rdsiter); + return (result); + } + dns_nsec_setbit(types, rdataset.type, 1); + if (rdataset.type > maxtype) { + maxtype = rdataset.type; + } + } else if (rdataset.type != dns_rdatatype_rrsig && + rdataset.type != dns_rdatatype_dnskey) + { + if (rdataset.type == dns_rdatatype_ns) { + dns_nsec_setbit(types, rdataset.type, 1); + } + result = check_no_rrsig(vctx, &rdataset, name, node); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + dns_rdatasetiter_destroy(&rdsiter); + return (result); + } + } else { + dns_nsec_setbit(types, rdataset.type, 1); + } + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_NOMORE) { + zoneverify_log_error(vctx, "rdataset iteration failed: %s", + isc_result_totext(result)); + return (result); + } + + if (vresult == NULL) { + return (ISC_R_SUCCESS); + } + + *vresult = ISC_R_SUCCESS; + + if (nsecset != NULL && dns_rdataset_isassociated(nsecset)) { + result = verifynsec(vctx, name, node, nextname, &tvresult); + if (result != ISC_R_SUCCESS) { + return (result); + } + *vresult = tvresult; + } + + if (nsec3paramset != NULL && dns_rdataset_isassociated(nsec3paramset)) { + result = verifynsec3s(vctx, name, nsec3paramset, delegation, + false, types, maxtype, &tvresult); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (*vresult == ISC_R_SUCCESS) { + *vresult = tvresult; + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +is_empty(const vctx_t *vctx, dns_dbnode_t *node, bool *empty) { + dns_rdatasetiter_t *rdsiter = NULL; + isc_result_t result; + + result = dns_db_allrdatasets(vctx->db, node, vctx->ver, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_allrdatasets(): %s", + isc_result_totext(result)); + return (result); + } + result = dns_rdatasetiter_first(rdsiter); + dns_rdatasetiter_destroy(&rdsiter); + + *empty = (result == ISC_R_NOMORE); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +check_no_nsec(const vctx_t *vctx, const dns_name_t *name, dns_dbnode_t *node) { + bool nsec_exists = false; + dns_rdataset_t rdataset; + isc_result_t result; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec, 0, 0, &rdataset, NULL); + if (result != ISC_R_NOTFOUND) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + zoneverify_log_error(vctx, "unexpected NSEC RRset at %s", + namebuf); + nsec_exists = true; + } + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + return (nsec_exists ? ISC_R_FAILURE : ISC_R_SUCCESS); +} + +static void +free_element(isc_mem_t *mctx, struct nsec3_chain_fixed *e) { + size_t len; + + len = sizeof(*e) + e->salt_length + 2 * e->next_length; + isc_mem_put(mctx, e, len); +} + +static void +free_element_heap(void *element, void *uap) { + struct nsec3_chain_fixed *e = (struct nsec3_chain_fixed *)element; + isc_mem_t *mctx = (isc_mem_t *)uap; + + free_element(mctx, e); +} + +static bool +_checknext(const vctx_t *vctx, const struct nsec3_chain_fixed *first, + const struct nsec3_chain_fixed *e) { + char buf[512]; + const unsigned char *d1 = (const unsigned char *)(first + 1); + const unsigned char *d2 = (const unsigned char *)(e + 1); + isc_buffer_t b; + isc_region_t sr; + + d1 += first->salt_length + first->next_length; + d2 += e->salt_length; + + if (memcmp(d1, d2, first->next_length) == 0) { + return (true); + } + + DE_CONST(d1 - first->next_length, sr.base); + sr.length = first->next_length; + isc_buffer_init(&b, buf, sizeof(buf)); + isc_base32hex_totext(&sr, 1, "", &b); + zoneverify_log_error(vctx, "Break in NSEC3 chain at: %.*s", + (int)isc_buffer_usedlength(&b), buf); + + DE_CONST(d1, sr.base); + sr.length = first->next_length; + isc_buffer_init(&b, buf, sizeof(buf)); + isc_base32hex_totext(&sr, 1, "", &b); + zoneverify_log_error(vctx, "Expected: %.*s", + (int)isc_buffer_usedlength(&b), buf); + + DE_CONST(d2, sr.base); + sr.length = first->next_length; + isc_buffer_init(&b, buf, sizeof(buf)); + isc_base32hex_totext(&sr, 1, "", &b); + zoneverify_log_error(vctx, "Found: %.*s", + (int)isc_buffer_usedlength(&b), buf); + + return (false); +} + +static bool +checknext(isc_mem_t *mctx, const vctx_t *vctx, + const struct nsec3_chain_fixed *first, struct nsec3_chain_fixed *prev, + const struct nsec3_chain_fixed *cur) { + bool result = _checknext(vctx, prev, cur); + + if (prev != first) { + free_element(mctx, prev); + } + + return (result); +} + +static bool +checklast(isc_mem_t *mctx, const vctx_t *vctx, struct nsec3_chain_fixed *first, + struct nsec3_chain_fixed *prev) { + bool result = _checknext(vctx, prev, first); + if (prev != first) { + free_element(mctx, prev); + } + free_element(mctx, first); + + return (result); +} + +static isc_result_t +verify_nsec3_chains(const vctx_t *vctx, isc_mem_t *mctx) { + isc_result_t result = ISC_R_SUCCESS; + struct nsec3_chain_fixed *e, *f = NULL; + struct nsec3_chain_fixed *first = NULL, *prev = NULL; + + while ((e = isc_heap_element(vctx->expected_chains, 1)) != NULL) { + isc_heap_delete(vctx->expected_chains, 1); + if (f == NULL) { + f = isc_heap_element(vctx->found_chains, 1); + } + if (f != NULL) { + isc_heap_delete(vctx->found_chains, 1); + + /* + * Check that they match. + */ + if (chain_equal(e, f, chain_length(e))) { + free_element(mctx, f); + f = NULL; + } else { + if (result == ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "Expected " + "and found " + "NSEC3 " + "chains not " + "equal"); + } + result = ISC_R_FAILURE; + /* + * Attempt to resync found_chain. + */ + while (f != NULL && !chain_compare(e, f)) { + free_element(mctx, f); + f = isc_heap_element(vctx->found_chains, + 1); + if (f != NULL) { + isc_heap_delete( + vctx->found_chains, 1); + } + if (f != NULL && + chain_equal(e, f, chain_length(e))) + { + free_element(mctx, f); + f = NULL; + break; + } + } + } + } else if (result == ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "Expected and found NSEC3 " + "chains " + "not equal"); + result = ISC_R_FAILURE; + } + + if (first == NULL) { + prev = first = e; + } else if (!chain_equal(first, e, first->salt_length)) { + if (!checklast(mctx, vctx, first, prev)) { + result = ISC_R_FAILURE; + } + + prev = first = e; + } else { + if (!checknext(mctx, vctx, first, prev, e)) { + result = ISC_R_FAILURE; + } + + prev = e; + } + } + if (prev != NULL) { + if (!checklast(mctx, vctx, first, prev)) { + result = ISC_R_FAILURE; + } + } + do { + if (f != NULL) { + if (result == ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "Expected and found " + "NSEC3 chains not " + "equal"); + result = ISC_R_FAILURE; + } + free_element(mctx, f); + } + f = isc_heap_element(vctx->found_chains, 1); + if (f != NULL) { + isc_heap_delete(vctx->found_chains, 1); + } + } while (f != NULL); + + return (result); +} + +static isc_result_t +verifyemptynodes(const vctx_t *vctx, const dns_name_t *name, + const dns_name_t *prevname, bool isdelegation, + dns_rdataset_t *nsec3paramset, isc_result_t *vresult) { + dns_namereln_t reln; + int order; + unsigned int labels, nlabels, i; + dns_name_t suffix; + isc_result_t result, tvresult = ISC_R_UNSET; + + *vresult = ISC_R_SUCCESS; + + reln = dns_name_fullcompare(prevname, name, &order, &labels); + if (order >= 0) { + return (ISC_R_SUCCESS); + } + + nlabels = dns_name_countlabels(name); + + if (reln == dns_namereln_commonancestor || + reln == dns_namereln_contains) + { + dns_name_init(&suffix, NULL); + for (i = labels + 1; i < nlabels; i++) { + dns_name_getlabelsequence(name, nlabels - i, i, + &suffix); + if (nsec3paramset != NULL && + dns_rdataset_isassociated(nsec3paramset)) + { + result = verifynsec3s( + vctx, &suffix, nsec3paramset, + isdelegation, true, NULL, 0, &tvresult); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (*vresult == ISC_R_SUCCESS) { + *vresult = tvresult; + } + } + } + } + + return (ISC_R_SUCCESS); +} + +static void +vctx_init(vctx_t *vctx, isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *ver, dns_name_t *origin, dns_keytable_t *secroots) { + memset(vctx, 0, sizeof(*vctx)); + + vctx->mctx = mctx; + vctx->zone = zone; + vctx->db = db; + vctx->ver = ver; + vctx->origin = origin; + vctx->secroots = secroots; + vctx->goodksk = false; + vctx->goodzsk = false; + + dns_rdataset_init(&vctx->keyset); + dns_rdataset_init(&vctx->keysigs); + dns_rdataset_init(&vctx->soaset); + dns_rdataset_init(&vctx->soasigs); + dns_rdataset_init(&vctx->nsecset); + dns_rdataset_init(&vctx->nsecsigs); + dns_rdataset_init(&vctx->nsec3paramset); + dns_rdataset_init(&vctx->nsec3paramsigs); + + vctx->expected_chains = NULL; + isc_heap_create(mctx, chain_compare, NULL, 1024, + &vctx->expected_chains); + + vctx->found_chains = NULL; + isc_heap_create(mctx, chain_compare, NULL, 1024, &vctx->found_chains); +} + +static void +vctx_destroy(vctx_t *vctx) { + if (dns_rdataset_isassociated(&vctx->keyset)) { + dns_rdataset_disassociate(&vctx->keyset); + } + if (dns_rdataset_isassociated(&vctx->keysigs)) { + dns_rdataset_disassociate(&vctx->keysigs); + } + if (dns_rdataset_isassociated(&vctx->soaset)) { + dns_rdataset_disassociate(&vctx->soaset); + } + if (dns_rdataset_isassociated(&vctx->soasigs)) { + dns_rdataset_disassociate(&vctx->soasigs); + } + if (dns_rdataset_isassociated(&vctx->nsecset)) { + dns_rdataset_disassociate(&vctx->nsecset); + } + if (dns_rdataset_isassociated(&vctx->nsecsigs)) { + dns_rdataset_disassociate(&vctx->nsecsigs); + } + if (dns_rdataset_isassociated(&vctx->nsec3paramset)) { + dns_rdataset_disassociate(&vctx->nsec3paramset); + } + if (dns_rdataset_isassociated(&vctx->nsec3paramsigs)) { + dns_rdataset_disassociate(&vctx->nsec3paramsigs); + } + isc_heap_foreach(vctx->expected_chains, free_element_heap, vctx->mctx); + isc_heap_destroy(&vctx->expected_chains); + isc_heap_foreach(vctx->found_chains, free_element_heap, vctx->mctx); + isc_heap_destroy(&vctx->found_chains); +} + +static isc_result_t +check_apex_rrsets(vctx_t *vctx) { + dns_dbnode_t *node = NULL; + isc_result_t result; + + result = dns_db_findnode(vctx->db, vctx->origin, false, &node); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, + "failed to find the zone's origin: %s", + isc_result_totext(result)); + return (result); + } + + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_dnskey, 0, 0, &vctx->keyset, + &vctx->keysigs); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "Zone contains no DNSSEC keys"); + goto done; + } + + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_soa, 0, 0, &vctx->soaset, + &vctx->soasigs); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "Zone contains no SOA record"); + goto done; + } + + result = dns_db_findrdataset(vctx->db, node, vctx->ver, + dns_rdatatype_nsec, 0, 0, &vctx->nsecset, + &vctx->nsecsigs); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + zoneverify_log_error(vctx, "NSEC lookup failed"); + goto done; + } + + result = dns_db_findrdataset( + vctx->db, node, vctx->ver, dns_rdatatype_nsec3param, 0, 0, + &vctx->nsec3paramset, &vctx->nsec3paramsigs); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + zoneverify_log_error(vctx, "NSEC3PARAM lookup failed"); + goto done; + } + + if (!dns_rdataset_isassociated(&vctx->keysigs)) { + zoneverify_log_error(vctx, "DNSKEY is not signed " + "(keys offline or inactive?)"); + result = ISC_R_FAILURE; + goto done; + } + + if (!dns_rdataset_isassociated(&vctx->soasigs)) { + zoneverify_log_error(vctx, "SOA is not signed " + "(keys offline or inactive?)"); + result = ISC_R_FAILURE; + goto done; + } + + if (dns_rdataset_isassociated(&vctx->nsecset) && + !dns_rdataset_isassociated(&vctx->nsecsigs)) + { + zoneverify_log_error(vctx, "NSEC is not signed " + "(keys offline or inactive?)"); + result = ISC_R_FAILURE; + goto done; + } + + if (dns_rdataset_isassociated(&vctx->nsec3paramset) && + !dns_rdataset_isassociated(&vctx->nsec3paramsigs)) + { + zoneverify_log_error(vctx, "NSEC3PARAM is not signed " + "(keys offline or inactive?)"); + result = ISC_R_FAILURE; + goto done; + } + + if (!dns_rdataset_isassociated(&vctx->nsecset) && + !dns_rdataset_isassociated(&vctx->nsec3paramset)) + { + zoneverify_log_error(vctx, "No valid NSEC/NSEC3 chain for " + "testing"); + result = ISC_R_FAILURE; + goto done; + } + + result = ISC_R_SUCCESS; + +done: + dns_db_detachnode(vctx->db, &node); + + return (result); +} + +/*% + * Update 'vctx' tables tracking active and standby key algorithms used in the + * verified zone based on the signatures made using 'dnskey' (prepared from + * 'rdata') found at zone apex. Set 'vctx->goodksk' or 'vctx->goodzsk' to true + * if 'dnskey' correctly signs the DNSKEY RRset at zone apex and either + * 'vctx->secroots' is NULL or 'dnskey' is present in 'vctx->secroots'. + * + * The variables to update are chosen based on 'is_ksk', which is true when + * 'dnskey' is a KSK and false otherwise. + */ +static void +check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, + dns_rdata_t *keyrdata, bool is_ksk) { + unsigned char *active_keys = NULL, *standby_keys = NULL; + dns_keynode_t *keynode = NULL; + bool *goodkey = NULL; + dst_key_t *key = NULL; + isc_result_t result; + dns_rdataset_t dsset; + + active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms); + standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk); + goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk); + + /* + * First, does this key sign the DNSKEY rrset? + */ + if (!dns_dnssec_selfsigns(keyrdata, vctx->origin, &vctx->keyset, + &vctx->keysigs, false, vctx->mctx)) + { + if (!is_ksk && + dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset, + &vctx->soasigs, false, vctx->mctx)) + { + if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { + active_keys[dnskey->algorithm]++; + } + } else { + if (standby_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { + standby_keys[dnskey->algorithm]++; + } + } + return; + } + + if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { + active_keys[dnskey->algorithm]++; + } + + /* + * If a trust anchor table was not supplied, a correctly self-signed + * DNSKEY RRset is good enough. + */ + if (vctx->secroots == NULL) { + *goodkey = true; + return; + } + + /* + * Convert the supplied key rdata to dst_key_t. (If this + * fails we can't go further.) + */ + result = dns_dnssec_keyfromrdata(vctx->origin, keyrdata, vctx->mctx, + &key); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * Look up the supplied key in the trust anchor table. + * If we don't find an exact match, or if the keynode data + * is NULL, then we have neither a DNSKEY nor a DS format + * trust anchor, and can give up. + */ + result = dns_keytable_find(vctx->secroots, vctx->origin, &keynode); + if (result != ISC_R_SUCCESS) { + /* No such trust anchor */ + goto cleanup; + } + + /* + * If the keynode has any DS format trust anchors, that means + * it doesn't have any DNSKEY ones. So, we can check for a DS + * match and then stop. + */ + dns_rdataset_init(&dsset); + if (dns_keynode_dsset(keynode, &dsset)) { + for (result = dns_rdataset_first(&dsset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&dsset)) + { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t newdsrdata = DNS_RDATA_INIT; + unsigned char buf[DNS_DS_BUFFERSIZE]; + dns_rdata_ds_t ds; + + dns_rdata_reset(&dsrdata); + dns_rdataset_current(&dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (ds.key_tag != dst_key_id(key) || + ds.algorithm != dst_key_alg(key)) + { + continue; + } + + result = dns_ds_buildrdata(vctx->origin, keyrdata, + ds.digest_type, buf, + &newdsrdata); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) { + dns_rdataset_settrust(&vctx->keyset, + dns_trust_secure); + dns_rdataset_settrust(&vctx->keysigs, + dns_trust_secure); + *goodkey = true; + break; + } + } + dns_rdataset_disassociate(&dsset); + + goto cleanup; + } + +cleanup: + if (keynode != NULL) { + dns_keytable_detachkeynode(vctx->secroots, &keynode); + } + if (key != NULL) { + dst_key_free(&key); + } +} + +/*% + * Check that the DNSKEY RR has at least one self signing KSK and one ZSK per + * algorithm in it (or, if -x was used, one self-signing KSK). + */ +static isc_result_t +check_dnskey(vctx_t *vctx) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t dnskey; + isc_result_t result; + bool is_ksk; + + for (result = dns_rdataset_first(&vctx->keyset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset)) + { + dns_rdataset_current(&vctx->keyset, &rdata); + result = dns_rdata_tostruct(&rdata, &dnskey, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + is_ksk = ((dnskey.flags & DNS_KEYFLAG_KSK) != 0); + + if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 && + (dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) + { + if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && + !dns_dnssec_selfsigns(&rdata, vctx->origin, + &vctx->keyset, &vctx->keysigs, + false, vctx->mctx)) + { + char namebuf[DNS_NAME_FORMATSIZE]; + char buffer[1024]; + isc_buffer_t buf; + + dns_name_format(vctx->origin, namebuf, + sizeof(namebuf)); + isc_buffer_init(&buf, buffer, sizeof(buffer)); + result = dns_rdata_totext(&rdata, NULL, &buf); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error( + vctx, "dns_rdata_totext: %s", + isc_result_totext(result)); + return (ISC_R_FAILURE); + } + zoneverify_log_error( + vctx, + "revoked KSK is not self signed:\n" + "%s DNSKEY %.*s", + namebuf, + (int)isc_buffer_usedlength(&buf), + buffer); + return (ISC_R_FAILURE); + } + if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && + vctx->revoked_ksk[dnskey.algorithm] != + DNS_KEYALG_MAX) + { + vctx->revoked_ksk[dnskey.algorithm]++; + } else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && + vctx->revoked_zsk[dnskey.algorithm] != + DNS_KEYALG_MAX) + { + vctx->revoked_zsk[dnskey.algorithm]++; + } + } else { + check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk); + } + dns_rdata_freestruct(&dnskey); + dns_rdata_reset(&rdata); + } + + return (ISC_R_SUCCESS); +} + +static void +determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag, + bool keyset_kskonly, + void (*report)(const char *, ...)) { + char algbuf[DNS_SECALG_FORMATSIZE]; + + report("Verifying the zone using the following algorithms:"); + + for (size_t i = 0; i < ARRAY_SIZE(vctx->act_algorithms); i++) { + if (ignore_kskflag) { + vctx->act_algorithms[i] = (vctx->ksk_algorithms[i] != + 0 || + vctx->zsk_algorithms[i] != 0) + ? 1 + : 0; + } else { + vctx->act_algorithms[i] = vctx->ksk_algorithms[i] != 0 + ? 1 + : 0; + } + if (vctx->act_algorithms[i] != 0) { + dns_secalg_format(i, algbuf, sizeof(algbuf)); + report("- %s", algbuf); + } + } + + if (ignore_kskflag || keyset_kskonly) { + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) { + /* + * The counts should both be zero or both be non-zero. Mark + * the algorithm as bad if this is not met. + */ + if ((vctx->ksk_algorithms[i] != 0) == + (vctx->zsk_algorithms[i] != 0)) + { + continue; + } + dns_secalg_format(i, algbuf, sizeof(algbuf)); + zoneverify_log_error(vctx, "Missing %s for algorithm %s", + (vctx->ksk_algorithms[i] != 0) ? "ZSK" + : "self-" + "signed " + "KSK", + algbuf); + vctx->bad_algorithms[i] = 1; + } +} + +/*% + * Check that all the records not yet verified were signed by keys that are + * present in the DNSKEY RRset. + */ +static isc_result_t +verify_nodes(vctx_t *vctx, isc_result_t *vresult) { + dns_fixedname_t fname, fnextname, fprevname, fzonecut; + dns_name_t *name, *nextname, *prevname, *zonecut; + dns_dbnode_t *node = NULL, *nextnode; + dns_dbiterator_t *dbiter = NULL; + dst_key_t **dstkeys; + size_t count, nkeys = 0; + bool done = false; + isc_result_t tvresult = ISC_R_UNSET; + isc_result_t result; + + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + dns_fixedname_init(&fprevname); + prevname = NULL; + dns_fixedname_init(&fzonecut); + zonecut = NULL; + + count = dns_rdataset_count(&vctx->keyset); + dstkeys = isc_mem_get(vctx->mctx, sizeof(*dstkeys) * count); + + for (result = dns_rdataset_first(&vctx->keyset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(&vctx->keyset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&vctx->keyset, &rdata); + dstkeys[nkeys] = NULL; + result = dns_dnssec_keyfromrdata(vctx->origin, &rdata, + vctx->mctx, &dstkeys[nkeys]); + if (result == ISC_R_SUCCESS) { + nkeys++; + } + } + + result = dns_db_createiterator(vctx->db, DNS_DB_NONSEC3, &dbiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_createiterator(): %s", + isc_result_totext(result)); + goto done; + } + + result = dns_dbiterator_first(dbiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_dbiterator_first(): %s", + isc_result_totext(result)); + goto done; + } + + while (!done) { + bool isdelegation = false; + + result = dns_dbiterator_current(dbiter, &node, name); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + zoneverify_log_error(vctx, + "dns_dbiterator_current(): %s", + isc_result_totext(result)); + goto done; + } + if (!dns_name_issubdomain(name, vctx->origin)) { + result = check_no_nsec(vctx, name, node); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(vctx->db, &node); + goto done; + } + dns_db_detachnode(vctx->db, &node); + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, + "dns_dbiterator_next(): " + "%s", + isc_result_totext(result)); + goto done; + } + continue; + } + if (is_delegation(vctx, name, node, NULL)) { + zonecut = dns_fixedname_name(&fzonecut); + dns_name_copy(name, zonecut); + isdelegation = true; + } else if (has_dname(vctx, node)) { + zonecut = dns_fixedname_name(&fzonecut); + dns_name_copy(name, zonecut); + } + nextnode = NULL; + result = dns_dbiterator_next(dbiter); + while (result == ISC_R_SUCCESS) { + bool empty; + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + if (result != ISC_R_SUCCESS && + result != DNS_R_NEWORIGIN) + { + zoneverify_log_error(vctx, + "dns_dbiterator_current():" + " %s", + isc_result_totext(result)); + dns_db_detachnode(vctx->db, &node); + goto done; + } + if (!dns_name_issubdomain(nextname, vctx->origin) || + (zonecut != NULL && + dns_name_issubdomain(nextname, zonecut))) + { + result = check_no_nsec(vctx, nextname, + nextnode); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(vctx->db, &node); + dns_db_detachnode(vctx->db, &nextnode); + goto done; + } + dns_db_detachnode(vctx->db, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + result = is_empty(vctx, nextnode, &empty); + dns_db_detachnode(vctx->db, &nextnode); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(vctx->db, &node); + goto done; + } + if (empty) { + result = dns_dbiterator_next(dbiter); + continue; + } + break; + } + if (result == ISC_R_NOMORE) { + done = true; + nextname = vctx->origin; + } else if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, + "iterating through the database " + "failed: %s", + isc_result_totext(result)); + dns_db_detachnode(vctx->db, &node); + goto done; + } + result = verifynode(vctx, name, node, isdelegation, dstkeys, + nkeys, &vctx->nsecset, &vctx->nsec3paramset, + nextname, &tvresult); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(vctx->db, &node); + goto done; + } + if (*vresult == ISC_R_UNSET) { + *vresult = ISC_R_SUCCESS; + } + if (*vresult == ISC_R_SUCCESS) { + *vresult = tvresult; + } + if (prevname != NULL) { + result = verifyemptynodes( + vctx, name, prevname, isdelegation, + &vctx->nsec3paramset, &tvresult); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(vctx->db, &node); + goto done; + } + } else { + prevname = dns_fixedname_name(&fprevname); + } + dns_name_copy(name, prevname); + if (*vresult == ISC_R_SUCCESS) { + *vresult = tvresult; + } + dns_db_detachnode(vctx->db, &node); + } + + dns_dbiterator_destroy(&dbiter); + + result = dns_db_createiterator(vctx->db, DNS_DB_NSEC3ONLY, &dbiter); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "dns_db_createiterator(): %s", + isc_result_totext(result)); + return (result); + } + + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + zoneverify_log_error(vctx, + "dns_dbiterator_current(): %s", + isc_result_totext(result)); + goto done; + } + result = verifynode(vctx, name, node, false, dstkeys, nkeys, + NULL, NULL, NULL, NULL); + if (result != ISC_R_SUCCESS) { + zoneverify_log_error(vctx, "verifynode: %s", + isc_result_totext(result)); + dns_db_detachnode(vctx->db, &node); + goto done; + } + result = record_found(vctx, name, node, &vctx->nsec3paramset); + dns_db_detachnode(vctx->db, &node); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + + result = ISC_R_SUCCESS; + +done: + while (nkeys-- > 0U) { + dst_key_free(&dstkeys[nkeys]); + } + isc_mem_put(vctx->mctx, dstkeys, sizeof(*dstkeys) * count); + if (dbiter != NULL) { + dns_dbiterator_destroy(&dbiter); + } + + return (result); +} + +static isc_result_t +check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) { + char algbuf[DNS_SECALG_FORMATSIZE]; + bool first = true; + + for (size_t i = 0; i < ARRAY_SIZE(vctx->bad_algorithms); i++) { + if (vctx->bad_algorithms[i] == 0) { + continue; + } + if (first) { + report("The zone is not fully signed " + "for the following algorithms:"); + } + dns_secalg_format(i, algbuf, sizeof(algbuf)); + report(" %s", algbuf); + first = false; + } + + if (!first) { + report("."); + } + + return (first ? ISC_R_SUCCESS : ISC_R_FAILURE); +} + +static void +print_summary(const vctx_t *vctx, bool keyset_kskonly, + void (*report)(const char *, ...)) { + char algbuf[DNS_SECALG_FORMATSIZE]; + + report("Zone fully signed:"); + for (size_t i = 0; i < ARRAY_SIZE(vctx->ksk_algorithms); i++) { + if ((vctx->ksk_algorithms[i] == 0) && + (vctx->standby_ksk[i] == 0) && + (vctx->revoked_ksk[i] == 0) && + (vctx->zsk_algorithms[i] == 0) && + (vctx->standby_zsk[i] == 0) && (vctx->revoked_zsk[i] == 0)) + { + continue; + } + dns_secalg_format(i, algbuf, sizeof(algbuf)); + report("Algorithm: %s: KSKs: " + "%u active, %u stand-by, %u revoked", + algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i], + vctx->revoked_ksk[i]); + report("%*sZSKs: " + "%u active, %u %s, %u revoked", + (int)strlen(algbuf) + 13, "", vctx->zsk_algorithms[i], + vctx->standby_zsk[i], + keyset_kskonly ? "present" : "stand-by", + vctx->revoked_zsk[i]); + } +} + +isc_result_t +dns_zoneverify_dnssec(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_name_t *origin, dns_keytable_t *secroots, + isc_mem_t *mctx, bool ignore_kskflag, bool keyset_kskonly, + void (*report)(const char *, ...)) { + const char *keydesc = (secroots == NULL ? "self-signed" : "trusted"); + isc_result_t result, vresult = ISC_R_UNSET; + vctx_t vctx; + + vctx_init(&vctx, mctx, zone, db, ver, origin, secroots); + + result = check_apex_rrsets(&vctx); + if (result != ISC_R_SUCCESS) { + goto done; + } + + result = check_dnskey(&vctx); + if (result != ISC_R_SUCCESS) { + goto done; + } + + if (ignore_kskflag) { + if (!vctx.goodksk && !vctx.goodzsk) { + zoneverify_log_error(&vctx, "No %s DNSKEY found", + keydesc); + result = ISC_R_FAILURE; + goto done; + } + } else if (!vctx.goodksk) { + zoneverify_log_error(&vctx, "No %s KSK DNSKEY found", keydesc); + result = ISC_R_FAILURE; + goto done; + } + + determine_active_algorithms(&vctx, ignore_kskflag, keyset_kskonly, + report); + + result = verify_nodes(&vctx, &vresult); + if (result != ISC_R_SUCCESS) { + goto done; + } + + result = verify_nsec3_chains(&vctx, mctx); + if (vresult == ISC_R_UNSET) { + vresult = ISC_R_SUCCESS; + } + if (result != ISC_R_SUCCESS && vresult == ISC_R_SUCCESS) { + vresult = result; + } + + result = check_bad_algorithms(&vctx, report); + if (result != ISC_R_SUCCESS) { + report("DNSSEC completeness test failed."); + goto done; + } + + result = vresult; + if (result != ISC_R_SUCCESS) { + report("DNSSEC completeness test failed (%s).", + isc_result_totext(result)); + goto done; + } + + if (vctx.goodksk || ignore_kskflag) { + print_summary(&vctx, keyset_kskonly, report); + } + +done: + vctx_destroy(&vctx); + + return (result); +} diff --git a/lib/dns/zt.c b/lib/dns/zt.c new file mode 100644 index 0000000..a0c300a --- /dev/null +++ b/lib/dns/zt.c @@ -0,0 +1,615 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct zt_load_params { + dns_zt_zoneloaded_t dl; + bool newonly; +}; + +struct dns_zt { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + dns_rdataclass_t rdclass; + isc_rwlock_t rwlock; + dns_zt_allloaded_t loaddone; + void *loaddone_arg; + struct zt_load_params *loadparams; + + /* Atomic */ + atomic_bool flush; + isc_refcount_t references; + isc_refcount_t loads_pending; + + /* Locked by lock. */ + dns_rbt_t *table; +}; + +struct zt_freeze_params { + dns_view_t *view; + bool freeze; +}; + +#define ZTMAGIC ISC_MAGIC('Z', 'T', 'b', 'l') +#define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC) + +static void +auto_detach(void *, void *); + +static isc_result_t +load(dns_zone_t *zone, void *uap); + +static isc_result_t +asyncload(dns_zone_t *zone, void *callback); + +static isc_result_t +freezezones(dns_zone_t *zone, void *uap); + +static isc_result_t +doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task); + +isc_result_t +dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) { + dns_zt_t *zt; + isc_result_t result; + + REQUIRE(ztp != NULL && *ztp == NULL); + + zt = isc_mem_get(mctx, sizeof(*zt)); + + zt->table = NULL; + result = dns_rbt_create(mctx, auto_detach, zt, &zt->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_zt; + } + + isc_rwlock_init(&zt->rwlock, 0, 0); + zt->mctx = NULL; + isc_mem_attach(mctx, &zt->mctx); + isc_refcount_init(&zt->references, 1); + atomic_init(&zt->flush, false); + zt->rdclass = rdclass; + zt->magic = ZTMAGIC; + zt->loaddone = NULL; + zt->loaddone_arg = NULL; + zt->loadparams = NULL; + isc_refcount_init(&zt->loads_pending, 0); + *ztp = zt; + + return (ISC_R_SUCCESS); + +cleanup_zt: + isc_mem_put(mctx, zt, sizeof(*zt)); + + return (result); +} + +isc_result_t +dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) { + isc_result_t result; + dns_zone_t *dummy = NULL; + dns_name_t *name; + + REQUIRE(VALID_ZT(zt)); + + name = dns_zone_getorigin(zone); + + RWLOCK(&zt->rwlock, isc_rwlocktype_write); + + result = dns_rbt_addname(zt->table, name, zone); + if (result == ISC_R_SUCCESS) { + dns_zone_attach(zone, &dummy); + } + + RWUNLOCK(&zt->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) { + isc_result_t result; + dns_name_t *name; + + REQUIRE(VALID_ZT(zt)); + + name = dns_zone_getorigin(zone); + + RWLOCK(&zt->rwlock, isc_rwlocktype_write); + + result = dns_rbt_deletename(zt->table, name, false); + + RWUNLOCK(&zt->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options, + dns_name_t *foundname, dns_zone_t **zonep) { + isc_result_t result; + dns_zone_t *dummy = NULL; + unsigned int rbtoptions = 0; + + REQUIRE(VALID_ZT(zt)); + + if ((options & DNS_ZTFIND_NOEXACT) != 0) { + rbtoptions |= DNS_RBTFIND_NOEXACT; + } + + RWLOCK(&zt->rwlock, isc_rwlocktype_read); + + result = dns_rbt_findname(zt->table, name, rbtoptions, foundname, + (void **)(void *)&dummy); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + /* + * If DNS_ZTFIND_MIRROR is set and the zone which was + * determined to be the deepest match for the supplied name is + * a mirror zone which is expired or not yet loaded, treat it + * as non-existent. This will trigger a fallback to recursion + * instead of returning a SERVFAIL. + * + * Note that currently only the deepest match in the zone table + * is checked. Consider a server configured with two mirror + * zones: "bar" and its child, "foo.bar". If zone data is + * available for "bar" but not for "foo.bar", a query with + * QNAME equal to or below "foo.bar" will cause ISC_R_NOTFOUND + * to be returned, not DNS_R_PARTIALMATCH, despite zone data + * being available for "bar". This is considered to be an edge + * case, handling which more appropriately is possible, but + * arguably not worth the added complexity. + */ + if ((options & DNS_ZTFIND_MIRROR) != 0 && + dns_zone_gettype(dummy) == dns_zone_mirror && + !dns_zone_isloaded(dummy)) + { + result = ISC_R_NOTFOUND; + } else { + dns_zone_attach(dummy, zonep); + } + } + + RWUNLOCK(&zt->rwlock, isc_rwlocktype_read); + + return (result); +} + +void +dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp) { + REQUIRE(VALID_ZT(zt)); + REQUIRE(ztp != NULL && *ztp == NULL); + + isc_refcount_increment(&zt->references); + + *ztp = zt; +} + +static isc_result_t +flush(dns_zone_t *zone, void *uap) { + UNUSED(uap); + return (dns_zone_flush(zone)); +} + +static void +zt_destroy(dns_zt_t *zt) { + isc_refcount_destroy(&zt->references); + isc_refcount_destroy(&zt->loads_pending); + + if (atomic_load_acquire(&zt->flush)) { + (void)dns_zt_apply(zt, isc_rwlocktype_none, false, NULL, flush, + NULL); + } + + dns_rbt_destroy(&zt->table); + isc_rwlock_destroy(&zt->rwlock); + zt->magic = 0; + isc_mem_putanddetach(&zt->mctx, zt, sizeof(*zt)); +} + +void +dns_zt_detach(dns_zt_t **ztp) { + dns_zt_t *zt; + + REQUIRE(ztp != NULL && VALID_ZT(*ztp)); + + zt = *ztp; + *ztp = NULL; + + if (isc_refcount_decrement(&zt->references) == 1) { + zt_destroy(zt); + } +} + +void +dns_zt_flush(dns_zt_t *zt) { + REQUIRE(VALID_ZT(zt)); + atomic_store_release(&zt->flush, true); +} + +isc_result_t +dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) { + isc_result_t result; + struct zt_load_params params; + REQUIRE(VALID_ZT(zt)); + params.newonly = newonly; + result = dns_zt_apply(zt, isc_rwlocktype_read, stop, NULL, load, + ¶ms); + return (result); +} + +static isc_result_t +load(dns_zone_t *zone, void *paramsv) { + isc_result_t result; + struct zt_load_params *params = (struct zt_load_params *)paramsv; + result = dns_zone_load(zone, params->newonly); + if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE || + result == DNS_R_DYNAMIC) + { + result = ISC_R_SUCCESS; + } + return (result); +} + +static void +call_loaddone(dns_zt_t *zt) { + dns_zt_allloaded_t loaddone = zt->loaddone; + void *loaddone_arg = zt->loaddone_arg; + + /* + * Set zt->loaddone, zt->loaddone_arg and zt->loadparams to NULL + * before calling loaddone. + */ + zt->loaddone = NULL; + zt->loaddone_arg = NULL; + + isc_mem_put(zt->mctx, zt->loadparams, sizeof(struct zt_load_params)); + zt->loadparams = NULL; + + /* + * Call the callback last. + */ + if (loaddone != NULL) { + loaddone(loaddone_arg); + } +} + +isc_result_t +dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone, + void *arg) { + isc_result_t result; + uint_fast32_t loads_pending; + + REQUIRE(VALID_ZT(zt)); + + /* + * Obtain a reference to zt->loads_pending so that asyncload can + * safely decrement both zt->references and zt->loads_pending + * without going to zero. + */ + loads_pending = isc_refcount_increment0(&zt->loads_pending); + INSIST(loads_pending == 0); + + /* + * Only one dns_zt_asyncload call at a time should be active so + * these pointers should be NULL. They are set back to NULL + * before the zt->loaddone (alldone) is called in call_loaddone. + */ + INSIST(zt->loadparams == NULL); + INSIST(zt->loaddone == NULL); + INSIST(zt->loaddone_arg == NULL); + + zt->loadparams = isc_mem_get(zt->mctx, sizeof(struct zt_load_params)); + zt->loadparams->dl = doneloading; + zt->loadparams->newonly = newonly; + zt->loaddone = alldone; + zt->loaddone_arg = arg; + + result = dns_zt_apply(zt, isc_rwlocktype_read, false, NULL, asyncload, + zt); + + /* + * Have all the loads completed? + */ + if (isc_refcount_decrement(&zt->loads_pending) == 1) { + call_loaddone(zt); + } + + return (result); +} + +/* + * Initiates asynchronous loading of zone 'zone'. 'callback' is a + * pointer to a function which will be used to inform the caller when + * the zone loading is complete. + */ +static isc_result_t +asyncload(dns_zone_t *zone, void *zt_) { + isc_result_t result; + struct dns_zt *zt = (dns_zt_t *)zt_; + REQUIRE(zone != NULL); + + isc_refcount_increment(&zt->references); + isc_refcount_increment(&zt->loads_pending); + + result = dns_zone_asyncload(zone, zt->loadparams->newonly, + *zt->loadparams->dl, zt); + if (result != ISC_R_SUCCESS) { + /* + * Caller is holding a reference to zt->loads_pending + * and zt->references so these can't decrement to zero. + */ + isc_refcount_decrement1(&zt->references); + isc_refcount_decrement1(&zt->loads_pending); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze) { + isc_result_t result, tresult; + struct zt_freeze_params params = { view, freeze }; + + REQUIRE(VALID_ZT(zt)); + + result = dns_zt_apply(zt, isc_rwlocktype_read, false, &tresult, + freezezones, ¶ms); + if (tresult == ISC_R_NOTFOUND) { + tresult = ISC_R_SUCCESS; + } + return ((result == ISC_R_SUCCESS) ? tresult : result); +} + +static isc_result_t +freezezones(dns_zone_t *zone, void *uap) { + struct zt_freeze_params *params = uap; + bool frozen; + isc_result_t result = ISC_R_SUCCESS; + char classstr[DNS_RDATACLASS_FORMATSIZE]; + char zonename[DNS_NAME_FORMATSIZE]; + dns_zone_t *raw = NULL; + dns_view_t *view; + const char *vname; + const char *sep; + int level; + + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + zone = raw; + } + if (params->view != dns_zone_getview(zone)) { + if (raw != NULL) { + dns_zone_detach(&raw); + } + return (ISC_R_SUCCESS); + } + if (dns_zone_gettype(zone) != dns_zone_primary) { + if (raw != NULL) { + dns_zone_detach(&raw); + } + return (ISC_R_SUCCESS); + } + if (!dns_zone_isdynamic(zone, true)) { + if (raw != NULL) { + dns_zone_detach(&raw); + } + return (ISC_R_SUCCESS); + } + + frozen = dns_zone_getupdatedisabled(zone); + if (params->freeze) { + if (frozen) { + result = DNS_R_FROZEN; + } + if (result == ISC_R_SUCCESS) { + result = dns_zone_flush(zone); + } + if (result == ISC_R_SUCCESS) { + dns_zone_setupdatedisabled(zone, params->freeze); + } + } else { + if (frozen) { + result = dns_zone_loadandthaw(zone); + if (result == DNS_R_CONTINUE || + result == DNS_R_UPTODATE) + { + result = ISC_R_SUCCESS; + } + } + } + view = dns_zone_getview(zone); + if (strcmp(view->name, "_bind") == 0 || strcmp(view->name, "_defaul" + "t") == 0) + { + vname = ""; + sep = ""; + } else { + vname = view->name; + sep = " "; + } + dns_rdataclass_format(dns_zone_getclass(zone), classstr, + sizeof(classstr)); + dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename)); + level = (result != ISC_R_SUCCESS) ? ISC_LOG_ERROR : ISC_LOG_DEBUG(1); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, + level, "%s zone '%s/%s'%s%s: %s", + params->freeze ? "freezing" : "thawing", zonename, + classstr, sep, vname, isc_result_totext(result)); + if (raw != NULL) { + dns_zone_detach(&raw); + } + return (result); +} + +void +dns_zt_setviewcommit(dns_zt_t *zt) { + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_result_t result; + + REQUIRE(VALID_ZT(zt)); + + RWLOCK(&zt->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + + result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL); + while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + result = dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (result == ISC_R_SUCCESS && node->data != NULL) { + dns_zone_setviewcommit(node->data); + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&zt->rwlock, isc_rwlocktype_read); +} + +void +dns_zt_setviewrevert(dns_zt_t *zt) { + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_result_t result; + + REQUIRE(VALID_ZT(zt)); + + dns_rbtnodechain_init(&chain); + + result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL); + while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + result = dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (result == ISC_R_SUCCESS && node->data != NULL) { + dns_zone_setviewrevert(node->data); + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + + dns_rbtnodechain_invalidate(&chain); +} + +isc_result_t +dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub, + isc_result_t (*action)(dns_zone_t *, void *), void *uap) { + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_result_t result, tresult = ISC_R_SUCCESS; + dns_zone_t *zone; + + REQUIRE(VALID_ZT(zt)); + REQUIRE(action != NULL); + + if (lock != isc_rwlocktype_none) { + RWLOCK(&zt->rwlock, lock); + } + + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL); + if (result == ISC_R_NOTFOUND) { + /* + * The tree is empty. + */ + tresult = result; + result = ISC_R_NOMORE; + } + while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + result = dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (result == ISC_R_SUCCESS) { + zone = node->data; + if (zone != NULL) { + result = (action)(zone, uap); + } + if (result != ISC_R_SUCCESS && stop) { + tresult = result; + goto cleanup; /* don't break */ + } else if (result != ISC_R_SUCCESS && + tresult == ISC_R_SUCCESS) + { + tresult = result; + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + if (sub != NULL) { + *sub = tresult; + } + + if (lock != isc_rwlocktype_none) { + RWUNLOCK(&zt->rwlock, lock); + } + + return (result); +} + +/* + * Decrement the loads_pending counter; when counter reaches + * zero, call the loaddone callback that was initially set by + * dns_zt_asyncload(). + */ +static isc_result_t +doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task) { + UNUSED(zone); + UNUSED(task); + + REQUIRE(VALID_ZT(zt)); + + if (isc_refcount_decrement(&zt->loads_pending) == 1) { + call_loaddone(zt); + } + + if (isc_refcount_decrement(&zt->references) == 1) { + zt_destroy(zt); + } + + return (ISC_R_SUCCESS); +} + +/*** + *** Private + ***/ + +static void +auto_detach(void *data, void *arg) { + dns_zone_t *zone = data; + + UNUSED(arg); + dns_zone_detach(&zone); +} -- cgit v1.2.3