From 45d6379135504814ab723b57f0eb8be23393a51d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 09:24:22 +0200 Subject: Adding upstream version 1:9.16.44. Signed-off-by: Daniel Baumann --- lib/dns/Kyuafile | 15 + lib/dns/Makefile.in | 215 + lib/dns/acl.c | 665 + lib/dns/adb.c | 4885 ++++ lib/dns/badcache.c | 525 + lib/dns/byaddr.c | 282 + lib/dns/cache.c | 1510 ++ lib/dns/callbacks.c | 107 + lib/dns/catz.c | 2105 ++ lib/dns/client.c | 1353 ++ lib/dns/clientinfo.c | 38 + lib/dns/compress.c | 584 + lib/dns/db.c | 1139 + lib/dns/dbiterator.c | 135 + lib/dns/dbtable.c | 249 + lib/dns/diff.c | 688 + lib/dns/dispatch.c | 3591 +++ lib/dns/dlz.c | 541 + lib/dns/dns64.c | 325 + lib/dns/dnsrps.c | 1005 + lib/dns/dnssec.c | 2522 ++ lib/dns/dnstap.c | 1386 ++ lib/dns/dnstap.proto | 289 + lib/dns/ds.c | 135 + lib/dns/dst_api.c | 2797 +++ lib/dns/dst_internal.h | 286 + lib/dns/dst_openssl.h | 73 + lib/dns/dst_parse.c | 804 + lib/dns/dst_parse.h | 134 + lib/dns/dst_pkcs11.h | 43 + lib/dns/dst_result.c | 110 + lib/dns/dyndb.c | 465 + lib/dns/ecdb.c | 797 + lib/dns/ecs.c | 113 + lib/dns/fixedname.c | 39 + lib/dns/forward.c | 227 + lib/dns/gen-unix.h | 100 + lib/dns/gen-win32.h | 295 + lib/dns/gen.c | 1028 + lib/dns/geoip2.c | 389 + lib/dns/gssapi_link.c | 358 + lib/dns/gssapictx.c | 957 + lib/dns/hmac_link.c | 521 + lib/dns/include/.clang-format | 1 + lib/dns/include/Makefile.in | 19 + lib/dns/include/dns/Makefile.in | 63 + lib/dns/include/dns/acl.h | 253 + lib/dns/include/dns/adb.h | 835 + lib/dns/include/dns/badcache.h | 152 + lib/dns/include/dns/bit.h | 28 + lib/dns/include/dns/byaddr.h | 152 + lib/dns/include/dns/cache.h | 360 + lib/dns/include/dns/callbacks.h | 103 + lib/dns/include/dns/catz.h | 473 + lib/dns/include/dns/cert.h | 63 + lib/dns/include/dns/client.h | 476 + lib/dns/include/dns/clientinfo.h | 95 + lib/dns/include/dns/compress.h | 302 + lib/dns/include/dns/db.h | 1800 ++ lib/dns/include/dns/dbiterator.h | 293 + lib/dns/include/dns/dbtable.h | 159 + lib/dns/include/dns/diff.h | 282 + lib/dns/include/dns/dispatch.h | 586 + lib/dns/include/dns/dlz.h | 336 + lib/dns/include/dns/dlz_dlopen.h | 159 + lib/dns/include/dns/dns64.h | 174 + lib/dns/include/dns/dnsrps.h | 115 + lib/dns/include/dns/dnssec.h | 401 + lib/dns/include/dns/dnstap.h | 396 + lib/dns/include/dns/ds.h | 68 + lib/dns/include/dns/dsdigest.h | 72 + lib/dns/include/dns/dyndb.h | 162 + lib/dns/include/dns/ecdb.h | 49 + lib/dns/include/dns/ecs.h | 84 + lib/dns/include/dns/edns.h | 29 + lib/dns/include/dns/events.h | 90 + lib/dns/include/dns/fixedname.h | 85 + lib/dns/include/dns/forward.h | 124 + lib/dns/include/dns/geoip.h | 114 + lib/dns/include/dns/ipkeylist.h | 90 + lib/dns/include/dns/iptable.h | 70 + lib/dns/include/dns/journal.h | 341 + lib/dns/include/dns/kasp.h | 717 + lib/dns/include/dns/keydata.h | 51 + lib/dns/include/dns/keyflags.h | 48 + lib/dns/include/dns/keymgr.h | 134 + lib/dns/include/dns/keytable.h | 350 + lib/dns/include/dns/keyvalues.h | 107 + lib/dns/include/dns/lib.h | 45 + lib/dns/include/dns/librpz.h | 956 + lib/dns/include/dns/lmdb.h | 25 + lib/dns/include/dns/log.h | 114 + lib/dns/include/dns/lookup.h | 131 + lib/dns/include/dns/master.h | 264 + lib/dns/include/dns/masterdump.h | 364 + lib/dns/include/dns/message.h | 1492 ++ lib/dns/include/dns/name.h | 1410 ++ lib/dns/include/dns/ncache.h | 187 + lib/dns/include/dns/nsec.h | 114 + lib/dns/include/dns/nsec3.h | 272 + lib/dns/include/dns/nta.h | 218 + lib/dns/include/dns/opcode.h | 46 + lib/dns/include/dns/order.h | 93 + lib/dns/include/dns/peer.h | 279 + lib/dns/include/dns/portlist.h | 102 + lib/dns/include/dns/private.h | 69 + lib/dns/include/dns/rbt.h | 1060 + lib/dns/include/dns/rcode.h | 110 + lib/dns/include/dns/rdata.h | 812 + lib/dns/include/dns/rdataclass.h | 94 + lib/dns/include/dns/rdatalist.h | 123 + lib/dns/include/dns/rdataset.h | 615 + lib/dns/include/dns/rdatasetiter.h | 164 + lib/dns/include/dns/rdataslab.h | 173 + lib/dns/include/dns/rdatatype.h | 97 + lib/dns/include/dns/request.h | 365 + lib/dns/include/dns/resolver.h | 747 + lib/dns/include/dns/result.h | 212 + lib/dns/include/dns/rootns.h | 39 + lib/dns/include/dns/rpz.h | 435 + lib/dns/include/dns/rriterator.h | 182 + lib/dns/include/dns/rrl.h | 272 + lib/dns/include/dns/sdb.h | 214 + lib/dns/include/dns/sdlz.h | 359 + lib/dns/include/dns/secalg.h | 72 + lib/dns/include/dns/secproto.h | 65 + lib/dns/include/dns/soa.h | 96 + lib/dns/include/dns/ssu.h | 243 + lib/dns/include/dns/stats.h | 826 + lib/dns/include/dns/tcpmsg.h | 143 + lib/dns/include/dns/time.h | 73 + lib/dns/include/dns/timer.h | 48 + lib/dns/include/dns/tkey.h | 246 + lib/dns/include/dns/tsec.h | 132 + lib/dns/include/dns/tsig.h | 297 + lib/dns/include/dns/ttl.h | 80 + lib/dns/include/dns/types.h | 438 + lib/dns/include/dns/update.h | 75 + lib/dns/include/dns/validator.h | 243 + lib/dns/include/dns/version.h | 25 + lib/dns/include/dns/view.h | 1359 ++ lib/dns/include/dns/xfrin.h | 98 + lib/dns/include/dns/zone.h | 2726 +++ lib/dns/include/dns/zonekey.h | 38 + lib/dns/include/dns/zoneverify.h | 50 + lib/dns/include/dns/zt.h | 224 + lib/dns/include/dst/Makefile.in | 36 + lib/dns/include/dst/dst.h | 1227 + lib/dns/include/dst/gssapi.h | 196 + lib/dns/include/dst/result.h | 67 + lib/dns/ipkeylist.c | 209 + lib/dns/iptable.c | 174 + lib/dns/journal.c | 2856 +++ lib/dns/kasp.c | 527 + lib/dns/key.c | 192 + lib/dns/keydata.c | 76 + lib/dns/keymgr.c | 2628 +++ lib/dns/keytable.c | 943 + lib/dns/lib.c | 119 + lib/dns/log.c | 84 + lib/dns/lookup.c | 450 + lib/dns/mapapi | 16 + lib/dns/master.c | 3264 +++ lib/dns/masterdump.c | 2133 ++ lib/dns/message.c | 4748 ++++ lib/dns/name.c | 2692 +++ lib/dns/ncache.c | 777 + lib/dns/nsec.c | 466 + lib/dns/nsec3.c | 2195 ++ lib/dns/nta.c | 722 + lib/dns/openssl_link.c | 214 + lib/dns/openssldh_link.c | 815 + lib/dns/opensslecdsa_link.c | 905 + lib/dns/openssleddsa_link.c | 708 + lib/dns/opensslrsa_link.c | 1405 ++ lib/dns/order.c | 150 + lib/dns/peer.c | 919 + lib/dns/pkcs11.c | 40 + lib/dns/pkcs11ecdsa_link.c | 1153 + lib/dns/pkcs11eddsa_link.c | 1124 + lib/dns/pkcs11rsa_link.c | 2115 ++ lib/dns/portlist.c | 252 + lib/dns/private.c | 417 + lib/dns/rbt.c | 3808 +++ lib/dns/rbtdb.c | 10667 +++++++++ lib/dns/rbtdb.h | 52 + lib/dns/rcode.c | 588 + lib/dns/rdata.c | 2365 ++ lib/dns/rdata/any_255/tsig_250.c | 621 + lib/dns/rdata/any_255/tsig_250.h | 32 + lib/dns/rdata/ch_3/a_1.c | 325 + lib/dns/rdata/ch_3/a_1.h | 31 + lib/dns/rdata/generic/afsdb_18.c | 316 + lib/dns/rdata/generic/afsdb_18.h | 27 + lib/dns/rdata/generic/amtrelay_260.c | 471 + lib/dns/rdata/generic/amtrelay_260.h | 30 + lib/dns/rdata/generic/avc_258.c | 144 + lib/dns/rdata/generic/avc_258.h | 32 + lib/dns/rdata/generic/caa_257.c | 627 + lib/dns/rdata/generic/caa_257.h | 27 + lib/dns/rdata/generic/cdnskey_60.c | 163 + lib/dns/rdata/generic/cdnskey_60.h | 20 + lib/dns/rdata/generic/cds_59.c | 166 + lib/dns/rdata/generic/cds_59.h | 20 + lib/dns/rdata/generic/cert_37.c | 284 + lib/dns/rdata/generic/cert_37.h | 28 + lib/dns/rdata/generic/cname_5.c | 230 + lib/dns/rdata/generic/cname_5.h | 23 + lib/dns/rdata/generic/csync_62.c | 273 + lib/dns/rdata/generic/csync_62.h | 30 + lib/dns/rdata/generic/dlv_32769.c | 162 + lib/dns/rdata/generic/dlv_32769.h | 20 + lib/dns/rdata/generic/dname_39.c | 230 + lib/dns/rdata/generic/dname_39.h | 26 + lib/dns/rdata/generic/dnskey_48.c | 164 + lib/dns/rdata/generic/dnskey_48.h | 23 + lib/dns/rdata/generic/doa_259.c | 361 + lib/dns/rdata/generic/doa_259.h | 29 + lib/dns/rdata/generic/ds_43.c | 385 + lib/dns/rdata/generic/ds_43.h | 29 + lib/dns/rdata/generic/eui48_108.c | 211 + lib/dns/rdata/generic/eui48_108.h | 23 + lib/dns/rdata/generic/eui64_109.c | 214 + lib/dns/rdata/generic/eui64_109.h | 23 + lib/dns/rdata/generic/gpos_27.c | 256 + lib/dns/rdata/generic/gpos_27.h | 31 + lib/dns/rdata/generic/hinfo_13.c | 219 + lib/dns/rdata/generic/hinfo_13.h | 26 + lib/dns/rdata/generic/hip_55.c | 525 + lib/dns/rdata/generic/hip_55.h | 42 + lib/dns/rdata/generic/ipseckey_45.c | 526 + lib/dns/rdata/generic/ipseckey_45.h | 30 + lib/dns/rdata/generic/isdn_20.c | 247 + lib/dns/rdata/generic/isdn_20.h | 29 + lib/dns/rdata/generic/key_25.c | 468 + lib/dns/rdata/generic/key_25.h | 30 + lib/dns/rdata/generic/keydata_65533.c | 462 + lib/dns/rdata/generic/keydata_65533.h | 30 + lib/dns/rdata/generic/l32_105.c | 230 + lib/dns/rdata/generic/l32_105.h | 24 + lib/dns/rdata/generic/l64_106.c | 224 + lib/dns/rdata/generic/l64_106.h | 24 + lib/dns/rdata/generic/loc_29.c | 838 + lib/dns/rdata/generic/loc_29.h | 37 + lib/dns/rdata/generic/lp_107.c | 276 + lib/dns/rdata/generic/lp_107.h | 25 + lib/dns/rdata/generic/mb_7.c | 232 + lib/dns/rdata/generic/mb_7.h | 24 + lib/dns/rdata/generic/md_3.c | 234 + lib/dns/rdata/generic/md_3.h | 24 + lib/dns/rdata/generic/mf_4.c | 233 + lib/dns/rdata/generic/mf_4.h | 24 + lib/dns/rdata/generic/mg_8.c | 228 + lib/dns/rdata/generic/mg_8.h | 24 + lib/dns/rdata/generic/minfo_14.c | 332 + lib/dns/rdata/generic/minfo_14.h | 25 + lib/dns/rdata/generic/mr_9.c | 229 + lib/dns/rdata/generic/mr_9.h | 24 + lib/dns/rdata/generic/mx_15.c | 356 + lib/dns/rdata/generic/mx_15.h | 25 + lib/dns/rdata/generic/naptr_35.c | 740 + lib/dns/rdata/generic/naptr_35.h | 34 + lib/dns/rdata/generic/nid_104.c | 224 + lib/dns/rdata/generic/nid_104.h | 24 + lib/dns/rdata/generic/ninfo_56.c | 169 + lib/dns/rdata/generic/ninfo_56.h | 36 + lib/dns/rdata/generic/ns_2.c | 254 + lib/dns/rdata/generic/ns_2.h | 24 + lib/dns/rdata/generic/nsec3_50.c | 424 + lib/dns/rdata/generic/nsec3_50.h | 112 + lib/dns/rdata/generic/nsec3param_51.c | 321 + lib/dns/rdata/generic/nsec3param_51.h | 32 + lib/dns/rdata/generic/nsec_47.c | 290 + lib/dns/rdata/generic/nsec_47.h | 28 + lib/dns/rdata/generic/null_10.c | 186 + lib/dns/rdata/generic/null_10.h | 25 + lib/dns/rdata/generic/nxt_30.c | 350 + lib/dns/rdata/generic/nxt_30.h | 28 + lib/dns/rdata/generic/openpgpkey_61.c | 249 + lib/dns/rdata/generic/openpgpkey_61.h | 24 + lib/dns/rdata/generic/opt_41.c | 472 + lib/dns/rdata/generic/opt_41.h | 49 + lib/dns/rdata/generic/proforma.c | 164 + lib/dns/rdata/generic/proforma.h | 25 + lib/dns/rdata/generic/ptr_12.c | 278 + lib/dns/rdata/generic/ptr_12.h | 24 + lib/dns/rdata/generic/rkey_57.c | 160 + lib/dns/rdata/generic/rkey_57.h | 19 + lib/dns/rdata/generic/rp_17.c | 320 + lib/dns/rdata/generic/rp_17.h | 27 + lib/dns/rdata/generic/rrsig_46.c | 639 + lib/dns/rdata/generic/rrsig_46.h | 34 + lib/dns/rdata/generic/rt_21.c | 322 + lib/dns/rdata/generic/rt_21.h | 27 + lib/dns/rdata/generic/sig_24.c | 590 + lib/dns/rdata/generic/sig_24.h | 35 + lib/dns/rdata/generic/sink_40.c | 291 + lib/dns/rdata/generic/sink_40.h | 27 + lib/dns/rdata/generic/smimea_53.c | 152 + lib/dns/rdata/generic/smimea_53.h | 19 + lib/dns/rdata/generic/soa_6.c | 452 + lib/dns/rdata/generic/soa_6.h | 30 + lib/dns/rdata/generic/spf_99.c | 145 + lib/dns/rdata/generic/spf_99.h | 35 + lib/dns/rdata/generic/sshfp_44.c | 296 + lib/dns/rdata/generic/sshfp_44.h | 29 + lib/dns/rdata/generic/ta_32768.c | 162 + lib/dns/rdata/generic/ta_32768.h | 22 + lib/dns/rdata/generic/talink_58.c | 267 + lib/dns/rdata/generic/talink_58.h | 28 + lib/dns/rdata/generic/tkey_249.c | 580 + lib/dns/rdata/generic/tkey_249.h | 34 + lib/dns/rdata/generic/tlsa_52.c | 339 + lib/dns/rdata/generic/tlsa_52.h | 30 + lib/dns/rdata/generic/txt_16.c | 359 + lib/dns/rdata/generic/txt_16.h | 46 + lib/dns/rdata/generic/uri_256.c | 318 + lib/dns/rdata/generic/uri_256.h | 26 + lib/dns/rdata/generic/x25_19.c | 232 + lib/dns/rdata/generic/x25_19.h | 27 + lib/dns/rdata/generic/zonemd_63.c | 350 + lib/dns/rdata/generic/zonemd_63.h | 34 + lib/dns/rdata/hs_4/a_1.c | 235 + lib/dns/rdata/hs_4/a_1.h | 23 + lib/dns/rdata/in_1/a6_38.c | 486 + lib/dns/rdata/in_1/a6_38.h | 28 + lib/dns/rdata/in_1/a_1.c | 278 + lib/dns/rdata/in_1/a_1.h | 23 + lib/dns/rdata/in_1/aaaa_28.c | 265 + lib/dns/rdata/in_1/aaaa_28.h | 25 + lib/dns/rdata/in_1/apl_42.c | 481 + lib/dns/rdata/in_1/apl_42.h | 53 + lib/dns/rdata/in_1/atma_34.c | 317 + lib/dns/rdata/in_1/atma_34.h | 28 + lib/dns/rdata/in_1/dhcid_49.c | 235 + lib/dns/rdata/in_1/dhcid_49.h | 25 + lib/dns/rdata/in_1/eid_31.c | 224 + lib/dns/rdata/in_1/eid_31.h | 28 + lib/dns/rdata/in_1/https_65.c | 186 + lib/dns/rdata/in_1/https_65.h | 35 + lib/dns/rdata/in_1/kx_36.c | 289 + lib/dns/rdata/in_1/kx_36.h | 27 + lib/dns/rdata/in_1/nimloc_32.c | 224 + lib/dns/rdata/in_1/nimloc_32.h | 28 + lib/dns/rdata/in_1/nsap-ptr_23.c | 243 + lib/dns/rdata/in_1/nsap-ptr_23.h | 26 + lib/dns/rdata/in_1/nsap_22.c | 259 + lib/dns/rdata/in_1/nsap_22.h | 27 + lib/dns/rdata/in_1/px_26.c | 379 + lib/dns/rdata/in_1/px_26.h | 28 + lib/dns/rdata/in_1/srv_33.c | 410 + lib/dns/rdata/in_1/srv_33.h | 29 + lib/dns/rdata/in_1/svcb_64.c | 1268 + lib/dns/rdata/in_1/svcb_64.h | 40 + lib/dns/rdata/in_1/wks_11.c | 440 + lib/dns/rdata/in_1/wks_11.h | 26 + lib/dns/rdata/rdatastructpre.h | 36 + lib/dns/rdata/rdatastructsuf.h | 16 + lib/dns/rdatalist.c | 448 + lib/dns/rdatalist_p.h | 65 + lib/dns/rdataset.c | 750 + lib/dns/rdatasetiter.c | 71 + lib/dns/rdataslab.c | 1005 + lib/dns/request.c | 1545 ++ lib/dns/resolver.c | 12095 ++++++++++ lib/dns/result.c | 454 + lib/dns/rootns.c | 566 + lib/dns/rpz.c | 2891 +++ lib/dns/rriterator.c | 220 + lib/dns/rrl.c | 1367 ++ lib/dns/sdb.c | 1613 ++ lib/dns/sdlz.c | 2108 ++ lib/dns/soa.c | 137 + lib/dns/ssu.c | 667 + lib/dns/ssu_external.c | 260 + lib/dns/stats.c | 653 + lib/dns/tcpmsg.c | 236 + lib/dns/tests/Kdh.+002+18602.key | 1 + lib/dns/tests/Krsa.+008+29238.key | 5 + lib/dns/tests/Kyuafile | 45 + lib/dns/tests/Makefile.in | 287 + lib/dns/tests/acl_test.c | 158 + .../tests/comparekeys/Kexample-d.+008+53461.key | 5 + .../comparekeys/Kexample-d.+008+53461.private | 13 + .../tests/comparekeys/Kexample-e.+008+53973.key | 5 + .../comparekeys/Kexample-e.+008+53973.private | 13 + .../tests/comparekeys/Kexample-n.+008+37464.key | 5 + .../comparekeys/Kexample-n.+008+37464.private | 13 + .../tests/comparekeys/Kexample-p.+008+53461.key | 5 + .../comparekeys/Kexample-p.+008+53461.private | 13 + .../comparekeys/Kexample-private.+002+65316.key | 1 + .../Kexample-private.+002+65316.private | 9 + .../tests/comparekeys/Kexample-q.+008+53461.key | 5 + .../comparekeys/Kexample-q.+008+53461.private | 13 + lib/dns/tests/comparekeys/Kexample.+002+65316.key | 1 + .../tests/comparekeys/Kexample.+002+65316.private | 9 + lib/dns/tests/comparekeys/Kexample.+008+53461.key | 5 + .../tests/comparekeys/Kexample.+008+53461.private | 13 + lib/dns/tests/comparekeys/Kexample.+013+19786.key | 5 + .../tests/comparekeys/Kexample.+013+19786.private | 6 + lib/dns/tests/comparekeys/Kexample.+015+63663.key | 5 + .../tests/comparekeys/Kexample.+015+63663.private | 6 + lib/dns/tests/comparekeys/Kexample2.+002+19823.key | 1 + .../tests/comparekeys/Kexample2.+002+19823.private | 9 + lib/dns/tests/comparekeys/Kexample2.+008+37993.key | 5 + .../tests/comparekeys/Kexample2.+008+37993.private | 13 + lib/dns/tests/comparekeys/Kexample2.+013+16384.key | 5 + .../tests/comparekeys/Kexample2.+013+16384.private | 6 + lib/dns/tests/comparekeys/Kexample2.+015+37529.key | 5 + .../tests/comparekeys/Kexample2.+015+37529.private | 6 + lib/dns/tests/comparekeys/Kexample3.+002+17187.key | 1 + .../tests/comparekeys/Kexample3.+002+17187.private | 9 + lib/dns/tests/db_test.c | 428 + lib/dns/tests/dbdiff_test.c | 185 + lib/dns/tests/dbiterator_test.c | 394 + lib/dns/tests/dbversion_test.c | 499 + lib/dns/tests/dh_test.c | 112 + lib/dns/tests/dispatch_test.c | 360 + lib/dns/tests/dnstap_test.c | 402 + lib/dns/tests/dnstest.c | 643 + lib/dns/tests/dnstest.h | 132 + lib/dns/tests/dst_test.c | 504 + lib/dns/tests/geoip_test.c | 433 + lib/dns/tests/keytable_test.c | 720 + lib/dns/tests/master_test.c | 633 + lib/dns/tests/mkraw.pl | 26 + lib/dns/tests/name_test.c | 796 + lib/dns/tests/nsec3_test.c | 196 + lib/dns/tests/nsec3param_test.c | 304 + lib/dns/tests/peer_test.c | 175 + lib/dns/tests/private_test.c | 236 + lib/dns/tests/rbt_serialize_test.c | 489 + lib/dns/tests/rbt_test.c | 1390 ++ lib/dns/tests/rbtdb_test.c | 423 + lib/dns/tests/rdata_test.c | 3229 +++ lib/dns/tests/rdataset_test.c | 146 + lib/dns/tests/rdatasetstats_test.c | 312 + lib/dns/tests/resolver_test.c | 228 + lib/dns/tests/result_test.c | 133 + lib/dns/tests/rsa_test.c | 242 + lib/dns/tests/sigs_test.c | 462 + lib/dns/tests/testdata/db/data.db | 22 + lib/dns/tests/testdata/dbiterator/zone1.data | 30 + lib/dns/tests/testdata/dbiterator/zone2.data | 319 + lib/dns/tests/testdata/diff/zone1.data | 13 + lib/dns/tests/testdata/diff/zone2.data | 14 + lib/dns/tests/testdata/diff/zone3.data | 12 + lib/dns/tests/testdata/dnstap/dnstap.saved | Bin 0 -> 30398 bytes lib/dns/tests/testdata/dnstap/dnstap.text | 96 + lib/dns/tests/testdata/dnstap/query.auth | 4 + lib/dns/tests/testdata/dnstap/query.recursive | 4 + lib/dns/tests/testdata/dnstap/response.auth | 19 + lib/dns/tests/testdata/dnstap/response.recursive | 19 + lib/dns/tests/testdata/dst/Ktest.+008+11349.key | 5 + .../tests/testdata/dst/Ktest.+008+11349.private | 13 + lib/dns/tests/testdata/dst/Ktest.+013+49130.key | 5 + .../tests/testdata/dst/Ktest.+013+49130.private | 6 + lib/dns/tests/testdata/dst/test1.data | 3077 +++ lib/dns/tests/testdata/dst/test1.ecdsa256sig | 1 + lib/dns/tests/testdata/dst/test1.rsasha256sig | 1 + lib/dns/tests/testdata/dst/test2.data | 3077 +++ lib/dns/tests/testdata/dstrandom/random.data | Bin 0 -> 4096 bytes lib/dns/tests/testdata/master/master1.data | 11 + lib/dns/tests/testdata/master/master10.data | 7 + lib/dns/tests/testdata/master/master11.data | 6 + lib/dns/tests/testdata/master/master12.data.in | 1 + lib/dns/tests/testdata/master/master13.data.in | 1 + lib/dns/tests/testdata/master/master14.data.in | 1 + lib/dns/tests/testdata/master/master15.data | 1609 ++ lib/dns/tests/testdata/master/master16.data | 1609 ++ lib/dns/tests/testdata/master/master17.data | 14 + lib/dns/tests/testdata/master/master18.data | 10 + lib/dns/tests/testdata/master/master2.data | 11 + lib/dns/tests/testdata/master/master3.data | 11 + lib/dns/tests/testdata/master/master4.data | 11 + lib/dns/tests/testdata/master/master5.data | 11 + lib/dns/tests/testdata/master/master6.data | 33 + lib/dns/tests/testdata/master/master7.data | 17 + lib/dns/tests/testdata/master/master8.data | 4 + lib/dns/tests/testdata/master/master9.data | 4 + lib/dns/tests/testdata/nsec3/1024.db | 16 + lib/dns/tests/testdata/nsec3/2048.db | 16 + lib/dns/tests/testdata/nsec3/4096.db | 16 + lib/dns/tests/testdata/nsec3/min-1024.db | 20 + lib/dns/tests/testdata/nsec3/min-2048.db | 18 + lib/dns/tests/testdata/nsec3param/nsec3.db.signed | 73 + lib/dns/tests/testdata/zt/zone1.db | 22 + lib/dns/tests/testkeys/Kexample.+008+20386.key | 5 + lib/dns/tests/testkeys/Kexample.+008+20386.private | 13 + lib/dns/tests/testkeys/Kexample.+008+37464.key | 5 + lib/dns/tests/testkeys/Kexample.+008+37464.private | 13 + lib/dns/tests/time_test.c | 218 + lib/dns/tests/tsig_test.c | 605 + lib/dns/tests/update_test.c | 381 + lib/dns/tests/zonemgr_test.c | 265 + lib/dns/tests/zt_test.c | 376 + lib/dns/time.c | 216 + lib/dns/timer.c | 54 + lib/dns/tkey.c | 1604 ++ lib/dns/tsec.c | 151 + lib/dns/tsig.c | 1902 ++ lib/dns/tsig_p.h | 43 + lib/dns/ttl.c | 225 + lib/dns/update.c | 2280 ++ lib/dns/validator.c | 3394 +++ lib/dns/version.c | 20 + lib/dns/view.c | 2642 +++ lib/dns/win32/DLLMain.c | 49 + lib/dns/win32/gen.vcxproj.filters.in | 27 + lib/dns/win32/gen.vcxproj.in | 134 + lib/dns/win32/gen.vcxproj.user | 3 + lib/dns/win32/libdns.def.in | 1548 ++ lib/dns/win32/libdns.vcxproj.filters.in | 668 + lib/dns/win32/libdns.vcxproj.in | 345 + lib/dns/win32/libdns.vcxproj.user | 3 + lib/dns/win32/version.c | 20 + lib/dns/xfrin.c | 1704 ++ lib/dns/zone.c | 23609 +++++++++++++++++++ lib/dns/zone_p.h | 53 + lib/dns/zonekey.c | 54 + lib/dns/zoneverify.c | 2038 ++ lib/dns/zt.c | 617 + 522 files changed, 246985 insertions(+) create mode 100644 lib/dns/Kyuafile 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/dbtable.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/dst_pkcs11.h create mode 100644 lib/dns/dst_result.c create mode 100644 lib/dns/dyndb.c create mode 100644 lib/dns/ecdb.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-unix.h create mode 100644 lib/dns/gen-win32.h 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 120000 lib/dns/include/.clang-format create mode 100644 lib/dns/include/Makefile.in create mode 100644 lib/dns/include/dns/Makefile.in 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/dbtable.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/ecdb.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/lib.h create mode 100644 lib/dns/include/dns/librpz.h create mode 100644 lib/dns/include/dns/lmdb.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/portlist.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/tcpmsg.h create mode 100644 lib/dns/include/dns/time.h create mode 100644 lib/dns/include/dns/timer.h create mode 100644 lib/dns/include/dns/tkey.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/version.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/Makefile.in create mode 100644 lib/dns/include/dst/dst.h create mode 100644 lib/dns/include/dst/gssapi.h create mode 100644 lib/dns/include/dst/result.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/lib.c create mode 100644 lib/dns/log.c create mode 100644 lib/dns/lookup.c create mode 100644 lib/dns/mapapi 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/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/pkcs11.c create mode 100644 lib/dns/pkcs11ecdsa_link.c create mode 100644 lib/dns/pkcs11eddsa_link.c create mode 100644 lib/dns/pkcs11rsa_link.c create mode 100644 lib/dns/portlist.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/tcpmsg.c create mode 100644 lib/dns/tests/Kdh.+002+18602.key create mode 100644 lib/dns/tests/Krsa.+008+29238.key create mode 100644 lib/dns/tests/Kyuafile create mode 100644 lib/dns/tests/Makefile.in create mode 100644 lib/dns/tests/acl_test.c create mode 100644 lib/dns/tests/comparekeys/Kexample-d.+008+53461.key create mode 100644 lib/dns/tests/comparekeys/Kexample-d.+008+53461.private create mode 100644 lib/dns/tests/comparekeys/Kexample-e.+008+53973.key create mode 100644 lib/dns/tests/comparekeys/Kexample-e.+008+53973.private create mode 100644 lib/dns/tests/comparekeys/Kexample-n.+008+37464.key create mode 100644 lib/dns/tests/comparekeys/Kexample-n.+008+37464.private create mode 100644 lib/dns/tests/comparekeys/Kexample-p.+008+53461.key create mode 100644 lib/dns/tests/comparekeys/Kexample-p.+008+53461.private create mode 100644 lib/dns/tests/comparekeys/Kexample-private.+002+65316.key create mode 100644 lib/dns/tests/comparekeys/Kexample-private.+002+65316.private create mode 100644 lib/dns/tests/comparekeys/Kexample-q.+008+53461.key create mode 100644 lib/dns/tests/comparekeys/Kexample-q.+008+53461.private create mode 100644 lib/dns/tests/comparekeys/Kexample.+002+65316.key create mode 100644 lib/dns/tests/comparekeys/Kexample.+002+65316.private create mode 100644 lib/dns/tests/comparekeys/Kexample.+008+53461.key create mode 100644 lib/dns/tests/comparekeys/Kexample.+008+53461.private create mode 100644 lib/dns/tests/comparekeys/Kexample.+013+19786.key create mode 100644 lib/dns/tests/comparekeys/Kexample.+013+19786.private create mode 100644 lib/dns/tests/comparekeys/Kexample.+015+63663.key create mode 100644 lib/dns/tests/comparekeys/Kexample.+015+63663.private create mode 100644 lib/dns/tests/comparekeys/Kexample2.+002+19823.key create mode 100644 lib/dns/tests/comparekeys/Kexample2.+002+19823.private create mode 100644 lib/dns/tests/comparekeys/Kexample2.+008+37993.key create mode 100644 lib/dns/tests/comparekeys/Kexample2.+008+37993.private create mode 100644 lib/dns/tests/comparekeys/Kexample2.+013+16384.key create mode 100644 lib/dns/tests/comparekeys/Kexample2.+013+16384.private create mode 100644 lib/dns/tests/comparekeys/Kexample2.+015+37529.key create mode 100644 lib/dns/tests/comparekeys/Kexample2.+015+37529.private create mode 100644 lib/dns/tests/comparekeys/Kexample3.+002+17187.key create mode 100644 lib/dns/tests/comparekeys/Kexample3.+002+17187.private create mode 100644 lib/dns/tests/db_test.c create mode 100644 lib/dns/tests/dbdiff_test.c create mode 100644 lib/dns/tests/dbiterator_test.c create mode 100644 lib/dns/tests/dbversion_test.c create mode 100644 lib/dns/tests/dh_test.c create mode 100644 lib/dns/tests/dispatch_test.c create mode 100644 lib/dns/tests/dnstap_test.c create mode 100644 lib/dns/tests/dnstest.c create mode 100644 lib/dns/tests/dnstest.h create mode 100644 lib/dns/tests/dst_test.c create mode 100644 lib/dns/tests/geoip_test.c create mode 100644 lib/dns/tests/keytable_test.c create mode 100644 lib/dns/tests/master_test.c create mode 100644 lib/dns/tests/mkraw.pl create mode 100644 lib/dns/tests/name_test.c create mode 100644 lib/dns/tests/nsec3_test.c create mode 100644 lib/dns/tests/nsec3param_test.c create mode 100644 lib/dns/tests/peer_test.c create mode 100644 lib/dns/tests/private_test.c create mode 100644 lib/dns/tests/rbt_serialize_test.c create mode 100644 lib/dns/tests/rbt_test.c create mode 100644 lib/dns/tests/rbtdb_test.c create mode 100644 lib/dns/tests/rdata_test.c create mode 100644 lib/dns/tests/rdataset_test.c create mode 100644 lib/dns/tests/rdatasetstats_test.c create mode 100644 lib/dns/tests/resolver_test.c create mode 100644 lib/dns/tests/result_test.c create mode 100644 lib/dns/tests/rsa_test.c create mode 100644 lib/dns/tests/sigs_test.c create mode 100644 lib/dns/tests/testdata/db/data.db create mode 100644 lib/dns/tests/testdata/dbiterator/zone1.data create mode 100644 lib/dns/tests/testdata/dbiterator/zone2.data create mode 100644 lib/dns/tests/testdata/diff/zone1.data create mode 100644 lib/dns/tests/testdata/diff/zone2.data create mode 100644 lib/dns/tests/testdata/diff/zone3.data create mode 100644 lib/dns/tests/testdata/dnstap/dnstap.saved create mode 100644 lib/dns/tests/testdata/dnstap/dnstap.text create mode 100644 lib/dns/tests/testdata/dnstap/query.auth create mode 100644 lib/dns/tests/testdata/dnstap/query.recursive create mode 100644 lib/dns/tests/testdata/dnstap/response.auth create mode 100644 lib/dns/tests/testdata/dnstap/response.recursive create mode 100644 lib/dns/tests/testdata/dst/Ktest.+008+11349.key create mode 100644 lib/dns/tests/testdata/dst/Ktest.+008+11349.private create mode 100644 lib/dns/tests/testdata/dst/Ktest.+013+49130.key create mode 100644 lib/dns/tests/testdata/dst/Ktest.+013+49130.private create mode 100644 lib/dns/tests/testdata/dst/test1.data create mode 100644 lib/dns/tests/testdata/dst/test1.ecdsa256sig create mode 100644 lib/dns/tests/testdata/dst/test1.rsasha256sig create mode 100644 lib/dns/tests/testdata/dst/test2.data create mode 100644 lib/dns/tests/testdata/dstrandom/random.data create mode 100644 lib/dns/tests/testdata/master/master1.data create mode 100644 lib/dns/tests/testdata/master/master10.data create mode 100644 lib/dns/tests/testdata/master/master11.data create mode 100644 lib/dns/tests/testdata/master/master12.data.in create mode 100644 lib/dns/tests/testdata/master/master13.data.in create mode 100644 lib/dns/tests/testdata/master/master14.data.in create mode 100644 lib/dns/tests/testdata/master/master15.data create mode 100644 lib/dns/tests/testdata/master/master16.data create mode 100644 lib/dns/tests/testdata/master/master17.data create mode 100644 lib/dns/tests/testdata/master/master18.data create mode 100644 lib/dns/tests/testdata/master/master2.data create mode 100644 lib/dns/tests/testdata/master/master3.data create mode 100644 lib/dns/tests/testdata/master/master4.data create mode 100644 lib/dns/tests/testdata/master/master5.data create mode 100644 lib/dns/tests/testdata/master/master6.data create mode 100644 lib/dns/tests/testdata/master/master7.data create mode 100644 lib/dns/tests/testdata/master/master8.data create mode 100644 lib/dns/tests/testdata/master/master9.data create mode 100644 lib/dns/tests/testdata/nsec3/1024.db create mode 100644 lib/dns/tests/testdata/nsec3/2048.db create mode 100644 lib/dns/tests/testdata/nsec3/4096.db create mode 100644 lib/dns/tests/testdata/nsec3/min-1024.db create mode 100644 lib/dns/tests/testdata/nsec3/min-2048.db create mode 100644 lib/dns/tests/testdata/nsec3param/nsec3.db.signed create mode 100644 lib/dns/tests/testdata/zt/zone1.db create mode 100644 lib/dns/tests/testkeys/Kexample.+008+20386.key create mode 100644 lib/dns/tests/testkeys/Kexample.+008+20386.private create mode 100644 lib/dns/tests/testkeys/Kexample.+008+37464.key create mode 100644 lib/dns/tests/testkeys/Kexample.+008+37464.private create mode 100644 lib/dns/tests/time_test.c create mode 100644 lib/dns/tests/tsig_test.c create mode 100644 lib/dns/tests/update_test.c create mode 100644 lib/dns/tests/zonemgr_test.c create mode 100644 lib/dns/tests/zt_test.c create mode 100644 lib/dns/time.c create mode 100644 lib/dns/timer.c create mode 100644 lib/dns/tkey.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/version.c create mode 100644 lib/dns/view.c create mode 100644 lib/dns/win32/DLLMain.c create mode 100644 lib/dns/win32/gen.vcxproj.filters.in create mode 100644 lib/dns/win32/gen.vcxproj.in create mode 100644 lib/dns/win32/gen.vcxproj.user create mode 100644 lib/dns/win32/libdns.def.in create mode 100644 lib/dns/win32/libdns.vcxproj.filters.in create mode 100644 lib/dns/win32/libdns.vcxproj.in create mode 100644 lib/dns/win32/libdns.vcxproj.user create mode 100644 lib/dns/win32/version.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/Kyuafile b/lib/dns/Kyuafile new file mode 100644 index 0000000..c796010 --- /dev/null +++ b/lib/dns/Kyuafile @@ -0,0 +1,15 @@ +-- Copyright (C) Internet Systems Consortium, Inc. ("ISC") +-- +-- SPDX-License-Identifier: MPL-2.0 +-- +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, you can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- See the COPYRIGHT file distributed with this work for additional +-- information regarding copyright ownership. + +syntax(2) +test_suite('bind9') + +include('tests/Kyuafile') diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in new file mode 100644 index 0000000..5061c47 --- /dev/null +++ b/lib/dns/Makefile.in @@ -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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +# Attempt to disable parallel processing. +.NOTPARALLEL: +.NO_PARALLEL: + +VERSION=@BIND9_VERSION@ +@BIND9_MAJOR@ + +@LIBDNS_MAPAPI@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I. -I${top_srcdir}/lib/dns -Iinclude ${DNS_INCLUDES} \ + ${ISC_INCLUDES} \ + ${FSTRM_CFLAGS} \ + ${OPENSSL_CFLAGS} @DST_GSSAPI_INC@ \ + ${PROTOBUF_C_CFLAGS} \ + ${JSON_C_CFLAGS} \ + ${LIBXML2_CFLAGS} \ + ${LMDB_CFLAGS} \ + ${MAXMINDDB_CFLAGS} + +CDEFINES = @USE_GSSAPI@ + +CWARNINGS = + +ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@ + +ISCDEPLIBS = ../../lib/isc/libisc.@A@ + +LIBS = ${FSTRM_LIBS} ${MAXMINDDB_LIBS} ${LMDB_LIBS} ${PROTOBUF_C_LIBS} @LIBS@ + +# Alphabetically + +DSTOBJS = @DST_EXTRA_OBJS@ \ + dst_api.@O@ dst_parse.@O@ dst_result.@O@ \ + gssapi_link.@O@ gssapictx.@O@ hmac_link.@O@ \ + openssl_link.@O@ openssldh_link.@O@ \ + opensslecdsa_link.@O@ openssleddsa_link.@O@ opensslrsa_link.@O@ \ + pkcs11rsa_link.@O@ \ + pkcs11ecdsa_link.@O@ pkcs11eddsa_link.@O@ pkcs11.@O@ \ + key.@O@ + +GEOIP2LINKOBJS = geoip2.@O@ + +DNSTAPOBJS = dnstap.@O@ dnstap.pb-c.@O@ + +# Alphabetically +DNSOBJS = acl.@O@ adb.@O@ badcache.@O@ byaddr.@O@ \ + cache.@O@ callbacks.@O@ catz.@O@ clientinfo.@O@ compress.@O@ \ + db.@O@ dbiterator.@O@ dbtable.@O@ diff.@O@ dispatch.@O@ \ + dlz.@O@ dns64.@O@ dnsrps.@O@ dnssec.@O@ ds.@O@ dyndb.@O@ \ + ecs.@O@ fixedname.@O@ forward.@O@ \ + ipkeylist.@O@ iptable.@O@ journal.@O@ kasp.@O@ keydata.@O@ \ + keymgr.@O@ keytable.@O@ lib.@O@ log.@O@ lookup.@O@ \ + master.@O@ masterdump.@O@ message.@O@ \ + name.@O@ ncache.@O@ nsec.@O@ nsec3.@O@ nta.@O@ \ + order.@O@ peer.@O@ portlist.@O@ private.@O@ \ + rbt.@O@ rbtdb.@O@ rcode.@O@ rdata.@O@ \ + rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ + request.@O@ resolver.@O@ result.@O@ rootns.@O@ \ + rpz.@O@ rrl.@O@ rriterator.@O@ sdb.@O@ \ + sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \ + stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \ + tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \ + version.@O@ view.@O@ xfrin.@O@ zone.@O@ zonekey.@O@ \ + zoneverify.@O@ zt.@O@ +PORTDNSOBJS = client.@O@ ecdb.@O@ + +OBJS= @DNSTAPOBJS@ ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS} \ + ${PORTDNSOBJS} @GEOIP2LINKOBJS@ + +DSTSRCS = @DST_EXTRA_SRCS@ \ + dst_api.c dst_parse.c \ + dst_result.c gssapi_link.c gssapictx.c hmac_link.c \ + openssl_link.c openssldh_link.c \ + opensslecdsa_link.c openssleddsa_link.c opensslrsa_link.c \ + pkcs11rsa_link.c \ + pkcs11ecdsa_link.c pkcs11eddsa_link.c pkcs11.c \ + key.c + +GEOIPL2INKSRCS = geoip2.c + +DNSTAPSRCS = dnstap.c dnstap.pb-c.c + +DNSSRCS = acl.c adb.c badcache.c byaddr.c \ + cache.c callbacks.c clientinfo.c compress.c \ + db.c dbiterator.c dbtable.c diff.c dispatch.c \ + dlz.c dns64.c dnsrps.c dnssec.c ds.c dyndb.c \ + ecs.c fixedname.c forward.c ipkeylist.c iptable.c \ + journal.c kasp.c keydata.c keymgr.c keytable.c \ + lib.c log.c lookup.c master.c masterdump.c message.c \ + name.c ncache.c nsec.c nsec3.c nta.c \ + order.c peer.c portlist.c \ + rbt.c 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 tcpmsg.c time.c timer.c tkey.c \ + tsec.c tsig.c ttl.c update.c validator.c \ + version.c view.c xfrin.c zone.c zoneverify.c \ + zonekey.c zt.c ${OTHERSRCS} +PORTDNSSRCS = client.c ecdb.c + +SRCS = ${DSTSRCS} ${DNSSRCS} \ + ${PORTDNSSRCS} @DNSTAPSRCS@ @GEOIP2LINKSRCS@ + +SUBDIRS = include +TARGETS = timestamp +TESTDIRS = @UNITTESTS@ + +DEPENDEXTRA = ./gen -F include/dns/rdatastruct.h \ + -s ${srcdir} -d >> Makefile ; + +@BIND9_MAKE_RULES@ + +PROTOC_C = @PROTOC_C@ + +version.@O@: version.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \ + -DVERSION=\"${VERSION}\" \ + -DMAJOR=\"${MAJOR}\" \ + -DMAPAPI=\"${MAPAPI}\" \ + -c ${srcdir}/version.c + +libdns.@SA@: ${OBJS} + ${AR} ${ARFLAGS} $@ ${OBJS} + ${RANLIB} $@ + +libdns.la: ${OBJS} + ${LIBTOOL_MODE_LINK} \ + ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libdns.la -rpath ${libdir} \ + -release "${VERSION}" \ + ${OBJS} ${ISCLIBS} @DNS_CRYPTO_LIBS@ ${LIBS} + +include: gen + ${MAKE} include/dns/enumtype.h + ${MAKE} include/dns/enumclass.h + ${MAKE} include/dns/rdatastruct.h + ${MAKE} code.h + +include/dns/enumtype.h: gen + ./gen -s ${srcdir} -t > $@ || { rm -f $@ ; exit 1; } + +include/dns/enumclass.h: gen + ./gen -s ${srcdir} -c > $@ || { rm -f $@ ; exit 1; } + +include/dns/rdatastruct.h: gen \ + ${srcdir}/rdata/rdatastructpre.h \ + ${srcdir}/rdata/rdatastructsuf.h + ./gen -s ${srcdir} -i \ + -P ${srcdir}/rdata/rdatastructpre.h \ + -S ${srcdir}/rdata/rdatastructsuf.h > $@ || \ + { rm -f $@ ; exit 1; } + +code.h: gen + ./gen -s ${srcdir} > code.h || { rm -f $@ ; exit 1; } + +gen: gen.c + ${BUILD_CC} ${BUILD_CFLAGS} -I${top_srcdir}/lib/isc/include \ + ${LFS_CFLAGS} ${LFS_LDFLAGS} \ + ${BUILD_CPPFLAGS} ${BUILD_LDFLAGS} -o $@ ${srcdir}/gen.c \ + ${BUILD_LIBS} ${LFS_LIBS} + +timestamp: include libdns.@A@ + touch timestamp + +testdirs: libdns.@A@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir} + +install:: timestamp installdirs + ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libdns.@A@ ${DESTDIR}${libdir} + +uninstall:: + ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libdns.@A@ + +clean distclean:: + rm -f libdns.@A@ timestamp + rm -f gen code.h include/dns/enumtype.h include/dns/enumclass.h + rm -f include/dns/rdatastruct.h + rm -f dnstap.pb-c.c dnstap.pb-c.h + +newrr:: + rm -f code.h include/dns/enumtype.h include/dns/enumclass.h + rm -f include/dns/rdatastruct.h + +rdata.@O@: include + +depend: include @DNSTAPSRCS@ +subdirs: include +${OBJS}: include + +# dnstap +dnstap.@O@: dnstap.c dnstap.pb-c.c + +dnstap.pb-c.c dnstap.pb-c.h: dnstap.proto + $(PROTOC_C) --c_out=. --proto_path ${srcdir} dnstap.proto + +dnstap.pb-c.@O@: dnstap.pb-c.c diff --git a/lib/dns/acl.c b/lib/dns/acl.c new file mode 100644 index 0000000..9359e53 --- /dev/null +++ b/lib/dns/acl.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/* + * 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)); + *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, const 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); +} + +/* + * 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; + } + + 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, const 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: + inner = e->nestedacl; + break; + + case dns_aclelementtype_localhost: + if (env == NULL || env->localhost == NULL) { + return (false); + } + inner = env->localhost; + break; + + case dns_aclelementtype_localnets: + if (env == NULL || env->localnets == NULL) { + return (false); + } + inner = env->localnets; + 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); + + /* + * 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; + + 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); + } + 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_init(isc_mem_t *mctx, dns_aclenv_t *env) { + isc_result_t result; + + env->localhost = NULL; + env->localnets = NULL; + result = dns_acl_create(mctx, 0, &env->localhost); + if (result != ISC_R_SUCCESS) { + goto cleanup_nothing; + } + 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) */ + return (ISC_R_SUCCESS); + +cleanup_localhost: + dns_acl_detach(&env->localhost); +cleanup_nothing: + return (result); +} + +void +dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) { + 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) */ +} + +void +dns_aclenv_destroy(dns_aclenv_t *env) { + if (env->localhost != NULL) { + dns_acl_detach(&env->localhost); + } + if (env->localnets != NULL) { + dns_acl_detach(&env->localnets); + } +} diff --git a/lib/dns/adb.c b/lib/dns/adb.c new file mode 100644 index 0000000..87f0f8b --- /dev/null +++ b/lib/dns/adb.c @@ -0,0 +1,4885 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 /* Required for HP/UX (and others?) */ +#include +#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; + 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; + 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 to4096; /* Our max. */ + + uint8_t mode; + atomic_uint_fast32_t quota; + atomic_uint_fast32_t active; + double atr; + + /* + * Allow for encapsulated IPv4/IPv6 UDP packet over ethernet. + * Ethernet 1500 - IP(20) - IP6(40) - UDP(8) = 1432. + */ + unsigned char to1432; /* Ethernet */ + unsigned char to1232; /* IPv6 nofrag */ + unsigned char to512; /* plain DNS */ + 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); +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); + +/* + * MUST NOT overlap DNS_ADBFIND_* flags! + */ +#define FIND_EVENT_SENT 0x40000000 +#define FIND_EVENT_FREED 0x80000000 +#define FIND_EVENTSENT(h) (((h)->flags & FIND_EVENT_SENT) != 0) +#define FIND_EVENTFREED(h) (((h)->flags & FIND_EVENT_FREED) != 0) + +#define NAME_NEEDS_POKE 0x80000000 +#define NAME_IS_DEAD 0x40000000 +#define NAME_HINT_OK DNS_ADBFIND_HINTOK +#define NAME_GLUE_OK DNS_ADBFIND_GLUEOK +#define NAME_STARTATZONE DNS_ADBFIND_STARTATZONE +#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 & NAME_GLUE_OK) != 0) +#define NAME_HINTOK(n) (((n)->flags & NAME_HINT_OK) != 0) + +/* + * Private flag(s) for entries. + * MUST NOT overlap FCTX_ADDRINFO_xxx and DNS_FETCHOPT_NOEDNS0. + */ +#define ENTRY_IS_DEAD 0x00400000 + +/* + * 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. + * Note: since we have removed the support of A6 in adb, FETCH_A and FETCH_AAAA + * are now equal to FETCH_V4 and FETCH_V6, respectively. + */ +#define NAME_FETCH_A(n) ((n)->fetch_a != NULL) +#define NAME_FETCH_AAAA(n) ((n)->fetch_aaaa != NULL) +#define NAME_FETCH_V4(n) (NAME_FETCH_A(n)) +#define NAME_FETCH_V6(n) (NAME_FETCH_AAAA(n)) +#define NAME_FETCH(n) (NAME_FETCH_V4(n) || NAME_FETCH_V6(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 & NAME_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->mctx, sizeof(*newentries) * n); + newdeadentries = isc_mem_get(adb->mctx, sizeof(*newdeadentries) * n); + newentrylocks = isc_mem_get(adb->mctx, sizeof(*newentrylocks) * n); + newentry_sd = isc_mem_get(adb->mctx, sizeof(*newentry_sd) * n); + newentry_refcnt = isc_mem_get(adb->mctx, 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->mctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + isc_mem_put(adb->mctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + isc_mem_put(adb->mctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + isc_mem_put(adb->mctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + isc_mem_put(adb->mctx, 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->mctx, sizeof(*newnames) * n); + newdeadnames = isc_mem_get(adb->mctx, sizeof(*newdeadnames) * n); + newnamelocks = isc_mem_get(adb->mctx, sizeof(*newnamelocks) * n); + newname_sd = isc_mem_get(adb->mctx, sizeof(*newname_sd) * n); + newname_refcnt = isc_mem_get(adb->mctx, 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->mctx, adb->names, sizeof(*adb->names) * adb->nnames); + isc_mem_put(adb->mctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + isc_mem_put(adb->mctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + isc_mem_put(adb->mctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + isc_mem_put(adb->mctx, 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->mctx, newnames, sizeof(*newnames) * n); + } + if (newdeadnames != NULL) { + isc_mem_put(adb->mctx, newdeadnames, sizeof(*newdeadnames) * n); + } + if (newnamelocks != NULL) { + isc_mem_put(adb->mctx, newnamelocks, sizeof(*newnamelocks) * n); + } + if (newname_sd != NULL) { + isc_mem_put(adb->mctx, newname_sd, sizeof(*newname_sd) * n); + } + if (newname_refcnt != NULL) { + isc_mem_put(adb->mctx, 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; + dns_adbnamehook_t *nh; + dns_adbnamehook_t *anh; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct in_addr ina; + struct in6_addr in6a; + isc_sockaddr_t sockaddr; + dns_adbentry_t *foundentry; /* NO CLEAN UP! */ + int addr_bucket; + bool new_addresses_added; + dns_rdatatype_t rdtype; + unsigned int findoptions; + dns_adbnamehooklist_t *hookhead; + + 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)); + if (rdtype == dns_rdatatype_a) { + findoptions = DNS_ADBFIND_INET; + } else { + findoptions = DNS_ADBFIND_INET6; + } + + addr_bucket = DNS_ADB_INVALIDBUCKET; + new_addresses_added = false; + + nh = NULL; + 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); + if (nh == NULL) { + adbname->partial_result |= findoptions; + result = ISC_R_NOMEMORY; + goto fail; + } + + foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket, + now); + if (foundentry == NULL) { + dns_adbentry_t *entry; + + entry = new_adbentry(adb); + if (entry == NULL) { + adbname->partial_result |= findoptions; + result = ISC_R_NOMEMORY; + goto fail; + } + + 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); + } + +fail: + if (nh != NULL) { + free_adbnamehook(adb, &nh); + } + + 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_V4(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_V6(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); + } + + /* + * 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 (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) { + int bucket; + bool destroy_entry; + bool result = false; + + bucket = entry->lock_bucket; + + if (lock) { + LOCK(&adb->entrylocks[bucket]); + } + + INSIST(entry->refcnt > 0); + entry->refcnt--; + + destroy_entry = false; + if (entry->refcnt == 0 && + (adb->entry_sd[bucket] || entry->expires == 0 || overmem || + (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->completed = 0; + e->timeouts = 0; + e->plain = 0; + e->plainto = 0; + e->to4096 = 0; + e->to1432 = 0; + e->to1232 = 0; + e->to512 = 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; + + INSIST(entry != NULL && DNS_ADBENTRY_VALID(*entry)); + e = *entry; + *entry = NULL; + + 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; + ai->dscp = -1; + 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); + if (addrinfo == NULL) { + find->partial_result |= DNS_ADBFIND_INET; + goto out; + } + /* + * 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); + if (addrinfo == NULL) { + find->partial_result |= DNS_ADBFIND_INET6; + goto out; + } + /* + * 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); + } + } + +out: + 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; + } + + 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->mctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + isc_mem_put(adb->mctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + isc_mem_put(adb->mctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + isc_mem_put(adb->mctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + isc_mem_put(adb->mctx, adb->entry_refcnt, + sizeof(*adb->entry_refcnt) * adb->nentries); + + isc_mutexblock_destroy(adb->namelocks, adb->nnames); + isc_mem_put(adb->mctx, adb->names, sizeof(*adb->names) * adb->nnames); + isc_mem_put(adb->mctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + isc_mem_put(adb->mctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + isc_mem_put(adb->mctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + isc_mem_put(adb->mctx, adb->name_refcnt, + sizeof(*adb->name_refcnt) * adb->nnames); + + 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->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; + 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); + +#define ALLOCENTRY(adb, el) \ + do { \ + (adb)->el = isc_mem_get((adb)->mctx, \ + 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)->mctx, \ + 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->mctx, adb->entries, + sizeof(*adb->entries) * adb->nentries); + } + if (adb->deadentries != NULL) { + isc_mem_put(adb->mctx, adb->deadentries, + sizeof(*adb->deadentries) * adb->nentries); + } + if (adb->entrylocks != NULL) { + isc_mem_put(adb->mctx, adb->entrylocks, + sizeof(*adb->entrylocks) * adb->nentries); + } + if (adb->entry_sd != NULL) { + isc_mem_put(adb->mctx, adb->entry_sd, + sizeof(*adb->entry_sd) * adb->nentries); + } + if (adb->entry_refcnt != NULL) { + isc_mem_put(adb->mctx, adb->entry_refcnt, + sizeof(*adb->entry_refcnt) * adb->nentries); + } + if (adb->names != NULL) { + isc_mem_put(adb->mctx, adb->names, + sizeof(*adb->names) * adb->nnames); + } + if (adb->deadnames != NULL) { + isc_mem_put(adb->mctx, adb->deadnames, + sizeof(*adb->deadnames) * adb->nnames); + } + if (adb->namelocks != NULL) { + isc_mem_put(adb->mctx, adb->namelocks, + sizeof(*adb->namelocks) * adb->nnames); + } + if (adb->name_sd != NULL) { + isc_mem_put(adb->mctx, adb->name_sd, + sizeof(*adb->name_sd) * adb->nnames); + } + if (adb->name_refcnt != NULL) { + isc_mem_put(adb->mctx, adb->name_refcnt, + sizeof(*adb->name_refcnt) * adb->nnames); + } + + 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(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 (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(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 (!adb->shutting_down) { + adb->shutting_down = true; + isc_mem_setwater(adb->mctx, water, adb, 0, 0); + /* + * 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; + dns_adbname_t *adbname; + int bucket; + bool want_event, start_at_zone, alias, have_address; + isc_result_t result; + unsigned int wanted_addresses; + unsigned int wanted_fetches; + unsigned int query_pending; + 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); + wanted_addresses = (options & DNS_ADBFIND_ADDRESSMASK); + wanted_fetches = 0; + query_pending = 0; + want_event = false; + start_at_zone = false; + alias = false; + + 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. If any of these allocations + * fail, clean up and return ISC_R_NOMEMORY. + * + * 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); + if (find == NULL) { + return (ISC_R_NOMEMORY); + } + + 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); + if (adbname == NULL) { + RUNTIME_CHECK(!free_adbfind(adb, &find)); + result = ISC_R_NOMEMORY; + goto out; + } + link_name(adb, bucket, adbname); + if (FIND_HINTOK(find)) { + adbname->flags |= NAME_HINT_OK; + } + if (FIND_GLUEOK(find)) { + adbname->flags |= NAME_GLUE_OK; + } + if (FIND_STARTATZONE(find)) { + adbname->flags |= NAME_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_V4(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_V6(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_V4(adbname)) { + query_pending |= DNS_ADBFIND_INET; + } + if (NAME_FETCH_V6(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. + */ + want_event = true; + 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) { + find->adbname = adbname; + find->name_bucket = bucket; + bool 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_copynf(&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) { + *findp = find; + + if (want_event) { + isc_task_t *taskp; + + INSIST((find->flags & DNS_ADBFIND_ADDRESSMASK) != 0); + taskp = NULL; + isc_task_attach(task, &taskp); + find->event.ev_sender = taskp; + find->event.ev_action = action; + find->event.ev_arg = arg; + } + } + + 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; + + 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. + */ + 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)); + 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/4096 timeout/1432 timeout/1232 timeout/" + "512 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/%u/%u/%u] " + "[plain %u/%u]", + addrbuf, entry->srtt, entry->flags, entry->edns, entry->to4096, + entry->to1432, entry->to1232, entry->to512, 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 NAME_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 & NAME_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; + } + + 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, + dns_fixedname_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", + dns_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_V4(adbname)) || + (type == dns_rdatatype_aaaa && !NAME_FETCH_V6(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); + if (fetch == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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); + if (li == NULL) { + result = ISC_R_NOMEMORY; + goto unlock; + } + + 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 +bool +dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + int bucket; + bool noedns = false; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + + if (addr->entry->edns == 0U && + (addr->entry->plain > EDNSTOS || addr->entry->to4096 > EDNSTOS)) + { + if (((addr->entry->plain + addr->entry->to4096) & 0x3f) != 0) { + noedns = true; + } else { + /* + * Increment plain so we don't get stuck. + */ + addr->entry->plain++; + if (addr->entry->plain == 0xff) { + addr->entry->edns >>= 1; + addr->entry->to4096 >>= 1; + addr->entry->to1432 >>= 1; + addr->entry->to1232 >>= 1; + addr->entry->to512 >>= 1; + addr->entry->plain >>= 1; + addr->entry->plainto >>= 1; + } + } + } + UNLOCK(&adb->entrylocks[bucket]); + return (noedns); +} + +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->to4096 >>= 1; + addr->entry->to1432 >>= 1; + addr->entry->to1232 >>= 1; + addr->entry->to512 >>= 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); + + /* + * If we have not had a successful query then clear all + * edns timeout information. + */ + if (addr->entry->edns == 0 && addr->entry->plain == 0) { + addr->entry->to512 = 0; + addr->entry->to1232 = 0; + addr->entry->to1432 = 0; + addr->entry->to4096 = 0; + } else { + addr->entry->to512 >>= 1; + addr->entry->to1232 >>= 1; + addr->entry->to1432 >>= 1; + addr->entry->to4096 >>= 1; + } + + addr->entry->plainto++; + if (addr->entry->plainto == 0xff) { + addr->entry->edns >>= 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, unsigned int size) { + 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); + + if (size <= 512U) { + if (addr->entry->to512 <= EDNSTOS) { + addr->entry->to512++; + addr->entry->to1232++; + addr->entry->to1432++; + addr->entry->to4096++; + } + } else if (size <= 1232U) { + if (addr->entry->to1232 <= EDNSTOS) { + addr->entry->to1232++; + addr->entry->to1432++; + addr->entry->to4096++; + } + } else if (size <= 1432U) { + if (addr->entry->to1432 <= EDNSTOS) { + addr->entry->to1432++; + addr->entry->to4096++; + } + } else { + if (addr->entry->to4096 <= EDNSTOS) { + addr->entry->to4096++; + } + } + + if (addr->entry->to4096 == 0xff) { + addr->entry->edns >>= 1; + addr->entry->to4096 >>= 1; + addr->entry->to1432 >>= 1; + addr->entry->to1232 >>= 1; + addr->entry->to512 >>= 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->to4096 >>= 1; + addr->entry->to1432 >>= 1; + addr->entry->to1232 >>= 1; + addr->entry->to512 >>= 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); +} + +unsigned int +dns_adb_probesize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, int lookups) { + int bucket; + unsigned int size; + + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + bucket = addr->entry->lock_bucket; + LOCK(&adb->entrylocks[bucket]); + if (addr->entry->to1232 > EDNSTOS || lookups >= 2) { + size = 512; + } else if (addr->entry->to1432 > EDNSTOS || lookups >= 1) { + size = 1232; + } else if (addr->entry->to4096 > EDNSTOS) { + size = 1432; + } else { + size = 4096; + } + /* + * Don't shrink probe size below what we have seen due to multiple + * lookups. + */ + if (lookups > 0 && size < addr->entry->udpsize && + addr->entry->udpsize < 4096) + { + 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); + if (entry == NULL) { + result = ISC_R_NOMEMORY; + goto unlock; + } + 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); + if (addr == NULL) { + result = ISC_R_NOMEMORY; + } else { + 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]); + + if (entry->expires == 0) { + isc_stdtime_get(&now); + entry->expires = now + ADB_ENTRY_WINDOW; + } + + want_check_exit = dec_entry_refcnt(adb, overmem, entry, false); + + 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_setwater(adb->mctx, water, adb, 0, 0); + } 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) { + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + INSIST(atomic_fetch_add_relaxed(&addr->entry->active, 1) != UINT32_MAX); +} + +void +dns_adb_endudpfetch(dns_adb_t *adb, dns_adbaddrinfo_t *addr) { + REQUIRE(DNS_ADB_VALID(adb)); + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + + INSIST(atomic_fetch_sub_release(&addr->entry->active, 1) != 0); +} diff --git a/lib/dns/badcache.c b/lib/dns/badcache.c new file mode 100644 index 0000000..92116c0 --- /dev/null +++ b/lib/dns/badcache.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_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) + bad->name.length); + 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) + bad->name.length); + atomic_fetch_sub_relaxed(&bc->count, 1); + } else { + prev = bad; + } + } + + if (bad == NULL) { + isc_buffer_t buffer; + bad = isc_mem_get(bc->mctx, sizeof(*bad) + name->length); + bad->type = type; + bad->hashval = hashval; + bad->expire = *expire; + bad->flags = flags; + isc_buffer_init(&buffer, bad + 1, name->length); + dns_name_init(&bad->name, NULL); + dns_name_copy(name, &bad->name, &buffer); + bad->next = bc->table[hash]; + bc->table[hash] = bad; + unsigned 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) + bad->name.length); + 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) + bad->name.length); + 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) + entry->name.length); + 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) + bad->name.length); + 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) + bad->name.length); + 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) + bad->name.length); + 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..c6f471b --- /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 /* Required for HP/UX (and others?) */ +#include +#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..417cdaf --- /dev/null +++ b/lib/dns/cache.c @@ -0,0 +1,1510 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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_mutex_t filelock; + 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; + + /* Locked by 'filelock'. */ + char *filename; + /* Access to the on-disk cache file is also locked by 'filelock'. */ +}; + +/*** + *** 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 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); + } + 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_mutex_init(&cache->filelock); + + 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_filelock; + } + + 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->filename = NULL; + + 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); +cleanup_filelock: + isc_mutex_destroy(&cache->filelock); + isc_stats_detach(&cache->stats); + 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_setwater(cache->mctx, NULL, NULL, 0, 0); + + 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->filename) { + isc_mem_free(cache->mctx, cache->filename); + cache->filename = NULL; + } + + 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); + isc_mutex_destroy(&cache->filelock); + + 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; + /* + * When the cache is shut down, dump it to a file if one is + * specified. + */ + isc_result_t result = dns_cache_dump(cache); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "error dumping cache: %s ", + isc_result_totext(result)); + } + + /* + * 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); +} + +isc_result_t +dns_cache_setfilename(dns_cache_t *cache, const char *filename) { + char *newname; + + REQUIRE(VALID_CACHE(cache)); + REQUIRE(filename != NULL); + + newname = isc_mem_strdup(cache->mctx, filename); + + LOCK(&cache->filelock); + if (cache->filename) { + isc_mem_free(cache->mctx, cache->filename); + } + cache->filename = newname; + UNLOCK(&cache->filelock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_cache_load(dns_cache_t *cache) { + isc_result_t result; + + REQUIRE(VALID_CACHE(cache)); + + if (cache->filename == NULL) { + return (ISC_R_SUCCESS); + } + + LOCK(&cache->filelock); + result = dns_db_load(cache->db, cache->filename, dns_masterformat_text, + 0); + UNLOCK(&cache->filelock); + + return (result); +} + +isc_result_t +dns_cache_dump(dns_cache_t *cache) { + isc_result_t result; + + REQUIRE(VALID_CACHE(cache)); + + if (cache->filename == NULL) { + return (ISC_R_SUCCESS); + } + + LOCK(&cache->filelock); + result = dns_master_dump(cache->mctx, cache->db, NULL, + &dns_master_style_cache, cache->filename, + dns_masterformat_text, NULL); + UNLOCK(&cache->filelock); + return (result); +} + +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(__FILE__, __LINE__, + "isc_task_create() failed: %s", + dns_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(__FILE__, __LINE__, + "cache cleaner: " + "isc_task_onshutdown() failed: %s", + dns_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(__FILE__, __LINE__, + "cache cleaner: " + "dns_dbiterator_first() failed: %s", + dns_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(__FILE__, __LINE__, + "cache cleaner: " + "dns_dbiterator_current() " + "failed: %s", + dns_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(__FILE__, __LINE__, + "cache cleaner: " + "dns_dbiterator_next() " + "failed: %s", + dns_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(__FILE__, __LINE__, + "cache cleaner: dns_db_expirenode() " + "failed: %s", + dns_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_setwater(cache->mctx, water, cache, 0, 0); + } else { + /* + * Establish new cache memory limits (either for the first + * time, or replacing other limits). + */ + isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater); + } + + dns_db_adjusthashsize(cache->db, size); +} + +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: + 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, "%20u %s\n", dns_db_nodecount(cache->db), + "cache 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("CacheNodes", dns_db_nodecount(cache->db), 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(dns_db_nodecount(cache->db)); + CHECKMEM(obj); + json_object_object_add(cstats, "CacheNodes", 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..2c00d6e --- /dev/null +++ b/lib/dns/catz.c @@ -0,0 +1,2105 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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_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) + +/*% + * 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 refs; +}; + +/*% + * Catalog zone + */ +struct dns_catz_zone { + unsigned int magic; + dns_name_t name; + dns_catz_zones_t *catzs; + dns_rdata_t soa; + /* key in entries is 'mhash', not domain name! */ + isc_ht_t *entries; + /* + * 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; + uint32_t version; + + dns_db_t *db; + dns_dbversion_t *dbversion; + + isc_timer_t *updatetimer; + isc_event_t updateevent; + + bool active; + bool db_registered; + + isc_refcount_t refs; +}; + +static isc_result_t +catz_process_zones_entry(dns_catz_zone_t *zone, dns_rdataset_t *value, + dns_label_t *mhash); +static isc_result_t +catz_process_zones_suboption(dns_catz_zone_t *zone, dns_rdataset_t *value, + dns_label_t *mhash, dns_name_t *name); +static void +catz_entry_add_or_mod(dns_catz_zone_t *target, 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 refs; + 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; +}; + +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); + } +} + +isc_result_t +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); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +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; + return (ISC_R_SUCCESS); +} + +isc_result_t +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(dns_catz_entry_t)); + + 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->refs, 1); + nentry->magic = DNS_CATZ_ENTRY_MAGIC; + *nentryp = nentry; + return (ISC_R_SUCCESS); +} + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *entry) { + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + return (&entry->name); +} + +isc_result_t +dns_catz_entry_copy(dns_catz_zone_t *zone, const dns_catz_entry_t *entry, + dns_catz_entry_t **nentryp) { + isc_result_t result; + dns_catz_entry_t *nentry = NULL; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(nentryp != NULL && *nentryp == NULL); + + result = dns_catz_entry_new(zone->catzs->mctx, &entry->name, &nentry); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_catz_options_copy(zone->catzs->mctx, &entry->opts, + &nentry->opts); + if (result != ISC_R_SUCCESS) { + dns_catz_entry_detach(zone, &nentry); + } + + *nentryp = nentry; + return (result); +} + +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->refs); + *entryp = entry; +} + +void +dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp) { + dns_catz_entry_t *entry; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(entryp != NULL); + entry = *entryp; + *entryp = NULL; + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + + if (isc_refcount_decrement(&entry->refs) == 1) { + isc_mem_t *mctx = zone->catzs->mctx; + entry->magic = 0; + isc_refcount_destroy(&entry->refs); + 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(dns_catz_entry_t)); + } +} + +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); + } + } + + /* 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); + } + } + + /* xxxwpk TODO compare dscps! */ + return (true); +} + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *zone) { + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + + return (&zone->name); +} + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *zone) { + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + + return (&zone->defoptions); +} + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone) { + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + + dns_catz_options_free(&zone->defoptions, zone->catzs->mctx); + dns_catz_options_init(&zone->defoptions); +} + +isc_result_t +dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { + 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(newzone)); + REQUIRE(DNS_CATZ_ZONE_VALID(target)); + + /* TODO verify the new zone first! */ + + addzone = target->catzs->zmm->addzone; + modzone = target->catzs->zmm->modzone; + delzone = target->catzs->zmm->delzone; + + /* Copy zoneoptions from newzone into target. */ + + dns_catz_options_free(&target->zoneoptions, target->catzs->mctx); + dns_catz_options_copy(target->catzs->mctx, &newzone->zoneoptions, + &target->zoneoptions); + dns_catz_options_setdefault(target->catzs->mctx, &target->defoptions, + &target->zoneoptions); + + dns_name_format(&target->name, czname, DNS_NAME_FORMATSIZE); + + isc_ht_init(&toadd, target->catzs->mctx, 16); + + isc_ht_init(&tomod, target->catzs->mctx, 16); + + isc_ht_iter_create(newzone->entries, &iter1); + + isc_ht_iter_create(target->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)) + { + 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(newzone, &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(target->catzs->mctx, + &target->zoneoptions, + &nentry->opts); + + result = isc_ht_find(target->entries, key, (uint32_t)keysize, + (void **)&oentry); + if (result != ISC_R_SUCCESS) { + catz_entry_add_or_mod(target, toadd, key, keysize, + nentry, NULL, "adding", zname, + czname); + continue; + } + + result = dns_zt_find(target->catzs->view->zonetable, + dns_catz_entry_getname(nentry), 0, NULL, + &zone); + if (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(target, toadd, key, keysize, + nentry, oentry, "adding", zname, + czname); + continue; + } + dns_zone_detach(&zone); + + if (dns_catz_entry_cmp(oentry, nentry) != true) { + catz_entry_add_or_mod(target, 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(target, &oentry); + result = isc_ht_delete(target->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, target, target->catzs->view, + target->catzs->taskmgr, + target->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(target, &entry); + } + RUNTIME_CHECK(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter2); + /* At this moment target->entries has to be be empty. */ + INSIST(isc_ht_count(target->entries) == 0); + isc_ht_destroy(&target->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, target, target->catzs->view, + target->catzs->taskmgr, + target->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, target, target->catzs->view, + target->catzs->taskmgr, + target->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)); + } + + target->entries = newzone->entries; + newzone->entries = NULL; + + result = ISC_R_SUCCESS; + + isc_ht_iter_destroy(&iteradd); + isc_ht_iter_destroy(&itermod); + isc_ht_destroy(&toadd); + isc_ht_destroy(&tomod); + + return (result); +} + +isc_result_t +dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr) { + dns_catz_zones_t *new_zones; + isc_result_t result; + + REQUIRE(catzsp != NULL && *catzsp == NULL); + REQUIRE(zmm != NULL); + + new_zones = isc_mem_get(mctx, sizeof(*new_zones)); + memset(new_zones, 0, sizeof(*new_zones)); + + isc_mutex_init(&new_zones->lock); + + isc_refcount_init(&new_zones->refs, 1); + + isc_ht_init(&new_zones->zones, mctx, 4); + + isc_mem_attach(mctx, &new_zones->mctx); + new_zones->zmm = zmm; + new_zones->timermgr = timermgr; + new_zones->taskmgr = taskmgr; + + result = isc_task_create(taskmgr, 0, &new_zones->updater); + if (result != ISC_R_SUCCESS) { + goto cleanup_ht; + } + new_zones->magic = DNS_CATZ_ZONES_MAGIC; + + *catzsp = new_zones; + return (ISC_R_SUCCESS); + +cleanup_ht: + isc_ht_destroy(&new_zones->zones); + isc_refcount_destroy(&new_zones->refs); + isc_mutex_destroy(&new_zones->lock); + isc_mem_putanddetach(&new_zones->mctx, new_zones, sizeof(*new_zones)); + + 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 **zonep, + const dns_name_t *name) { + isc_result_t result; + dns_catz_zone_t *new_zone; + + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(zonep != NULL && *zonep == NULL); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + new_zone = isc_mem_get(catzs->mctx, sizeof(*new_zone)); + + memset(new_zone, 0, sizeof(*new_zone)); + + dns_name_init(&new_zone->name, NULL); + dns_name_dup(name, catzs->mctx, &new_zone->name); + + isc_ht_init(&new_zone->entries, catzs->mctx, 16); + + new_zone->updatetimer = NULL; + result = isc_timer_create(catzs->timermgr, isc_timertype_inactive, NULL, + NULL, catzs->updater, + dns_catz_update_taskaction, new_zone, + &new_zone->updatetimer); + if (result != ISC_R_SUCCESS) { + goto cleanup_ht; + } + + isc_time_settoepoch(&new_zone->lastupdated); + new_zone->updatepending = false; + new_zone->db = NULL; + new_zone->dbversion = NULL; + new_zone->catzs = catzs; + dns_catz_options_init(&new_zone->defoptions); + dns_catz_options_init(&new_zone->zoneoptions); + new_zone->active = true; + new_zone->db_registered = false; + new_zone->version = (uint32_t)(-1); + isc_refcount_init(&new_zone->refs, 1); + new_zone->magic = DNS_CATZ_ZONE_MAGIC; + + *zonep = new_zone; + + return (ISC_R_SUCCESS); + +cleanup_ht: + isc_ht_destroy(&new_zone->entries); + dns_name_free(&new_zone->name, catzs->mctx); + isc_mem_put(catzs->mctx, new_zone, sizeof(*new_zone)); + + return (result); +} + +isc_result_t +dns_catz_add_zone(dns_catz_zones_t *catzs, const dns_name_t *name, + dns_catz_zone_t **zonep) { + dns_catz_zone_t *new_zone = 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(zonep != NULL && *zonep == 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, &new_zone, name); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = isc_ht_add(catzs->zones, new_zone->name.ndata, + new_zone->name.length, new_zone); + if (result != ISC_R_SUCCESS) { + dns_catz_zone_detach(&new_zone); + if (result != ISC_R_EXISTS) { + goto cleanup; + } + } + + if (result == ISC_R_EXISTS) { + tresult = isc_ht_find(catzs->zones, name->ndata, name->length, + (void **)&new_zone); + INSIST(tresult == ISC_R_SUCCESS && !new_zone->active); + new_zone->active = true; + } + + *zonep = new_zone; + +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); +} + +void +dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp) { + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + REQUIRE(catzsp != NULL && *catzsp == NULL); + + isc_refcount_increment(&catzs->refs); + *catzsp = catzs; +} + +void +dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep) { + REQUIRE(zonep != NULL && *zonep == NULL); + + isc_refcount_increment(&zone->refs); + *zonep = zone; +} + +void +dns_catz_zone_detach(dns_catz_zone_t **zonep) { + REQUIRE(zonep != NULL && *zonep != NULL); + dns_catz_zone_t *zone = *zonep; + *zonep = NULL; + + if (isc_refcount_decrement(&zone->refs) == 1) { + isc_mem_t *mctx = zone->catzs->mctx; + isc_refcount_destroy(&zone->refs); + if (zone->entries != NULL) { + isc_ht_iter_t *iter = NULL; + isc_result_t result; + isc_ht_iter_create(zone->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(zone, &entry); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(zone->entries) == 0); + isc_ht_destroy(&zone->entries); + } + zone->magic = 0; + isc_timer_destroy(&zone->updatetimer); + if (zone->db_registered) { + dns_db_updatenotify_unregister( + zone->db, dns_catz_dbupdate_callback, + zone->catzs); + } + if (zone->dbversion) { + dns_db_closeversion(zone->db, &zone->dbversion, false); + } + if (zone->db != NULL) { + dns_db_detach(&zone->db); + } + + dns_name_free(&zone->name, mctx); + dns_catz_options_free(&zone->defoptions, mctx); + dns_catz_options_free(&zone->zoneoptions, mctx); + + zone->catzs = NULL; + isc_mem_put(mctx, zone, sizeof(dns_catz_zone_t)); + } +} + +void +dns_catz_catzs_detach(dns_catz_zones_t **catzsp) { + dns_catz_zones_t *catzs; + + REQUIRE(catzsp != NULL && DNS_CATZ_ZONES_VALID(*catzsp)); + + catzs = *catzsp; + *catzsp = NULL; + + if (isc_refcount_decrement(&catzs->refs) == 1) { + catzs->magic = 0; + isc_task_destroy(&catzs->updater); + isc_mutex_destroy(&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 *zone = NULL; + isc_ht_iter_current(iter, (void **)&zone); + result = isc_ht_iter_delcurrent_next(iter); + dns_catz_zone_detach(&zone); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + INSIST(isc_ht_count(catzs->zones) == 0); + isc_ht_destroy(&catzs->zones); + } + isc_refcount_destroy(&catzs->refs); + isc_mem_putanddetach(&catzs->mctx, catzs, sizeof(*catzs)); + } +} + +typedef enum { + CATZ_OPT_NONE, + CATZ_OPT_ZONES, + CATZ_OPT_MASTERS, + CATZ_OPT_ALLOW_QUERY, + CATZ_OPT_ALLOW_TRANSFER, + CATZ_OPT_VERSION, +} catz_opt_t; + +static bool +catz_opt_cmp(const dns_label_t *option, const char *opt) { + unsigned int l = strlen(opt); + if (option->length - 1 == l && + memcmp(opt, option->base + 1, l - 1) == 0) + { + return (true); + } else { + return (false); + } +} + +static catz_opt_t +catz_get_option(const dns_label_t *option) { + if (catz_opt_cmp(option, "zones")) { + return (CATZ_OPT_ZONES); + } else if (catz_opt_cmp(option, "masters")) { + return (CATZ_OPT_MASTERS); + } 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, "version")) { + return (CATZ_OPT_VERSION); + } else { + return (CATZ_OPT_NONE); + } +} + +static isc_result_t +catz_process_zones(dns_catz_zone_t *zone, dns_rdataset_t *value, + dns_name_t *name) { + dns_label_t mhash; + dns_name_t opt; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + if (value->rdclass != dns_rdataclass_in) { + return (ISC_R_FAILURE); + } + + 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(zone, value, &mhash)); + } else { + dns_name_init(&opt, NULL); + dns_name_split(name, 1, &opt, NULL); + return (catz_process_zones_suboption(zone, value, &mhash, + &opt)); + } +} + +static isc_result_t +catz_process_zones_entry(dns_catz_zone_t *zone, 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; + + /* + * We only take -first- value, as mhash must be + * different. + */ + if (value->type != dns_rdatatype_ptr) { + return (ISC_R_FAILURE); + } + + result = dns_rdataset_first(value); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = isc_ht_find(zone->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, zone->catzs->mctx, &entry->name); + } + } else { + result = dns_catz_entry_new(zone->catzs->mctx, &ptr.ptr, + &entry); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + return (result); + } + + result = isc_ht_add(zone->entries, mhash->base, mhash->length, + entry); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + dns_catz_entry_detach(zone, &entry); + return (result); + } + } + + dns_rdata_freestruct(&ptr); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_version(dns_catz_zone_t *zone, 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(zone)); + REQUIRE(DNS_RDATASET_VALID(value)); + + if (value->rdclass != dns_rdataclass_in || + value->type != dns_rdatatype_txt) + { + 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); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + 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; + } + zone->version = tversion; + result = ISC_R_SUCCESS; + +cleanup: + dns_rdata_freestruct(&rdatatxt); + return (result); +} + +static isc_result_t +catz_process_masters(dns_catz_zone_t *zone, 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; + unsigned int i; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(ipkl != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(dns_rdataset_isassociated(value)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + + mctx = zone->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 (value->rdclass != dns_rdataclass_in) { + return (ISC_R_FAILURE); + } + + if (name->labels > 0) { + isc_sockaddr_t sockaddr; + + /* + * We're pre-preparing the data once, we'll put it into + * the right spot in the masters 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); + 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); + 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) { + return (result); + } + + result = dns_rdata_txt_current(&rdata_txt, &rdatastr); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_rdata_txt_next(&rdata_txt); + if (result != ISC_R_NOMORE) { + return (ISC_R_FAILURE); + } + + /* rdatastr.length < DNS_NAME_MAXTEXT */ + keyname = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(keyname, 0); + memmove(keycbuf, rdatastr.data, rdatastr.length); + keycbuf[rdatastr.length] = 0; + result = dns_name_fromstring(keyname, keycbuf, 0, mctx); + if (result != ISC_R_SUCCESS) { + dns_name_free(keyname, mctx); + isc_mem_put(mctx, keyname, sizeof(dns_name_t)); + return (result); + } + break; + default: + return (ISC_R_FAILURE); + } + + /* + * We have to find the appropriate labeled record in masters + * if it exists. + * In 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(isc_sockaddr_t)); + } + } else { + result = dns_ipkeylist_resize(mctx, ipkl, i + 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + + ipkl->labels[i] = isc_mem_get(mctx, sizeof(dns_name_t)); + 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(isc_sockaddr_t)); + } + 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); + } 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); + } + ipkl->keys[ipkl->count] = NULL; + ipkl->labels[ipkl->count] = NULL; + ipkl->count++; + dns_rdata_freestruct(&rdata_a); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_process_apl(dns_catz_zone_t *zone, 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(zone)); + REQUIRE(aclbp != NULL); + REQUIRE(*aclbp == NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(dns_rdataset_isassociated(value)); + + if (value->rdclass != dns_rdataclass_in || + 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, zone->catzs->mctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_allocate(zone->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 *zone, 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; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(mhash != NULL); + 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, &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(zone->entries, mhash->base, mhash->length, + (void **)&entry); + if (result != ISC_R_SUCCESS) { + result = dns_catz_entry_new(zone->catzs->mctx, NULL, &entry); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = isc_ht_add(zone->entries, mhash->base, mhash->length, + entry); + if (result != ISC_R_SUCCESS) { + dns_catz_entry_detach(zone, &entry); + return (result); + } + } + + dns_name_init(&prefix, NULL); + dns_name_split(name, 1, &prefix, NULL); + switch (opt) { + case CATZ_OPT_MASTERS: + return (catz_process_masters(zone, &entry->opts.masters, value, + &prefix)); + case CATZ_OPT_ALLOW_QUERY: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(zone, &entry->opts.allow_query, + value)); + case CATZ_OPT_ALLOW_TRANSFER: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(zone, &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 *target, 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(target, &oentry); + result = isc_ht_delete(target->entries, key, (uint32_t)keysize); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +} + +static isc_result_t +catz_process_value(dns_catz_zone_t *zone, dns_name_t *name, + dns_rdataset_t *rdataset) { + dns_label_t option; + dns_name_t prefix; + catz_opt_t opt; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC)); + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + dns_name_getlabel(name, name->labels - 1, &option); + opt = catz_get_option(&option); + dns_name_init(&prefix, NULL); + dns_name_split(name, 1, &prefix, NULL); + switch (opt) { + case CATZ_OPT_ZONES: + return (catz_process_zones(zone, rdataset, &prefix)); + case CATZ_OPT_MASTERS: + return (catz_process_masters(zone, &zone->zoneoptions.masters, + rdataset, &prefix)); + case CATZ_OPT_ALLOW_QUERY: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl(zone, &zone->zoneoptions.allow_query, + rdataset)); + case CATZ_OPT_ALLOW_TRANSFER: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_apl( + zone, &zone->zoneoptions.allow_transfer, rdataset)); + case CATZ_OPT_VERSION: + if (prefix.labels != 0) { + return (ISC_R_FAILURE); + } + return (catz_process_version(zone, rdataset)); + default: + return (ISC_R_FAILURE); + } +} + +isc_result_t +dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone, + 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_ZONES_VALID(catzs)); + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(ISC_MAGIC_VALID(src_name, DNS_NAME_MAGIC)); + + nrres = dns_name_fullcompare(src_name, &zone->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? + */ + 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, zone->name.labels, &prefix, NULL); + result = catz_process_value(zone, &prefix, rdataset); + + return (result); +} + +static isc_result_t +digest2hex(unsigned char *digest, unsigned int digestlen, char *hash, + size_t hashlen) { + unsigned int i; + int ret; + for (i = 0; i < digestlen; i++) { + size_t left = hashlen - i * 2; + 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 *zone, 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(zone)); + REQUIRE(DNS_CATZ_ENTRY_VALID(entry)); + REQUIRE(buffer != NULL && *buffer != NULL); + + isc_buffer_allocate(zone->catzs->mctx, &tbuf, + strlen(zone->catzs->view->name) + + 2 * DNS_NAME_FORMATSIZE + 2); + + isc_buffer_putstr(tbuf, zone->catzs->view->name); + isc_buffer_putstr(tbuf, "_"); + result = dns_name_totext(&zone->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); +} + +isc_result_t +dns_catz_generate_zonecfg(dns_catz_zone_t *zone, dns_catz_entry_t *entry, + isc_buffer_t **buf) { + /* + * We have to generate a text buffer with regular zone config: + * zone "foo.bar" { + * type slave; + * masters [ dscp X ] { ip1 port port1; ip2 port port2; }; + * } + */ + 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 both for port number and DSCP */ + char zname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + 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(zone->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 slave; masters"); + + /* + * DSCP value has no default, but when it is specified, it is identical + * for all masters and cannot be overridden for a specific master IP, so + * use the DSCP value set for the first master + */ + if (entry->opts.masters.count > 0 && entry->opts.masters.dscps[0] >= 0) + { + isc_buffer_putstr(buffer, " dscp "); + snprintf(pbuf, sizeof(pbuf), "%hd", + entry->opts.masters.dscps[0]); + isc_buffer_putstr(buffer, pbuf); + } + + isc_buffer_putstr(buffer, " { "); + for (i = 0; i < entry->opts.masters.count; i++) { + /* + * Every master 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 master " + "(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; + } + } + isc_buffer_putstr(buffer, "; "); + } + isc_buffer_putstr(buffer, "}; "); + if (!entry->opts.in_memory) { + isc_buffer_putstr(buffer, "file \""); + result = dns_catz_generate_masterfilename(zone, 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); +} + +void +dns_catz_update_taskaction(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_catz_zone_t *zone; + (void)task; + + REQUIRE(event != NULL); + zone = event->ev_arg; + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + + LOCK(&zone->catzs->lock); + zone->updatepending = false; + dns_catz_update_from_db(zone->db, zone->catzs); + result = isc_timer_reset(zone->updatetimer, isc_timertype_inactive, + NULL, NULL, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_event_free(&event); + result = isc_time_now(&zone->lastupdated); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + UNLOCK(&zone->catzs->lock); +} + +isc_result_t +dns_catz_dbupdate_callback(dns_db_t *db, void *fn_arg) { + dns_catz_zones_t *catzs; + dns_catz_zone_t *zone = NULL; + isc_time_t now; + uint64_t tdiff; + isc_result_t result = ISC_R_SUCCESS; + isc_region_t r; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CATZ_ZONES_VALID(fn_arg)); + catzs = (dns_catz_zones_t *)fn_arg; + + dns_name_toregion(&db->origin, &r); + + LOCK(&catzs->lock); + result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&zone); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* New zone came as AXFR */ + if (zone->db != NULL && zone->db != db) { + if (zone->dbversion != NULL) { + dns_db_closeversion(zone->db, &zone->dbversion, false); + } + dns_db_updatenotify_unregister( + zone->db, dns_catz_dbupdate_callback, zone->catzs); + dns_db_detach(&zone->db); + /* + * We're not registering db update callback, it will be + * registered at the end of update_from_db + */ + zone->db_registered = false; + } + if (zone->db == NULL) { + dns_db_attach(db, &zone->db); + } + + if (!zone->updatepending) { + zone->updatepending = true; + isc_time_now(&now); + tdiff = isc_time_microdiff(&now, &zone->lastupdated) / 1000000; + if (tdiff < zone->defoptions.min_update_interval) { + isc_interval_t interval; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_INFO, + "catz: new zone version came too soon, " + "deferring update"); + isc_interval_set(&interval, + zone->defoptions.min_update_interval - + (unsigned int)tdiff, + 0); + dns_db_currentversion(db, &zone->dbversion); + result = isc_timer_reset(zone->updatetimer, + isc_timertype_once, NULL, + &interval, true); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + isc_event_t *event; + + dns_db_currentversion(db, &zone->dbversion); + ISC_EVENT_INIT(&zone->updateevent, + sizeof(zone->updateevent), 0, NULL, + DNS_EVENT_CATZUPDATED, + dns_catz_update_taskaction, zone, zone, + NULL, NULL); + event = &zone->updateevent; + isc_task_send(catzs->updater, &event); + } + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "catz: update already queued"); + if (zone->dbversion != NULL) { + dns_db_closeversion(zone->db, &zone->dbversion, false); + } + dns_db_currentversion(zone->db, &zone->dbversion); + } + +cleanup: + UNLOCK(&catzs->lock); + + return (result); +} + +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); +} + +void +dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs) { + dns_catz_zone_t *oldzone = NULL, *newzone = NULL; + isc_result_t result; + isc_region_t r; + dns_dbnode_t *node = NULL; + dns_dbiterator_t *it = NULL; + dns_fixedname_t fixname; + dns_name_t *name; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + char bname[DNS_NAME_FORMATSIZE]; + isc_buffer_t ibname; + uint32_t vers; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(DNS_CATZ_ZONES_VALID(catzs)); + + /* + * Create a new catz in the same context as current catz. + */ + dns_name_toregion(&db->origin, &r); + result = isc_ht_find(catzs->zones, r.base, r.length, (void **)&oldzone); + 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); + return; + } + + if (!oldzone->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); + return; + } + + isc_buffer_init(&ibname, bname, DNS_NAME_FORMATSIZE); + result = dns_name_totext(&db->origin, true, &ibname); + INSIST(result == ISC_R_SUCCESS); + + result = dns_db_getsoaserial(db, oldzone->dbversion, &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)); + return; + } + + 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, &newzone, &db->origin); + if (result != ISC_R_SUCCESS) { + dns_db_closeversion(db, &oldzone->dbversion, false); + 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)); + return; + } + + result = dns_db_createiterator(db, DNS_DB_NONSEC3, &it); + if (result != ISC_R_SUCCESS) { + dns_catz_zone_detach(&newzone); + dns_db_closeversion(db, &oldzone->dbversion, false); + 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)); + return; + } + + name = dns_fixedname_initname(&fixname); + + /* + * Iterate over database to fill the new zone. + */ + result = dns_dbiterator_first(it); + 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)); + } + + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(it, &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_db_allrdatasets(db, node, oldzone->dbversion, 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(db, &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; + } + + result = dns_catz_update_process(catzs, newzone, name, + &rdataset); + if (result != ISC_R_SUCCESS) { + char cname[DNS_NAME_FORMATSIZE]; + 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: unknown 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(db, &node); + result = dns_dbiterator_next(it); + } + + dns_dbiterator_destroy(&it); + dns_db_closeversion(db, &oldzone->dbversion, false); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: update_from_db: iteration finished"); + + /* + * Finally merge new zone into old zone. + */ + result = dns_catz_zones_merge(oldzone, newzone); + dns_catz_zone_detach(&newzone); + 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)); + + return; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: update_from_db: new zone merged"); + + /* + * 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 (!oldzone->db_registered) { + result = dns_db_updatenotify_register( + db, dns_catz_dbupdate_callback, oldzone->catzs); + if (result == ISC_R_SUCCESS) { + oldzone->db_registered = true; + } + } +} + +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 *zone = NULL; + isc_ht_iter_current(iter, (void **)&zone); + zone->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 *newzone = 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 *zone = NULL; + + isc_ht_iter_current(iter, (void **)&zone); + if (!zone->active) { + char cname[DNS_NAME_FORMATSIZE]; + dns_name_format(&zone->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, &newzone, + &zone->name); + INSIST(result == ISC_R_SUCCESS); + dns_catz_zones_merge(zone, newzone); + dns_catz_zone_detach(&newzone); + + /* Make sure that we have an empty catalog zone. */ + INSIST(isc_ht_count(zone->entries) == 0); + result = isc_ht_iter_delcurrent_next(iter); + dns_catz_zone_detach(&zone); + } 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..9cfc810 --- /dev/null +++ b/lib/dns/client.c @@ -0,0 +1,1353 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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_socketmgr_t *socketmgr; + 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); + +/* + * 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, + isc_socketmgr_t *socketmgr, isc_taskmgr_t *taskmgr, + bool is_shared, dns_dispatch_t **dispp, + const isc_sockaddr_t *localaddr) { + unsigned int attrs, attrmask; + dns_dispatch_t *disp; + unsigned buffersize, maxbuffers, maxrequests, buckets, increment; + isc_result_t result; + isc_sockaddr_t anyaddr; + + attrs = 0; + attrs |= DNS_DISPATCHATTR_UDP; + switch (family) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + break; + default: + UNREACHABLE(); + } + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + + if (localaddr == NULL) { + isc_sockaddr_anyofpf(&anyaddr, family); + localaddr = &anyaddr; + } + + buffersize = 4096; + maxbuffers = is_shared ? 1000 : 8; + maxrequests = 32768; + buckets = is_shared ? 16411 : 3; + increment = is_shared ? 16433 : 5; + + disp = NULL; + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, localaddr, + buffersize, maxbuffers, maxrequests, + buckets, increment, attrs, attrmask, + &disp); + if (result == ISC_R_SUCCESS) { + *dispp = disp; + } + + return (result); +} + +static isc_result_t +createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, unsigned int options, + isc_taskmgr_t *taskmgr, unsigned int ntasks, + isc_socketmgr_t *socketmgr, 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; + const char *dbtype; + + 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, socketmgr, + timermgr, 0, dispatchmgr, dispatchv4, + dispatchv6); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + /* + * Set cache DB. + * XXX: it may be better if specific DB implementations can be + * specified via some configuration knob. + */ + if ((options & DNS_CLIENTCREATEOPT_USECACHE) != 0) { + dbtype = "rbt"; + } else { + dbtype = "ecdb"; + } + result = dns_db_create(mctx, dbtype, 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_socketmgr_t *socketmgr, 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_dispatchmgr_t *dispatchmgr = 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(socketmgr != NULL); + REQUIRE(clientp != NULL && *clientp == NULL); + + client = isc_mem_get(mctx, sizeof(*client)); + + isc_mutex_init(&client->lock); + + client->actx = actx; + client->taskmgr = taskmgr; + client->socketmgr = socketmgr; + client->timermgr = timermgr; + + client->task = NULL; + result = isc_task_create(client->taskmgr, 0, &client->task); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + result = dns_dispatchmgr_create(mctx, &dispatchmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + client->dispatchmgr = dispatchmgr; + (void)setsourceports(mctx, 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, dispatchmgr, socketmgr, + taskmgr, true, &dispatchv4, localaddr4); + if (result == ISC_R_SUCCESS) { + client->dispatchv4 = dispatchv4; + } + } + + client->dispatchv6 = NULL; + if (localaddr6 != NULL || localaddr4 == NULL) { + result = getudpdispatch(AF_INET6, dispatchmgr, socketmgr, + taskmgr, true, &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, options, taskmgr, + RESOLVER_NTASKS, socketmgr, timermgr, 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); + + client->mctx = NULL; + isc_mem_attach(mctx, &client->mctx); + + client->find_timeout = DEF_FIND_TIMEOUT; + client->find_udpretries = DEF_FIND_UDPRETRIES; + client->attributes = 0; + + 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_destroy(&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; + + 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_destroy(&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_destroy(dns_client_t **clientp) { + dns_client_t *client; + + REQUIRE(clientp != NULL); + client = *clientp; + *clientp = NULL; + REQUIRE(DNS_CLIENT_VALID(client)); + + 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, *prefix; + dns_fixedname_t foundname, fixed; + dns_rdataset_t *trdataset; + 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 = dns_fixedname_name(&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_copynf(&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; + 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); + } + + dns_client_destroyrestrans(&resarg->trans); + isc_event_free(&event); + + 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, resarg->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(resarg->client->mctx, resarg, sizeof(*resarg)); + } +} + +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; + + 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; + dns_client_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_copynf(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); +} + +void +dns_client_cancelresolve(dns_clientrestrans_t *trans) { + resctx_t *rctx; + + 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)); + } +} + +void +dns_client_destroyrestrans(dns_clientrestrans_t **transp) { + resctx_t *rctx; + isc_mem_t *mctx; + dns_client_t *client; + + 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)); + + dns_client_destroy(&client); +} + +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)); + +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..fe81d59 --- /dev/null +++ b/lib/dns/clientinfo.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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, dns_ecs_t *ecs, + void *versionp) { + ci->version = DNS_CLIENTINFO_VERSION; + ci->data = data; + ci->dbversion = versionp; + 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..e8e8954 --- /dev/null +++ b/lib/dns/compress.c @@ -0,0 +1,584 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 (ISC_UNLIKELY((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 (ISC_LIKELY((cctx->allowed & DNS_COMPRESS_CASESENSITIVE) != + 0)) + { + for (node = cctx->table[i]; node != NULL; + node = node->next) + { + if (ISC_UNLIKELY(node->name.length != length)) { + continue; + } + + if (ISC_LIKELY(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 (ISC_UNLIKELY(node->name.length != length)) { + continue; + } + + l = labels - n; + if (ISC_UNLIKELY(node->name.labels != l)) { + continue; + } + + label1 = node->name.ndata; + label2 = p; + while (ISC_LIKELY(l-- > 0)) { + count = *label1++; + if (count != *label2++) { + goto cont1; + } + + /* no bitstring support */ + INSIST(count <= 63); + + /* Loop unrolled for performance */ + while (ISC_LIKELY(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 (ISC_LIKELY(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 (ISC_UNLIKELY((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 (ISC_UNLIKELY((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..41dbaa3 --- /dev/null +++ b/lib/dns/db.c @@ -0,0 +1,1139 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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" + +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_serialize(dns_db_t *db, dns_dbversion_t *version, FILE *file) { + REQUIRE(DNS_DB_VALID(db)); + if (db->methods->serialize == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + return ((db->methods->serialize)(db, version, file)); +} + +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) { + REQUIRE(DNS_DB_VALID(db)); + + return ((db->methods->nodecount)(db)); +} + +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)); +} + +isc_result_t +dns_db_adjusthashsize(dns_db_t *db, size_t size) { + REQUIRE(DNS_DB_VALID(db)); + + if (db->methods->adjusthashsize != NULL) { + return ((db->methods->adjusthashsize)(db, size)); + } + + return (ISC_R_NOTIMPLEMENTED); +} + +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/dbtable.c b/lib/dns/dbtable.c new file mode 100644 index 0000000..e36594b --- /dev/null +++ b/lib/dns/dbtable.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +struct dns_dbtable { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + dns_rdataclass_t rdclass; + isc_rwlock_t tree_lock; + /* Protected by atomics */ + isc_refcount_t references; + /* Locked by tree_lock. */ + dns_rbt_t *rbt; + dns_db_t *default_db; +}; + +#define DBTABLE_MAGIC ISC_MAGIC('D', 'B', '-', '-') +#define VALID_DBTABLE(dbtable) ISC_MAGIC_VALID(dbtable, DBTABLE_MAGIC) + +static void +dbdetach(void *data, void *arg) { + dns_db_t *db = data; + + UNUSED(arg); + + dns_db_detach(&db); +} + +isc_result_t +dns_dbtable_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + dns_dbtable_t **dbtablep) { + dns_dbtable_t *dbtable; + isc_result_t result; + + REQUIRE(mctx != NULL); + REQUIRE(dbtablep != NULL && *dbtablep == NULL); + + dbtable = isc_mem_get(mctx, sizeof(*dbtable)); + + dbtable->rbt = NULL; + result = dns_rbt_create(mctx, dbdetach, NULL, &dbtable->rbt); + if (result != ISC_R_SUCCESS) { + goto clean1; + } + + isc_rwlock_init(&dbtable->tree_lock, 0, 0); + dbtable->default_db = NULL; + dbtable->mctx = NULL; + isc_mem_attach(mctx, &dbtable->mctx); + dbtable->rdclass = rdclass; + dbtable->magic = DBTABLE_MAGIC; + isc_refcount_init(&dbtable->references, 1); + + *dbtablep = dbtable; + + return (ISC_R_SUCCESS); + +clean1: + isc_mem_putanddetach(&mctx, dbtable, sizeof(*dbtable)); + + return (result); +} + +static void +dbtable_free(dns_dbtable_t *dbtable) { + /* + * Caller must ensure that it is safe to call. + */ + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + if (dbtable->default_db != NULL) { + dns_db_detach(&dbtable->default_db); + } + + dns_rbt_destroy(&dbtable->rbt); + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + isc_rwlock_destroy(&dbtable->tree_lock); + + dbtable->magic = 0; + + isc_mem_putanddetach(&dbtable->mctx, dbtable, sizeof(*dbtable)); +} + +void +dns_dbtable_attach(dns_dbtable_t *source, dns_dbtable_t **targetp) { + REQUIRE(VALID_DBTABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_dbtable_detach(dns_dbtable_t **dbtablep) { + dns_dbtable_t *dbtable; + + REQUIRE(dbtablep != NULL); + dbtable = *dbtablep; + *dbtablep = NULL; + REQUIRE(VALID_DBTABLE(dbtable)); + + if (isc_refcount_decrement(&dbtable->references) == 1) { + dbtable_free(dbtable); + } +} + +isc_result_t +dns_dbtable_add(dns_dbtable_t *dbtable, dns_db_t *db) { + isc_result_t result; + dns_db_t *dbclone; + + REQUIRE(VALID_DBTABLE(dbtable)); + REQUIRE(dns_db_class(db) == dbtable->rdclass); + + dbclone = NULL; + dns_db_attach(db, &dbclone); + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + result = dns_rbt_addname(dbtable->rbt, dns_db_origin(dbclone), dbclone); + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + return (result); +} + +void +dns_dbtable_remove(dns_dbtable_t *dbtable, dns_db_t *db) { + dns_db_t *stored_data = NULL; + isc_result_t result; + dns_name_t *name; + + REQUIRE(VALID_DBTABLE(dbtable)); + + name = dns_db_origin(db); + + /* + * There is a requirement that the association of name with db + * be verified. With the current rbt.c this is expensive to do, + * because effectively two find operations are being done, but + * deletion is relatively infrequent. + * XXXDCL ... this could be cheaper now with dns_rbt_deletenode. + */ + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + result = dns_rbt_findname(dbtable->rbt, name, 0, NULL, + (void **)(void *)&stored_data); + + if (result == ISC_R_SUCCESS) { + INSIST(stored_data == db); + + (void)dns_rbt_deletename(dbtable->rbt, name, false); + } + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write); +} + +void +dns_dbtable_adddefault(dns_dbtable_t *dbtable, dns_db_t *db) { + REQUIRE(VALID_DBTABLE(dbtable)); + REQUIRE(dbtable->default_db == NULL); + REQUIRE(dns_name_compare(dns_db_origin(db), dns_rootname) == 0); + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + dbtable->default_db = NULL; + dns_db_attach(db, &dbtable->default_db); + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write); +} + +void +dns_dbtable_getdefault(dns_dbtable_t *dbtable, dns_db_t **dbp) { + REQUIRE(VALID_DBTABLE(dbtable)); + REQUIRE(dbp != NULL && *dbp == NULL); + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read); + + dns_db_attach(dbtable->default_db, dbp); + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read); +} + +void +dns_dbtable_removedefault(dns_dbtable_t *dbtable) { + REQUIRE(VALID_DBTABLE(dbtable)); + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write); + + dns_db_detach(&dbtable->default_db); + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write); +} + +isc_result_t +dns_dbtable_find(dns_dbtable_t *dbtable, const dns_name_t *name, + unsigned int options, dns_db_t **dbp) { + dns_db_t *stored_data = NULL; + isc_result_t result; + unsigned int rbtoptions = 0; + + REQUIRE(dbp != NULL && *dbp == NULL); + + if ((options & DNS_DBTABLEFIND_NOEXACT) != 0) { + rbtoptions |= DNS_RBTFIND_NOEXACT; + } + + RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read); + + result = dns_rbt_findname(dbtable->rbt, name, rbtoptions, NULL, + (void **)(void *)&stored_data); + + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + dns_db_attach(stored_data, dbp); + } else if (dbtable->default_db != NULL) { + dns_db_attach(dbtable->default_db, dbp); + result = DNS_R_PARTIALMATCH; + } else { + result = ISC_R_NOTFOUND; + } + + RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read); + + return (result); +} diff --git a/lib/dns/diff.c b/lib/dns/diff.c new file mode 100644 index 0000000..200d711 --- /dev/null +++ b/lib/dns/diff.c @@ -0,0 +1,688 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "diff_tuple_tordataset failed: %s", + dns_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..5f27d63 --- /dev/null +++ b/lib/dns/dispatch.c @@ -0,0 +1,3591 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +typedef ISC_LIST(dns_dispentry_t) dns_displist_t; + +typedef struct dispsocket dispsocket_t; +typedef ISC_LIST(dispsocket_t) dispsocketlist_t; + +typedef struct dispportentry dispportentry_t; +typedef ISC_LIST(dispportentry_t) dispportlist_t; + +typedef struct dns_qid { + unsigned int magic; + unsigned int qid_nbuckets; /*%< hash table size */ + unsigned int qid_increment; /*%< id increment on collision */ + isc_mutex_t lock; + dns_displist_t *qid_table; /*%< the table itself */ + dispsocketlist_t *sock_table; /*%< socket table */ +} dns_qid_t; + +struct dns_dispatchmgr { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + dns_acl_t *blackhole; + dns_portlist_t *portlist; + isc_stats_t *stats; + + /* Locked by "lock". */ + isc_mutex_t lock; + unsigned int state; + ISC_LIST(dns_dispatch_t) list; + + /* locked by buffer_lock */ + dns_qid_t *qid; + isc_mutex_t buffer_lock; + unsigned int buffers; /*%< allocated buffers */ + unsigned int buffersize; /*%< size of each buffer */ + unsigned int maxbuffers; /*%< max buffers */ + + isc_refcount_t irefs; + + /*% + * Locked by qid->lock if qid exists; otherwise, can be used without + * being locked. + * Memory footprint considerations: this is a simple implementation of + * available ports, i.e., an ordered array of the actual port numbers. + * This will require about 256KB of memory in the worst case (128KB for + * each of IPv4 and IPv6). We could reduce it by representing it as a + * more sophisticated way such as a list (or array) of ranges that are + * searched to identify a specific port. Our decision here is the saved + * memory isn't worth the implementation complexity, considering the + * fact that the whole BIND9 process (which is mainly named) already + * requires a pretty large memory footprint. We may, however, have to + * revisit the decision when we want to use it as a separate module for + * an environment where memory requirement is severer. + */ + 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 */ +}; + +#define MGR_SHUTTINGDOWN 0x00000001U +#define MGR_IS_SHUTTINGDOWN(l) (((l)->state & MGR_SHUTTINGDOWN) != 0) + +#define IS_PRIVATE(d) (((d)->attributes & DNS_DISPATCHATTR_PRIVATE) != 0) + +struct dns_dispentry { + unsigned int magic; + dns_dispatch_t *disp; + dns_messageid_t id; + in_port_t port; + unsigned int bucket; + isc_sockaddr_t host; + isc_task_t *task; + isc_taskaction_t action; + void *arg; + bool item_out; + dispsocket_t *dispsocket; + ISC_LIST(dns_dispatchevent_t) items; + ISC_LINK(dns_dispentry_t) link; +}; + +/*% + * Maximum number of dispatch sockets that can be pooled for reuse. The + * appropriate value may vary, but experiments have shown a busy caching server + * may need more than 1000 sockets concurrently opened. The maximum allowable + * number of dispatch sockets (per manager) will be set to the double of this + * value. + */ +#ifndef DNS_DISPATCH_POOLSOCKS +#define DNS_DISPATCH_POOLSOCKS 2048 +#endif /* ifndef DNS_DISPATCH_POOLSOCKS */ + +/*% + * Quota to control the number of dispatch sockets. If a dispatch has more + * than the quota of sockets, new queries will purge oldest ones, so that + * a massive number of outstanding queries won't prevent subsequent queries + * (especially if the older ones take longer time and result in timeout). + */ +#ifndef DNS_DISPATCH_SOCKSQUOTA +#define DNS_DISPATCH_SOCKSQUOTA 3072 +#endif /* ifndef DNS_DISPATCH_SOCKSQUOTA */ + +struct dispsocket { + unsigned int magic; + isc_socket_t *socket; + dns_dispatch_t *disp; + isc_sockaddr_t host; + in_port_t localport; /* XXX: should be removed later */ + dispportentry_t *portentry; + dns_dispentry_t *resp; + isc_task_t *task; + ISC_LINK(dispsocket_t) link; + unsigned int bucket; + ISC_LINK(dispsocket_t) blink; +}; + +/*% + * A port table entry. We remember every port we first open in a table with a + * reference counter so that we can 'reuse' the same port (with different + * destination addresses) using the SO_REUSEADDR socket option. + */ +struct dispportentry { + in_port_t port; + isc_refcount_t refs; + ISC_LINK(struct dispportentry) link; +}; + +#ifndef DNS_DISPATCH_PORTTABLESIZE +#define DNS_DISPATCH_PORTTABLESIZE 1024 +#endif /* ifndef DNS_DISPATCH_PORTTABLESIZE */ + +#define INVALID_BUCKET (0xffffdead) + +/*% + * Number of tasks for each dispatch that use separate sockets for different + * transactions. This must be a power of 2 as it will divide 32 bit numbers + * to get an uniformly random tasks selection. See get_dispsocket(). + */ +#define MAX_INTERNAL_TASKS 64 + +struct dns_dispatch { + /* Unlocked. */ + unsigned int magic; /*%< magic */ + dns_dispatchmgr_t *mgr; /*%< dispatch manager */ + int ntasks; + /*% + * internal task buckets. We use multiple tasks to distribute various + * socket events well when using separate dispatch sockets. We use the + * 1st task (task[0]) for internal control events. + */ + isc_task_t *task[MAX_INTERNAL_TASKS]; + isc_socket_t *socket; /*%< isc socket attached to */ + isc_sockaddr_t local; /*%< local address */ + in_port_t localport; /*%< local UDP port */ + isc_sockaddr_t peer; /*%< peer address (TCP) */ + isc_dscp_t dscp; /*%< "listen-on" DSCP value */ + unsigned int maxrequests; /*%< max requests */ + isc_event_t *ctlevent; + + isc_mem_t *sepool; /*%< pool for socket events */ + + /*% Locked by mgr->lock. */ + ISC_LINK(dns_dispatch_t) link; + + /* Locked by "lock". */ + isc_mutex_t lock; /*%< locks all below */ + isc_sockettype_t socktype; + unsigned int attributes; + unsigned int refcount; /*%< number of users */ + dns_dispatchevent_t *failsafe_ev; /*%< failsafe cancel event */ + unsigned int shutting_down : 1, shutdown_out : 1, connected : 1, + tcpmsg_valid : 1, recv_pending : 1; /*%< is a + * recv() + * pending? + * */ + isc_result_t shutdown_why; + ISC_LIST(dispsocket_t) activesockets; + ISC_LIST(dispsocket_t) inactivesockets; + unsigned int nsockets; + unsigned int requests; /*%< how many requests we have */ + unsigned int tcpbuffers; /*%< allocated buffers */ + dns_tcpmsg_t tcpmsg; /*%< for tcp streams */ + dns_qid_t *qid; + dispportlist_t *port_table; /*%< hold ports 'owned' by us */ +}; + +#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) + +#define DNS_QID(disp) \ + ((disp)->socktype == isc_sockettype_tcp) ? (disp)->qid \ + : (disp)->mgr->qid + +/*% + * Locking a query port buffer is a bit tricky. We access the buffer without + * locking until qid is created. Technically, there is a possibility of race + * between the creation of qid and access to the port buffer; in practice, + * however, this should be safe because qid isn't created until the first + * dispatch is created and there should be no contending situation until then. + */ +#define PORTBUFLOCK(mgr) \ + if ((mgr)->qid != NULL) \ + LOCK(&((mgr)->qid->lock)) +#define PORTBUFUNLOCK(mgr) \ + if ((mgr)->qid != NULL) \ + UNLOCK((&(mgr)->qid->lock)) + +/* + * Statics. + */ +static dns_dispentry_t * +entry_search(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t, + unsigned int); +static bool +destroy_disp_ok(dns_dispatch_t *); +static void +destroy_disp(isc_task_t *task, isc_event_t *event); +static void +destroy_dispsocket(dns_dispatch_t *, dispsocket_t **); +static void +deactivate_dispsocket(dns_dispatch_t *, dispsocket_t *); +static void +udp_exrecv(isc_task_t *, isc_event_t *); +static void +udp_shrecv(isc_task_t *, isc_event_t *); +static void +udp_recv(isc_event_t *, dns_dispatch_t *, dispsocket_t *); +static void +tcp_recv(isc_task_t *, isc_event_t *); +static isc_result_t +startrecv(dns_dispatch_t *, dispsocket_t *); +static uint32_t +dns_hash(dns_qid_t *, const isc_sockaddr_t *, dns_messageid_t, in_port_t); +static void +free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len); +static void * +allocate_udp_buffer(dns_dispatch_t *disp); +static void +free_devent(dns_dispatch_t *disp, dns_dispatchevent_t *ev); +static dns_dispatchevent_t * +allocate_devent(dns_dispatch_t *disp); +static void +do_cancel(dns_dispatch_t *disp); +static dns_dispentry_t * +linear_first(dns_qid_t *disp); +static dns_dispentry_t * +linear_next(dns_qid_t *disp, dns_dispentry_t *resp); +static void +dispatch_free(dns_dispatch_t **dispp); +static isc_result_t +get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp, + isc_socketmgr_t *sockmgr, const isc_sockaddr_t *localaddr, + isc_socket_t **sockp, isc_socket_t *dup_socket, bool duponly); +static isc_result_t +dispatch_createudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int maxrequests, unsigned int attributes, + dns_dispatch_t **dispp, isc_socket_t *dup_socket); +static bool +destroy_mgr_ok(dns_dispatchmgr_t *mgr); +static void +destroy_mgr(dns_dispatchmgr_t **mgrp); +static isc_result_t +qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets, + unsigned int increment, dns_qid_t **qidp, bool needaddrtable); +static void +qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp); +static isc_result_t +open_socket(isc_socketmgr_t *mgr, const isc_sockaddr_t *local, + unsigned int options, isc_socket_t **sockp, + isc_socket_t *dup_socket, bool duponly); +static bool +portavailable(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_sockaddr_t *sockaddrp); + +#define LVL(x) ISC_LOG_DEBUG(x) + +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; + + 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, "dispatch %p: %s", disp, + msgbuf); +} + +static void +request_log(dns_dispatch_t *disp, dns_dispentry_t *resp, int level, + const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5); + +static void +request_log(dns_dispatch_t *disp, dns_dispentry_t *resp, int level, + const char *fmt, ...) { + char msgbuf[2048]; + char peerbuf[256]; + va_list ap; + + if (!isc_log_wouldlog(dns_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + if (VALID_RESPONSE(resp)) { + isc_sockaddr_format(&resp->host, peerbuf, sizeof(peerbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH, + DNS_LOGMODULE_DISPATCH, level, + "dispatch %p response %p %s: %s", disp, resp, + peerbuf, msgbuf); + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH, + DNS_LOGMODULE_DISPATCH, level, + "dispatch %p req/resp %p: %s", disp, 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); +} + +/* + * Find the first entry in 'qid'. Returns NULL if there are no entries. + */ +static dns_dispentry_t * +linear_first(dns_qid_t *qid) { + dns_dispentry_t *ret; + unsigned int bucket; + + bucket = 0; + + while (bucket < qid->qid_nbuckets) { + ret = ISC_LIST_HEAD(qid->qid_table[bucket]); + if (ret != NULL) { + return (ret); + } + bucket++; + } + + return (NULL); +} + +/* + * Find the next entry after 'resp' in 'qid'. Return NULL if there are + * no more entries. + */ +static dns_dispentry_t * +linear_next(dns_qid_t *qid, dns_dispentry_t *resp) { + dns_dispentry_t *ret; + unsigned int bucket; + + ret = ISC_LIST_NEXT(resp, link); + if (ret != NULL) { + return (ret); + } + + bucket = resp->bucket; + bucket++; + while (bucket < qid->qid_nbuckets) { + ret = ISC_LIST_HEAD(qid->qid_table[bucket]); + if (ret != NULL) { + return (ret); + } + bucket++; + } + + return (NULL); +} + +/* + * The dispatch must be locked. + */ +static bool +destroy_disp_ok(dns_dispatch_t *disp) { + if (disp->refcount != 0) { + return (false); + } + + if (disp->recv_pending != 0) { + return (false); + } + + if (!ISC_LIST_EMPTY(disp->activesockets)) { + return (false); + } + + if (disp->shutting_down == 0) { + return (false); + } + + return (true); +} + +/* + * Called when refcount reaches 0 (and safe to destroy). + * + * The dispatcher must be locked. + * The manager must not be locked. + */ +static void +destroy_disp(isc_task_t *task, isc_event_t *event) { + dns_dispatch_t *disp; + dns_dispatchmgr_t *mgr; + bool killmgr; + dispsocket_t *dispsocket; + int i; + + INSIST(event->ev_type == DNS_EVENT_DISPATCHCONTROL); + + UNUSED(task); + + disp = event->ev_arg; + mgr = disp->mgr; + + LOCK(&mgr->lock); + ISC_LIST_UNLINK(mgr->list, disp, link); + + dispatch_log(disp, LVL(90), + "shutting down; detaching from sock %p, task %p", + disp->socket, disp->task[0]); /* XXXX */ + + if (disp->sepool != NULL) { + isc_mem_destroy(&disp->sepool); + } + + if (disp->socket != NULL) { + isc_socket_detach(&disp->socket); + } + while ((dispsocket = ISC_LIST_HEAD(disp->inactivesockets)) != NULL) { + ISC_LIST_UNLINK(disp->inactivesockets, dispsocket, link); + destroy_dispsocket(disp, &dispsocket); + } + for (i = 0; i < disp->ntasks; i++) { + isc_task_detach(&disp->task[i]); + } + isc_event_free(&event); + + dispatch_free(&disp); + + killmgr = destroy_mgr_ok(mgr); + UNLOCK(&mgr->lock); + if (killmgr) { + destroy_mgr(&mgr); + } +} + +/*% + * Manipulate port table per dispatch: find an entry for a given port number, + * create a new entry, and decrement a given entry with possible clean-up. + */ +static dispportentry_t * +port_search(dns_dispatch_t *disp, in_port_t port) { + dispportentry_t *portentry; + + REQUIRE(disp->port_table != NULL); + + portentry = ISC_LIST_HEAD( + disp->port_table[port % DNS_DISPATCH_PORTTABLESIZE]); + while (portentry != NULL) { + if (portentry->port == port) { + return (portentry); + } + portentry = ISC_LIST_NEXT(portentry, link); + } + + return (NULL); +} + +static dispportentry_t * +new_portentry(dns_dispatch_t *disp, in_port_t port) { + dispportentry_t *portentry; + dns_qid_t *qid; + + REQUIRE(disp->port_table != NULL); + + portentry = isc_mem_get(disp->mgr->mctx, sizeof(*portentry)); + + portentry->port = port; + isc_refcount_init(&portentry->refs, 1); + ISC_LINK_INIT(portentry, link); + qid = DNS_QID(disp); + LOCK(&qid->lock); + ISC_LIST_APPEND(disp->port_table[port % DNS_DISPATCH_PORTTABLESIZE], + portentry, link); + UNLOCK(&qid->lock); + + return (portentry); +} + +/*% + * The caller must hold the qid->lock. + */ +static void +deref_portentry(dns_dispatch_t *disp, dispportentry_t **portentryp) { + dispportentry_t *portentry = *portentryp; + *portentryp = NULL; + + REQUIRE(disp->port_table != NULL); + REQUIRE(portentry != NULL); + + if (isc_refcount_decrement(&portentry->refs) == 1) { + ISC_LIST_UNLINK(disp->port_table[portentry->port % + DNS_DISPATCH_PORTTABLESIZE], + portentry, link); + isc_mem_put(disp->mgr->mctx, portentry, sizeof(*portentry)); + } +} + +/*% + * Find a dispsocket for socket address 'dest', and port number 'port'. + * Return NULL if no such entry exists. Requires qid->lock to be held. + */ +static dispsocket_t * +socket_search(dns_qid_t *qid, const isc_sockaddr_t *dest, in_port_t port, + unsigned int bucket) { + dispsocket_t *dispsock; + + REQUIRE(VALID_QID(qid)); + REQUIRE(bucket < qid->qid_nbuckets); + + dispsock = ISC_LIST_HEAD(qid->sock_table[bucket]); + + while (dispsock != NULL) { + if (dispsock->portentry != NULL && + dispsock->portentry->port == port && + isc_sockaddr_equal(dest, &dispsock->host)) + { + return (dispsock); + } + dispsock = ISC_LIST_NEXT(dispsock, blink); + } + + return (NULL); +} + +/*% + * Make a new socket for a single dispatch with a random port number. + * The caller must hold the disp->lock + */ +static isc_result_t +get_dispsocket(dns_dispatch_t *disp, const isc_sockaddr_t *dest, + isc_socketmgr_t *sockmgr, dispsocket_t **dispsockp, + in_port_t *portp) { + int i; + dns_dispatchmgr_t *mgr = disp->mgr; + isc_socket_t *sock = NULL; + isc_result_t result = ISC_R_FAILURE; + in_port_t port; + isc_sockaddr_t localaddr; + unsigned int bucket = 0; + dispsocket_t *dispsock; + unsigned int nports; + in_port_t *ports; + isc_socket_options_t bindoptions; + dispportentry_t *portentry = NULL; + dns_qid_t *qid; + + if (isc_sockaddr_pf(&disp->local) == AF_INET) { + nports = disp->mgr->nv4ports; + ports = disp->mgr->v4ports; + } else { + nports = disp->mgr->nv6ports; + ports = disp->mgr->v6ports; + } + if (nports == 0) { + return (ISC_R_ADDRNOTAVAIL); + } + + dispsock = ISC_LIST_HEAD(disp->inactivesockets); + if (dispsock != NULL) { + ISC_LIST_UNLINK(disp->inactivesockets, dispsock, link); + sock = dispsock->socket; + dispsock->socket = NULL; + } else { + dispsock = isc_mem_get(mgr->mctx, sizeof(*dispsock)); + + disp->nsockets++; + dispsock->socket = NULL; + dispsock->disp = disp; + dispsock->resp = NULL; + dispsock->portentry = NULL; + dispsock->task = NULL; + isc_task_attach(disp->task[isc_random_uniform(disp->ntasks)], + &dispsock->task); + ISC_LINK_INIT(dispsock, link); + ISC_LINK_INIT(dispsock, blink); + dispsock->magic = DISPSOCK_MAGIC; + } + + /* + * Pick up a random UDP port and open a new socket with it. Avoid + * choosing ports that share the same destination because it will be + * very likely to fail in bind(2) or connect(2). + */ + localaddr = disp->local; + qid = DNS_QID(disp); + + for (i = 0; i < 64; i++) { + port = ports[isc_random_uniform(nports)]; + isc_sockaddr_setport(&localaddr, port); + + LOCK(&qid->lock); + bucket = dns_hash(qid, dest, 0, port); + if (socket_search(qid, dest, port, bucket) != NULL) { + UNLOCK(&qid->lock); + continue; + } + UNLOCK(&qid->lock); + bindoptions = 0; + portentry = port_search(disp, port); + + if (portentry != NULL) { + bindoptions |= ISC_SOCKET_REUSEADDRESS; + } + result = open_socket(sockmgr, &localaddr, bindoptions, &sock, + NULL, false); + if (result == ISC_R_SUCCESS) { + if (portentry == NULL) { + portentry = new_portentry(disp, port); + if (portentry == NULL) { + result = ISC_R_NOMEMORY; + break; + } + } else { + isc_refcount_increment(&portentry->refs); + } + break; + } else if (result == ISC_R_NOPERM) { + char buf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&localaddr, buf, sizeof(buf)); + dispatch_log(disp, ISC_LOG_WARNING, + "open_socket(%s) -> %s: continuing", buf, + isc_result_totext(result)); + } else if (result != ISC_R_ADDRINUSE) { + break; + } + } + + if (result == ISC_R_SUCCESS) { + dispsock->socket = sock; + dispsock->host = *dest; + dispsock->bucket = bucket; + LOCK(&qid->lock); + dispsock->portentry = portentry; + ISC_LIST_APPEND(qid->sock_table[bucket], dispsock, blink); + UNLOCK(&qid->lock); + *dispsockp = dispsock; + *portp = port; + } else { + /* + * We could keep it in the inactive list, but since this should + * be an exceptional case and might be resource shortage, we'd + * rather destroy it. + */ + if (sock != NULL) { + isc_socket_detach(&sock); + } + destroy_dispsocket(disp, &dispsock); + } + + return (result); +} + +/*% + * Destroy a dedicated dispatch socket. + */ +static void +destroy_dispsocket(dns_dispatch_t *disp, dispsocket_t **dispsockp) { + dispsocket_t *dispsock; + dns_qid_t *qid = DNS_QID(disp); + + /* + * The dispatch must be locked. + */ + + REQUIRE(dispsockp != NULL && *dispsockp != NULL); + dispsock = *dispsockp; + *dispsockp = NULL; + REQUIRE(!ISC_LINK_LINKED(dispsock, link)); + + disp->nsockets--; + dispsock->magic = 0; + if (dispsock->portentry != NULL) { + /* socket_search() tests and dereferences portentry. */ + LOCK(&qid->lock); + deref_portentry(disp, &dispsock->portentry); + UNLOCK(&qid->lock); + } + if (dispsock->socket != NULL) { + isc_socket_detach(&dispsock->socket); + } + if (ISC_LINK_LINKED(dispsock, blink)) { + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock, + blink); + UNLOCK(&qid->lock); + } + if (dispsock->task != NULL) { + isc_task_detach(&dispsock->task); + } + isc_mem_put(disp->mgr->mctx, dispsock, sizeof(*dispsock)); +} + +/*% + * Deactivate a dedicated dispatch socket. Move it to the inactive list for + * future reuse unless the total number of sockets are exceeding the maximum. + */ +static void +deactivate_dispsocket(dns_dispatch_t *disp, dispsocket_t *dispsock) { + isc_result_t result; + dns_qid_t *qid = DNS_QID(disp); + + /* + * The dispatch must be locked. + */ + ISC_LIST_UNLINK(disp->activesockets, dispsock, link); + if (dispsock->resp != NULL) { + INSIST(dispsock->resp->dispsocket == dispsock); + dispsock->resp->dispsocket = NULL; + } + + INSIST(dispsock->portentry != NULL); + /* socket_search() tests and dereferences portentry. */ + LOCK(&qid->lock); + deref_portentry(disp, &dispsock->portentry); + UNLOCK(&qid->lock); + + if (disp->nsockets > DNS_DISPATCH_POOLSOCKS) { + destroy_dispsocket(disp, &dispsock); + } else { + result = isc_socket_close(dispsock->socket); + + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock, + blink); + UNLOCK(&qid->lock); + + if (result == ISC_R_SUCCESS) { + ISC_LIST_APPEND(disp->inactivesockets, dispsock, link); + } else { + /* + * If the underlying system does not allow this + * optimization, destroy this temporary structure (and + * create a new one for a new transaction). + */ + INSIST(result == ISC_R_NOTIMPLEMENTED); + destroy_dispsocket(disp, &dispsock); + } + } +} + +/* + * 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; + + 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->host) && + res->port == port) + { + return (res); + } + res = ISC_LIST_NEXT(res, link); + } + + return (NULL); +} + +static void +free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len) { + unsigned int buffersize; + INSIST(buf != NULL && len != 0); + + switch (disp->socktype) { + case isc_sockettype_tcp: + INSIST(disp->tcpbuffers > 0); + disp->tcpbuffers--; + isc_mem_put(disp->mgr->mctx, buf, len); + break; + case isc_sockettype_udp: + LOCK(&disp->mgr->buffer_lock); + INSIST(disp->mgr->buffers > 0); + INSIST(len == disp->mgr->buffersize); + disp->mgr->buffers--; + buffersize = disp->mgr->buffersize; + UNLOCK(&disp->mgr->buffer_lock); + isc_mem_put(disp->mgr->mctx, buf, buffersize); + break; + default: + UNREACHABLE(); + } +} + +static void * +allocate_udp_buffer(dns_dispatch_t *disp) { + unsigned int buffersize; + + LOCK(&disp->mgr->buffer_lock); + if (disp->mgr->buffers >= disp->mgr->maxbuffers) { + UNLOCK(&disp->mgr->buffer_lock); + return (NULL); + } + buffersize = disp->mgr->buffersize; + disp->mgr->buffers++; + UNLOCK(&disp->mgr->buffer_lock); + + return (isc_mem_get(disp->mgr->mctx, buffersize)); +} + +static void +free_sevent(isc_event_t *ev) { + isc_mem_t *pool = ev->ev_destroy_arg; + isc_socketevent_t *sev = (isc_socketevent_t *)ev; + isc_mem_put(pool, sev, sizeof(*sev)); +} + +static isc_socketevent_t * +allocate_sevent(dns_dispatch_t *disp, isc_socket_t *sock, isc_eventtype_t type, + isc_taskaction_t action, const void *arg) { + isc_socketevent_t *ev; + void *deconst_arg; + + ev = isc_mem_get(disp->sepool, sizeof(*ev)); + DE_CONST(arg, deconst_arg); + ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, type, action, deconst_arg, + sock, free_sevent, disp->sepool); + ev->result = ISC_R_UNSET; + ISC_LINK_INIT(ev, ev_link); + ev->region.base = NULL; + ev->n = 0; + ev->offset = 0; + ev->attributes = 0; + + return (ev); +} + +static void +free_devent(dns_dispatch_t *disp, dns_dispatchevent_t *ev) { + if (disp->failsafe_ev == ev) { + INSIST(disp->shutdown_out == 1); + disp->shutdown_out = 0; + + return; + } + + isc_refcount_decrement(&disp->mgr->irefs); + isc_mem_put(disp->mgr->mctx, ev, sizeof(*ev)); +} + +static dns_dispatchevent_t * +allocate_devent(dns_dispatch_t *disp) { + dns_dispatchevent_t *ev; + + ev = isc_mem_get(disp->mgr->mctx, sizeof(*ev)); + isc_refcount_increment0(&disp->mgr->irefs); + ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, 0, NULL, NULL, NULL, NULL, + NULL); + + return (ev); +} + +static void +udp_exrecv(isc_task_t *task, isc_event_t *ev) { + dispsocket_t *dispsock = ev->ev_arg; + + UNUSED(task); + + REQUIRE(VALID_DISPSOCK(dispsock)); + udp_recv(ev, dispsock->disp, dispsock); +} + +static void +udp_shrecv(isc_task_t *task, isc_event_t *ev) { + dns_dispatch_t *disp = ev->ev_arg; + + UNUSED(task); + + REQUIRE(VALID_DISPATCH(disp)); + udp_recv(ev, disp, NULL); +} + +/* + * 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_event_t *ev_in, dns_dispatch_t *disp, dispsocket_t *dispsock) { + isc_socketevent_t *ev = (isc_socketevent_t *)ev_in; + dns_messageid_t id; + isc_result_t dres; + isc_buffer_t source; + unsigned int flags; + dns_dispentry_t *resp = NULL; + dns_dispatchevent_t *rev; + unsigned int bucket; + bool killit; + bool queue_response; + dns_dispatchmgr_t *mgr; + dns_qid_t *qid; + isc_netaddr_t netaddr; + int match; + int result; + bool qidlocked = false; + + LOCK(&disp->lock); + + mgr = disp->mgr; + qid = mgr->qid; + + LOCK(&disp->mgr->buffer_lock); + dispatch_log(disp, LVL(90), + "got packet: requests %d, buffers %d, recvs %d", + disp->requests, disp->mgr->buffers, disp->recv_pending); + UNLOCK(&disp->mgr->buffer_lock); + + if (dispsock == NULL && ev->ev_type == ISC_SOCKEVENT_RECVDONE) { + /* + * Unless the receive event was imported from a listening + * interface, in which case the event type is + * DNS_EVENT_IMPORTRECVDONE, receive operation must be pending. + */ + INSIST(disp->recv_pending != 0); + disp->recv_pending = 0; + } + + if (dispsock != NULL && + (ev->result == ISC_R_CANCELED || dispsock->resp == NULL)) + { + /* + * dispsock->resp can be NULL if this transaction was canceled + * just after receiving a response. Since this socket is + * exclusively used and there should be at most one receive + * event the canceled event should have been no effect. So + * we can (and should) deactivate the socket right now. + */ + deactivate_dispsocket(disp, dispsock); + dispsock = NULL; + } + + if (disp->shutting_down) { + /* + * This dispatcher is shutting down. + */ + free_buffer(disp, ev->region.base, ev->region.length); + + isc_event_free(&ev_in); + ev = NULL; + + killit = destroy_disp_ok(disp); + UNLOCK(&disp->lock); + if (killit) { + isc_task_send(disp->task[0], &disp->ctlevent); + } + + return; + } + + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + if (dispsock != NULL) { + resp = dispsock->resp; + id = resp->id; + if (ev->result != ISC_R_SUCCESS) { + /* + * This is most likely a network error on a + * connected socket. It makes no sense to + * check the address or parse the packet, but it + * will help to return the error to the caller. + */ + goto sendresponse; + } + } else { + free_buffer(disp, ev->region.base, ev->region.length); + + isc_event_free(&ev_in); + UNLOCK(&disp->lock); + return; + } + } else if (ev->result != ISC_R_SUCCESS) { + free_buffer(disp, ev->region.base, ev->region.length); + + if (ev->result != ISC_R_CANCELED) { + dispatch_log(disp, ISC_LOG_ERROR, + "odd socket result in udp_recv(): %s", + isc_result_totext(ev->result)); + } + + isc_event_free(&ev_in); + UNLOCK(&disp->lock); + return; + } + + /* + * If this is from a blackholed address, drop it. + */ + isc_netaddr_fromsockaddr(&netaddr, &ev->address); + 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)); + dispatch_log(disp, LVL(10), "blackholed packet from %s", + netaddrstr); + } + free_buffer(disp, ev->region.base, ev->region.length); + goto restart; + } + + /* + * Peek into the buffer to see what we can see. + */ + isc_buffer_init(&source, ev->region.base, ev->region.length); + isc_buffer_add(&source, ev->n); + dres = dns_message_peekheader(&source, &id, &flags); + if (dres != ISC_R_SUCCESS) { + free_buffer(disp, ev->region.base, ev->region.length); + dispatch_log(disp, LVL(10), "got garbage packet"); + goto restart; + } + + dispatch_log(disp, LVL(92), + "got valid DNS message header, /QR %c, id %u", + (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id); + + /* + * Look at flags. If query, drop it. If response, + * look to see where it goes. + */ + if ((flags & DNS_MESSAGEFLAG_QR) == 0) { + /* query */ + free_buffer(disp, ev->region.base, ev->region.length); + goto restart; + } + + /* + * Search for the corresponding response. If we are using an exclusive + * socket, we've already identified it and we can skip the search; but + * the ID and the address must match the expected ones. + */ + if (resp == NULL) { + bucket = dns_hash(qid, &ev->address, id, disp->localport); + LOCK(&qid->lock); + qidlocked = true; + resp = entry_search(qid, &ev->address, id, disp->localport, + bucket); + dispatch_log(disp, LVL(90), + "search for response in bucket %d: %s", bucket, + (resp == NULL ? "not found" : "found")); + + } else if (resp->id != id || + !isc_sockaddr_equal(&ev->address, &resp->host)) + { + dispatch_log(disp, LVL(90), + "response to an exclusive socket doesn't match"); + inc_stats(mgr, dns_resstatscounter_mismatch); + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + + if (resp == NULL) { + inc_stats(mgr, dns_resstatscounter_mismatch); + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + + /* + * Now that we have the original dispatch the query was sent + * from check that the address and port the response was + * sent to make sense. + */ + if (disp != resp->disp) { + isc_sockaddr_t a1; + isc_sockaddr_t a2; + + /* + * Check that the socket types and ports match. + */ + if (disp->socktype != resp->disp->socktype || + isc_sockaddr_getport(&disp->local) != + isc_sockaddr_getport(&resp->disp->local)) + { + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + + /* + * If each dispatch is bound to a different address + * then fail. + * + * Note under Linux a packet can be sent out via IPv4 socket + * and the response be received via a IPv6 socket. + * + * Requests sent out via IPv6 should always come back in + * via IPv6. + */ + if (isc_sockaddr_pf(&resp->disp->local) == PF_INET6 && + isc_sockaddr_pf(&disp->local) != PF_INET6) + { + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + isc_sockaddr_anyofpf(&a1, isc_sockaddr_pf(&resp->disp->local)); + isc_sockaddr_anyofpf(&a2, isc_sockaddr_pf(&disp->local)); + if (!isc_sockaddr_eqaddr(&disp->local, &resp->disp->local) && + !isc_sockaddr_eqaddr(&a1, &resp->disp->local) && + !isc_sockaddr_eqaddr(&a2, &disp->local)) + { + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + } + +sendresponse: + queue_response = resp->item_out; + rev = allocate_devent(resp->disp); + if (rev == NULL) { + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + + /* + * At this point, rev contains the event we want to fill in, and + * resp contains the information on the place to send it to. + * Send the event off. + */ + isc_buffer_init(&rev->buffer, ev->region.base, ev->region.length); + isc_buffer_add(&rev->buffer, ev->n); + rev->result = ev->result; + rev->id = id; + rev->addr = ev->address; + rev->pktinfo = ev->pktinfo; + rev->attributes = ev->attributes; + if (queue_response) { + ISC_LIST_APPEND(resp->items, rev, ev_link); + } else { + ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, DNS_EVENT_DISPATCH, + resp->action, resp->arg, resp, NULL, NULL); + request_log(disp, resp, LVL(90), + "[a] Sent event %p buffer %p len %d to task %p", + rev, rev->buffer.base, rev->buffer.length, + resp->task); + resp->item_out = true; + isc_task_send(resp->task, ISC_EVENT_PTR(&rev)); + } +unlock: + if (qidlocked) { + UNLOCK(&qid->lock); + } + + /* + * Restart recv() to get the next packet. + */ +restart: + result = startrecv(disp, dispsock); + if (result != ISC_R_SUCCESS && dispsock != NULL) { + /* + * XXX: wired. There seems to be no recovery process other than + * deactivate this socket anyway (since we cannot start + * receiving, we won't be able to receive a cancel event + * from the user). + */ + deactivate_dispsocket(disp, dispsock); + } + isc_event_free(&ev_in); + UNLOCK(&disp->lock); +} + +/* + * General flow: + * + * If I/O result == CANCELED, EOF, or error, notify everyone as the + * various queues drain. + * + * If query, restart. + * + * 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_task_t *task, isc_event_t *ev_in) { + dns_dispatch_t *disp = ev_in->ev_arg; + dns_tcpmsg_t *tcpmsg = &disp->tcpmsg; + dns_messageid_t id; + isc_result_t dres; + unsigned int flags; + dns_dispentry_t *resp; + dns_dispatchevent_t *rev; + unsigned int bucket; + bool killit; + bool queue_response; + dns_qid_t *qid; + int level; + char buf[ISC_SOCKADDR_FORMATSIZE]; + + UNUSED(task); + + REQUIRE(VALID_DISPATCH(disp)); + + qid = disp->qid; + + LOCK(&disp->lock); + + dispatch_log(disp, LVL(90), + "got TCP packet: requests %d, buffers %d, recvs %d", + disp->requests, disp->tcpbuffers, disp->recv_pending); + + INSIST(disp->recv_pending != 0); + disp->recv_pending = 0; + + if (disp->refcount == 0) { + /* + * This dispatcher is shutting down. Force cancellation. + */ + tcpmsg->result = ISC_R_CANCELED; + } + + if (tcpmsg->result != ISC_R_SUCCESS) { + switch (tcpmsg->result) { + case ISC_R_CANCELED: + break; + + case ISC_R_EOF: + dispatch_log(disp, LVL(90), "shutting down on EOF"); + do_cancel(disp); + break; + + case ISC_R_CONNECTIONRESET: + level = ISC_LOG_INFO; + goto logit; + + default: + level = ISC_LOG_ERROR; + logit: + isc_sockaddr_format(&tcpmsg->address, buf, sizeof(buf)); + dispatch_log(disp, level, + "shutting down due to TCP " + "receive error: %s: %s", + buf, isc_result_totext(tcpmsg->result)); + do_cancel(disp); + break; + } + + /* + * The event is statically allocated in the tcpmsg + * structure, and destroy_disp() frees the tcpmsg, so we must + * free the event *before* calling destroy_disp(). + */ + isc_event_free(&ev_in); + + disp->shutting_down = 1; + disp->shutdown_why = tcpmsg->result; + + /* + * If the recv() was canceled pass the word on. + */ + killit = destroy_disp_ok(disp); + UNLOCK(&disp->lock); + if (killit) { + isc_task_send(disp->task[0], &disp->ctlevent); + } + return; + } + + dispatch_log(disp, LVL(90), "result %d, length == %d, addr = %p", + tcpmsg->result, tcpmsg->buffer.length, + tcpmsg->buffer.base); + + /* + * Peek into the buffer to see what we can see. + */ + dres = dns_message_peekheader(&tcpmsg->buffer, &id, &flags); + if (dres != ISC_R_SUCCESS) { + dispatch_log(disp, LVL(10), "got garbage packet"); + goto restart; + } + + dispatch_log(disp, LVL(92), + "got valid DNS message header, /QR %c, id %u", + (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id); + + /* + * Allocate an event to send to the query or response client, and + * allocate a new buffer for our use. + */ + + /* + * Look at flags. If query, drop it. If response, + * look to see where it goes. + */ + if ((flags & DNS_MESSAGEFLAG_QR) == 0) { + /* + * Query. + */ + goto restart; + } + + /* + * Response. + */ + bucket = dns_hash(qid, &tcpmsg->address, id, disp->localport); + LOCK(&qid->lock); + resp = entry_search(qid, &tcpmsg->address, id, disp->localport, bucket); + dispatch_log(disp, LVL(90), "search for response in bucket %d: %s", + bucket, (resp == NULL ? "not found" : "found")); + + if (resp == NULL) { + goto unlock; + } + queue_response = resp->item_out; + rev = allocate_devent(disp); + if (rev == NULL) { + goto unlock; + } + + /* + * At this point, rev contains the event we want to fill in, and + * resp contains the information on the place to send it to. + * Send the event off. + */ + dns_tcpmsg_keepbuffer(tcpmsg, &rev->buffer); + disp->tcpbuffers++; + rev->result = ISC_R_SUCCESS; + rev->id = id; + rev->addr = tcpmsg->address; + if (queue_response) { + ISC_LIST_APPEND(resp->items, rev, ev_link); + } else { + ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, DNS_EVENT_DISPATCH, + resp->action, resp->arg, resp, NULL, NULL); + request_log(disp, resp, LVL(90), + "[b] Sent event %p buffer %p len %d to task %p", + rev, rev->buffer.base, rev->buffer.length, + resp->task); + resp->item_out = true; + isc_task_send(resp->task, ISC_EVENT_PTR(&rev)); + } +unlock: + UNLOCK(&qid->lock); + + /* + * Restart recv() to get the next packet. + */ +restart: + (void)startrecv(disp, NULL); + + isc_event_free(&ev_in); + UNLOCK(&disp->lock); +} + +/* + * disp must be locked. + */ +static isc_result_t +startrecv(dns_dispatch_t *disp, dispsocket_t *dispsock) { + isc_result_t res; + isc_region_t region; + isc_socket_t *sock; + + if (disp->shutting_down == 1) { + return (ISC_R_SUCCESS); + } + + if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) != 0) { + return (ISC_R_SUCCESS); + } + + if (disp->recv_pending != 0 && dispsock == NULL) { + return (ISC_R_SUCCESS); + } + + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0 && + dispsock == NULL) + { + return (ISC_R_SUCCESS); + } + + if (dispsock != NULL) { + sock = dispsock->socket; + } else { + sock = disp->socket; + } + INSIST(sock != NULL); + + switch (disp->socktype) { + /* + * UDP reads are always maximal. + */ + case isc_sockettype_udp: + region.length = disp->mgr->buffersize; + region.base = allocate_udp_buffer(disp); + if (region.base == NULL) { + return (ISC_R_NOMEMORY); + } + if (dispsock != NULL) { + isc_task_t *dt = dispsock->task; + isc_socketevent_t *sev = allocate_sevent( + disp, sock, ISC_SOCKEVENT_RECVDONE, udp_exrecv, + dispsock); + if (sev == NULL) { + free_buffer(disp, region.base, region.length); + return (ISC_R_NOMEMORY); + } + + res = isc_socket_recv2(sock, ®ion, 1, dt, sev, 0); + if (res != ISC_R_SUCCESS) { + free_buffer(disp, region.base, region.length); + return (res); + } + } else { + isc_task_t *dt = disp->task[0]; + isc_socketevent_t *sev = allocate_sevent( + disp, sock, ISC_SOCKEVENT_RECVDONE, udp_shrecv, + disp); + if (sev == NULL) { + free_buffer(disp, region.base, region.length); + return (ISC_R_NOMEMORY); + } + + res = isc_socket_recv2(sock, ®ion, 1, dt, sev, 0); + if (res != ISC_R_SUCCESS) { + free_buffer(disp, region.base, region.length); + disp->shutdown_why = res; + disp->shutting_down = 1; + do_cancel(disp); + return (ISC_R_SUCCESS); /* recover by cancel */ + } + INSIST(disp->recv_pending == 0); + disp->recv_pending = 1; + } + break; + + case isc_sockettype_tcp: + res = dns_tcpmsg_readmessage(&disp->tcpmsg, disp->task[0], + tcp_recv, disp); + if (res != ISC_R_SUCCESS) { + disp->shutdown_why = res; + disp->shutting_down = 1; + do_cancel(disp); + return (ISC_R_SUCCESS); /* recover by cancel */ + } + INSIST(disp->recv_pending == 0); + disp->recv_pending = 1; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); +} + +/* + * Mgr must be locked when calling this function. + */ +static bool +destroy_mgr_ok(dns_dispatchmgr_t *mgr) { + mgr_log(mgr, LVL(90), + "destroy_mgr_ok: shuttingdown=%d, listnonempty=%d, ", + MGR_IS_SHUTTINGDOWN(mgr), !ISC_LIST_EMPTY(mgr->list)); + if (!MGR_IS_SHUTTINGDOWN(mgr)) { + return (false); + } + if (!ISC_LIST_EMPTY(mgr->list)) { + return (false); + } + if (isc_refcount_current(&mgr->irefs) != 0) { + return (false); + } + + return (true); +} + +/* + * Mgr must be unlocked when calling this function. + */ +static void +destroy_mgr(dns_dispatchmgr_t **mgrp) { + dns_dispatchmgr_t *mgr; + + mgr = *mgrp; + *mgrp = NULL; + + mgr->magic = 0; + isc_mutex_destroy(&mgr->lock); + mgr->state = 0; + + if (mgr->qid != NULL) { + qid_destroy(mgr->mctx, &mgr->qid); + } + + isc_mutex_destroy(&mgr->buffer_lock); + + 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_mem_putanddetach(&mgr->mctx, mgr, sizeof(dns_dispatchmgr_t)); +} + +static isc_result_t +open_socket(isc_socketmgr_t *mgr, const isc_sockaddr_t *local, + unsigned int options, isc_socket_t **sockp, + isc_socket_t *dup_socket, bool duponly) { + isc_socket_t *sock; + isc_result_t result; + + sock = *sockp; + if (sock != NULL) { + result = isc_socket_open(sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + } else if (dup_socket != NULL && + (!isc_socket_hasreuseport() || duponly)) + { + result = isc_socket_dup(dup_socket, &sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_socket_setname(sock, "dispatcher", NULL); + *sockp = sock; + return (ISC_R_SUCCESS); + } else { + result = isc_socket_create(mgr, isc_sockaddr_pf(local), + isc_sockettype_udp, &sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + isc_socket_setname(sock, "dispatcher", NULL); + +#ifndef ISC_ALLOW_MAPPED + isc_socket_ipv6only(sock, true); +#endif /* ifndef ISC_ALLOW_MAPPED */ + result = isc_socket_bind(sock, local, options); + if (result != ISC_R_SUCCESS) { + if (*sockp == NULL) { + isc_socket_detach(&sock); + } else { + isc_socket_close(sock); + } + return (result); + } + + *sockp = sock; + return (ISC_R_SUCCESS); +} + +/*% + * Create a temporary port list to set the initial default set of dispatch + * ports: [1024, 65535]. This is almost meaningless as the application will + * normally set the ports explicitly, but is provided to fill some minor corner + * cases. + */ +static isc_result_t +create_default_portset(isc_mem_t *mctx, isc_portset_t **portsetp) { + isc_result_t result; + + result = isc_portset_create(mctx, portsetp); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_portset_addrange(*portsetp, 1024, 65535); + + return (ISC_R_SUCCESS); +} + +/* + * Publics. + */ + +isc_result_t +dns_dispatchmgr_create(isc_mem_t *mctx, dns_dispatchmgr_t **mgrp) { + dns_dispatchmgr_t *mgr; + isc_result_t result; + 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){ 0 }; + + isc_mem_attach(mctx, &mgr->mctx); + + isc_mutex_init(&mgr->lock); + isc_mutex_init(&mgr->buffer_lock); + + isc_refcount_init(&mgr->irefs, 0); + + ISC_LIST_INIT(mgr->list); + + mgr->magic = DNS_DISPATCHMGR_MAGIC; + + result = create_default_portset(mctx, &v4portset); + if (result == ISC_R_SUCCESS) { + result = create_default_portset(mctx, &v6portset); + if (result == ISC_R_SUCCESS) { + result = dns_dispatchmgr_setavailports(mgr, v4portset, + v6portset); + } + } + if (v4portset != NULL) { + isc_portset_destroy(mctx, &v4portset); + } + if (v6portset != NULL) { + isc_portset_destroy(mctx, &v6portset); + } + if (result != ISC_R_SUCCESS) { + goto kill_dpool; + } + + *mgrp = mgr; + return (ISC_R_SUCCESS); + +kill_dpool: + isc_mutex_destroy(&mgr->buffer_lock); + isc_mutex_destroy(&mgr->lock); + isc_mem_putanddetach(&mctx, mgr, sizeof(dns_dispatchmgr_t)); + + return (result); +} + +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); +} + +void +dns_dispatchmgr_setblackportlist(dns_dispatchmgr_t *mgr, + dns_portlist_t *portlist) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + UNUSED(portlist); + + /* This function is deprecated: use dns_dispatchmgr_setavailports(). */ + return; +} + +dns_portlist_t * +dns_dispatchmgr_getblackportlist(dns_dispatchmgr_t *mgr) { + REQUIRE(VALID_DISPATCHMGR(mgr)); + return (NULL); /* this function is deprecated */ +} + +isc_result_t +dns_dispatchmgr_setavailports(dns_dispatchmgr_t *mgr, isc_portset_t *v4portset, + isc_portset_t *v6portset) { + in_port_t *v4ports, *v6ports, p; + unsigned int nv4ports, nv6ports, i4, i6; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + + 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); + } + + p = 0; + i4 = 0; + i6 = 0; + 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); + + PORTBUFLOCK(mgr); + 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; + PORTBUFUNLOCK(mgr); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +dns_dispatchmgr_setudp(dns_dispatchmgr_t *mgr, unsigned int buffersize, + unsigned int maxbuffers, unsigned int maxrequests, + unsigned int buckets, unsigned int increment) { + isc_result_t result; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(buffersize >= 512 && buffersize < (64 * 1024)); + REQUIRE(maxbuffers > 0); + REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */ + REQUIRE(increment > buckets); + UNUSED(maxrequests); + + /* + * Keep some number of items around. This should be a config + * option. For now, keep 8, but later keep at least two even + * if the caller wants less. This allows us to ensure certain + * things, like an event can be "freed" and the next allocation + * will always succeed. + * + * Note that if limits are placed on anything here, we use one + * event internally, so the actual limit should be "wanted + 1." + * + * XXXMLG + */ + + if (maxbuffers < 8) { + maxbuffers = 8; + } + + LOCK(&mgr->buffer_lock); + + if (maxbuffers > mgr->maxbuffers) { + mgr->maxbuffers = maxbuffers; + } + + /* Create or adjust socket pool */ + if (mgr->qid != NULL) { + UNLOCK(&mgr->buffer_lock); + return (ISC_R_SUCCESS); + } + + result = qid_allocate(mgr, buckets, increment, &mgr->qid, true); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + mgr->buffersize = buffersize; + mgr->maxbuffers = maxbuffers; + UNLOCK(&mgr->buffer_lock); + return (ISC_R_SUCCESS); + +cleanup: + UNLOCK(&mgr->buffer_lock); + return (result); +} + +void +dns_dispatchmgr_destroy(dns_dispatchmgr_t **mgrp) { + dns_dispatchmgr_t *mgr; + bool killit; + + REQUIRE(mgrp != NULL); + REQUIRE(VALID_DISPATCHMGR(*mgrp)); + + mgr = *mgrp; + *mgrp = NULL; + + LOCK(&mgr->lock); + mgr->state |= MGR_SHUTTINGDOWN; + killit = destroy_mgr_ok(mgr); + UNLOCK(&mgr->lock); + + mgr_log(mgr, LVL(90), "destroy: killit=%d", killit); + + if (killit) { + destroy_mgr(&mgr); + } +} + +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 int +port_cmp(const void *key, const void *ent) { + in_port_t p1 = *(const in_port_t *)key; + in_port_t p2 = *(const in_port_t *)ent; + + if (p1 < p2) { + return (-1); + } else if (p1 == p2) { + return (0); + } else { + return (1); + } +} + +static bool +portavailable(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_sockaddr_t *sockaddrp) { + isc_sockaddr_t sockaddr; + isc_result_t result; + in_port_t *ports, port; + unsigned int nports; + bool available = false; + + REQUIRE(sock != NULL || sockaddrp != NULL); + + PORTBUFLOCK(mgr); + if (sock != NULL) { + sockaddrp = &sockaddr; + result = isc_socket_getsockname(sock, sockaddrp); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + } + + if (isc_sockaddr_pf(sockaddrp) == AF_INET) { + ports = mgr->v4ports; + nports = mgr->nv4ports; + } else { + ports = mgr->v6ports; + nports = mgr->nv6ports; + } + if (ports == NULL) { + goto unlock; + } + + port = isc_sockaddr_getport(sockaddrp); + if (bsearch(&port, ports, nports, sizeof(in_port_t), port_cmp) != NULL) + { + available = true; + } + +unlock: + PORTBUFUNLOCK(mgr); + return (available); +} + +#define ATTRMATCH(_a1, _a2, _mask) (((_a1) & (_mask)) == ((_a2) & (_mask))) + +static bool +local_addr_match(dns_dispatch_t *disp, const isc_sockaddr_t *addr) { + isc_sockaddr_t sockaddr; + isc_result_t result; + + REQUIRE(disp->socket != NULL); + + if (addr == NULL) { + return (true); + } + + /* + * Don't match wildcard ports unless the port is available in the + * current configuration. + */ + if (isc_sockaddr_getport(addr) == 0 && + isc_sockaddr_getport(&disp->local) == 0 && + !portavailable(disp->mgr, disp->socket, NULL)) + { + return (false); + } + + /* + * Check if we match the binding . + * Wildcard ports match/fail here. + */ + if (isc_sockaddr_equal(&disp->local, addr)) { + return (true); + } + if (isc_sockaddr_getport(addr) == 0) { + return (false); + } + + /* + * Check if we match a bound wildcard port . + */ + if (!isc_sockaddr_eqaddr(&disp->local, addr)) { + return (false); + } + result = isc_socket_getsockname(disp->socket, &sockaddr); + if (result != ISC_R_SUCCESS) { + return (false); + } + + return (isc_sockaddr_equal(&sockaddr, addr)); +} + +/* + * Requires mgr be locked. + * + * No dispatcher can be locked by this thread when calling this function. + * + * + * NOTE: + * If a matching dispatcher is found, it is locked after this function + * returns, and must be unlocked by the caller. + */ +static isc_result_t +dispatch_find(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *local, + unsigned int attributes, unsigned int mask, + dns_dispatch_t **dispp) { + dns_dispatch_t *disp; + isc_result_t result; + + /* + * Make certain that we will not match a private or exclusive dispatch. + */ + attributes &= ~(DNS_DISPATCHATTR_PRIVATE | DNS_DISPATCHATTR_EXCLUSIVE); + mask |= (DNS_DISPATCHATTR_PRIVATE | DNS_DISPATCHATTR_EXCLUSIVE); + + disp = ISC_LIST_HEAD(mgr->list); + while (disp != NULL) { + LOCK(&disp->lock); + if ((disp->shutting_down == 0) && + ATTRMATCH(disp->attributes, attributes, mask) && + local_addr_match(disp, local)) + { + break; + } + UNLOCK(&disp->lock); + disp = ISC_LIST_NEXT(disp, link); + } + + if (disp == NULL) { + result = ISC_R_NOTFOUND; + goto out; + } + + *dispp = disp; + result = ISC_R_SUCCESS; +out: + + return (result); +} + +static isc_result_t +qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets, + unsigned int increment, dns_qid_t **qidp, bool needsocktable) { + dns_qid_t *qid; + unsigned int i; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */ + REQUIRE(increment > buckets); + REQUIRE(qidp != NULL && *qidp == NULL); + + qid = isc_mem_get(mgr->mctx, sizeof(*qid)); + + qid->qid_table = isc_mem_get(mgr->mctx, + buckets * sizeof(dns_displist_t)); + + qid->sock_table = NULL; + if (needsocktable) { + qid->sock_table = isc_mem_get( + mgr->mctx, buckets * sizeof(dispsocketlist_t)); + } + + isc_mutex_init(&qid->lock); + + for (i = 0; i < buckets; i++) { + ISC_LIST_INIT(qid->qid_table[i]); + if (qid->sock_table != NULL) { + ISC_LIST_INIT(qid->sock_table[i]); + } + } + + qid->qid_nbuckets = buckets; + qid->qid_increment = increment; + qid->magic = QID_MAGIC; + *qidp = qid; + return (ISC_R_SUCCESS); +} + +static void +qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp) { + dns_qid_t *qid; + + 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)); + if (qid->sock_table != NULL) { + isc_mem_put(mctx, qid->sock_table, + qid->qid_nbuckets * sizeof(dispsocketlist_t)); + } + isc_mutex_destroy(&qid->lock); + isc_mem_put(mctx, qid, sizeof(*qid)); +} + +/* + * Allocate and set important limits. + */ +static isc_result_t +dispatch_allocate(dns_dispatchmgr_t *mgr, unsigned int maxrequests, + dns_dispatch_t **dispp) { + dns_dispatch_t *disp; + isc_result_t result; + + 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)); + isc_refcount_increment0(&mgr->irefs); + + disp->magic = 0; + disp->mgr = mgr; + disp->maxrequests = maxrequests; + disp->attributes = 0; + ISC_LINK_INIT(disp, link); + disp->refcount = 1; + disp->recv_pending = 0; + memset(&disp->local, 0, sizeof(disp->local)); + memset(&disp->peer, 0, sizeof(disp->peer)); + disp->localport = 0; + disp->shutting_down = 0; + disp->shutdown_out = 0; + disp->connected = 0; + disp->tcpmsg_valid = 0; + disp->shutdown_why = ISC_R_UNEXPECTED; + disp->requests = 0; + disp->tcpbuffers = 0; + disp->qid = NULL; + ISC_LIST_INIT(disp->activesockets); + ISC_LIST_INIT(disp->inactivesockets); + disp->nsockets = 0; + disp->port_table = NULL; + disp->dscp = -1; + + isc_mutex_init(&disp->lock); + + disp->failsafe_ev = allocate_devent(disp); + if (disp->failsafe_ev == NULL) { + result = ISC_R_NOMEMORY; + goto kill_lock; + } + + disp->magic = DISPATCH_MAGIC; + + *dispp = disp; + return (ISC_R_SUCCESS); + + /* + * error returns + */ +kill_lock: + isc_mutex_destroy(&disp->lock); + isc_refcount_decrement(&mgr->irefs); + isc_mem_put(mgr->mctx, disp, sizeof(*disp)); + + return (result); +} + +/* + * MUST be unlocked, and not used by anything. + */ +static void +dispatch_free(dns_dispatch_t **dispp) { + dns_dispatch_t *disp; + dns_dispatchmgr_t *mgr; + + REQUIRE(VALID_DISPATCH(*dispp)); + disp = *dispp; + *dispp = NULL; + + mgr = disp->mgr; + REQUIRE(VALID_DISPATCHMGR(mgr)); + + if (disp->tcpmsg_valid) { + dns_tcpmsg_invalidate(&disp->tcpmsg); + disp->tcpmsg_valid = 0; + } + + INSIST(disp->tcpbuffers == 0); + INSIST(disp->requests == 0); + INSIST(disp->recv_pending == 0); + INSIST(ISC_LIST_EMPTY(disp->activesockets)); + INSIST(ISC_LIST_EMPTY(disp->inactivesockets)); + + isc_refcount_decrement(&mgr->irefs); + isc_mem_put(mgr->mctx, disp->failsafe_ev, sizeof(*disp->failsafe_ev)); + disp->failsafe_ev = NULL; + + if (disp->qid != NULL) { + qid_destroy(mgr->mctx, &disp->qid); + } + + if (disp->port_table != NULL) { + for (int i = 0; i < DNS_DISPATCH_PORTTABLESIZE; i++) { + INSIST(ISC_LIST_EMPTY(disp->port_table[i])); + } + isc_mem_put(mgr->mctx, disp->port_table, + sizeof(disp->port_table[0]) * + DNS_DISPATCH_PORTTABLESIZE); + } + + disp->mgr = NULL; + isc_mutex_destroy(&disp->lock); + disp->magic = 0; + isc_refcount_decrement(&mgr->irefs); + isc_mem_put(mgr->mctx, disp, sizeof(*disp)); +} + +isc_result_t +dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + const isc_sockaddr_t *destaddr, unsigned int buffersize, + unsigned int maxbuffers, unsigned int maxrequests, + unsigned int buckets, unsigned int increment, + unsigned int attributes, dns_dispatch_t **dispp) { + isc_result_t result; + dns_dispatch_t *disp; + + UNUSED(maxbuffers); + UNUSED(buffersize); + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(isc_socket_gettype(sock) == isc_sockettype_tcp); + REQUIRE((attributes & DNS_DISPATCHATTR_TCP) != 0); + REQUIRE((attributes & DNS_DISPATCHATTR_UDP) == 0); + + if (destaddr == NULL) { + attributes |= DNS_DISPATCHATTR_PRIVATE; /* XXXMLG */ + } + + LOCK(&mgr->lock); + + /* + * dispatch_allocate() checks mgr for us. + * qid_allocate() checks buckets and increment for us. + */ + disp = NULL; + result = dispatch_allocate(mgr, maxrequests, &disp); + if (result != ISC_R_SUCCESS) { + UNLOCK(&mgr->lock); + return (result); + } + + result = qid_allocate(mgr, buckets, increment, &disp->qid, false); + if (result != ISC_R_SUCCESS) { + goto deallocate_dispatch; + } + + disp->socktype = isc_sockettype_tcp; + disp->socket = NULL; + isc_socket_attach(sock, &disp->socket); + + disp->sepool = NULL; + + disp->ntasks = 1; + disp->task[0] = NULL; + result = isc_task_create(taskmgr, 50, &disp->task[0]); + if (result != ISC_R_SUCCESS) { + goto kill_socket; + } + + disp->ctlevent = + isc_event_allocate(mgr->mctx, disp, DNS_EVENT_DISPATCHCONTROL, + destroy_disp, disp, sizeof(isc_event_t)); + + isc_task_setname(disp->task[0], "tcpdispatch", disp); + + dns_tcpmsg_init(mgr->mctx, disp->socket, &disp->tcpmsg); + disp->tcpmsg_valid = 1; + + disp->attributes = attributes; + + if (localaddr == NULL) { + if (destaddr != NULL) { + switch (isc_sockaddr_pf(destaddr)) { + case AF_INET: + isc_sockaddr_any(&disp->local); + break; + case AF_INET6: + isc_sockaddr_any6(&disp->local); + break; + } + } + } else { + disp->local = *localaddr; + } + + if (destaddr != NULL) { + disp->peer = *destaddr; + } + + /* + * Append it to the dispatcher list. + */ + ISC_LIST_APPEND(mgr->list, disp, link); + UNLOCK(&mgr->lock); + + mgr_log(mgr, LVL(90), "created TCP dispatcher %p", disp); + dispatch_log(disp, LVL(90), "created task %p", disp->task[0]); + *dispp = disp; + + return (ISC_R_SUCCESS); + +kill_socket: + isc_socket_detach(&disp->socket); +deallocate_dispatch: + dispatch_free(&disp); + + UNLOCK(&mgr->lock); + + return (result); +} + +isc_result_t +dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr, + const isc_sockaddr_t *localaddr, bool *connected, + dns_dispatch_t **dispp) { + dns_dispatch_t *disp; + isc_result_t result; + isc_sockaddr_t peeraddr; + isc_sockaddr_t sockname; + unsigned int attributes, mask; + bool match = false; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(destaddr != NULL); + REQUIRE(dispp != NULL && *dispp == NULL); + + /* First pass */ + attributes = DNS_DISPATCHATTR_TCP | DNS_DISPATCHATTR_CONNECTED; + mask = DNS_DISPATCHATTR_TCP | DNS_DISPATCHATTR_PRIVATE | + DNS_DISPATCHATTR_EXCLUSIVE | DNS_DISPATCHATTR_CONNECTED; + + LOCK(&mgr->lock); + disp = ISC_LIST_HEAD(mgr->list); + while (disp != NULL && !match) { + LOCK(&disp->lock); + if ((disp->shutting_down == 0) && + ATTRMATCH(disp->attributes, attributes, mask) && + (localaddr == NULL || + isc_sockaddr_eqaddr(localaddr, &disp->local))) + { + result = isc_socket_getsockname(disp->socket, + &sockname); + if (result == ISC_R_SUCCESS) { + result = isc_socket_getpeername(disp->socket, + &peeraddr); + } + if (result == ISC_R_SUCCESS && + isc_sockaddr_equal(destaddr, &peeraddr) && + (localaddr == NULL || + isc_sockaddr_eqaddr(localaddr, &sockname))) + { + /* attach */ + disp->refcount++; + *dispp = disp; + match = true; + if (connected != NULL) { + *connected = true; + } + } + } + UNLOCK(&disp->lock); + disp = ISC_LIST_NEXT(disp, link); + } + if (match || connected == NULL) { + UNLOCK(&mgr->lock); + return (match ? ISC_R_SUCCESS : ISC_R_NOTFOUND); + } + + /* Second pass, only if connected != NULL */ + attributes = DNS_DISPATCHATTR_TCP; + + disp = ISC_LIST_HEAD(mgr->list); + while (disp != NULL && !match) { + LOCK(&disp->lock); + if ((disp->shutting_down == 0) && + ATTRMATCH(disp->attributes, attributes, mask) && + (localaddr == NULL || + isc_sockaddr_eqaddr(localaddr, &disp->local)) && + isc_sockaddr_equal(destaddr, &disp->peer)) + { + /* attach */ + disp->refcount++; + *dispp = disp; + match = true; + } + UNLOCK(&disp->lock); + disp = ISC_LIST_NEXT(disp, link); + } + UNLOCK(&mgr->lock); + return (match ? ISC_R_SUCCESS : ISC_R_NOTFOUND); +} + +isc_result_t +dns_dispatch_getudp_dup(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int buffersize, unsigned int maxbuffers, + unsigned int maxrequests, unsigned int buckets, + unsigned int increment, unsigned int attributes, + unsigned int mask, dns_dispatch_t **dispp, + dns_dispatch_t *dup_dispatch) { + isc_result_t result; + dns_dispatch_t *disp = NULL; + + REQUIRE(VALID_DISPATCHMGR(mgr)); + REQUIRE(sockmgr != NULL); + REQUIRE(localaddr != NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(buffersize >= 512 && buffersize < (64 * 1024)); + REQUIRE(maxbuffers > 0); + REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */ + REQUIRE(increment > buckets); + REQUIRE(dispp != NULL && *dispp == NULL); + REQUIRE((attributes & DNS_DISPATCHATTR_TCP) == 0); + + result = dns_dispatchmgr_setudp(mgr, buffersize, maxbuffers, + maxrequests, buckets, increment); + if (result != ISC_R_SUCCESS) { + return (result); + } + + LOCK(&mgr->lock); + + if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + REQUIRE(isc_sockaddr_getport(localaddr) == 0); + goto createudp; + } + + /* + * See if we have a dispatcher that matches. + */ + if (dup_dispatch == NULL) { + result = dispatch_find(mgr, localaddr, attributes, mask, &disp); + if (result == ISC_R_SUCCESS) { + disp->refcount++; + + if (disp->maxrequests < maxrequests) { + disp->maxrequests = maxrequests; + } + + if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) == + 0 && + (attributes & DNS_DISPATCHATTR_NOLISTEN) != 0) + { + disp->attributes |= DNS_DISPATCHATTR_NOLISTEN; + if (disp->recv_pending != 0) { + isc_socket_cancel(disp->socket, + disp->task[0], + ISC_SOCKCANCEL_RECV); + } + } + + UNLOCK(&disp->lock); + UNLOCK(&mgr->lock); + + *dispp = disp; + + return (ISC_R_SUCCESS); + } + } + +createudp: + /* + * Nope, create one. + */ + result = dispatch_createudp( + mgr, sockmgr, taskmgr, localaddr, maxrequests, attributes, + &disp, dup_dispatch == NULL ? NULL : dup_dispatch->socket); + + if (result != ISC_R_SUCCESS) { + UNLOCK(&mgr->lock); + return (result); + } + + UNLOCK(&mgr->lock); + *dispp = disp; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_dispatch_getudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int buffersize, unsigned int maxbuffers, + unsigned int maxrequests, unsigned int buckets, + unsigned int increment, unsigned int attributes, + unsigned int mask, dns_dispatch_t **dispp) { + return (dns_dispatch_getudp_dup(mgr, sockmgr, taskmgr, localaddr, + buffersize, maxbuffers, maxrequests, + buckets, increment, attributes, mask, + dispp, NULL)); +} + +/* + * mgr should be locked. + */ + +#ifndef DNS_DISPATCH_HELD +#define DNS_DISPATCH_HELD 20U +#endif /* ifndef DNS_DISPATCH_HELD */ + +static isc_result_t +get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp, + isc_socketmgr_t *sockmgr, const isc_sockaddr_t *localaddr, + isc_socket_t **sockp, isc_socket_t *dup_socket, bool duponly) { + unsigned int i, j; + isc_socket_t *held[DNS_DISPATCH_HELD]; + isc_sockaddr_t localaddr_bound; + isc_socket_t *sock = NULL; + isc_result_t result = ISC_R_SUCCESS; + bool anyport; + + INSIST(sockp != NULL && *sockp == NULL); + + localaddr_bound = *localaddr; + anyport = (isc_sockaddr_getport(localaddr) == 0); + + if (anyport) { + unsigned int nports; + in_port_t *ports; + + /* + * If no port is specified, we first try to pick up a random + * port by ourselves. + */ + if (isc_sockaddr_pf(localaddr) == AF_INET) { + nports = disp->mgr->nv4ports; + ports = disp->mgr->v4ports; + } else { + nports = disp->mgr->nv6ports; + ports = disp->mgr->v6ports; + } + if (nports == 0) { + return (ISC_R_ADDRNOTAVAIL); + } + + for (i = 0; i < 1024; i++) { + in_port_t prt; + + prt = ports[isc_random_uniform(nports)]; + isc_sockaddr_setport(&localaddr_bound, prt); + result = open_socket(sockmgr, &localaddr_bound, 0, + &sock, NULL, false); + /* + * Continue if the port chosen is already in use + * or the OS has reserved it. + */ + if (result == ISC_R_NOPERM || result == ISC_R_ADDRINUSE) + { + continue; + } + disp->localport = prt; + *sockp = sock; + return (result); + } + + /* + * If this fails 1024 times, we then ask the kernel for + * choosing one. + */ + } else { + /* Allow to reuse address for non-random ports. */ + result = open_socket(sockmgr, localaddr, + ISC_SOCKET_REUSEADDRESS, &sock, dup_socket, + duponly); + + if (result == ISC_R_SUCCESS) { + *sockp = sock; + } + + return (result); + } + + memset(held, 0, sizeof(held)); + i = 0; + + for (j = 0; j < 0xffffU; j++) { + result = open_socket(sockmgr, localaddr, 0, &sock, NULL, false); + if (result != ISC_R_SUCCESS) { + goto end; + } else if (portavailable(mgr, sock, NULL)) { + break; + } + if (held[i] != NULL) { + isc_socket_detach(&held[i]); + } + held[i++] = sock; + sock = NULL; + if (i == DNS_DISPATCH_HELD) { + i = 0; + } + } + if (j == 0xffffU) { + mgr_log(mgr, ISC_LOG_ERROR, + "avoid-v%s-udp-ports: unable to allocate " + "an available port", + isc_sockaddr_pf(localaddr) == AF_INET ? "4" : "6"); + result = ISC_R_FAILURE; + goto end; + } + *sockp = sock; + +end: + for (i = 0; i < DNS_DISPATCH_HELD; i++) { + if (held[i] != NULL) { + isc_socket_detach(&held[i]); + } + } + + return (result); +} + +static isc_result_t +dispatch_createudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int maxrequests, unsigned int attributes, + dns_dispatch_t **dispp, isc_socket_t *dup_socket) { + isc_result_t result; + dns_dispatch_t *disp; + isc_socket_t *sock = NULL; + int i = 0; + bool duponly = ((attributes & DNS_DISPATCHATTR_CANREUSE) == 0); + + /* This is an attribute needed only at creation time */ + attributes &= ~DNS_DISPATCHATTR_CANREUSE; + /* + * dispatch_allocate() checks mgr for us. + */ + disp = NULL; + result = dispatch_allocate(mgr, maxrequests, &disp); + if (result != ISC_R_SUCCESS) { + return (result); + } + + disp->socktype = isc_sockettype_udp; + + if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0) { + result = get_udpsocket(mgr, disp, sockmgr, localaddr, &sock, + dup_socket, duponly); + if (result != ISC_R_SUCCESS) { + goto deallocate_dispatch; + } + + 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), + "dns_dispatch_createudp: Created" + " UDP dispatch for %s with socket fd %d", + addrbuf, isc_socket_getfd(sock)); + } + } else { + isc_sockaddr_t sa_any; + + /* + * For dispatches using exclusive sockets with a specific + * source address, we only check if the specified address is + * available on the system. Query sockets will be created later + * on demand. + */ + isc_sockaddr_anyofpf(&sa_any, isc_sockaddr_pf(localaddr)); + if (!isc_sockaddr_eqaddr(&sa_any, localaddr)) { + result = open_socket(sockmgr, localaddr, 0, &sock, NULL, + false); + if (sock != NULL) { + isc_socket_detach(&sock); + } + if (result != ISC_R_SUCCESS) { + goto deallocate_dispatch; + } + } + + disp->port_table = isc_mem_get( + mgr->mctx, sizeof(disp->port_table[0]) * + DNS_DISPATCH_PORTTABLESIZE); + for (i = 0; i < DNS_DISPATCH_PORTTABLESIZE; i++) { + ISC_LIST_INIT(disp->port_table[i]); + } + } + disp->socket = sock; + disp->local = *localaddr; + + if ((attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + disp->ntasks = MAX_INTERNAL_TASKS; + } else { + disp->ntasks = 1; + } + for (i = 0; i < disp->ntasks; i++) { + disp->task[i] = NULL; + result = isc_task_create(taskmgr, 0, &disp->task[i]); + if (result != ISC_R_SUCCESS) { + while (--i >= 0) { + isc_task_shutdown(disp->task[i]); + isc_task_detach(&disp->task[i]); + } + goto kill_socket; + } + isc_task_setname(disp->task[i], "udpdispatch", disp); + } + + disp->ctlevent = + isc_event_allocate(mgr->mctx, disp, DNS_EVENT_DISPATCHCONTROL, + destroy_disp, disp, sizeof(isc_event_t)); + + disp->sepool = NULL; + isc_mem_create(&disp->sepool); + isc_mem_setname(disp->sepool, "disp_sepool", NULL); + + attributes &= ~DNS_DISPATCHATTR_TCP; + attributes |= DNS_DISPATCHATTR_UDP; + disp->attributes = attributes; + + /* + * Append it to the dispatcher list. + */ + ISC_LIST_APPEND(mgr->list, disp, link); + + mgr_log(mgr, LVL(90), "created UDP dispatcher %p", disp); + dispatch_log(disp, LVL(90), "created task %p", disp->task[0]); /* XXX */ + if (disp->socket != NULL) { + dispatch_log(disp, LVL(90), "created socket %p", disp->socket); + } + + *dispp = disp; + + return (result); + + /* + * Error returns. + */ +kill_socket: + if (disp->socket != NULL) { + isc_socket_detach(&disp->socket); + } +deallocate_dispatch: + dispatch_free(&disp); + + return (result); +} + +void +dns_dispatch_attach(dns_dispatch_t *disp, dns_dispatch_t **dispp) { + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(dispp != NULL && *dispp == NULL); + + LOCK(&disp->lock); + disp->refcount++; + UNLOCK(&disp->lock); + + *dispp = disp; +} + +/* + * It is important to lock the manager while we are deleting the dispatch, + * since dns_dispatch_getudp will call dispatch_find, which returns to + * the caller a dispatch but does not attach to it until later. _getudp + * locks the manager, however, so locking it here will keep us from attaching + * to a dispatcher that is in the process of going away. + */ +void +dns_dispatch_detach(dns_dispatch_t **dispp) { + dns_dispatch_t *disp; + dispsocket_t *dispsock; + bool killit; + + REQUIRE(dispp != NULL && VALID_DISPATCH(*dispp)); + + disp = *dispp; + *dispp = NULL; + + LOCK(&disp->lock); + + INSIST(disp->refcount > 0); + disp->refcount--; + if (disp->refcount == 0) { + if (disp->recv_pending > 0) { + isc_socket_cancel(disp->socket, disp->task[0], + ISC_SOCKCANCEL_RECV); + } + for (dispsock = ISC_LIST_HEAD(disp->activesockets); + dispsock != NULL; dispsock = ISC_LIST_NEXT(dispsock, link)) + { + isc_socket_cancel(dispsock->socket, dispsock->task, + ISC_SOCKCANCEL_RECV); + } + disp->shutting_down = 1; + } + + dispatch_log(disp, LVL(90), "detach: refcount %d", disp->refcount); + + killit = destroy_disp_ok(disp); + UNLOCK(&disp->lock); + if (killit) { + isc_task_send(disp->task[0], &disp->ctlevent); + } +} + +isc_result_t +dns_dispatch_addresponse(dns_dispatch_t *disp, unsigned int options, + const isc_sockaddr_t *dest, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_messageid_t *idp, dns_dispentry_t **resp, + isc_socketmgr_t *sockmgr) { + dns_dispentry_t *res; + unsigned int bucket; + in_port_t localport = 0; + dns_messageid_t id; + int i; + bool ok; + dns_qid_t *qid; + dispsocket_t *dispsocket = NULL; + isc_result_t result; + + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(task != NULL); + REQUIRE(dest != NULL); + REQUIRE(resp != NULL && *resp == NULL); + REQUIRE(idp != NULL); + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + REQUIRE(sockmgr != NULL); + } + + LOCK(&disp->lock); + + if (disp->shutting_down == 1) { + UNLOCK(&disp->lock); + return (ISC_R_SHUTTINGDOWN); + } + + if (disp->requests >= disp->maxrequests) { + UNLOCK(&disp->lock); + return (ISC_R_QUOTA); + } + + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0 && + disp->nsockets > DNS_DISPATCH_SOCKSQUOTA) + { + dispsocket_t *oldestsocket; + dns_dispentry_t *oldestresp; + dns_dispatchevent_t *rev; + + /* + * Kill oldest outstanding query if the number of sockets + * exceeds the quota to keep the room for new queries. + */ + oldestsocket = ISC_LIST_HEAD(disp->activesockets); + oldestresp = oldestsocket->resp; + if (oldestresp != NULL && !oldestresp->item_out) { + rev = allocate_devent(oldestresp->disp); + if (rev != NULL) { + rev->buffer.base = NULL; + rev->result = ISC_R_CANCELED; + rev->id = oldestresp->id; + ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, + DNS_EVENT_DISPATCH, + oldestresp->action, + oldestresp->arg, oldestresp, + NULL, NULL); + oldestresp->item_out = true; + isc_task_send(oldestresp->task, + ISC_EVENT_PTR(&rev)); + inc_stats(disp->mgr, + dns_resstatscounter_dispabort); + } + } + + /* + * Move this entry to the tail so that it won't (easily) be + * examined before actually being canceled. + */ + ISC_LIST_UNLINK(disp->activesockets, oldestsocket, link); + ISC_LIST_APPEND(disp->activesockets, oldestsocket, link); + } + + qid = DNS_QID(disp); + + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + /* + * Get a separate UDP socket with a random port number. + */ + result = get_dispsocket(disp, dest, sockmgr, &dispsocket, + &localport); + if (result != ISC_R_SUCCESS) { + UNLOCK(&disp->lock); + inc_stats(disp->mgr, dns_resstatscounter_dispsockfail); + return (result); + } + } else { + localport = disp->localport; + } + + /* + * Try somewhat hard to find an unique ID unless FIXEDID is set + * in which case we use the id passed in via *idp. + */ + LOCK(&qid->lock); + if ((options & DNS_DISPATCHOPT_FIXEDID) != 0) { + id = *idp; + } else { + id = (dns_messageid_t)isc_random16(); + } + ok = false; + i = 0; + do { + bucket = dns_hash(qid, dest, id, localport); + if (entry_search(qid, dest, id, localport, bucket) == NULL) { + ok = true; + break; + } + if ((disp->attributes & DNS_DISPATCHATTR_FIXEDID) != 0) { + break; + } + id += qid->qid_increment; + id &= 0x0000ffff; + } while (i++ < 64); + UNLOCK(&qid->lock); + + if (!ok) { + UNLOCK(&disp->lock); + return (ISC_R_NOMORE); + } + + res = isc_mem_get(disp->mgr->mctx, sizeof(*res)); + isc_refcount_increment0(&disp->mgr->irefs); + + disp->refcount++; + disp->requests++; + res->task = NULL; + isc_task_attach(task, &res->task); + res->disp = disp; + res->id = id; + res->port = localport; + res->bucket = bucket; + res->host = *dest; + res->action = action; + res->arg = arg; + res->dispsocket = dispsocket; + if (dispsocket != NULL) { + dispsocket->resp = res; + } + res->item_out = false; + ISC_LIST_INIT(res->items); + ISC_LINK_INIT(res, link); + res->magic = RESPONSE_MAGIC; + + LOCK(&qid->lock); + ISC_LIST_APPEND(qid->qid_table[bucket], res, link); + UNLOCK(&qid->lock); + + inc_stats(disp->mgr, (qid == disp->mgr->qid) + ? dns_resstatscounter_disprequdp + : dns_resstatscounter_dispreqtcp); + + request_log(disp, res, LVL(90), "attached to task %p", res->task); + + if (((disp->attributes & DNS_DISPATCHATTR_UDP) != 0) || + ((disp->attributes & DNS_DISPATCHATTR_CONNECTED) != 0)) + { + result = startrecv(disp, dispsocket); + if (result != ISC_R_SUCCESS) { + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->qid_table[bucket], res, link); + UNLOCK(&qid->lock); + + if (dispsocket != NULL) { + destroy_dispsocket(disp, &dispsocket); + } + + disp->refcount--; + disp->requests--; + + dec_stats(disp->mgr, + (qid == disp->mgr->qid) + ? dns_resstatscounter_disprequdp + : dns_resstatscounter_dispreqtcp); + + UNLOCK(&disp->lock); + isc_task_detach(&res->task); + isc_refcount_decrement(&disp->mgr->irefs); + isc_mem_put(disp->mgr->mctx, res, sizeof(*res)); + return (result); + } + } + + if (dispsocket != NULL) { + ISC_LIST_APPEND(disp->activesockets, dispsocket, link); + } + + UNLOCK(&disp->lock); + + *idp = id; + *resp = res; + + if ((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + INSIST(res->dispsocket != NULL); + } + + return (ISC_R_SUCCESS); +} + +void +dns_dispatch_starttcp(dns_dispatch_t *disp) { + REQUIRE(VALID_DISPATCH(disp)); + + dispatch_log(disp, LVL(90), "starttcp %p", disp->task[0]); + + LOCK(&disp->lock); + if ((disp->attributes & DNS_DISPATCHATTR_CONNECTED) == 0) { + disp->attributes |= DNS_DISPATCHATTR_CONNECTED; + (void)startrecv(disp, NULL); + } + UNLOCK(&disp->lock); +} + +isc_result_t +dns_dispatch_getnext(dns_dispentry_t *resp, dns_dispatchevent_t **sockevent) { + dns_dispatch_t *disp; + dns_dispatchevent_t *ev; + + REQUIRE(VALID_RESPONSE(resp)); + REQUIRE(sockevent != NULL && *sockevent != NULL); + + disp = resp->disp; + REQUIRE(VALID_DISPATCH(disp)); + + ev = *sockevent; + *sockevent = NULL; + + LOCK(&disp->lock); + + REQUIRE(resp->item_out); + resp->item_out = false; + + if (ev->buffer.base != NULL) { + free_buffer(disp, ev->buffer.base, ev->buffer.length); + } + free_devent(disp, ev); + + if (disp->shutting_down == 1) { + UNLOCK(&disp->lock); + return (ISC_R_SHUTTINGDOWN); + } + ev = ISC_LIST_HEAD(resp->items); + if (ev != NULL) { + ISC_LIST_UNLINK(resp->items, ev, ev_link); + ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH, + resp->action, resp->arg, resp, NULL, NULL); + request_log(disp, resp, LVL(90), + "[c] Sent event %p buffer %p len %d to task %p", ev, + ev->buffer.base, ev->buffer.length, resp->task); + resp->item_out = true; + isc_task_send(resp->task, ISC_EVENT_PTR(&ev)); + } + UNLOCK(&disp->lock); + return (ISC_R_SUCCESS); +} + +void +dns_dispatch_removeresponse(dns_dispentry_t **resp, + dns_dispatchevent_t **sockevent) { + dns_dispatchmgr_t *mgr; + dns_dispatch_t *disp; + dns_dispentry_t *res; + dispsocket_t *dispsock; + dns_dispatchevent_t *ev; + unsigned int bucket; + bool killit; + unsigned int n; + isc_eventlist_t events; + dns_qid_t *qid; + + REQUIRE(resp != NULL); + REQUIRE(VALID_RESPONSE(*resp)); + + res = *resp; + *resp = NULL; + + disp = res->disp; + REQUIRE(VALID_DISPATCH(disp)); + mgr = disp->mgr; + REQUIRE(VALID_DISPATCHMGR(mgr)); + + qid = DNS_QID(disp); + + if (sockevent != NULL) { + REQUIRE(*sockevent != NULL); + ev = *sockevent; + *sockevent = NULL; + } else { + ev = NULL; + } + + LOCK(&disp->lock); + + INSIST(disp->requests > 0); + disp->requests--; + dec_stats(disp->mgr, (qid == disp->mgr->qid) + ? dns_resstatscounter_disprequdp + : dns_resstatscounter_dispreqtcp); + INSIST(disp->refcount > 0); + disp->refcount--; + if (disp->refcount == 0) { + if (disp->recv_pending > 0) { + isc_socket_cancel(disp->socket, disp->task[0], + ISC_SOCKCANCEL_RECV); + } + for (dispsock = ISC_LIST_HEAD(disp->activesockets); + dispsock != NULL; dispsock = ISC_LIST_NEXT(dispsock, link)) + { + isc_socket_cancel(dispsock->socket, dispsock->task, + ISC_SOCKCANCEL_RECV); + } + disp->shutting_down = 1; + } + + bucket = res->bucket; + + LOCK(&qid->lock); + ISC_LIST_UNLINK(qid->qid_table[bucket], res, link); + UNLOCK(&qid->lock); + + if (ev == NULL && res->item_out) { + /* + * We've posted our event, but the caller hasn't gotten it + * yet. Take it back. + */ + ISC_LIST_INIT(events); + n = isc_task_unsend(res->task, res, DNS_EVENT_DISPATCH, NULL, + &events); + /* + * We had better have gotten it back. + */ + INSIST(n == 1); + ev = (dns_dispatchevent_t *)ISC_LIST_HEAD(events); + } + + if (ev != NULL) { + REQUIRE(res->item_out); + res->item_out = false; + if (ev->buffer.base != NULL) { + free_buffer(disp, ev->buffer.base, ev->buffer.length); + } + free_devent(disp, ev); + } + + request_log(disp, res, LVL(90), "detaching from task %p", res->task); + isc_task_detach(&res->task); + + if (res->dispsocket != NULL) { + isc_socket_cancel(res->dispsocket->socket, + res->dispsocket->task, ISC_SOCKCANCEL_RECV); + res->dispsocket->resp = NULL; + } + + /* + * Free any buffered responses as well + */ + ev = ISC_LIST_HEAD(res->items); + while (ev != NULL) { + ISC_LIST_UNLINK(res->items, ev, ev_link); + if (ev->buffer.base != NULL) { + free_buffer(disp, ev->buffer.base, ev->buffer.length); + } + free_devent(disp, ev); + ev = ISC_LIST_HEAD(res->items); + } + res->magic = 0; + isc_refcount_decrement(&disp->mgr->irefs); + isc_mem_put(disp->mgr->mctx, res, sizeof(*res)); + if (disp->shutting_down == 1) { + do_cancel(disp); + } else { + (void)startrecv(disp, NULL); + } + + killit = destroy_disp_ok(disp); + UNLOCK(&disp->lock); + if (killit) { + isc_task_send(disp->task[0], &disp->ctlevent); + } +} + +/* + * disp must be locked. + */ +static void +do_cancel(dns_dispatch_t *disp) { + dns_dispatchevent_t *ev; + dns_dispentry_t *resp; + dns_qid_t *qid; + + if (disp->shutdown_out == 1) { + return; + } + + qid = DNS_QID(disp); + + /* + * Search for the first response handler without packets outstanding + * unless a specific handler is given. + */ + LOCK(&qid->lock); + for (resp = linear_first(qid); resp != NULL && resp->item_out; + /* Empty. */) + { + resp = linear_next(qid, resp); + } + + /* + * No one to send the cancel event to, so nothing to do. + */ + if (resp == NULL) { + goto unlock; + } + + /* + * Send the shutdown failsafe event to this resp. + */ + ev = disp->failsafe_ev; + ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH, + resp->action, resp->arg, resp, NULL, NULL); + ev->result = disp->shutdown_why; + ev->buffer.base = NULL; + ev->buffer.length = 0; + disp->shutdown_out = 1; + request_log(disp, resp, LVL(10), "cancel: failsafe event %p -> task %p", + ev, resp->task); + resp->item_out = true; + isc_task_send(resp->task, ISC_EVENT_PTR(&ev)); +unlock: + UNLOCK(&qid->lock); +} + +isc_socket_t * +dns_dispatch_getsocket(dns_dispatch_t *disp) { + REQUIRE(VALID_DISPATCH(disp)); + + return (disp->socket); +} + +isc_socket_t * +dns_dispatch_getentrysocket(dns_dispentry_t *resp) { + REQUIRE(VALID_RESPONSE(resp)); + + if (resp->dispsocket != NULL) { + return (resp->dispsocket->socket); + } else { + return (NULL); + } +} + +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_sockettype_udp) { + *addrp = disp->local; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTIMPLEMENTED); +} + +void +dns_dispatch_cancel(dns_dispatch_t *disp) { + REQUIRE(VALID_DISPATCH(disp)); + + LOCK(&disp->lock); + + if (disp->shutting_down == 1) { + UNLOCK(&disp->lock); + return; + } + + disp->shutdown_why = ISC_R_CANCELED; + disp->shutting_down = 1; + do_cancel(disp); + + UNLOCK(&disp->lock); + + return; +} + +unsigned int +dns_dispatch_getattributes(dns_dispatch_t *disp) { + REQUIRE(VALID_DISPATCH(disp)); + + /* + * We don't bother locking disp here; it's the caller's responsibility + * to use only non volatile flags. + */ + return (disp->attributes); +} + +void +dns_dispatch_changeattributes(dns_dispatch_t *disp, unsigned int attributes, + unsigned int mask) { + REQUIRE(VALID_DISPATCH(disp)); + /* Exclusive attribute can only be set on creation */ + REQUIRE((attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0); + /* Also, a dispatch with randomport specified cannot start listening */ + REQUIRE((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0 || + (attributes & DNS_DISPATCHATTR_NOLISTEN) == 0); + + /* XXXMLG + * Should check for valid attributes here! + */ + + LOCK(&disp->lock); + + if ((mask & DNS_DISPATCHATTR_NOLISTEN) != 0) { + if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) != 0 && + (attributes & DNS_DISPATCHATTR_NOLISTEN) == 0) + { + disp->attributes &= ~DNS_DISPATCHATTR_NOLISTEN; + (void)startrecv(disp, NULL); + } else if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) == + 0 && + (attributes & DNS_DISPATCHATTR_NOLISTEN) != 0) + { + disp->attributes |= DNS_DISPATCHATTR_NOLISTEN; + if (disp->recv_pending != 0) { + isc_socket_cancel(disp->socket, disp->task[0], + ISC_SOCKCANCEL_RECV); + } + } + } + + disp->attributes &= ~mask; + disp->attributes |= (attributes & mask); + UNLOCK(&disp->lock); +} + +void +dns_dispatch_importrecv(dns_dispatch_t *disp, isc_event_t *event) { + void *buf; + isc_socketevent_t *sevent, *newsevent; + + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE(event != NULL); + + if ((disp->attributes & DNS_DISPATCHATTR_NOLISTEN) == 0) { + return; + } + + sevent = (isc_socketevent_t *)event; + INSIST(sevent->n <= disp->mgr->buffersize); + + newsevent = (isc_socketevent_t *)isc_event_allocate( + disp->mgr->mctx, NULL, DNS_EVENT_IMPORTRECVDONE, udp_shrecv, + disp, sizeof(isc_socketevent_t)); + + buf = allocate_udp_buffer(disp); + if (buf == NULL) { + isc_event_free(ISC_EVENT_PTR(&newsevent)); + return; + } + memmove(buf, sevent->region.base, sevent->n); + newsevent->region.base = buf; + newsevent->region.length = disp->mgr->buffersize; + newsevent->n = sevent->n; + newsevent->result = sevent->result; + newsevent->address = sevent->address; + newsevent->timestamp = sevent->timestamp; + newsevent->pktinfo = sevent->pktinfo; + newsevent->attributes = sevent->attributes; + + isc_task_send(disp->task[0], ISC_EVENT_PTR(&newsevent)); +} + +dns_dispatch_t * +dns_dispatchset_get(dns_dispatchset_t *dset) { + dns_dispatch_t *disp; + + /* 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, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, dns_dispatch_t *source, + dns_dispatchset_t **dsetp, int n) { + isc_result_t result; + dns_dispatchset_t *dset; + dns_dispatchmgr_t *mgr; + int i, j; + + REQUIRE(VALID_DISPATCH(source)); + REQUIRE((source->attributes & DNS_DISPATCHATTR_UDP) != 0); + REQUIRE(dsetp != NULL && *dsetp == NULL); + + mgr = source->mgr; + + dset = isc_mem_get(mctx, sizeof(dns_dispatchset_t)); + memset(dset, 0, sizeof(*dset)); + + isc_mutex_init(&dset->lock); + + dset->dispatches = isc_mem_get(mctx, sizeof(dns_dispatch_t *) * n); + + isc_mem_attach(mctx, &dset->mctx); + dset->ndisp = n; + dset->cur = 0; + + dset->dispatches[0] = NULL; + dns_dispatch_attach(source, &dset->dispatches[0]); + + LOCK(&mgr->lock); + for (i = 1; i < n; i++) { + dset->dispatches[i] = NULL; + result = dispatch_createudp( + mgr, sockmgr, taskmgr, &source->local, + source->maxrequests, source->attributes, + &dset->dispatches[i], source->socket); + 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])); + } + 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_cancelall(dns_dispatchset_t *dset, isc_task_t *task) { + int i; + + REQUIRE(dset != NULL); + + for (i = 0; i < dset->ndisp; i++) { + isc_socket_t *sock; + sock = dns_dispatch_getsocket(dset->dispatches[i]); + isc_socket_cancel(sock, task, ISC_SOCKCANCEL_ALL); + } +} + +void +dns_dispatchset_destroy(dns_dispatchset_t **dsetp) { + dns_dispatchset_t *dset; + int i; + + REQUIRE(dsetp != NULL && *dsetp != NULL); + + dset = *dsetp; + *dsetp = NULL; + for (i = 0; i < dset->ndisp; i++) { + dns_dispatch_detach(&(dset->dispatches[i])); + } + 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)); +} + +void +dns_dispatch_setdscp(dns_dispatch_t *disp, isc_dscp_t dscp) { + REQUIRE(VALID_DISPATCH(disp)); + disp->dscp = dscp; +} + +isc_dscp_t +dns_dispatch_getdscp(dns_dispatch_t *disp) { + REQUIRE(VALID_DISPATCH(disp)); + return (disp->dscp); +} + +#if 0 +void +dns_dispatchmgr_dump(dns_dispatchmgr_t *mgr) { + dns_dispatch_t *disp; + char foo[1024]; + + disp = ISC_LIST_HEAD(mgr->list); + while (disp != NULL) { + isc_sockaddr_format(&disp->local, foo, sizeof(foo)); + printf("\tdispatch %p, addr %s\n", disp, foo); + disp = ISC_LIST_NEXT(disp, link); + } +} +#endif /* if 0 */ diff --git a/lib/dns/dlz.c b/lib/dns/dlz.c new file mode 100644 index 0000000..6d77f5b --- /dev/null +++ b/lib/dns/dlz.c @@ -0,0 +1,541 @@ +/* + * 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 master 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) { + result = dns_ssutable_createdlz(dlzdb->mctx, &dlzdb->ssutable, + dlzdb); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + 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..03b3dca --- /dev/null +++ b/lib/dns/dns64.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 + +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, const 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, const 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); +} diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c new file mode 100644 index 0000000..8c04337 --- /dev/null +++ b/lib/dns/dnsrps.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 + +#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_copynf(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, /* serialize */ + 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 */ + NULL /* adjusthashsize */ +}; + +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..17ee8bd --- /dev/null +++ b/lib/dns/dnssec.c @@ -0,0 +1,2522 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 /* for DNS_TSIG_FUDGE */ + +#include + +LIBDNS_EXTERNAL_DATA 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; + + 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; + } + + 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: + 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_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.", + 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_result_t result; + unsigned char buf[DST_KEY_MAXSIZE]; + dns_rdata_t dnskey = DNS_RDATA_INIT; + char alg[80]; + + dns_secalg_format(dst_key_alg(key->key), alg, sizeof(alg)); + report("Removing %s key %d/%s from DNSKEY RRset.", reason, + 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_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..97f0709 --- /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; + +ISC_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..feb73a7 --- /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..d49beb2 --- /dev/null +++ b/lib/dns/dst_api.c @@ -0,0 +1,2797 @@ +/* + * 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 + +#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); + + dst_result_register(); + + 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])); +#if USE_OPENSSL + 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 */ +#endif /* USE_OPENSSL */ + +#if USE_PKCS11 + RETERR(dst__pkcs11_init(mctx, engine)); + RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA1])); + RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_NSEC3RSASHA1])); + RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA256])); + RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSASHA512])); + RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA256])); + RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA384])); + RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED25519])); + RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED448])); +#endif /* USE_PKCS11 */ +#ifdef GSSAPI + RETERR(dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI])); +#endif /* ifdef 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(); +#if USE_PKCS11 + (void)dst__pkcs11_destroy(); +#endif /* USE_PKCS11 */ +} + +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; + dst_key_t *k; + + REQUIRE(VALID_KEY(key)); + + DE_CONST(key, k); + + isc_mutex_lock(&k->mdlock); + modified = key->modified; + isc_mutex_unlock(&k->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 */ +#ifndef WIN32 + if (filename[0] == '/') { + dirname = NULL; + } +#else /* WIN32 */ + if (filename[0] == '/' || filename[0] == '\\') { + dirname = NULL; + } +#endif /* ifndef WIN32 */ + + 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); +} + +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) { + dst_key_t *k; + + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_BOOLEAN); + + DE_CONST(key, k); + + isc_mutex_lock(&k->mdlock); + if (!key->boolset[type]) { + isc_mutex_unlock(&k->mdlock); + return (ISC_R_NOTFOUND); + } + *valuep = key->bools[type]; + isc_mutex_unlock(&k->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) { + dst_key_t *k; + + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_NUMERIC); + + DE_CONST(key, k); + + isc_mutex_lock(&k->mdlock); + if (!key->numset[type]) { + isc_mutex_unlock(&k->mdlock); + return (ISC_R_NOTFOUND); + } + *valuep = key->nums[type]; + isc_mutex_unlock(&k->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) { + dst_key_t *k; + + REQUIRE(VALID_KEY(key)); + REQUIRE(timep != NULL); + REQUIRE(type <= DST_MAX_TIMES); + + DE_CONST(key, k); + + isc_mutex_lock(&k->mdlock); + if (!key->timeset[type]) { + isc_mutex_unlock(&k->mdlock); + return (ISC_R_NOTFOUND); + } + *timep = key->times[type]; + isc_mutex_unlock(&k->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) { + dst_key_t *k; + + REQUIRE(VALID_KEY(key)); + REQUIRE(statep != NULL); + REQUIRE(type <= DST_MAX_KEYSTATES); + + DE_CONST(key, k); + + isc_mutex_lock(&k->mdlock); + if (!key->keystateset[type]) { + isc_mutex_unlock(&k->mdlock); + return (ISC_R_NOTFOUND); + } + *statep = key->keystates[type]; + isc_mutex_unlock(&k->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 == 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; + char filename[NAME_MAX]; + isc_result_t ret; + isc_fsaccess_t access; + + REQUIRE(VALID_KEY(key)); + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + ret = dst_key_buildfilename(key, DST_TYPE_STATE, directory, &fileb); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + /* + * Create public key file. + */ + if ((fp = fopen(filename, "w")) == NULL) { + return (DST_R_WRITEERROR); + } + + if (issymmetric(key)) { + access = 0; + isc_fsaccess_add(ISC_FSACCESS_OWNER, + ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, + &access); + (void)isc_fsaccess_set(filename, access); + } + + /* Write key state */ + if ((type & DST_TYPE_KEY) == 0) { + fprintf(fp, "; This is the state of key %d, for ", key->key_id); + ret = dns_name_print(key->key_name, fp); + if (ret != ISC_R_SUCCESS) { + fclose(fp); + return (ret); + } + 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); + } + + fflush(fp); + if (ferror(fp)) { + ret = DST_R_WRITEERROR; + } + fclose(fp); + + return (ret); +} + +/*% + * 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, textb, fileb, classb; + isc_region_t r; + char filename[NAME_MAX]; + unsigned char key_array[DST_KEY_MAXSIZE]; + char text_array[DST_KEY_MAXTEXTSIZE]; + char class_array[10]; + isc_result_t ret; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_fsaccess_t access; + + 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)); + + ret = dst_key_todns(key, &keyb); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + isc_buffer_usedregion(&keyb, &r); + dns_rdata_fromregion(&rdata, key->key_class, dns_rdatatype_dnskey, &r); + + ret = dns_rdata_totext(&rdata, (dns_name_t *)NULL, &textb); + if (ret != ISC_R_SUCCESS) { + return (DST_R_INVALIDPUBLICKEY); + } + + ret = dns_rdataclass_totext(key->key_class, &classb); + if (ret != ISC_R_SUCCESS) { + return (DST_R_INVALIDPUBLICKEY); + } + + /* + * Make the filename. + */ + isc_buffer_init(&fileb, filename, sizeof(filename)); + ret = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &fileb); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + /* + * Create public key file. + */ + if ((fp = fopen(filename, "w")) == NULL) { + return (DST_R_WRITEERROR); + } + + if (issymmetric(key)) { + access = 0; + isc_fsaccess_add(ISC_FSACCESS_OWNER, + ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, + &access); + (void)isc_fsaccess_set(filename, access); + } + + /* 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); + ret = dns_name_print(key->key_name, fp); + if (ret != ISC_R_SUCCESS) { + fclose(fp); + return (ret); + } + 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 */ + ret = dns_name_print(key->key_name, 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) { + ret = DST_R_WRITEERROR; + } + + 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) { + ret = DST_R_WRITEERROR; + } + + fputc('\n', fp); + fflush(fp); + if (ferror(fp)) { + ret = DST_R_WRITEERROR; + } + fclose(fp); + + return (ret); +} + +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"; + } + + 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)); +} diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h new file mode 100644 index 0000000..4933cfd --- /dev/null +++ b/lib/dns/dst_internal.h @@ -0,0 +1,286 @@ +/* + * 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 + +#if USE_PKCS11 +#include +#include +#endif /* USE_PKCS11 */ + +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef GSSAPI +#ifdef WIN32 +/* + * MSVC does not like macros in #include lines. + */ +#include +#include +#else /* ifdef WIN32 */ +#include ISC_PLATFORM_GSSAPIHEADER +#ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER +#include ISC_PLATFORM_GSSAPI_KRB5_HEADER +#endif /* ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER */ +#endif /* ifdef WIN32 */ +#endif /* ifdef GSSAPI */ + +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; +#if USE_OPENSSL + EVP_PKEY *pkey; +#endif /* if USE_OPENSSL */ +#if USE_PKCS11 + pk11_object_t *pkey; +#endif /* if USE_PKCS11 */ + 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; +#if USE_PKCS11 + pk11_context_t *pk11_ctx; +#endif /* if USE_PKCS11 */ + } 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); +#define dst__pkcs11_init pk11_initialize + +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); +#if USE_OPENSSL +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 */ +#endif /* USE_OPENSSL */ +#if USE_PKCS11 +isc_result_t +dst__pkcs11rsa_init(struct dst_func **funcp); +isc_result_t +dst__pkcs11dsa_init(struct dst_func **funcp); +isc_result_t +dst__pkcs11ecdsa_init(struct dst_func **funcp); +isc_result_t +dst__pkcs11eddsa_init(struct dst_func **funcp); +#endif /* USE_PKCS11 */ +#ifdef GSSAPI +isc_result_t +dst__gssapi_init(struct dst_func **funcp); +#endif /* GSSAPI */ + +/*% + * Destructors + */ +void +dst__openssl_destroy(void); +#define dst__pkcs11_destroy pk11_finalize + +/*% + * 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); + +ISC_LANG_ENDDECLS + +/*! \file */ diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h new file mode 100644 index 0000000..5727848 --- /dev/null +++ b/lib/dns/dst_openssl.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DST_OPENSSL_H +#define DST_OPENSSL_H 1 + +#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) +ENGINE * +dst__openssl_getengine(const char *engine); +#else /* if !defined(OPENSSL_NO_ENGINE) */ +#define dst__openssl_getengine(x) NULL +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + +ISC_LANG_ENDDECLS + +#endif /* DST_OPENSSL_H */ +/*! \file */ diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c new file mode 100644 index 0000000..b7bbee9 --- /dev/null +++ b/lib/dns/dst_parse.c @@ -0,0 +1,804 @@ +/* + * 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 + +#include "dst/result.h" +#include "dst_internal.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 buffer[MAXFIELDSIZE * 2]; + isc_fsaccess_t access; + isc_stdtime_t when; + uint32_t value; + isc_buffer_t b; + 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(&b, filename, sizeof(filename)); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &b); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = isc_file_mode(filename, &mode); + if (result == ISC_R_SUCCESS && mode != 0600) { + /* File exists; warn that we are changing its permissions */ + int level; + +#ifdef _WIN32 + /* Windows security model is pretty different, + * e.g., there is no umask... */ + level = ISC_LOG_NOTICE; +#else /* ifdef _WIN32 */ + level = ISC_LOG_WARNING; +#endif /* ifdef _WIN32 */ + 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); + } + + if ((fp = fopen(filename, "w")) == NULL) { + return (DST_R_WRITEERROR); + } + + access = 0; + isc_fsaccess_add(ISC_FSACCESS_OWNER, + ISC_FSACCESS_READ | ISC_FSACCESS_WRITE, &access); + (void)isc_fsaccess_set(filename, access); + + 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) { + fclose(fp); + return (DST_R_INVALIDPRIVATEKEY); + } + 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) { + fclose(fp); + return (DST_R_INVALIDPRIVATEKEY); + } + + isc_buffer_usedregion(&b, &r); + + if (timetags[i] != NULL) { + fprintf(fp, "%s %.*s\n", timetags[i], + (int)r.length, r.base); + } + } + } + + fflush(fp); + result = ferror(fp) ? DST_R_WRITEERROR : ISC_R_SUCCESS; + fclose(fp); + return (result); +} + +/*! \file */ diff --git a/lib/dns/dst_parse.h b/lib/dns/dst_parse.h new file mode 100644 index 0000000..347a729 --- /dev/null +++ b/lib/dns/dst_parse.h @@ -0,0 +1,134 @@ +/* + * 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 */ +#ifndef DST_DST_PARSE_H +#define DST_DST_PARSE_H 1 + +#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 + +#endif /* DST_DST_PARSE_H */ diff --git a/lib/dns/dst_pkcs11.h b/lib/dns/dst_pkcs11.h new file mode 100644 index 0000000..4589022 --- /dev/null +++ b/lib/dns/dst_pkcs11.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. + */ + +#ifndef DST_PKCS11_H +#define DST_PKCS11_H 1 + +#include +#include +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dst__pkcs11_toresult(const char *funcname, const char *file, int line, + isc_result_t fallback, CK_RV rv); + +#define PK11_CALL(func, args, fallback) \ + ((void)(((rv = (func)args) == CKR_OK) || \ + ((ret = dst__pkcs11_toresult(#func, __FILE__, __LINE__, \ + fallback, rv)), \ + 0))) + +#define PK11_RET(func, args, fallback) \ + ((void)(((rv = (func)args) == CKR_OK) || \ + ((ret = dst__pkcs11_toresult(#func, __FILE__, __LINE__, \ + fallback, rv)), \ + 0))); \ + if (rv != CKR_OK) \ + goto err; + +ISC_LANG_ENDDECLS + +#endif /* DST_PKCS11_H */ diff --git a/lib/dns/dst_result.c b/lib/dns/dst_result.c new file mode 100644 index 0000000..6c3acae --- /dev/null +++ b/lib/dns/dst_result.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +static const char *text[DST_R_NRESULTS] = { + "algorithm is unsupported", /*%< 0 */ + "crypto failure", /*%< 1 */ + "built with no crypto support", /*%< 2 */ + "illegal operation for a null key", /*%< 3 */ + "public key is invalid", /*%< 4 */ + "private key is invalid", /*%< 5 */ + "external key", /*%< 6 */ + "error occurred writing key to disk", /*%< 7 */ + "invalid algorithm specific parameter", /*%< 8 */ + "UNUSED9", /*%< 9 */ + "UNUSED10", /*%< 10 */ + "sign failure", /*%< 11 */ + "UNUSED12", /*%< 12 */ + "UNUSED13", /*%< 13 */ + "verify failure", /*%< 14 */ + "not a public key", /*%< 15 */ + "not a private key", /*%< 16 */ + "not a key that can compute a secret", /*%< 17 */ + "failure computing a shared secret", /*%< 18 */ + "no randomness available", /*%< 19 */ + "bad key type", /*%< 20 */ + "no engine", /*%< 21 */ + "illegal operation for an external key", /*%< 22 */ +}; + +static const char *ids[DST_R_NRESULTS] = { + "DST_R_UNSUPPORTEDALG", + "DST_R_CRYPTOFAILURE", + "DST_R_NOCRYPTO", + "DST_R_NULLKEY", + "DST_R_INVALIDPUBLICKEY", + "DST_R_INVALIDPRIVATEKEY", + "UNUSED", + "DST_R_WRITEERROR", + "DST_R_INVALIDPARAM", + "UNUSED", + "UNUSED", + "DST_R_SIGNFAILURE", + "UNUSED", + "UNUSED", + "DST_R_VERIFYFAILURE", + "DST_R_NOTPUBLICKEY", + "DST_R_NOTPRIVATEKEY", + "DST_R_KEYCANNOTCOMPUTESECRET", + "DST_R_COMPUTESECRETFAILURE", + "DST_R_NORANDOMNESS", + "DST_R_BADKEYTYPE", + "DST_R_NOENGINE", + "DST_R_EXTERNALKEY", +}; + +#define DST_RESULT_RESULTSET 2 + +static isc_once_t once = ISC_ONCE_INIT; + +static void +initialize_action(void) { + isc_result_t result; + + result = isc_result_register(ISC_RESULTCLASS_DST, DST_R_NRESULTS, text, + DST_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_register() failed: %u", result); + } + result = isc_result_registerids(ISC_RESULTCLASS_DST, DST_R_NRESULTS, + ids, DST_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_registerids() failed: %u", result); + } +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +const char * +dst_result_totext(isc_result_t result) { + initialize(); + + return (isc_result_totext(result)); +} + +void +dst_result_register(void) { + initialize(); +} + +/*! \file */ diff --git a/lib/dns/dyndb.c b/lib/dns/dyndb.c new file mode 100644 index 0000000..1caf90b --- /dev/null +++ b/lib/dns/dyndb.c @@ -0,0 +1,465 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_DLFCN_H +#include +#elif _WIN32 +#include +#endif /* if HAVE_DLFCN_H */ + +#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; + void *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); +} + +#if HAVE_DLFCN_H && HAVE_DLOPEN +static isc_result_t +load_symbol(void *handle, const char *filename, const char *symbol_name, + void **symbolp) { + const char *errmsg; + void *symbol; + + REQUIRE(handle != NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + symbol = dlsym(handle, symbol_name); + if (symbol == NULL) { + errmsg = dlerror(); + 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); + } + dlerror(); + + *symbolp = symbol; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_library(isc_mem_t *mctx, const char *filename, const char *instname, + dyndb_implementation_t **impp) { + isc_result_t result; + void *handle = NULL; + dyndb_implementation_t *imp = NULL; + dns_dyndb_register_t *register_func = NULL; + dns_dyndb_destroy_t *destroy_func = NULL; + dns_dyndb_version_t *version_func = NULL; + int version; + + 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); + + handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + CHECK(ISC_R_FAILURE); + } + + /* Clear dlerror */ + dlerror(); + + CHECK(load_symbol(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(handle, filename, "dyndb_init", + (void **)®ister_func)); + CHECK(load_symbol(handle, filename, "dyndb_destroy", + (void **)&destroy_func)); + + imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t)); + + imp->mctx = NULL; + isc_mem_attach(mctx, &imp->mctx); + imp->handle = handle; + imp->register_func = register_func; + imp->destroy_func = destroy_func; + imp->name = isc_mem_strdup(mctx, instname); + + imp->inst = NULL; + INIT_LINK(imp, link); + + *impp = imp; + imp = NULL; + +cleanup: + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "failed to dynamically load instance '%s' " + "driver '%s': %s (%s)", + instname, filename, dlerror(), + isc_result_totext(result)); + } + if (imp != NULL) { + isc_mem_putanddetach(&imp->mctx, imp, + sizeof(dyndb_implementation_t)); + } + if (result != ISC_R_SUCCESS && handle != NULL) { + dlclose(handle); + } + + return (result); +} + +static void +unload_library(dyndb_implementation_t **impp) { + dyndb_implementation_t *imp; + + REQUIRE(impp != NULL && *impp != NULL); + + imp = *impp; + *impp = NULL; + + isc_mem_free(imp->mctx, imp->name); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); +} +#elif _WIN32 +static isc_result_t +load_symbol(HMODULE handle, const char *filename, const char *symbol_name, + void **symbolp) { + void *symbol; + + REQUIRE(handle != NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + symbol = GetProcAddress(handle, symbol_name); + if (symbol == NULL) { + int errstatus = GetLastError(); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "failed to lookup symbol %s in " + "dyndb module '%s': %d", + symbol_name, filename, errstatus); + return (ISC_R_FAILURE); + } + + *symbolp = symbol; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_library(isc_mem_t *mctx, const char *filename, const char *instname, + dyndb_implementation_t **impp) { + isc_result_t result; + HMODULE handle; + dyndb_implementation_t *imp = NULL; + dns_dyndb_register_t *register_func = NULL; + dns_dyndb_destroy_t *destroy_func = NULL; + dns_dyndb_version_t *version_func = NULL; + int version; + + 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); + + handle = LoadLibraryA(filename); + if (handle == NULL) { + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(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(handle, filename, "dyndb_init", + (void **)®ister_func)); + CHECK(load_symbol(handle, filename, "dyndb_destroy", + (void **)&destroy_func)); + + imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t)); + + imp->mctx = NULL; + isc_mem_attach(mctx, &imp->mctx); + imp->handle = handle; + imp->register_func = register_func; + imp->destroy_func = destroy_func; + imp->name = isc_mem_strdup(mctx, instname); + + imp->inst = NULL; + INIT_LINK(imp, link); + + *impp = imp; + imp = NULL; + +cleanup: + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, + "failed to dynamically load instance '%s' " + "driver '%s': %d (%s)", + instname, filename, GetLastError(), + isc_result_totext(result)); + } + if (imp != NULL) { + isc_mem_putanddetach(&imp->mctx, imp, + sizeof(dyndb_implementation_t)); + } + if (result != ISC_R_SUCCESS && handle != NULL) { + FreeLibrary(handle); + } + + return (result); +} + +static void +unload_library(dyndb_implementation_t **impp) { + dyndb_implementation_t *imp; + + REQUIRE(impp != NULL && *impp != NULL); + + imp = *impp; + *impp = NULL; + + isc_mem_free(imp->mctx, imp->name); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); +} +#else /* HAVE_DLFCN_H || _WIN32 */ +static isc_result_t +load_library(isc_mem_t *mctx, const char *filename, const char *instname, + dyndb_implementation_t **impp) { + UNUSED(mctx); + UNUSED(filename); + UNUSED(instname); + UNUSED(impp); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, + ISC_LOG_ERROR, + "dynamic database support is not implemented"); + + return (ISC_R_NOTIMPLEMENTED); +} + +static void +unload_library(dyndb_implementation_t **impp) { + UNUSED(impp); +} +#endif /* HAVE_DLFCN_H */ + +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)); + + memset(dctx, 0, sizeof(*dctx)); + 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); + } + dctx->timermgr = tmgr; + dctx->hashinit = hashinit; + dctx->lctx = lctx; + dctx->memdebug = &isc_mem_debugging; + + 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/ecdb.c b/lib/dns/ecdb.c new file mode 100644 index 0000000..abbb8d9 --- /dev/null +++ b/lib/dns/ecdb.c @@ -0,0 +1,797 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#define ECDB_MAGIC ISC_MAGIC('E', 'C', 'D', 'B') +#define VALID_ECDB(db) ((db) != NULL && (db)->common.impmagic == ECDB_MAGIC) + +#define ECDBNODE_MAGIC ISC_MAGIC('E', 'C', 'D', 'N') +#define VALID_ECDBNODE(ecdbn) ISC_MAGIC_VALID(ecdbn, ECDBNODE_MAGIC) + +/*% + * The 'ephemeral' cache DB (ecdb) implementation. An ecdb just provides + * temporary storage for ongoing name resolution with the common DB interfaces. + * It actually doesn't cache anything. The implementation expects any stored + * data is released within a short period, and does not care about the + * scalability in terms of the number of nodes. + */ + +typedef struct dns_ecdb { + /* Unlocked */ + dns_db_t common; + isc_mutex_t lock; + + /* Protected by atomics */ + isc_refcount_t references; + + /* Locked */ + ISC_LIST(struct dns_ecdbnode) nodes; +} dns_ecdb_t; + +typedef struct dns_ecdbnode { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; + dns_ecdb_t *ecdb; + dns_name_t name; + ISC_LINK(struct dns_ecdbnode) link; + + /* Locked */ + ISC_LIST(struct rdatasetheader) rdatasets; + + /* Protected by atomics */ + isc_refcount_t references; +} dns_ecdbnode_t; + +typedef struct rdatasetheader { + dns_rdatatype_t type; + dns_ttl_t ttl; + dns_trust_t trust; + dns_rdatatype_t covers; + unsigned int attributes; + + ISC_LINK(struct rdatasetheader) link; +} rdatasetheader_t; + +/* Copied from rbtdb.c */ +#define RDATASET_ATTR_NXDOMAIN 0x0010 +#define RDATASET_ATTR_NEGATIVE 0x0100 +#define NXDOMAIN(header) (((header)->attributes & RDATASET_ATTR_NXDOMAIN) != 0) +#define NEGATIVE(header) (((header)->attributes & RDATASET_ATTR_NEGATIVE) != 0) + +static isc_result_t +dns_ecdb_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 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 void +rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t 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 */ +}; + +typedef struct ecdb_rdatasetiter { + dns_rdatasetiter_t common; + rdatasetheader_t *current; +} ecdb_rdatasetiter_t; + +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 +}; + +isc_result_t +dns_ecdb_register(isc_mem_t *mctx, dns_dbimplementation_t **dbimp) { + REQUIRE(mctx != NULL); + REQUIRE(dbimp != NULL && *dbimp == NULL); + + return (dns_db_register("ecdb", dns_ecdb_create, NULL, mctx, dbimp)); +} + +void +dns_ecdb_unregister(dns_dbimplementation_t **dbimp) { + REQUIRE(dbimp != NULL && *dbimp != NULL); + + dns_db_unregister(dbimp); +} + +/*% + * DB routines + */ + +static void +attach(dns_db_t *source, dns_db_t **targetp) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)source; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&ecdb->references); + + *targetp = source; +} + +static void +destroy_ecdb(dns_ecdb_t *ecdb) { + if (isc_refcount_decrement(&ecdb->references) == 1) { + isc_refcount_destroy(&ecdb->references); + + INSIST(ISC_LIST_EMPTY(ecdb->nodes)); + + if (dns_name_dynamic(&ecdb->common.origin)) { + dns_name_free(&ecdb->common.origin, ecdb->common.mctx); + } + + isc_mutex_destroy(&ecdb->lock); + + ecdb->common.impmagic = 0; + ecdb->common.magic = 0; + + isc_mem_putanddetach(&ecdb->common.mctx, ecdb, sizeof(*ecdb)); + } +} + +static void +detach(dns_db_t **dbp) { + dns_ecdb_t *ecdb; + + REQUIRE(dbp != NULL); + ecdb = (dns_ecdb_t *)*dbp; + REQUIRE(VALID_ECDB(ecdb)); + + *dbp = NULL; + + destroy_ecdb(ecdb); +} + +static void +attachnode(dns_db_t *db, dns_dbnode_t *source, dns_dbnode_t **targetp) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)db; + dns_ecdbnode_t *node = (dns_ecdbnode_t *)source; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(VALID_ECDBNODE(node)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&node->references); + isc_refcount_increment(&node->references); + + *targetp = node; +} + +static void +destroynode(dns_ecdbnode_t *node) { + isc_mem_t *mctx; + dns_ecdb_t *ecdb = node->ecdb; + rdatasetheader_t *header; + + mctx = ecdb->common.mctx; + + LOCK(&ecdb->lock); + ISC_LIST_UNLINK(ecdb->nodes, node, link); + UNLOCK(&ecdb->lock); + + dns_name_free(&node->name, mctx); + + while ((header = ISC_LIST_HEAD(node->rdatasets)) != NULL) { + unsigned int headersize; + + ISC_LIST_UNLINK(node->rdatasets, header, link); + headersize = dns_rdataslab_size((unsigned char *)header, + sizeof(*header)); + isc_mem_put(mctx, header, headersize); + } + + isc_mutex_destroy(&node->lock); + isc_refcount_destroy(&node->references); + + node->magic = 0; + isc_mem_put(mctx, node, sizeof(*node)); + + destroy_ecdb(ecdb); +} + +static void +detachnode(dns_db_t *db, dns_dbnode_t **nodep) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)db; + dns_ecdbnode_t *node; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(nodep != NULL); + node = (dns_ecdbnode_t *)*nodep; + REQUIRE(VALID_ECDBNODE(node)); + *nodep = NULL; + + if (isc_refcount_decrement(&node->references) == 1) { + destroynode(node); + } +} + +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) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)db; + + REQUIRE(VALID_ECDB(ecdb)); + + UNUSED(name); + UNUSED(version); + UNUSED(type); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + return (ISC_R_NOTFOUND); +} + +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) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)db; + + REQUIRE(VALID_ECDB(ecdb)); + + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(dcname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + return (ISC_R_NOTFOUND); +} + +static isc_result_t +findnode(dns_db_t *db, const dns_name_t *name, bool create, + dns_dbnode_t **nodep) { + dns_ecdb_t *ecdb = (dns_ecdb_t *)db; + isc_mem_t *mctx; + dns_ecdbnode_t *node; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(nodep != NULL && *nodep == NULL); + + UNUSED(name); + + if (create != true) { + /* an 'ephemeral' node is never reused. */ + return (ISC_R_NOTFOUND); + } + + mctx = ecdb->common.mctx; + node = isc_mem_get(mctx, sizeof(*node)); + + isc_mutex_init(&node->lock); + + dns_name_init(&node->name, NULL); + dns_name_dup(name, mctx, &node->name); + + isc_refcount_init(&node->references, 1); + ISC_LIST_INIT(node->rdatasets); + + ISC_LINK_INIT(node, link); + + isc_refcount_increment(&ecdb->references); + node->ecdb = ecdb; + + LOCK(&ecdb->lock); + ISC_LIST_APPEND(ecdb->nodes, node, link); + UNLOCK(&ecdb->lock); + + node->magic = ECDBNODE_MAGIC; + + *nodep = node; + + return (ISC_R_SUCCESS); +} + +static void +bind_rdataset(dns_ecdb_t *ecdb, dns_ecdbnode_t *node, rdatasetheader_t *header, + dns_rdataset_t *rdataset) { + unsigned char *raw; + + /* + * Caller must be holding the node lock. + */ + + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = ecdb->common.rdclass; + rdataset->type = header->type; + rdataset->covers = header->covers; + rdataset->ttl = header->ttl; + rdataset->trust = header->trust; + if (NXDOMAIN(header)) { + rdataset->attributes |= DNS_RDATASETATTR_NXDOMAIN; + } + if (NEGATIVE(header)) { + rdataset->attributes |= DNS_RDATASETATTR_NEGATIVE; + } + + rdataset->private1 = ecdb; + rdataset->private2 = node; + raw = (unsigned char *)header + sizeof(*header); + rdataset->private3 = raw; + rdataset->count = 0; + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; + + isc_refcount_increment(&node->references); +} + +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_ecdb_t *ecdb = (dns_ecdb_t *)db; + isc_region_t r; + isc_result_t result = ISC_R_SUCCESS; + isc_mem_t *mctx; + dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)node; + rdatasetheader_t *header; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(VALID_ECDBNODE(ecdbnode)); + + UNUSED(version); + UNUSED(now); + UNUSED(options); + + mctx = ecdb->common.mctx; + + LOCK(&ecdbnode->lock); + + /* + * Sanity check: this implementation does not allow overriding an + * existing rdataset of the same type. + */ + for (header = ISC_LIST_HEAD(ecdbnode->rdatasets); header != NULL; + header = ISC_LIST_NEXT(header, link)) + { + INSIST(header->type != rdataset->type || + header->covers != rdataset->covers); + } + + result = dns_rdataslab_fromrdataset(rdataset, mctx, &r, + sizeof(rdatasetheader_t)); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + header = (rdatasetheader_t *)r.base; + header->type = rdataset->type; + header->ttl = rdataset->ttl; + header->trust = rdataset->trust; + header->covers = rdataset->covers; + header->attributes = 0; + if ((rdataset->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) { + header->attributes |= RDATASET_ATTR_NXDOMAIN; + } + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) { + header->attributes |= RDATASET_ATTR_NEGATIVE; + } + ISC_LINK_INIT(header, link); + ISC_LIST_APPEND(ecdbnode->rdatasets, header, link); + + if (addedrdataset == NULL) { + goto unlock; + } + + bind_rdataset(ecdb, ecdbnode, header, addedrdataset); + +unlock: + UNLOCK(&ecdbnode->lock); + + 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) { + UNUSED(db); + UNUSED(node); + UNUSED(version); + UNUSED(type); + UNUSED(covers); + + return (ISC_R_NOTIMPLEMENTED); +} + +static isc_result_t +createiterator(dns_db_t *db, unsigned int options, + dns_dbiterator_t **iteratorp) { + UNUSED(db); + UNUSED(options); + UNUSED(iteratorp); + + return (ISC_R_NOTIMPLEMENTED); +} + +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_ecdb_t *ecdb = (dns_ecdb_t *)db; + dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)node; + isc_mem_t *mctx; + ecdb_rdatasetiter_t *iterator; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(VALID_ECDBNODE(ecdbnode)); + + mctx = ecdb->common.mctx; + + iterator = isc_mem_get(mctx, sizeof(ecdb_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 dns_dbmethods_t ecdb_methods = { + attach, + detach, + NULL, /* beginload */ + NULL, /* endload */ + NULL, /* serialize */ + NULL, /* dump */ + NULL, /* currentversion */ + NULL, /* newversion */ + NULL, /* attachversion */ + NULL, /* closeversion */ + findnode, + find, + findzonecut, + attachnode, + detachnode, + NULL, /* expirenode */ + NULL, /* printnode */ + createiterator, /* createiterator */ + NULL, /* findrdataset */ + allrdatasets, + addrdataset, + NULL, /* subtractrdataset */ + deleterdataset, + NULL, /* issecure */ + NULL, /* nodecount */ + NULL, /* ispersistent */ + NULL, /* overmem */ + NULL, /* settask */ + NULL, /* 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 /* setgluecachestats */ +}; + +static isc_result_t +dns_ecdb_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_ecdb_t *ecdb; + isc_result_t result; + + REQUIRE(mctx != NULL); + REQUIRE(origin == dns_rootname); + REQUIRE(type == dns_dbtype_cache); + REQUIRE(dbp != NULL && *dbp == NULL); + + UNUSED(argc); + UNUSED(argv); + UNUSED(driverarg); + + ecdb = isc_mem_get(mctx, sizeof(*ecdb)); + + ecdb->common.attributes = DNS_DBATTR_CACHE; + ecdb->common.rdclass = rdclass; + ecdb->common.methods = &ecdb_methods; + dns_name_init(&ecdb->common.origin, NULL); + result = dns_name_dupwithoffsets(origin, mctx, &ecdb->common.origin); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, ecdb, sizeof(*ecdb)); + return (result); + } + + isc_mutex_init(&ecdb->lock); + + isc_refcount_init(&ecdb->references, 1); + ISC_LIST_INIT(ecdb->nodes); + + ecdb->common.mctx = NULL; + isc_mem_attach(mctx, &ecdb->common.mctx); + ecdb->common.impmagic = ECDB_MAGIC; + ecdb->common.magic = DNS_DB_MAGIC; + + *dbp = (dns_db_t *)ecdb; + + return (ISC_R_SUCCESS); +} + +/*% + * Rdataset Methods + */ + +static void +rdataset_disassociate(dns_rdataset_t *rdataset) { + dns_db_t *db = rdataset->private1; + dns_dbnode_t *node = rdataset->private2; + + dns_db_detachnode(db, &node); +} + +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); + } +#if DNS_RDATASET_FIXED + raw += 2 + (4 * count); +#else /* if DNS_RDATASET_FIXED */ + raw += 2; +#endif /* if DNS_RDATASET_FIXED */ + /* + * 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]; +#if DNS_RDATASET_FIXED + raw += length + 4; +#else /* if DNS_RDATASET_FIXED */ + raw += length + 2; +#endif /* if DNS_RDATASET_FIXED */ + 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; + unsigned int length; + unsigned int flags = 0; + + REQUIRE(raw != NULL); + + length = raw[0] * 256 + raw[1]; +#if DNS_RDATASET_FIXED + raw += 4; +#else /* if DNS_RDATASET_FIXED */ + raw += 2; +#endif /* if DNS_RDATASET_FIXED */ + if (rdataset->type == dns_rdatatype_rrsig) { + if ((*raw & DNS_RDATASLAB_OFFLINE) != 0) { + 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); + *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) { + rdatasetheader_t *header = rdataset->private3; + + header--; + header->trust = rdataset->trust = trust; +} + +/* + * Rdataset Iterator Methods + */ + +static void +rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + isc_mem_t *mctx; + union { + dns_rdatasetiter_t *rdatasetiterator; + ecdb_rdatasetiter_t *ecdbiterator; + } u; + + REQUIRE(iteratorp != NULL); + REQUIRE(DNS_RDATASETITER_VALID(*iteratorp)); + + u.rdatasetiterator = *iteratorp; + *iteratorp = NULL; + + mctx = u.ecdbiterator->common.db->mctx; + u.ecdbiterator->common.magic = 0; + + dns_db_detachnode(u.ecdbiterator->common.db, + &u.ecdbiterator->common.node); + isc_mem_put(mctx, u.ecdbiterator, sizeof(ecdb_rdatasetiter_t)); +} + +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator) { + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + + ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator; + dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)iterator->node; + + if (ISC_LIST_EMPTY(ecdbnode->rdatasets)) { + return (ISC_R_NOMORE); + } + ecdbiterator->current = ISC_LIST_HEAD(ecdbnode->rdatasets); + return (ISC_R_SUCCESS); +} + +static isc_result_t +rdatasetiter_next(dns_rdatasetiter_t *iterator) { + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + + ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator; + + ecdbiterator->current = ISC_LIST_NEXT(ecdbiterator->current, link); + if (ecdbiterator->current == NULL) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_SUCCESS); + } +} + +static void +rdatasetiter_current(dns_rdatasetiter_t *iterator, dns_rdataset_t *rdataset) { + ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator; + dns_ecdb_t *ecdb; + + ecdb = (dns_ecdb_t *)iterator->db; + REQUIRE(VALID_ECDB(ecdb)); + + bind_rdataset(ecdb, iterator->node, ecdbiterator->current, rdataset); +} 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..e59f23c --- /dev/null +++ b/lib/dns/forward.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + fwd->dscp = -1; + 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); + + if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + + 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); + if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_SUCCESS; + } + + 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-unix.h b/lib/dns/gen-unix.h new file mode 100644 index 0000000..5974635 --- /dev/null +++ b/lib/dns/gen-unix.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + * \brief + * This file is responsible for defining two operations that are not + * directly portable between Unix-like systems and Windows NT, option + * parsing and directory scanning. It is here because it was decided + * that the "gen" build utility was not to depend on libisc.a, so + * the functions declared in isc/commandline.h and isc/dir.h could not + * be used. + * + * The commandline stuff is really just a wrapper around getopt(). + * The dir stuff was shrunk to fit the needs of gen.c. + */ + +#ifndef DNS_GEN_UNIX_H +#define DNS_GEN_UNIX_H 1 + +#include +#include +#include +#include +#include /* Required on some systems for dirent.h. */ +#include /* XXXDCL Required for ?. */ + +#include + +#ifdef NEED_OPTARG +extern char *optarg; +#endif /* ifdef NEED_OPTARG */ + +#define isc_commandline_parse getopt +#define isc_commandline_argument optarg + +typedef struct { + DIR *handle; + char *filename; +} isc_dir_t; + +ISC_LANG_BEGINDECLS + +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) { + 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; +} + +ISC_LANG_ENDDECLS + +#endif /* DNS_GEN_UNIX_H */ diff --git a/lib/dns/gen-win32.h b/lib/dns/gen-win32.h new file mode 100644 index 0000000..1718295 --- /dev/null +++ b/lib/dns/gen-win32.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. + */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. 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 University 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 REGENTS 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 REGENTS 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. + */ + +/* + * \note This file was adapted from the NetBSD project's source tree, RCS ID: + * NetBSD: getopt.c,v 1.15 1999/09/20 04:39:37 lukem Exp + * + * The primary change has been to rename items to the ISC namespace + * and format in the ISC coding style. + * + * This file is responsible for defining two operations that are not + * directly portable between Unix-like systems and Windows NT, option + * parsing and directory scanning. It is here because it was decided + * that the "gen" build utility was not to depend on libisc.a, so + * the functions declared in isc/commandline.h and isc/dir.h could not + * be used. + * + * The commandline stuff is pretty much a straight copy from the initial + * isc/commandline.c. The dir stuff was shrunk to fit the needs of gen.c. + */ + +#ifndef DNS_GEN_WIN32_H +#define DNS_GEN_WIN32_H 1 + +#include +#include +#include +#include +#include + +#include + +int isc_commandline_index = 1; /* Index into parent argv vector. */ +int isc_commandline_option; /* Character checked for validity. */ + +char *isc_commandline_argument; /* Argument associated with option. */ +char *isc_commandline_progname; /* For printing error messages. */ + +bool isc_commandline_errprint = true; /* Print error messages. */ +bool isc_commandline_reset = true; /* Reset processing. */ + +#define BADOPT '?' +#define BADARG ':' +#define ENDOPT "" + +ISC_LANG_BEGINDECLS + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +isc_commandline_parse(int argc, char *const *argv, const char *options) { + static char *place = ENDOPT; + char *option; /* Index into *options of option. */ + + /* + * Update scanning pointer, either because a reset was requested or + * the previous argv was finished. + */ + if (isc_commandline_reset || *place == '\0') { + isc_commandline_reset = false; + + if (isc_commandline_progname == NULL) { + isc_commandline_progname = argv[0]; + } + + if (isc_commandline_index >= argc || + *(place = argv[isc_commandline_index]) != '-') + { + /* + * Index out of range or points to non-option. + */ + place = ENDOPT; + return (-1); + } + + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + /* + * Found '--' to signal end of options. Advance + * index to next argv, the first non-option. + */ + isc_commandline_index++; + place = ENDOPT; + return (-1); + } + } + + isc_commandline_option = *place++; + option = strchr(options, isc_commandline_option); + + /* + * Ensure valid option has been passed as specified by options string. + * '-:' is never a valid command line option because it could not + * distinguish ':' from the argument specifier in the options string. + */ + if (isc_commandline_option == ':' || option == NULL) { + if (*place == '\0') { + isc_commandline_index++; + } + + if (isc_commandline_errprint && *options != ':') { + fprintf(stderr, "%s: illegal option -- %c\n", + isc_commandline_progname, + isc_commandline_option); + } + + return (BADOPT); + } + + if (*++option != ':') { + /* + * Option does not take an argument. + */ + isc_commandline_argument = NULL; + + /* + * Skip to next argv if at the end of the current argv. + */ + if (*place == '\0') { + ++isc_commandline_index; + } + } else { + /* + * Option needs an argument. + */ + if (*place != '\0') { + /* + * Option is in this argv, -D1 style. + */ + isc_commandline_argument = place; + } else if (argc > ++isc_commandline_index) { + /* + * Option is next argv, -D 1 style. + */ + isc_commandline_argument = argv[isc_commandline_index]; + } else { + /* + * Argument needed, but no more argv. + */ + place = ENDOPT; + + /* + * Silent failure with "missing argument" return + * when ':' starts options string, per historical spec. + */ + if (*options == ':') { + return (BADARG); + } + + if (isc_commandline_errprint) { + fprintf(stderr, + "%s: option requires an argument -- " + "%c\n", + isc_commandline_progname, + isc_commandline_option); + } + + return (BADOPT); + } + + place = ENDOPT; + + /* + * Point to argv that follows argument. + */ + isc_commandline_index++; + } + + return (isc_commandline_option); +} + +typedef struct { + HANDLE handle; + WIN32_FIND_DATA find_data; + bool first_file; + char *filename; +} isc_dir_t; + +bool +start_directory(const char *path, isc_dir_t *dir) { + char pattern[_MAX_PATH], *p; + + /* + * Need space for slash-splat and final NUL. + */ + if (strlen(path) + 3 > sizeof(pattern)) { + return (false); + } + + strcpy(pattern, path); + + /* + * Append slash (if needed) and splat. + */ + p = pattern + strlen(pattern); + if (p != pattern && p[-1] != '\\' && p[-1] != ':') { + *p++ = '\\'; + } + *p++ = '*'; + *p++ = '\0'; + + dir->first_file = true; + + dir->handle = FindFirstFile(pattern, &dir->find_data); + + if (dir->handle == INVALID_HANDLE_VALUE) { + dir->filename = NULL; + return (false); + } else { + dir->filename = dir->find_data.cFileName; + return (true); + } +} + +bool +next_file(isc_dir_t *dir) { + if (dir->first_file) { + dir->first_file = false; + } else if (dir->handle != INVALID_HANDLE_VALUE) { + if (FindNextFile(dir->handle, &dir->find_data) == TRUE) { + dir->filename = dir->find_data.cFileName; + } else { + dir->filename = NULL; + } + } else { + dir->filename = NULL; + } + + if (dir->filename != NULL) { + return (true); + } else { + return (false); + } +} + +void +end_directory(isc_dir_t *dir) { + if (dir->handle != INVALID_HANDLE_VALUE) { + FindClose(dir->handle); + } +} + +inline struct tm * +gmtime_r(const time_t *clock, struct tm *result) { + errno_t ret = gmtime_s(result, clock); + if (ret != 0) { + errno = ret; + return (NULL); + } + return (result); +} + +ISC_LANG_ENDDECLS + +#endif /* DNS_GEN_WIN32_H */ diff --git a/lib/dns/gen.c b/lib/dns/gen.c new file mode 100644 index 0000000..6d40858 --- /dev/null +++ b/lib/dns/gen.c @@ -0,0 +1,1028 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +#ifdef WIN32 +/* + * Silence compiler warnings about using strcpy and friends. + */ +#define _CRT_SECURE_NO_DEPRECATE 1 +/* + * We use snprintf which was defined late in Windows even it is in C99. + */ +#if _MSC_VER < 1900 +#define snprintf _snprintf +#endif /* if _MSC_VER < 1900 */ +#endif /* ifdef WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif /* ifndef PATH_MAX */ + +#ifdef WIN32 +#include "gen-win32.h" +#else /* ifdef WIN32 */ +#include "gen-unix.h" +#endif /* ifdef WIN32 */ + +#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, 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]_%d" +#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; + +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(int, const char *, int, const char *, const char *); +static void +sd(int, const char *, const char *, char); +static void +insert_into_typenames(int, const char *, const char *); + +/*% + * 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) { + fprintf(stdout, "\n#define %s \\\n", name); + fprintf(stdout, "\tswitch (%s) { \\\n" /*}*/, tsw); + first = 0; + } + if (tt->type != lasttype && subswitch) { + if (res == NULL) { + fprintf(stdout, "\t\tdefault: break; \\\n"); + } else { + fprintf(stdout, "\t\tdefault: %s; break; \\\n", + res); + } + fputs(/*{*/ "\t\t} \\\n", stdout); + fputs("\t\tbreak; \\\n", stdout); + subswitch = 0; + } + if (tt->rdclass && tt->type != lasttype) { + fprintf(stdout, "\tcase %d: switch (%s) { \\\n" /*}*/, + tt->type, csw); + subswitch = 1; + } + if (tt->rdclass == 0) { + fprintf(stdout, "\tcase %d:%s %s_%s(%s); break;", + tt->type, result, function, + funname(tt->typebuf, buf1), args); + } else { + fprintf(stdout, "\t\tcase %d:%s %s_%s_%s(%s); break;", + tt->rdclass, result, function, + funname(tt->classbuf, buf1), + funname(tt->typebuf, buf2), args); + } + fputs(" \\\n", stdout); + lasttype = tt->type; + } + if (subswitch) { + if (res == NULL) { + fprintf(stdout, "\t\tdefault: break; \\\n"); + } else { + fprintf(stdout, "\t\tdefault: %s; break; \\\n", res); + } + fputs(/*{*/ "\t\t} \\\n", stdout); + fputs("\t\tbreak; \\\n", stdout); + } + if (first) { + if (res == NULL) { + fprintf(stdout, "\n#define %s\n", name); + } else { + fprintf(stdout, "\n#define %s %s;\n", name, res); + } + } else { + if (res == NULL) { + fprintf(stdout, "\tdefault: break; \\\n"); + } else { + fprintf(stdout, "\tdefault: %s; break; \\\n", res); + } + fputs(/*{*/ "\t}\n", stdout); + } +} + +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(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(int rdclass, const char *classbuf, const char *dirbuf, char filetype) { + char buf[TYPECLASSLEN + sizeof("_65535.h")]; + char typebuf[TYPECLASSBUF]; + int type, n; + isc_dir_t dir; + + if (!start_directory(dirbuf, &dir)) { + return; + } + + while (next_file(&dir)) { + if (sscanf(dir.filename, TYPECLASSFMT, typebuf, &type) != 2) { + continue; + } + if ((type > 65535) || (type < 0)) { + continue; + } + + n = snprintf(buf, sizeof(buf), "%s_%d.%c", typebuf, type, + filetype); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf, dir.filename) != 0) { + continue; + } + 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]; + 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 = isc_commandline_parse(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(isc_commandline_argument) > + PATH_MAX - 2 * TYPECLASSLEN - + sizeof("/rdata/_65535_65535")) + { + fprintf(stderr, "\"%s\" too long\n", + isc_commandline_argument); + exit(1); + } + n = snprintf(srcdir, sizeof(srcdir), "%s/", + isc_commandline_argument); + INSIST(n > 0 && (unsigned)n < sizeof(srcdir)); + break; + case 'F': + file = isc_commandline_argument; + break; + case 'P': + prefix = isc_commandline_argument; + break; + case 'S': + suffix = isc_commandline_argument; + 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; + } + if ((rdclass > 65535) || (rdclass < 0)) { + continue; + } + + n = snprintf(buf, sizeof(buf), "%srdata/%s_%d", srcdir, + classbuf, rdclass); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf + 6 + strlen(srcdir), dir.filename) != 0) { + continue; + } + 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) { + fprintf(stdout, copyright, year); + } + + if (code) { + fputs("#ifndef DNS_CODE_H\n", stdout); + fputs("#define DNS_CODE_H 1\n\n", stdout); + + fputs("#include \n", stdout); + fputs("#include \n\n", stdout); + fputs("#include \n\n", stdout); + + for (tt = types; tt != NULL; tt = tt->next) { + fprintf(stdout, "#include \"%s/%s_%d.c\"\n", tt->dirbuf, + tt->typebuf, tt->type); + } + + fputs("\n\n", stdout); + + 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. + */ + fprintf(stdout, "#define RDATATYPE_COMPARE(_s, _d, _tn, _n, " + "_tp) \\\n"); + fprintf(stdout, "\tdo { \\\n"); + fprintf(stdout, "\t\tif (sizeof(_s) - 1 == _n && \\\n" + "\t\t strncasecmp(_s,(_tn)," + "(sizeof(_s) - 1)) == 0) { \\\n"); + fprintf(stdout, "\t\t\tif ((dns_rdatatype_attributes(_d) & " + "DNS_RDATATYPEATTR_RESERVED) != 0) \\\n"); + fprintf(stdout, "\t\t\t\treturn (ISC_R_NOTIMPLEMENTED); \\\n"); + fprintf(stdout, "\t\t\t*(_tp) = _d; \\\n"); + fprintf(stdout, "\t\t\treturn (ISC_R_SUCCESS); \\\n"); + fprintf(stdout, "\t\t} \\\n"); + fprintf(stdout, "\t} while (0)\n\n"); + + fprintf(stdout, "#define RDATATYPE_FROMTEXT_SW(_hash," + "_typename,_length,_typep) \\\n"); + fprintf(stdout, "\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); + fprintf(stdout, "\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)) { + fprintf(stdout, + "\t\t\t" + "RDATATYPE_COMPARE" + "(\"%s\", %d, _typename, " + " _length, _typep); \\\n", + ttn2->typebuf, ttn2->type); + ttn2->sorted = 1; + } + } + fprintf(stdout, "\t\t\tbreak; \\\n"); + } + fprintf(stdout, "\t}\n"); + + fprintf(stdout, "#define RDATATYPE_ATTRIBUTE_SW \\\n"); + fprintf(stdout, "\tswitch (type) { \\\n"); + for (i = 0; i <= maxtype; i++) { + ttn = find_typename(i); + if (ttn == NULL) { + continue; + } + fprintf(stdout, "\tcase %d: return (%s); \\\n", i, + upper(ttn->attr)); + } + fprintf(stdout, "\t}\n"); + + fprintf(stdout, "#define RDATATYPE_TOTEXT_SW \\\n"); + fprintf(stdout, "\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; + } + fprintf(stdout, + "\tcase %d: return " + "(str_totext(\"%s\", target)); \\\n", + i, upper(ttn->typebuf)); + } + fprintf(stdout, "\t}\n"); + + fputs("#endif /* DNS_CODE_H */\n", stdout); + } else if (type_enum) { + char *s; + + fprintf(stdout, "#ifndef DNS_ENUMTYPE_H\n"); + fprintf(stdout, "#define DNS_ENUMTYPE_H 1\n\n"); + + fprintf(stdout, "enum {\n"); + fprintf(stdout, "\tdns_rdatatype_none = 0,\n"); + + lasttype = 0; + for (tt = types; tt != NULL; tt = tt->next) { + if (tt->type != lasttype) { + fprintf(stdout, "\tdns_rdatatype_%s = %d,\n", + funname(tt->typebuf, buf1), + lasttype = tt->type); + } + } + + fprintf(stdout, "\tdns_rdatatype_ixfr = 251,\n"); + fprintf(stdout, "\tdns_rdatatype_axfr = 252,\n"); + fprintf(stdout, "\tdns_rdatatype_mailb = 253,\n"); + fprintf(stdout, "\tdns_rdatatype_maila = 254,\n"); + fprintf(stdout, "\tdns_rdatatype_any = 255\n"); + + fprintf(stdout, "};\n\n"); + + fprintf(stdout, "#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); + fprintf(stdout, + "#define dns_rdatatype_%s\t%s" + "((dns_rdatatype_t)dns_rdatatype_%s)" + "\n", + s, strlen(s) < 2U ? "\t" : "", s); + lasttype = tt->type; + } + } + + fprintf(stdout, "#define dns_rdatatype_ixfr\t" + "((dns_rdatatype_t)dns_rdatatype_ixfr)\n"); + fprintf(stdout, "#define dns_rdatatype_axfr\t" + "((dns_rdatatype_t)dns_rdatatype_axfr)\n"); + fprintf(stdout, "#define dns_rdatatype_mailb\t" + "((dns_rdatatype_t)dns_rdatatype_mailb)\n"); + fprintf(stdout, "#define dns_rdatatype_maila\t" + "((dns_rdatatype_t)dns_rdatatype_maila)\n"); + fprintf(stdout, "#define dns_rdatatype_any\t" + "((dns_rdatatype_t)dns_rdatatype_any)\n"); + + fprintf(stdout, "\n#endif /* DNS_ENUMTYPE_H */\n"); + } else if (class_enum) { + char *s; + int classnum; + + fprintf(stdout, "#ifndef DNS_ENUMCLASS_H\n"); + fprintf(stdout, "#define DNS_ENUMCLASS_H 1\n\n"); + + fprintf(stdout, "enum {\n"); + + fprintf(stdout, "\tdns_rdataclass_reserved0 = 0,\n"); + fprintf(stdout, "#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; \ + fprintf(stdout, "\tdns_rdataclass_%s = %d%s\n", s, classnum, \ + classnum != 255 ? "," : ""); \ + fprintf(stdout, \ + "#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 + + fprintf(stdout, "};\n\n"); + fprintf(stdout, "#endif /* DNS_ENUMCLASS_H */\n"); + } else if (structs) { + if (prefix != NULL) { + if ((fd = fopen(prefix, "r")) != NULL) { + while (fgets(buf, sizeof(buf), fd) != NULL) { + fputs(buf, stdout); + } + 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) { + fputs(buf, stdout); + } + fclose(fd); + } + } + if (suffix != NULL) { + if ((fd = fopen(suffix, "r")) != NULL) { + while (fgets(buf, sizeof(buf), fd) != NULL) { + fputs(buf, stdout); + } + fclose(fd); + } + } + } else if (depend) { + for (tt = types; tt != NULL; tt = tt->next) { + fprintf(stdout, "%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..94a3ed8 --- /dev/null +++ b/lib/dns/geoip2.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 +#ifndef WIN32 +#include +#else /* ifndef WIN32 */ +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ +#endif /* ifndef _WINSOCKAPI_ */ +#include +#endif /* WIN32 */ +#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; + +ISC_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..966cc2c --- /dev/null +++ b/lib/dns/gssapi_link.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef GSSAPI + +#include + +#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); +} + +#else /* ifdef GSSAPI */ +int gssapi_link_unneeded = 1; +#endif /* ifdef GSSAPI */ + +/*! \file */ diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c new file mode 100644 index 0000000..f2a64e8 --- /dev/null +++ b/lib/dns/gssapictx.c @@ -0,0 +1,957 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 "dst_internal.h" + +/* + * Solaris8 apparently needs an explicit OID set, and Solaris10 needs + * one for anything but Kerberos. Supplying an explicit OID set + * doesn't appear to hurt anything in other implementations, so we + * always use one. If we're not using our own SPNEGO implementation, + * we include SPNEGO's OID. + */ +#ifdef GSSAPI +#ifdef WIN32 +#include +#else /* ifdef WIN32 */ +#include ISC_PLATFORM_KRB5HEADER +#endif /* ifdef WIN32 */ + +#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 dns_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, (gss_cred_id_t)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 = NULL; + + 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 defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) + 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 /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */ + /* + * 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 /* if defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) */ + } + + 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 /* ifdef GSSAPI */ + +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 /* ifdef GSSAPI */ + +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); +} + +/*! \file */ diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c new file mode 100644 index 0000000..2872ff2 --- /dev/null +++ b/lib/dns/hmac_link.c @@ -0,0 +1,521 @@ +/* + * 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 +#ifndef WIN32 +#include +#endif /* WIN32 */ + +#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) { \ + return (hmac_parse(ISC_MD_##alg, key, lexer, pub)); \ + } \ + 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 int digestlen; + unsigned char digest[ISC_MAX_MD_SIZE]; + + 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 int digestlen; + unsigned char digest[ISC_MAX_MD_SIZE]; + + 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/.clang-format b/lib/dns/include/.clang-format new file mode 120000 index 0000000..0e62f72 --- /dev/null +++ b/lib/dns/include/.clang-format @@ -0,0 +1 @@ +../../../.clang-format.headers \ No newline at end of file diff --git a/lib/dns/include/Makefile.in b/lib/dns/include/Makefile.in new file mode 100644 index 0000000..cf869c0 --- /dev/null +++ b/lib/dns/include/Makefile.in @@ -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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = dns dst +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/dns/include/dns/Makefile.in b/lib/dns/include/dns/Makefile.in new file mode 100644 index 0000000..88b9bcf --- /dev/null +++ b/lib/dns/include/dns/Makefile.in @@ -0,0 +1,63 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +HEADERS = acl.h adb.h badcache.h bit.h byaddr.h \ + cache.h callbacks.h catz.h cert.h \ + client.h clientinfo.h compress.h \ + db.h dbiterator.h dbtable.h diff.h dispatch.h \ + dlz.h dlz_dlopen.h dns64.h dnsrps.h dnssec.h ds.h dsdigest.h \ + dnstap.h dyndb.h ecs.h \ + edns.h ecdb.h events.h fixedname.h forward.h geoip.h \ + ipkeylist.h iptable.h \ + journal.h kasp.h keydata.h keyflags.h keymgr.h keytable.h \ + keyvalues.h lib.h librpz.h lmdb.h lookup.h log.h \ + master.h masterdump.h message.h \ + name.h ncache.h nsec.h nsec3.h nta.h opcode.h order.h \ + peer.h portlist.h private.h \ + rbt.h rcode.h rdata.h rdataclass.h rdatalist.h \ + rdataset.h rdatasetiter.h rdataslab.h rdatatype.h request.h \ + resolver.h result.h rootns.h rpz.h rriterator.h rrl.h \ + sdb.h sdlz.h secalg.h secproto.h soa.h ssu.h stats.h \ + tcpmsg.h time.h timer.h tkey.h tsec.h tsig.h ttl.h types.h \ + update.h validator.h version.h view.h xfrin.h \ + zone.h zonekey.h zoneverify.h zt.h + +GENHEADERS = enumclass.h enumtype.h rdatastruct.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/dns + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/dns || exit 1; \ + done + for i in ${GENHEADERS}; do \ + ${INSTALL_DATA} $$i ${DESTDIR}${includedir}/dns || exit 1; \ + done + +uninstall:: + for i in ${GENHEADERS}; do \ + rm -f ${DESTDIR}${includedir}/dns/$$i || exit 1; \ + done + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/dns/$$i || exit 1; \ + done diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h new file mode 100644 index 0000000..43fe15b --- /dev/null +++ b/lib/dns/include/dns/acl.h @@ -0,0 +1,253 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ACL_H +#define DNS_ACL_H 1 + +/***** +***** Module Info +*****/ + +/*! \file dns/acl.h + * \brief + * Address match list handling. + */ + +/*** + *** Imports + ***/ + +#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_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 */ +}; + +struct dns_aclenv { + 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_init(isc_mem_t *mctx, dns_aclenv_t *env); +/*%< + * Initialize ACL environment, setting up localhost and localnets ACLs + */ + +void +dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s); + +void +dns_aclenv_destroy(dns_aclenv_t *env); + +isc_result_t +dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner, + const dns_acl_t *acl, const 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, const 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_LANG_ENDDECLS + +#endif /* DNS_ACL_H */ diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h new file mode 100644 index 0000000..4e05fd6 --- /dev/null +++ b/lib/dns/include/dns/adb.h @@ -0,0 +1,835 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ADB_H +#define DNS_ADB_H 1 + +/***** +***** 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 */ + isc_dscp_t dscp; + + 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. + */ + +unsigned int +dns_adb_probesize(dns_adb_t *adb, dns_adbaddrinfo_t *addr, int lookups); +/*% + * Return suggested EDNS UDP size based on observed responses / failures. + * 'lookups' is the number of times the current lookup has been attempted. + * + * 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, unsigned int size); +/*% + * Record a failed EDNS UDP response and the advertised EDNS UDP buffer size + * used. + * + * Requires: + * + *\li adb be valid. + * + *\li addr be valid. + */ + +bool +dns_adb_noedns(dns_adb_t *adb, dns_adbaddrinfo_t *addr); +/*% + * Return whether EDNS should be disabled for this server. + * + * 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 + +#endif /* DNS_ADB_H */ diff --git a/lib/dns/include/dns/badcache.h b/lib/dns/include/dns/badcache.h new file mode 100644 index 0000000..c45ea0c --- /dev/null +++ b/lib/dns/include/dns/badcache.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_BADCACHE_H +#define DNS_BADCACHE_H 1 + +/***** +***** 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 + +#endif /* DNS_BADCACHE_H */ diff --git a/lib/dns/include/dns/bit.h b/lib/dns/include/dns/bit.h new file mode 100644 index 0000000..55696e2 --- /dev/null +++ b/lib/dns/include/dns/bit.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. + */ + +#ifndef DNS_BIT_H +#define DNS_BIT_H 1 + +/*! \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))) + +#endif /* DNS_BIT_H */ diff --git a/lib/dns/include/dns/byaddr.h b/lib/dns/include/dns/byaddr.h new file mode 100644 index 0000000..f527ed3 --- /dev/null +++ b/lib/dns/include/dns/byaddr.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_BYADDR_H +#define DNS_BYADDR_H 1 + +/***** +***** 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 + +#endif /* DNS_BYADDR_H */ diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h new file mode 100644 index 0000000..b840a54 --- /dev/null +++ b/lib/dns/include/dns/cache.h @@ -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 DNS_CACHE_H +#define DNS_CACHE_H 1 + +/***** +***** 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_setfilename(dns_cache_t *cache, const char *filename); +/*%< + * If 'filename' is non-NULL, make the cache persistent. + * The cache's data will be stored in the given file. + * If 'filename' is NULL, make the cache non-persistent. + * Files that are no longer used are not unlinked automatically. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li Various file-related failures + */ + +isc_result_t +dns_cache_load(dns_cache_t *cache); +/*%< + * If the cache has a file name, load the cache contents from the file. + * Previous cache contents are not discarded. + * If no file name has been set, do nothing and return success. + * + * MT: + *\li Multiple simultaneous attempts to load or dump the cache + * will be serialized with respect to one another, but + * the cache may be read and updated while the dump is + * in progress. Updates performed during loading + * may or may not be preserved, and reads may return + * either the old or the newly loaded data. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * \li Various failures depending on the database implementation type + */ + +isc_result_t +dns_cache_dump(dns_cache_t *cache); +/*%< + * If the cache has a file name, write the cache contents to disk, + * overwriting any preexisting file. If no file name has been set, + * do nothing and return success. + * + * MT: + *\li Multiple simultaneous attempts to load or dump the cache + * will be serialized with respect to one another, but + * the cache may be read and updated while the dump is + * in progress. Updates performed during the dump may + * or may not be reflected in the dumped file. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * \li Various failures depending on the database implementation type + */ + +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 + +#endif /* DNS_CACHE_H */ diff --git a/lib/dns/include/dns/callbacks.h b/lib/dns/include/dns/callbacks.h new file mode 100644 index 0000000..4d71915 --- /dev/null +++ b/lib/dns/include/dns/callbacks.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_CALLBACKS_H +#define DNS_CALLBACKS_H 1 + +/*! \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; + + /*% + * This is called when reading in a database image from a 'map' + * format zone file. + */ + dns_deserializefunc_t deserialize; + + /*% + * 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 *deserialize_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 + +#endif /* DNS_CALLBACKS_H */ diff --git a/lib/dns/include/dns/catz.h b/lib/dns/include/dns/catz.h new file mode 100644 index 0000000..7502829 --- /dev/null +++ b/lib/dns/include/dns/catz.h @@ -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. + */ + +#ifndef DNS_CATZ_H +#define DNS_CATZ_H 1 + +#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 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. + */ + +isc_result_t +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. + */ + +isc_result_t +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. + */ + +isc_result_t +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 + */ + +isc_result_t +dns_catz_entry_copy(dns_catz_zone_t *zone, 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 *zone, dns_catz_entry_t **entryp); +/*%< + * Detach an entry, free if no further references + * + * Requires: + * \li 'zone' 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. + */ + +void +dns_catz_zone_attach(dns_catz_zone_t *zone, dns_catz_zone_t **zonep); +/*%< + * Attach a catzone + * + * Requires: + * \li 'zone' is a valid dns_catz_zone_t. + * \li 'zonep' is not NULL and '*zonep' is NULL. + */ + +void +dns_catz_zone_detach(dns_catz_zone_t **zonep); +/*%< + * Detach a zone, free if no further references + * + * Requires: + * \li 'zonep' is not NULL and '*zonep' is not NULL. + */ + +isc_result_t +dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep, + const dns_name_t *name); +/*%< + * Allocate a new catz zone on catzs mctx + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'zonep' 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 *zone); +/*%< + * Get catalog zone name + * + * Requires: + * \li 'zone' is a valid dns_catz_zone_t. + */ + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *zone); +/*%< + * Get default member zone options for catalog zone 'zone' + * + * Requires: + * \li 'zone' is a valid dns_catz_zone_t. + */ + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone); +/*%< + * Reset the default member zone options for catalog zone 'zone' to + * the default values. + * + * Requires: + * \li 'zone' is a valid dns_catz_zone_t. + */ + +isc_result_t +dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone); +/*%< + * Merge 'newzone' into 'target', calling addzone/delzone/modzone + * (from zone->catzs->zmm) for appropriate member zones. + * + * Requires: + * \li 'orig' is a valid dns_catz_zone_t. + * \li 'newzone' is not NULL and '*newzone' is not NULL. + * + */ + +isc_result_t +dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone, + const dns_name_t *src_name, dns_rdataset_t *rdataset); +/*%< + * Process a single rdataset from a catalog zone 'zone' update, src_name is the + * record name. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'zone' is a valid dns_catz_zone_t. + * \li 'src_name' is a valid dns_name_t. + * \li 'rdataset' is valid rdataset. + */ + +isc_result_t +dns_catz_generate_masterfilename(dns_catz_zone_t *zone, 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 'zone' 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 *zone, 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 'zone' 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(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr); +/*%< + * Allocate a new catz_zones object, a collection storing all catalog zones + * for a view. + * + * Requires: + * \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 'zonep' is not NULL and *zonep 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_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp); +/*%< + * Attach 'catzs' to 'catzsp'. + * + * Requires: + * \li 'catzs' is a valid dns_catz_zones_t. + * \li 'catzsp' is not NULL and *catzsp is NULL. + */ + +void +dns_catz_catzs_detach(dns_catz_zones_t **catzsp); +/*%< + * Detach 'catzsp', free if no further references. + * + * Requires: + * \li 'catzsp' is not NULL and *catzsp is not NULL. + */ + +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_update_taskaction(isc_task_t *task, isc_event_t *event); +/*%< + * Task that launches dns_catz_update_from_db. + * + * Requires: + * \li 'event' is not NULL. + */ + +void +dns_catz_update_from_db(dns_db_t *db, dns_catz_zones_t *catzs); +/*%< + * 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. + * + * Requires: + * \li 'db' is a valid DB. + * \li 'catzs' is a valid dns_catz_zones_t. + * + */ + +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. + * + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_CATZ_H_ */ diff --git a/lib/dns/include/dns/cert.h b/lib/dns/include/dns/cert.h new file mode 100644 index 0000000..cdfa245 --- /dev/null +++ b/lib/dns/include/dns/cert.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_CERT_H +#define DNS_CERT_H 1 + +/*! \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 + +#endif /* DNS_CERT_H */ diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h new file mode 100644 index 0000000..fdcb698 --- /dev/null +++ b/lib/dns/include/dns/client.h @@ -0,0 +1,476 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_CLIENT_H +#define DNS_CLIENT_H 1 + +/***** +***** 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 + +typedef enum { + updateop_none = 0, + updateop_add = 1, + updateop_delete = 2, + updateop_exist = 3, + updateop_notexist = 4, + updateop_max = 5 +} dns_client_updateop_t; + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% + * Optional flags for dns_client_create(x). + */ +/*%< Enable caching resolution results (experimental). */ +#define DNS_CLIENTCREATEOPT_USECACHE 0x8000 + +/*% + * 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 + +/*% + * Optional flags for dns_client_(start)request. + */ +/*%< Allow running external context. */ +#define DNS_CLIENTREQOPT_RESERVED 0x01 +/*%< Use TCP transport. */ +#define DNS_CLIENTREQOPT_TCP 0x02 + +/*% + * Optional flags for dns_client_(start)update. + */ +/*%< Allow running external context. */ +#define DNS_CLIENTUPDOPT_RESERVED 0x01 +/*%< Use TCP transport. */ +#define DNS_CLIENTUPDOPT_TCP 0x02 + +/*% + * 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? */ + +/*% + * A dns_clientreqevent_t is sent when a DNS request is completed by a client. + * 'result' stores the result code of the entire transaction. + * If the transaction is successfully completed but the response packet cannot + * be parsed, 'result' will store the result code of dns_message_parse(). + * If the response packet is received, 'rmessage' will contain the response + * message, whether it is successfully parsed or not. + */ +typedef struct dns_clientreqevent { + ISC_EVENT_COMMON(struct dns_clientreqevent); + isc_result_t result; + dns_message_t *rmessage; +} dns_clientreqevent_t; /* too long? */ + +isc_result_t +dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, + isc_socketmgr_t *socketmgr, 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 'socketmgr' is a valid socket 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_destroy(dns_client_t **clientp); +/*%< + * Destroy 'client'. + * + * 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_cancelresolve(dns_clientrestrans_t *trans); +/*%< + * Cancel an ongoing resolution procedure started via + * dns_client_startresolve(). + * + * Notes: + * + *\li If the resolution procedure has not completed, post its CLIENTRESDONE + * event with a result code of #ISC_R_CANCELED. + * + * Requires: + * + *\li 'trans' is a valid transaction ID. + */ + +void +dns_client_destroyrestrans(dns_clientrestrans_t **transp); +/*%< + * Destroy name resolution transaction state identified by '*transp'. + * + * Requires: + * + *\li '*transp' is a valid transaction ID. + * + *\li The caller has received the CLIENTRESDONE event (either because the + * resolution completed or because dns_client_cancelresolve() was called). + * + * Ensures: + * + *\li *transp == NULL. + */ + +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_result_t +dns_client_request(dns_client_t *client, dns_message_t *qmessage, + dns_message_t *rmessage, const isc_sockaddr_t *server, + unsigned int options, unsigned int parseoptions, + dns_tsec_t *tsec, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries); + +isc_result_t +dns_client_startrequest(dns_client_t *client, dns_message_t *qmessage, + dns_message_t *rmessage, const isc_sockaddr_t *server, + unsigned int options, unsigned int parseoptions, + dns_tsec_t *tsec, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_clientreqtrans_t **transp); + +/*%< + * Send a DNS request containing a query message 'query' to 'server'. + * + * 'parseoptions' will be used when the response packet is parsed, and will be + * passed to dns_message_parse() via dns_request_getresponse(). See + * dns_message_parse() for more details. + * + * 'tsec' is a transaction security object containing, e.g. a TSIG key for + * authenticating the request/response transaction. This is optional and can + * be NULL, in which case this library performs the transaction without any + * transaction authentication. + * + * 'timeout', 'udptimeout', and 'udpretries' are passed to + * dns_request_createvia3(). See dns_request_createvia3() for more details. + * + * dns_client_request() provides a synchronous service. This function sends + * the request and blocks until a response is received. On success, + * 'rmessage' will contain the response message. The caller must provide a + * valid initialized message. + * + * It is expected that the client object passed to dns_client_request() was + * created via dns_client_create() and has external managers and contexts. + * + * dns_client_startrequest() is an asynchronous version of dns_client_request() + * and does not block. When the transaction is completed, 'action' will be + * called with the argument of a 'dns_clientreqevent_t' object, which contains + * the response message (on success). On return, '*transp' is set to an opaque + * transaction ID so that the caller can cancel this request. + * + * DNS_CLIENTREQOPT_TCP switches to the TCP (vs. UDP) transport. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'qmessage' and 'rmessage' are valid initialized message. + * + *\li 'server' is a valid socket address structure. + * + *\li 'task' is a valid task. + * + *\li 'transp' != NULL && *transp == NULL; + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + * + *\li Any result that dns_message_parse() can return. + */ + +void +dns_client_cancelrequest(dns_clientreqtrans_t *transp); +/*%< + * Cancel an ongoing DNS request procedure started via + * dns_client_startrequest(). + * + * Notes: + * + *\li If the request procedure has not completed, post its CLIENTREQDONE + * event with a result code of #ISC_R_CANCELED. + * + * Requires: + * + *\li 'trans' is a valid transaction ID. + */ + +void +dns_client_destroyreqtrans(dns_clientreqtrans_t **transp); +/*% + * Destroy DNS request transaction state identified by '*transp'. + * + * Requires: + * + *\li '*transp' is a valid transaction ID. + * + *\li The caller has received the CLIENTREQDONE event (either because the + * request completed or because dns_client_cancelrequest() was called). + * + * Ensures: + * + *\li *transp == NULL. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_CLIENT_H */ diff --git a/lib/dns/include/dns/clientinfo.h b/lib/dns/include/dns/clientinfo.h new file mode 100644 index 0000000..3928d21 --- /dev/null +++ b/lib/dns/include/dns/clientinfo.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_CLIENTINFO_H +#define DNS_CLIENTINFO_H 1 + +/***** +***** 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, dns_ecs_t *ecs, + void *versionp); + +ISC_LANG_ENDDECLS + +#endif /* DNS_CLIENTINFO_H */ diff --git a/lib/dns/include/dns/compress.h b/lib/dns/include/dns/compress.h new file mode 100644 index 0000000..7e19af6 --- /dev/null +++ b/lib/dns/include/dns/compress.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. + */ + +#ifndef DNS_COMPRESS_H +#define DNS_COMPRESS_H 1 + +#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 + +#endif /* DNS_COMPRESS_H */ diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h new file mode 100644 index 0000000..84b1cb1 --- /dev/null +++ b/lib/dns/include/dns/db.h @@ -0,0 +1,1800 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DB_H +#define DNS_DB_H 1 + +/***** +***** 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 + +/***** +***** 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 (*serialize)(dns_db_t *db, dns_dbversion_t *version, + FILE *file); + 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); + 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); + isc_result_t (*adjusthashsize)(dns_db_t *db, size_t size); +} 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. + * callbacks->deserialize will be a valid dns_deserialize_func_t + * suitable for loading 'db' from a map format zone file. + * + * 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_serialize(dns_db_t *db, dns_dbversion_t *version, FILE *rbtfile); +/*%< + * Dump version 'version' of 'db' to map-format 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. + */ + +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); +/*%< + * Count the number of nodes in 'db'. + * + * 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. + */ + +isc_result_t +dns_db_adjusthashsize(dns_db_t *db, size_t size); +/*%< + * For database implementations using a hash table, adjust the size of + * the hash table to store objects with a maximum total memory footprint + * of 'size' bytes. If 'size' is set to 0, it means no finite limit is + * requested. + * + * Requires: + * + * \li 'db' is a valid database. + * \li 'size' is maximum memory footprint of the database in bytes + * + * Returns: + * \li #ISC_R_SUCCESS The registration succeeded + * \li #ISC_R_NOMEMORY Out of memory + */ + +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 + +#endif /* DNS_DB_H */ diff --git a/lib/dns/include/dns/dbiterator.h b/lib/dns/include/dns/dbiterator.h new file mode 100644 index 0000000..32f204a --- /dev/null +++ b/lib/dns/include/dns/dbiterator.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. + */ + +#ifndef DNS_DBITERATOR_H +#define DNS_DBITERATOR_H 1 + +/***** +***** 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 + +#endif /* DNS_DBITERATOR_H */ diff --git a/lib/dns/include/dns/dbtable.h b/lib/dns/include/dns/dbtable.h new file mode 100644 index 0000000..dc1a991 --- /dev/null +++ b/lib/dns/include/dns/dbtable.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. + */ + +#ifndef DNS_DBTABLE_H +#define DNS_DBTABLE_H 1 + +/***** +***** Module Info +*****/ + +/*! \file dns/dbtable.h + * \brief + * DNS DB Tables + * + * XXX TBS XXX + * + * MP: + *\li The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li None. + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li None. + */ + +#include + +#include + +#define DNS_DBTABLEFIND_NOEXACT 0x01 + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_dbtable_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + dns_dbtable_t **dbtablep); +/*%< + * Make a new dbtable of class 'rdclass' + * + * Requires: + *\li mctx != NULL + * \li dbtablep != NULL && *dptablep == NULL + *\li 'rdclass' is a valid class + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + */ + +void +dns_dbtable_attach(dns_dbtable_t *source, dns_dbtable_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid dbtable. + * + *\li 'targetp' points to a NULL dns_dbtable_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + */ + +void +dns_dbtable_detach(dns_dbtable_t **dbtablep); +/*%< + * Detach *dbtablep from its dbtable. + * + * Requires: + * + *\li '*dbtablep' points to a valid dbtable. + * + * Ensures: + * + *\li *dbtablep is NULL. + * + *\li If '*dbtablep' is the last reference to the dbtable, + * all resources used by the dbtable will be freed + */ + +isc_result_t +dns_dbtable_add(dns_dbtable_t *dbtable, dns_db_t *db); +/*%< + * Add 'db' to 'dbtable'. + * + * Requires: + *\li 'dbtable' is a valid dbtable. + * + *\li 'db' is a valid database with the same class as 'dbtable' + */ + +void +dns_dbtable_remove(dns_dbtable_t *dbtable, dns_db_t *db); +/*%< + * Remove 'db' from 'dbtable'. + * + * Requires: + *\li 'db' was previously added to 'dbtable'. + */ + +void +dns_dbtable_adddefault(dns_dbtable_t *dbtable, dns_db_t *db); +/*%< + * Use 'db' as the result of a dns_dbtable_find() if no better match is + * available. + */ + +void +dns_dbtable_getdefault(dns_dbtable_t *dbtable, dns_db_t **db); +/*%< + * Get the 'db' used as the result of a dns_dbtable_find() + * if no better match is available. + */ + +void +dns_dbtable_removedefault(dns_dbtable_t *dbtable); +/*%< + * Remove the default db from 'dbtable'. + */ + +isc_result_t +dns_dbtable_find(dns_dbtable_t *dbtable, const dns_name_t *name, + unsigned int options, dns_db_t **dbp); +/*%< + * Find the deepest match to 'name' in the dbtable, and return it + * + * Notes: + *\li If the DNS_DBTABLEFIND_NOEXACT option is set, the best partial + * match (if any) to 'name' will be returned. + * + * Returns: + * \li #ISC_R_SUCCESS on success + *\li something else: no default and match + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_DBTABLE_H */ diff --git a/lib/dns/include/dns/diff.h b/lib/dns/include/dns/diff.h new file mode 100644 index 0000000..48e1b2e --- /dev/null +++ b/lib/dns/include/dns/diff.h @@ -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. + */ + +#ifndef DNS_DIFF_H +#define DNS_DIFF_H 1 + +/***** +***** 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 + +#endif /* DNS_DIFF_H */ diff --git a/lib/dns/include/dns/dispatch.h b/lib/dns/include/dns/dispatch.h new file mode 100644 index 0000000..6061843 --- /dev/null +++ b/lib/dns/include/dns/dispatch.h @@ -0,0 +1,586 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DISPATCH_H +#define DNS_DISPATCH_H 1 + +/***** +***** 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_removeresponse(). + * + * Reliability: + * + * Resources: + * + * Security: + * + *\li Depends on the isc_socket_t and dns_message_t for prevention of + * buffer overruns. + * + * Standards: + * + *\li None. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +/*% + * This event is sent to a task when a response comes in. + * No part of this structure should ever be modified by the caller, + * other than parts of the buffer. The holy parts of the buffer are + * the base and size of the buffer. All other parts of the buffer may + * be used. On event delivery the used region contains the packet. + * + * "id" is the received message id, + * + * "addr" is the host that sent it to us, + * + * "buffer" holds state on the received data. + * + * The "free" routine for this event will clean up itself as well as + * any buffer space allocated from common pools. + */ + +struct dns_dispatchevent { + ISC_EVENT_COMMON(dns_dispatchevent_t); /*%< standard event common */ + isc_result_t result; /*%< result code */ + int32_t id; /*%< message id */ + isc_sockaddr_t addr; /*%< address recv'd from */ + struct in6_pktinfo pktinfo; /*%< reply info for v6 */ + isc_buffer_t buffer; /*%< data buffer */ + uint32_t attributes; /*%< mirrored from socket.h */ +}; + +/*% + * 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; +}; + +/*@{*/ +/*% + * Attributes for added dispatchers. + * + * Values with the mask 0xffff0000 are application defined. + * Values with the mask 0x0000ffff are library defined. + * + * Insane values (like setting both TCP and UDP) are not caught. Don't + * do that. + * + * _PRIVATE + * The dispatcher cannot be shared. + * + * _TCP, _UDP + * The dispatcher is a TCP or UDP socket. + * + * _IPV4, _IPV6 + * The dispatcher uses an IPv4 or IPv6 socket. + * + * _NOLISTEN + * The dispatcher should not listen on the socket. + * + * _MAKEQUERY + * The dispatcher can be used to issue queries to other servers, and + * accept replies from them. + * + * _RANDOMPORT + * Previously used to indicate that the port of a dispatch UDP must be + * chosen randomly. This behavior now always applies and the attribute + * is obsoleted. + * + * _EXCLUSIVE + * A separate socket will be used on-demand for each transaction. + */ +#define DNS_DISPATCHATTR_PRIVATE 0x00000001U +#define DNS_DISPATCHATTR_TCP 0x00000002U +#define DNS_DISPATCHATTR_UDP 0x00000004U +#define DNS_DISPATCHATTR_IPV4 0x00000008U +#define DNS_DISPATCHATTR_IPV6 0x00000010U +#define DNS_DISPATCHATTR_NOLISTEN 0x00000020U +#define DNS_DISPATCHATTR_MAKEQUERY 0x00000040U +#define DNS_DISPATCHATTR_CONNECTED 0x00000080U +#define DNS_DISPATCHATTR_FIXEDID 0x00000100U +#define DNS_DISPATCHATTR_EXCLUSIVE 0x00000200U +#define DNS_DISPATCHATTR_CANREUSE 0x00000400U +/*@}*/ + +/* + */ +#define DNS_DISPATCHOPT_FIXEDID 0x00000001U + +isc_result_t +dns_dispatchmgr_create(isc_mem_t *mctx, dns_dispatchmgr_t **mgrp); +/*%< + * Creates a new dispatchmgr object. + * + * Requires: + *\li "mctx" be a valid memory context. + * + *\li mgrp != NULL && *mgrp == NULL + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +void +dns_dispatchmgr_destroy(dns_dispatchmgr_t **mgrp); +/*%< + * Destroys the dispatchmgr when it becomes empty. This could be + * immediately. + * + * Requires: + *\li mgrp != NULL && *mgrp is a valid dispatchmgr. + */ + +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. + */ + +void +dns_dispatchmgr_setblackportlist(dns_dispatchmgr_t *mgr, + dns_portlist_t *portlist); +/*%< + * This function is deprecated. Use dns_dispatchmgr_setavailports() instead. + * + * Requires: + *\li mgr is a valid dispatchmgr + */ + +dns_portlist_t * +dns_dispatchmgr_getblackportlist(dns_dispatchmgr_t *mgr); +/*%< + * This function is deprecated and always returns NULL. + * + * Requires: + *\li mgr is a valid dispatchmgr + */ + +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_getudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int buffersize, unsigned int maxbuffers, + unsigned int maxrequests, unsigned int buckets, + unsigned int increment, unsigned int attributes, + unsigned int mask, dns_dispatch_t **dispp); + +isc_result_t +dns_dispatch_getudp_dup(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + unsigned int buffersize, unsigned int maxbuffers, + unsigned int maxrequests, unsigned int buckets, + unsigned int increment, unsigned int attributes, + unsigned int mask, dns_dispatch_t **dispp, + dns_dispatch_t *dup); +/*%< + * Attach to existing dns_dispatch_t if one is found with dns_dispatchmgr_find, + * otherwise create a new UDP dispatch. + * + * Requires: + *\li All pointer parameters be valid for their respective types. + * + *\li dispp != NULL && *disp == NULL + * + *\li 512 <= buffersize <= 64k + * + *\li maxbuffers > 0 + * + *\li buckets < 2097169 + * + *\li increment > buckets + * + *\li (attributes & DNS_DISPATCHATTR_TCP) == 0 + * + * Returns: + *\li ISC_R_SUCCESS -- success. + * + *\li Anything else -- failure. + */ + +isc_result_t +dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_taskmgr_t *taskmgr, const isc_sockaddr_t *localaddr, + const isc_sockaddr_t *destaddr, unsigned int buffersize, + unsigned int maxbuffers, unsigned int maxrequests, + unsigned int buckets, unsigned int increment, + unsigned int attributes, dns_dispatch_t **dispp); +/*%< + * Create a new dns_dispatch and attach it to the provided isc_socket_t. + * + * For all dispatches, "buffersize" is the maximum packet size we will + * accept. + * + * "maxbuffers" and "maxrequests" control the number of buffers in the + * overall system and the number of buffers which can be allocated to + * requests. + * + * "buckets" is the number of buckets to use, and should be prime. + * + * "increment" is used in a collision avoidance function, and needs to be + * a prime > buckets, and not 2. + * + * Requires: + * + *\li mgr is a valid dispatch manager. + * + *\li sock is a valid. + * + *\li task is a valid task that can be used internally to this dispatcher. + * + * \li 512 <= buffersize <= 64k + * + *\li maxbuffers > 0. + * + *\li maxrequests <= maxbuffers. + * + *\li buckets < 2097169 (the next prime after 65536 * 32) + * + *\li increment > buckets (and prime). + * + *\li attributes includes #DNS_DISPATCHATTR_TCP and does not include + * #DNS_DISPATCHATTR_UDP. + * + * Returns: + *\li ISC_R_SUCCESS -- success. + * + *\li Anything else -- failure. + */ + +void +dns_dispatch_attach(dns_dispatch_t *disp, dns_dispatch_t **dispp); +/*%< + * Attach to a dispatch handle. + * + * Requires: + *\li disp is valid. + * + *\li dispp != NULL && *dispp == NULL + */ + +void +dns_dispatch_detach(dns_dispatch_t **dispp); +/*%< + * Detaches from the dispatch. + * + * Requires: + *\li dispp != NULL and *dispp be a valid dispatch. + */ + +void +dns_dispatch_starttcp(dns_dispatch_t *disp); +/*%< + * Start processing of a TCP dispatch once the socket connects. + * + * Requires: + *\li 'disp' is valid. + */ + +isc_result_t +dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr, + const isc_sockaddr_t *localaddr, bool *connected, + dns_dispatch_t **dispp); +/* + * Attempt to connect to a existing TCP connection (connection completed + * if connected == NULL). + */ + +isc_result_t +dns_dispatch_addresponse(dns_dispatch_t *disp, unsigned int options, + const isc_sockaddr_t *dest, isc_task_t *task, + isc_taskaction_t action, void *arg, uint16_t *idp, + dns_dispentry_t **resp, isc_socketmgr_t *sockmgr); +/*%< + * Add a response entry for this dispatch. + * + * "*idp" is filled in with the assigned message ID, and *resp is filled in + * to contain the magic token used to request event flow stop. + * + * Arranges for the given task to get a callback for response packets. When + * the event is delivered, it must be returned using dns_dispatch_freeevent() + * or through dns_dispatch_removeresponse() for another to be delivered. + * + * Requires: + *\li "idp" be non-NULL. + * + *\li "task" "action" and "arg" be set as appropriate. + * + *\li "dest" be non-NULL and valid. + * + *\li "resp" be non-NULL and *resp be NULL + * + *\li "sockmgr" be NULL or a valid socket manager. If 'disp' has + * the DNS_DISPATCHATTR_EXCLUSIVE attribute, this must not be NULL, + * which also means dns_dispatch_addresponse() cannot be used. + * + * 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_removeresponse(dns_dispentry_t **resp, + dns_dispatchevent_t **sockevent); +/*%< + * Stops the flow of responses for the provided id and destination. + * If "sockevent" is non-NULL, the dispatch event and associated buffer is + * also returned to the system. + * + * Requires: + *\li "resp" != NULL and "*resp" contain a value previously allocated + * by dns_dispatch_addresponse(); + * + *\li May only be called from within the task given as the 'task' + * argument to dns_dispatch_addresponse() when allocating '*resp'. + */ + +isc_socket_t * +dns_dispatch_getentrysocket(dns_dispentry_t *resp); + +isc_socket_t * +dns_dispatch_getsocket(dns_dispatch_t *disp); +/*%< + * Return the socket associated with this dispatcher. + * + * Requires: + *\li disp is valid. + * + * Returns: + *\li The socket the dispatcher is using. + */ + +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 + */ + +void +dns_dispatch_cancel(dns_dispatch_t *disp); +/*%< + * cancel outstanding clients + * + * Requires: + *\li disp is valid. + */ + +unsigned int +dns_dispatch_getattributes(dns_dispatch_t *disp); +/*%< + * Return the attributes (DNS_DISPATCHATTR_xxx) of this dispatch. Only the + * non-changeable attributes are expected to be referenced by the caller. + * + * Requires: + *\li disp is valid. + */ + +void +dns_dispatch_changeattributes(dns_dispatch_t *disp, unsigned int attributes, + unsigned int mask); +/*%< + * Set the bits described by "mask" to the corresponding values in + * "attributes". + * + * That is: + * + * \code + * new = (old & ~mask) | (attributes & mask) + * \endcode + * + * This function has a side effect when #DNS_DISPATCHATTR_NOLISTEN changes. + * When the flag becomes off, the dispatch will start receiving on the + * corresponding socket. When the flag becomes on, receive events on the + * corresponding socket will be canceled. + * + * Requires: + *\li disp is valid. + * + *\li attributes are reasonable for the dispatch. That is, setting the UDP + * attribute on a TCP socket isn't reasonable. + */ + +void +dns_dispatch_importrecv(dns_dispatch_t *disp, isc_event_t *event); +/*%< + * Inform the dispatcher of a socket receive. This is used for sockets + * shared between dispatchers and clients. If the dispatcher fails to copy + * or send the event, nothing happens. + * + * If the attribute DNS_DISPATCHATTR_NOLISTEN is not set, then + * the dispatch is already handling a recv; return immediately. + * + * Requires: + *\li disp is valid, and the attribute DNS_DISPATCHATTR_NOLISTEN is set. + * event != NULL + */ + +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, isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, 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_cancelall(dns_dispatchset_t *dset, isc_task_t *task); +/*%< + * Cancel socket operations for the dispatches in 'dset'. + */ + +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 + */ + +void +dns_dispatch_setdscp(dns_dispatch_t *disp, isc_dscp_t dscp); +isc_dscp_t +dns_dispatch_getdscp(dns_dispatch_t *disp); +/*%< + * Set/get the DSCP value to be used when sending responses to clients, + * as defined in the "listen-on" or "listen-on-v6" statements. + * + * Requires: + *\li disp is valid. + */ + +isc_result_t +dns_dispatch_getnext(dns_dispentry_t *resp, dns_dispatchevent_t **sockevent); +/*%< + * Free the sockevent and trigger the sending of the next item off the + * dispatch queue if present. + * + * Requires: + *\li resp is valid + *\li *sockevent to be valid + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_DISPATCH_H */ diff --git a/lib/dns/include/dns/dlz.h b/lib/dns/include/dns/dlz.h new file mode 100644 index 0000000..962db29 --- /dev/null +++ b/lib/dns/include/dns/dlz.h @@ -0,0 +1,336 @@ +/* + * 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 */ + +#ifndef DLZ_H +#define DLZ_H 1 + +/***** +***** 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 + +#endif /* DLZ_H */ diff --git a/lib/dns/include/dns/dlz_dlopen.h b/lib/dns/include/dns/dlz_dlopen.h new file mode 100644 index 0000000..058a23e --- /dev/null +++ b/lib/dns/include/dns/dlz_dlopen.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. + */ + +/*! \file dns/dlz_dlopen.h */ + +#ifndef DLZ_DLOPEN_H +#define DLZ_DLOPEN_H + +#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 + +#endif /* ifndef DLZ_DLOPEN_H */ diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h new file mode 100644 index 0000000..07bc06f --- /dev/null +++ b/lib/dns/include/dns/dns64.h @@ -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. + */ + +#ifndef DNS_DNS64_H +#define DNS_DNS64_H 1 + +#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, const 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, const 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_LANG_ENDDECLS + +#endif /* DNS_DNS64_H */ diff --git a/lib/dns/include/dns/dnsrps.h b/lib/dns/include/dns/dnsrps.h new file mode 100644 index 0000000..aa903b8 --- /dev/null +++ b/lib/dns/include/dns/dnsrps.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DNSRPS_H +#define DNS_DNSRPS_H + +#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 + +#endif /* DNS_DNSRPS_H */ diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h new file mode 100644 index 0000000..9791ef1 --- /dev/null +++ b/lib/dns/include/dns/dnssec.h @@ -0,0 +1,401 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DNSSEC_H +#define DNS_DNSSEC_H 1 + +/*! \file dns/dnssec.h */ + +#include + +#include +#include +#include + +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +LIBDNS_EXTERNAL_DATA 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 *, ...)); +/*%< + * 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 + +#endif /* DNS_DNSSEC_H */ diff --git a/lib/dns/include/dns/dnstap.h b/lib/dns/include/dns/dnstap.h new file mode 100644 index 0000000..e1da53d --- /dev/null +++ b/lib/dns/include/dns/dnstap.h @@ -0,0 +1,396 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 _DNSTAP_H +#define _DNSTAP_H + +/***** +***** 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 + */ + +#endif /* _DNSTAP_H */ diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h new file mode 100644 index 0000000..6e02d4c --- /dev/null +++ b/lib/dns/include/dns/ds.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DS_H +#define DNS_DS_H 1 + +#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 + +#endif /* DNS_DS_H */ diff --git a/lib/dns/include/dns/dsdigest.h b/lib/dns/include/dns/dsdigest.h new file mode 100644 index 0000000..b466fed --- /dev/null +++ b/lib/dns/include/dns/dsdigest.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. + */ + +#ifndef DNS_DSDIGEST_H +#define DNS_DSDIGEST_H 1 + +/*! \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 + +#endif /* DNS_DSDIGEST_H */ diff --git a/lib/dns/include/dns/dyndb.h b/lib/dns/include/dns/dyndb.h new file mode 100644 index 0000000..cc2820a --- /dev/null +++ b/lib/dns/include/dns/dyndb.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_DYNDB_H +#define DNS_DYNDB_H + +#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; + unsigned int *memdebug; +}; + +#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 + +#endif /* DNS_DYNDB_H */ diff --git a/lib/dns/include/dns/ecdb.h b/lib/dns/include/dns/ecdb.h new file mode 100644 index 0000000..d94cbac --- /dev/null +++ b/lib/dns/include/dns/ecdb.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. + */ + +#ifndef DNS_ECDB_H +#define DNS_ECDB_H 1 + +/***** +***** Module Info +*****/ + +/* TBD */ + +/*** + *** Imports + ***/ + +#include + +/*** + *** Types + ***/ + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +/* TBD: describe those */ + +isc_result_t +dns_ecdb_register(isc_mem_t *mctx, dns_dbimplementation_t **dbimp); + +void +dns_ecdb_unregister(dns_dbimplementation_t **dbimp); + +ISC_LANG_ENDDECLS + +#endif /* DNS_ECDB_H */ diff --git a/lib/dns/include/dns/ecs.h b/lib/dns/include/dns/ecs.h new file mode 100644 index 0000000..b4b0d6b --- /dev/null +++ b/lib/dns/include/dns/ecs.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ECS_H +#define DNS_ECS_H 1 + +#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 + +#endif /* DNS_ECS_H */ diff --git a/lib/dns/include/dns/edns.h b/lib/dns/include/dns/edns.h new file mode 100644 index 0000000..214abe8 --- /dev/null +++ b/lib/dns/include/dns/edns.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. + */ + +#ifndef DNS_EDNS_H +#define DNS_EDNS_H 1 + +/*% + * 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 */ + +#endif /* ifndef DNS_EDNS_H */ diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h new file mode 100644 index 0000000..608f35f --- /dev/null +++ b/lib/dns/include/dns/events.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. + */ + +#ifndef DNS_EVENTS_H +#define DNS_EVENTS_H 1 + +#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) + +#endif /* DNS_EVENTS_H */ diff --git a/lib/dns/include/dns/fixedname.h b/lib/dns/include/dns/fixedname.h new file mode 100644 index 0000000..edd55f4 --- /dev/null +++ b/lib/dns/include/dns/fixedname.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_FIXEDNAME_H +#define DNS_FIXEDNAME_H 1 + +/***** +***** 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 + +#endif /* DNS_FIXEDNAME_H */ diff --git a/lib/dns/include/dns/forward.h b/lib/dns/include/dns/forward.h new file mode 100644 index 0000000..12430c3 --- /dev/null +++ b/lib/dns/include/dns/forward.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_FORWARD_H +#define DNS_FORWARD_H 1 + +/*! \file dns/forward.h */ + +#include +#include +#include + +#include + +ISC_LANG_BEGINDECLS + +struct dns_forwarder { + isc_sockaddr_t addr; + isc_dscp_t dscp; + 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 + */ + +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 + * \li #ISC_R_NOTFOUND + */ + +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 + +#endif /* DNS_FORWARD_H */ diff --git a/lib/dns/include/dns/geoip.h b/lib/dns/include/dns/geoip.h new file mode 100644 index 0000000..721c297 --- /dev/null +++ b/lib/dns/include/dns/geoip.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_GEOIP_H +#define DNS_GEOIP_H 1 + +/***** +***** 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 */ + +#endif /* DNS_GEOIP_H */ diff --git a/lib/dns/include/dns/ipkeylist.h b/lib/dns/include/dns/ipkeylist.h new file mode 100644 index 0000000..b3dbfb6 --- /dev/null +++ b/lib/dns/include/dns/ipkeylist.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. + */ + +#ifndef DNS_IPKEYLIST_H +#define DNS_IPKEYLIST_H 1 + +#include + +#include + +#include + +/*% + * A structure holding a list of addresses, dscps and keys. Used to + * store masters for a slave zone, created by parsing config options. + */ +struct dns_ipkeylist { + isc_sockaddr_t *addrs; + isc_dscp_t *dscps; + dns_name_t **keys; + 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 + */ + +#endif /* ifndef DNS_IPKEYLIST_H */ diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h new file mode 100644 index 0000000..83ecf04 --- /dev/null +++ b/lib/dns/include/dns/iptable.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. + */ + +#ifndef DNS_IPTABLE_H +#define DNS_IPTABLE_H 1 + +#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 + +#endif /* DNS_IPTABLE_H */ diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h new file mode 100644 index 0000000..2b9875e --- /dev/null +++ b/lib/dns/include/dns/journal.h @@ -0,0 +1,341 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_JOURNAL_H +#define DNS_JOURNAL_H 1 + +/***** +***** 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 + +#endif /* DNS_JOURNAL_H */ diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h new file mode 100644 index 0000000..48d773a --- /dev/null +++ b/lib/dns/include/dns/kasp.h @@ -0,0 +1,717 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_KASP_H +#define DNS_KASP_H 1 + +/***** +***** 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 (86400 * 5) +#define DNS_KASP_SIG_VALIDITY (86400 * 14) +#define DNS_KASP_SIG_VALIDITY_DNSKEY (86400 * 14) +#define DNS_KASP_KEY_TTL (3600) +#define DNS_KASP_DS_TTL (86400) +#define DNS_KASP_PUBLISH_SAFETY (3600) +#define DNS_KASP_PURGE_KEYS (86400 * 90) +#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); +/*%< + * Get maximum zone TTL. + * + * 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 + +#endif /* DNS_KASP_H */ diff --git a/lib/dns/include/dns/keydata.h b/lib/dns/include/dns/keydata.h new file mode 100644 index 0000000..43a3ef7 --- /dev/null +++ b/lib/dns/include/dns/keydata.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_KEYDATA_H +#define DNS_KEYDATA_H 1 + +/***** +***** 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 + +#endif /* DNS_KEYDATA_H */ diff --git a/lib/dns/include/dns/keyflags.h b/lib/dns/include/dns/keyflags.h new file mode 100644 index 0000000..a603b6f --- /dev/null +++ b/lib/dns/include/dns/keyflags.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. + */ + +#ifndef DNS_KEYFLAGS_H +#define DNS_KEYFLAGS_H 1 + +/*! \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 + +#endif /* DNS_KEYFLAGS_H */ diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h new file mode 100644 index 0000000..7b31eb8 --- /dev/null +++ b/lib/dns/include/dns/keymgr.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_KEYMGR_H +#define DNS_KEYMGR_H 1 + +/*! \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 + +#endif /* DNS_KEYMGR_H */ diff --git a/lib/dns/include/dns/keytable.h b/lib/dns/include/dns/keytable.h new file mode 100644 index 0000000..9557ab8 --- /dev/null +++ b/lib/dns/include/dns/keytable.h @@ -0,0 +1,350 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_KEYTABLE_H +#define DNS_KEYTABLE_H 1 + +/***** +***** 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 + +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); +/*%< + * 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); +/*%< + * 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 + +#endif /* DNS_KEYTABLE_H */ diff --git a/lib/dns/include/dns/keyvalues.h b/lib/dns/include/dns/keyvalues.h new file mode 100644 index 0000000..e27ada1 --- /dev/null +++ b/lib/dns/include/dns/keyvalues.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. + */ + +#ifndef DNS_KEYVALUES_H +#define DNS_KEYVALUES_H 1 + +/*! \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 + +#endif /* DNS_KEYVALUES_H */ diff --git a/lib/dns/include/dns/lib.h b/lib/dns/include/dns/lib.h new file mode 100644 index 0000000..649b483 --- /dev/null +++ b/lib/dns/include/dns/lib.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. + */ + +#ifndef DNS_LIB_H +#define DNS_LIB_H 1 + +/*! \file dns/lib.h */ + +#include +#include + +ISC_LANG_BEGINDECLS + +/*% + * Tuning: external query load in packets per seconds. + */ +LIBDNS_EXTERNAL_DATA extern unsigned int dns_pps; + +isc_result_t +dns_lib_init(void); +/*%< + * A set of initialization procedures used in the DNS library. This function + * is provided for an application that is not aware of the underlying ISC or + * DNS libraries much. + */ + +void +dns_lib_shutdown(void); +/*%< + * Free temporary resources allocated in dns_lib_init(). + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_LIB_H */ diff --git a/lib/dns/include/dns/librpz.h b/lib/dns/include/dns/librpz.h new file mode 100644 index 0000000..110b798 --- /dev/null +++ b/lib/dns/include/dns/librpz.h @@ -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. + */ + +/* + * 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 + */ + +#ifndef LIBRPZ_H +#define LIBRPZ_H + +#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 */ + +#ifdef HAVE_BUILTIN_EXPECT +#define LIBRPZ_LIKELY(c) __builtin_expect(!!(c), 1) +#define LIBRPZ_UNLIKELY(c) __builtin_expect(!!(c), 0) +#else /* ifdef HAVE_BUILTIN_EXPECT */ +#define LIBRPZ_LIKELY(c) (c) +#define LIBRPZ_UNLIKELY(c) (c) +#endif /* ifdef HAVE_BUILTIN_EXPECT */ + +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 */ + +#endif /* LIBRPZ_H */ diff --git a/lib/dns/include/dns/lmdb.h b/lib/dns/include/dns/lmdb.h new file mode 100644 index 0000000..f6986cf --- /dev/null +++ b/lib/dns/include/dns/lmdb.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 + +#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__ */ diff --git a/lib/dns/include/dns/log.h b/lib/dns/include/dns/log.h new file mode 100644 index 0000000..d78dda6 --- /dev/null +++ b/lib/dns/include/dns/log.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + */ + +#ifndef DNS_LOG_H +#define DNS_LOG_H 1 + +#include +#include + +LIBDNS_EXTERNAL_DATA extern isc_log_t *dns_lctx; +LIBDNS_EXTERNAL_DATA extern isc_logcategory_t dns_categories[]; +LIBDNS_EXTERNAL_DATA 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]) + +/* 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 + +#endif /* DNS_LOG_H */ diff --git a/lib/dns/include/dns/lookup.h b/lib/dns/include/dns/lookup.h new file mode 100644 index 0000000..2d1ce13 --- /dev/null +++ b/lib/dns/include/dns/lookup.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. + */ + +#ifndef DNS_LOOKUP_H +#define DNS_LOOKUP_H 1 + +/***** +***** 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 + +#endif /* DNS_LOOKUP_H */ diff --git a/lib/dns/include/dns/master.h b/lib/dns/include/dns/master.h new file mode 100644 index 0000000..78ebadd --- /dev/null +++ b/lib/dns/include/dns/master.h @@ -0,0 +1,264 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_MASTER_H +#define DNS_MASTER_H 1 + +/*! \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_SLAVE 0x00000020 /*%< Loading a slave 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 + * or + * dns_masterformat_map */ + 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 slave 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 + +#endif /* DNS_MASTER_H */ diff --git a/lib/dns/include/dns/masterdump.h b/lib/dns/include/dns/masterdump.h new file mode 100644 index 0000000..917e31f --- /dev/null +++ b/lib/dns/include/dns/masterdump.h @@ -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. + */ + +#ifndef DNS_MASTERDUMP_H +#define DNS_MASTERDUMP_H 1 + +/*! \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. + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_simple; + +/*% + * The style used for debugging, "dig" output, etc. + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_debug; + +/*% + * Similar to dns_master_style_debug but data is prepended with ";" + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_comment; + +/*% + * Similar to dns_master_style_debug but data is indented with "\t" (tab) + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_indent; + +/*% + * The style used for dumping "key" zones. + */ +LIBDNS_EXTERNAL_DATA extern const dns_master_style_t dns_master_style_keyzone; + +/*% + * YAML-compatible output + */ +LIBDNS_EXTERNAL_DATA 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 + +#endif /* DNS_MASTERDUMP_H */ diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h new file mode 100644 index 0000000..8214021 --- /dev/null +++ b/lib/dns/include/dns/message.h @@ -0,0 +1,1492 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 number of EDNS options we know about. */ +#define DNS_EDNSOPTIONS 7 + +#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; + const dns_acl_t *acl; + const dns_aclelement_t *element; +}; + +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 tkey : 1; + unsigned int rdclass_set : 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; + + dns_rdatasetorderfunc_t order; + dns_sortlist_arg_t order_arg; + + dns_indent_t indent; +}; + +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. + *\li #ISC_R_NOMEMORY -- No item can be allocated. + */ + +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. + *\li #ISC_R_NOMEMORY -- No item can be allocated. + */ + +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. + *\li #ISC_R_NOMEMORY -- No item can be allocated. + */ + +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. + *\li #ISC_R_NOMEMORY -- No item can be allocated. + */ + +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, const 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_LANG_ENDDECLS diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h new file mode 100644 index 0000000..683f71d --- /dev/null +++ b/lib/dns/include/dns/name.h @@ -0,0 +1,1410 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_NAME_H +#define DNS_NAME_H 1 + +/***** +***** 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. */ + +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_rootname; +LIBDNS_EXTERNAL_DATA 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'. + * + * \li If DNS_NAME_DOWNCASE is set, any uppercase letters in 'source' will be + * downcased when they are copied into 'target'. + * + * 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. + * + * 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 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: Too many compression pointers + * \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. + */ + +isc_result_t +dns_name_copy(const dns_name_t *source, dns_name_t *dest, isc_buffer_t *target); +/*%< + * Copies the name in 'source' into 'dest'. The name data is copied to + * the 'target' buffer, which is then set as the buffer for 'dest'. + * + * Requires: + * \li 'source' is a valid name. + * + * \li 'dest' is an initialized name. + * + * \li 'target' is an initialized buffer. + * + * Ensures: + * + *\li On success, the used space in target is updated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + */ + +void +dns_name_copynf(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'. + * + * 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 */ + +#endif /* DNS_NAME_H */ diff --git a/lib/dns/include/dns/ncache.h b/lib/dns/include/dns/ncache.h new file mode 100644 index 0000000..783808c --- /dev/null +++ b/lib/dns/include/dns/ncache.h @@ -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 DNS_NCACHE_H +#define DNS_NCACHE_H 1 + +/***** +***** 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 + +#endif /* DNS_NCACHE_H */ diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h new file mode 100644 index 0000000..efe2f94 --- /dev/null +++ b/lib/dns/include/dns/nsec.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_NSEC_H +#define DNS_NSEC_H 1 + +/*! \file dns/nsec.h */ + +#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, 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. + * + * 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. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_NSEC_H */ diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h new file mode 100644 index 0000000..3f20816 --- /dev/null +++ b/lib/dns/include/dns/nsec3.h @@ -0,0 +1,272 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_NSEC3_H +#define DNS_NSEC3_H 1 + +#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 + +#endif /* DNS_NSEC3_H */ diff --git a/lib/dns/include/dns/nta.h b/lib/dns/include/dns/nta.h new file mode 100644 index 0000000..905bf64 --- /dev/null +++ b/lib/dns/include/dns/nta.h @@ -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. + */ + +#ifndef DNS_NTA_H +#define DNS_NTA_H 1 + +/***** +***** 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_dump(dns_ntatable_t *ntatable, FILE *fp); +/*%< + * Dump the NTA table to the file opened as 'fp'. + */ + +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 + +#endif /* DNS_NTA_H */ diff --git a/lib/dns/include/dns/opcode.h b/lib/dns/include/dns/opcode.h new file mode 100644 index 0000000..8fc5020 --- /dev/null +++ b/lib/dns/include/dns/opcode.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. + */ + +#ifndef DNS_OPCODE_H +#define DNS_OPCODE_H 1 + +/*! \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 + +#endif /* DNS_OPCODE_H */ diff --git a/lib/dns/include/dns/order.h b/lib/dns/include/dns/order.h new file mode 100644 index 0000000..0d6c267 --- /dev/null +++ b/lib/dns/include/dns/order.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. + */ + +#ifndef DNS_ORDER_H +#define DNS_ORDER_H 1 + +/*! \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 + +#endif /* DNS_ORDER_H */ diff --git a/lib/dns/include/dns/peer.h b/lib/dns/include/dns/peer.h new file mode 100644 index 0000000..5f9a4de --- /dev/null +++ b/lib/dns/include/dns/peer.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. + */ + +#ifndef DNS_PEER_H +#define DNS_PEER_H 1 + +/***** +***** 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) + +/*** + *** 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; + dns_name_t *key; + isc_sockaddr_t *transfer_source; + isc_dscp_t transfer_dscp; + isc_sockaddr_t *notify_source; + isc_dscp_t notify_dscp; + isc_sockaddr_t *query_source; + isc_dscp_t query_dscp; + 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; +}; + +/*** + *** 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_setnotifydscp(dns_peer_t *peer, isc_dscp_t dscp); + +isc_result_t +dns_peer_getnotifydscp(dns_peer_t *peer, isc_dscp_t *dscpp); + +isc_result_t +dns_peer_settransferdscp(dns_peer_t *peer, isc_dscp_t dscp); + +isc_result_t +dns_peer_gettransferdscp(dns_peer_t *peer, isc_dscp_t *dscpp); + +isc_result_t +dns_peer_setquerydscp(dns_peer_t *peer, isc_dscp_t dscp); + +isc_result_t +dns_peer_getquerydscp(dns_peer_t *peer, isc_dscp_t *dscpp); + +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 + +#endif /* DNS_PEER_H */ diff --git a/lib/dns/include/dns/portlist.h b/lib/dns/include/dns/portlist.h new file mode 100644 index 0000000..03306db --- /dev/null +++ b/lib/dns/include/dns/portlist.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. + */ + +/*! \file dns/portlist.h */ + +#include + +#include +#include +#include + +#include + +#ifndef DNS_PORTLIST_H +#define DNS_PORTLIST_H 1 + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp); +/*%< + * Create a port list. + * + * Requires: + *\li 'mctx' to be valid. + *\li 'portlistp' to be non NULL and '*portlistp' to be NULL; + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port); +/*%< + * Add the given tuple to the portlist. + * + * Requires: + *\li 'portlist' to be valid. + *\li 'af' to be AF_INET or AF_INET6 + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port); +/*%< + * Remove the given tuple to the portlist. + * + * Requires: + *\li 'portlist' to be valid. + *\li 'af' to be AF_INET or AF_INET6 + */ + +bool +dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port); +/*%< + * Find the given tuple to the portlist. + * + * Requires: + *\li 'portlist' to be valid. + *\li 'af' to be AF_INET or AF_INET6 + * + * Returns + * \li #true if the tuple is found, false otherwise. + */ + +void +dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp); +/*%< + * Attach to a port list. + * + * Requires: + *\li 'portlist' to be valid. + *\li 'portlistp' to be non NULL and '*portlistp' to be NULL; + */ + +void +dns_portlist_detach(dns_portlist_t **portlistp); +/*%< + * Detach from a port list. + * + * Requires: + *\li '*portlistp' to be valid. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_PORTLIST_H */ diff --git a/lib/dns/include/dns/private.h b/lib/dns/include/dns/private.h new file mode 100644 index 0000000..8e1c4c7 --- /dev/null +++ b/lib/dns/include/dns/private.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. + */ + +#include + +#include +#include + +#include +#include + +#ifndef DNS_PRIVATE_H +#define DNS_PRIVATE_H + +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 + +#endif /* ifndef DNS_PRIVATE_H */ diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h new file mode 100644 index 0000000..4a6b078 --- /dev/null +++ b/lib/dns/include/dns/rbt.h @@ -0,0 +1,1060 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RBT_H +#define DNS_RBT_H 1 + +/*! \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 */ + /*@}*/ + + /* flags needed for serialization to file */ + unsigned int is_mmapped : 1; + unsigned int parent_is_relative : 1; + unsigned int left_is_relative : 1; + unsigned int right_is_relative : 1; + unsigned int down_is_relative : 1; + unsigned int data_is_relative : 1; + + /* + * full name length; set during serialization, and used + * during deserialization to calculate database size. + * should be cleared after use. + */ + unsigned int fullnamelen : 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. + */ + +isc_result_t +dns_rbt_adjusthashsize(dns_rbt_t *rbt, size_t size); +/*%< + * Adjust the number of buckets in the 'rbt' hash table, according to the + * expected maximum size of the rbt database. + * + * Requires: + * \li rbt is a valid rbt manager. + * \li size is expected maximum memory footprint of rbt. + */ + +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. + */ + +off_t +dns_rbt_serialize_align(off_t target); +/*%< + * Align the provided integer to a pointer-size boundary. + * This should be used if, during serialization of data to a will-be + * mmap()ed file, a pointer alignment is needed for some data. + */ + +isc_result_t +dns_rbt_serialize_tree(FILE *file, dns_rbt_t *rbt, + dns_rbtdatawriter_t datawriter, void *writer_arg, + off_t *offset); +/*%< + * Write out the RBT structure and its data to a file. + * + * Notes: + * \li The file must be an actual file which allows seek() calls, so it cannot + * be a stream. Returns ISC_R_INVALIDFILE if not. + */ + +isc_result_t +dns_rbt_deserialize_tree(void *base_address, size_t filesize, + off_t header_offset, isc_mem_t *mctx, + dns_rbtdeleter_t deleter, void *deleter_arg, + dns_rbtdatafixer_t datafixer, void *fixer_arg, + dns_rbtnode_t **originp, dns_rbt_t **rbtp); +/*%< + * Read a RBT structure and its data from a file. + * + * If 'originp' is not NULL, then it is pointed to the root node of the RBT. + * + * Notes: + * \li The file must be an actual file which allows seek() calls, so it cannot + * be a stream. This condition is not checked in the code. + */ + +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 + +#endif /* DNS_RBT_H */ diff --git a/lib/dns/include/dns/rcode.h b/lib/dns/include/dns/rcode.h new file mode 100644 index 0000000..dbc2dfe --- /dev/null +++ b/lib/dns/include/dns/rcode.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RCODE_H +#define DNS_RCODE_H 1 + +/*! \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 + +#endif /* DNS_RCODE_H */ diff --git a/lib/dns/include/dns/rdata.h b/lib/dns/include/dns/rdata.h new file mode 100644 index 0000000..2118dc4 --- /dev/null +++ b/lib/dns/include/dns/rdata.h @@ -0,0 +1,812 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RDATA_H +#define DNS_RDATA_H 1 + +/***** +***** 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, 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 + +#endif /* DNS_RDATA_H */ diff --git a/lib/dns/include/dns/rdataclass.h b/lib/dns/include/dns/rdataclass.h new file mode 100644 index 0000000..033d08d --- /dev/null +++ b/lib/dns/include/dns/rdataclass.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. + */ + +#ifndef DNS_RDATACLASS_H +#define DNS_RDATACLASS_H 1 + +/*! \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 + +#endif /* DNS_RDATACLASS_H */ diff --git a/lib/dns/include/dns/rdatalist.h b/lib/dns/include/dns/rdatalist.h new file mode 100644 index 0000000..3d49064 --- /dev/null +++ b/lib/dns/include/dns/rdatalist.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. + */ + +#ifndef DNS_RDATALIST_H +#define DNS_RDATALIST_H 1 + +/***** +***** 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 + +#endif /* DNS_RDATALIST_H */ diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h new file mode 100644 index 0000000..de3f638 --- /dev/null +++ b/lib/dns/include/dns/rdataset.h @@ -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. + */ + +#ifndef DNS_RDATASET_H +#define DNS_RDATASET_H 1 + +/***** +***** 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; + /*@}*/ +}; + +/*! + * \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, + 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 + +#endif /* DNS_RDATASET_H */ diff --git a/lib/dns/include/dns/rdatasetiter.h b/lib/dns/include/dns/rdatasetiter.h new file mode 100644 index 0000000..2df4aad --- /dev/null +++ b/lib/dns/include/dns/rdatasetiter.h @@ -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. + */ + +#ifndef DNS_RDATASETITER_H +#define DNS_RDATASETITER_H 1 + +/***** +***** 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 + +#endif /* DNS_RDATASETITER_H */ diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h new file mode 100644 index 0000000..5a1c30f --- /dev/null +++ b/lib/dns/include/dns/rdataslab.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RDATASLAB_H +#define DNS_RDATASLAB_H 1 + +/*! \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 + +#endif /* DNS_RDATASLAB_H */ diff --git a/lib/dns/include/dns/rdatatype.h b/lib/dns/include/dns/rdatatype.h new file mode 100644 index 0000000..008f4da --- /dev/null +++ b/lib/dns/include/dns/rdatatype.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RDATATYPE_H +#define DNS_RDATATYPE_H 1 + +/*! \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 + +#endif /* DNS_RDATATYPE_H */ diff --git a/lib/dns/include/dns/request.h b/lib/dns/include/dns/request.h new file mode 100644 index 0000000..f745f02 --- /dev/null +++ b/lib/dns/include/dns/request.h @@ -0,0 +1,365 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_REQUEST_H +#define DNS_REQUEST_H 1 + +/***** +***** 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 +#define DNS_REQUESTOPT_SHARE 0x00000008U + +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_timermgr_t *timermgr, + isc_socketmgr_t *socketmgr, 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 'timermgr' is a valid timer manager. + * + *\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 *address, unsigned int options, + dns_tsigkey_t *key, unsigned int timeout, 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. + * + *\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 'address' is a valid sockaddr. + * + *\li 'timeout' > 0 + * + *\li 'task' is a valid task. + * + *\li requestp != NULL && *requestp == NULL + */ + +isc_result_t +dns_request_createvia(dns_requestmgr_t *requestmgr, dns_message_t *message, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, isc_dscp_t dscp, + 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, isc_dscp_t dscp, + 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 + +#endif /* DNS_REQUEST_H */ diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h new file mode 100644 index 0000000..22ad9df --- /dev/null +++ b/lib/dns/include/dns/resolver.h @@ -0,0 +1,747 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RESOLVER_H +#define DNS_RESOLVER_H 1 + +/***** +***** 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 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. + */ +#define DNS_FETCHOPT_TCP 0x00000001 /*%< Use TCP. */ +#define DNS_FETCHOPT_UNSHARED 0x00000002 /*%< See below. */ +#define DNS_FETCHOPT_RECURSIVE 0x00000004 /*%< Set RD? */ +#define DNS_FETCHOPT_NOEDNS0 0x00000008 /*%< Do not use EDNS. */ +#define DNS_FETCHOPT_FORWARDONLY 0x00000010 /*%< Only use forwarders. */ +#define DNS_FETCHOPT_NOVALIDATE 0x00000020 /*%< Disable validation. */ +#define DNS_FETCHOPT_EDNS512 \ + 0x00000040 /*%< Advertise a 512 byte \ + * UDP buffer. */ +#define DNS_FETCHOPT_WANTNSID 0x00000080 /*%< Request NSID */ +#define DNS_FETCHOPT_PREFETCH 0x00000100 /*%< Do prefetch */ +#define DNS_FETCHOPT_NOCDFLAG 0x00000200 /*%< Don't set CD flag. */ +#define DNS_FETCHOPT_NONTA 0x00000400 /*%< Ignore NTA table. */ +/* RESERVED ECS 0x00000000 */ +/* RESERVED ECS 0x00001000 */ +/* RESERVED ECS 0x00002000 */ +/* RESERVED TCPCLIENT 0x00004000 */ +#define DNS_FETCHOPT_NOCACHED 0x00008000 /*%< Force cache update. */ +#define DNS_FETCHOPT_QMINIMIZE \ + 0x00010000 /*%< Use qname \ + * minimization. */ +#define DNS_FETCHOPT_NOFOLLOW \ + 0x00020000 /*%< Don't follow \ + * delegations */ +#define DNS_FETCHOPT_QMIN_STRICT \ + 0x00040000 /*%< Do not work around \ + * servers that return \ + * errors on non-empty \ + * terminals. */ +#define DNS_FETCHOPT_QMIN_USE_A \ + 0x00080000 /*%< Use A type queries \ + * instead of NS when \ + * doing minimization */ +#define DNS_FETCHOPT_QMIN_SKIP_IP6A \ + 0x00100000 /*%< Skip some labels \ + * when doing qname \ + * minimization on \ + * ip6.arpa. */ +#define DNS_FETCHOPT_NOFORWARD \ + 0x00200000 /*%< Do not use forwarders \ + * if possible. */ + +/* Reserved in use by adb.c 0x00400000 */ +#define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000 +#define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000 +#define DNS_FETCHOPT_EDNSVERSIONSHIFT 24 +#define DNS_FETCHOPT_TRYSTALE_ONTIMEOUT 0x01000000 + +/* + * 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_socketmgr_t *socketmgr, 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 'socketmgr' is a valid socket 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_socketmgr_t * +dns_resolver_socketmgr(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. + */ + +isc_result_t +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_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp); +isc_dscp_t +dns_resolver_getquerydscp4(dns_resolver_t *resolver); + +void +dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp); +isc_dscp_t +dns_resolver_getquerydscp6(dns_resolver_t *resolver); +/*% + * Get and set the DSCP values for the resolver's IPv4 and IPV6 query + * sources. + * + * 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 + +#endif /* DNS_RESOLVER_H */ diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h new file mode 100644 index 0000000..0ec874b --- /dev/null +++ b/lib/dns/include/dns/result.h @@ -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 DNS_RESULT_H +#define DNS_RESULT_H 1 + +/*! \file dns/result.h */ + +#include +#include + +#include + +/* + * Nothing in this file truly depends on , but the + * DNS result codes are considered to be publicly derived from + * the ISC result codes, so including this file buys you the ISC_R_ + * namespace too. + */ +#include /* Contractual promise. */ + +/* + * DNS library result codes + */ +#define DNS_R_LABELTOOLONG (ISC_RESULTCLASS_DNS + 0) +#define DNS_R_BADESCAPE (ISC_RESULTCLASS_DNS + 1) +/* + * Since we dropped the support of bitstring labels, deprecate the related + * result codes too. + * + #define DNS_R_BADBITSTRING (ISC_RESULTCLASS_DNS + 2) + #define DNS_R_BITSTRINGTOOLONG (ISC_RESULTCLASS_DNS + 3) + */ +#define DNS_R_EMPTYLABEL (ISC_RESULTCLASS_DNS + 4) +#define DNS_R_BADDOTTEDQUAD (ISC_RESULTCLASS_DNS + 5) +#define DNS_R_INVALIDNS (ISC_RESULTCLASS_DNS + 6) +#define DNS_R_UNKNOWN (ISC_RESULTCLASS_DNS + 7) +#define DNS_R_BADLABELTYPE (ISC_RESULTCLASS_DNS + 8) +#define DNS_R_BADPOINTER (ISC_RESULTCLASS_DNS + 9) +#define DNS_R_TOOMANYHOPS (ISC_RESULTCLASS_DNS + 10) +#define DNS_R_DISALLOWED (ISC_RESULTCLASS_DNS + 11) +#define DNS_R_EXTRATOKEN (ISC_RESULTCLASS_DNS + 12) +#define DNS_R_EXTRADATA (ISC_RESULTCLASS_DNS + 13) +#define DNS_R_TEXTTOOLONG (ISC_RESULTCLASS_DNS + 14) +#define DNS_R_NOTZONETOP (ISC_RESULTCLASS_DNS + 15) +#define DNS_R_SYNTAX (ISC_RESULTCLASS_DNS + 16) +#define DNS_R_BADCKSUM (ISC_RESULTCLASS_DNS + 17) +#define DNS_R_BADAAAA (ISC_RESULTCLASS_DNS + 18) +#define DNS_R_NOOWNER (ISC_RESULTCLASS_DNS + 19) +#define DNS_R_NOTTL (ISC_RESULTCLASS_DNS + 20) +#define DNS_R_BADCLASS (ISC_RESULTCLASS_DNS + 21) +#define DNS_R_NAMETOOLONG (ISC_RESULTCLASS_DNS + 22) +#define DNS_R_PARTIALMATCH (ISC_RESULTCLASS_DNS + 23) +#define DNS_R_NEWORIGIN (ISC_RESULTCLASS_DNS + 24) +#define DNS_R_UNCHANGED (ISC_RESULTCLASS_DNS + 25) +#define DNS_R_BADTTL (ISC_RESULTCLASS_DNS + 26) +#define DNS_R_NOREDATA (ISC_RESULTCLASS_DNS + 27) +#define DNS_R_CONTINUE (ISC_RESULTCLASS_DNS + 28) +#define DNS_R_DELEGATION (ISC_RESULTCLASS_DNS + 29) +#define DNS_R_GLUE (ISC_RESULTCLASS_DNS + 30) +#define DNS_R_DNAME (ISC_RESULTCLASS_DNS + 31) +#define DNS_R_CNAME (ISC_RESULTCLASS_DNS + 32) +#define DNS_R_BADDB (ISC_RESULTCLASS_DNS + 33) +#define DNS_R_ZONECUT (ISC_RESULTCLASS_DNS + 34) +#define DNS_R_BADZONE (ISC_RESULTCLASS_DNS + 35) +#define DNS_R_MOREDATA (ISC_RESULTCLASS_DNS + 36) +#define DNS_R_UPTODATE (ISC_RESULTCLASS_DNS + 37) +#define DNS_R_TSIGVERIFYFAILURE (ISC_RESULTCLASS_DNS + 38) +#define DNS_R_TSIGERRORSET (ISC_RESULTCLASS_DNS + 39) +#define DNS_R_SIGINVALID (ISC_RESULTCLASS_DNS + 40) +#define DNS_R_SIGEXPIRED (ISC_RESULTCLASS_DNS + 41) +#define DNS_R_SIGFUTURE (ISC_RESULTCLASS_DNS + 42) +#define DNS_R_KEYUNAUTHORIZED (ISC_RESULTCLASS_DNS + 43) +#define DNS_R_INVALIDTIME (ISC_RESULTCLASS_DNS + 44) +#define DNS_R_EXPECTEDTSIG (ISC_RESULTCLASS_DNS + 45) +#define DNS_R_UNEXPECTEDTSIG (ISC_RESULTCLASS_DNS + 46) +#define DNS_R_INVALIDTKEY (ISC_RESULTCLASS_DNS + 47) +#define DNS_R_HINT (ISC_RESULTCLASS_DNS + 48) +#define DNS_R_DROP (ISC_RESULTCLASS_DNS + 49) +#define DNS_R_NOTLOADED (ISC_RESULTCLASS_DNS + 50) +#define DNS_R_NCACHENXDOMAIN (ISC_RESULTCLASS_DNS + 51) +#define DNS_R_NCACHENXRRSET (ISC_RESULTCLASS_DNS + 52) +#define DNS_R_WAIT (ISC_RESULTCLASS_DNS + 53) +#define DNS_R_NOTVERIFIEDYET (ISC_RESULTCLASS_DNS + 54) +#define DNS_R_NOIDENTITY (ISC_RESULTCLASS_DNS + 55) +#define DNS_R_NOJOURNAL (ISC_RESULTCLASS_DNS + 56) +#define DNS_R_ALIAS (ISC_RESULTCLASS_DNS + 57) +#define DNS_R_USETCP (ISC_RESULTCLASS_DNS + 58) +#define DNS_R_NOVALIDSIG (ISC_RESULTCLASS_DNS + 59) +#define DNS_R_NOVALIDNSEC (ISC_RESULTCLASS_DNS + 60) +#define DNS_R_NOTINSECURE (ISC_RESULTCLASS_DNS + 61) +#define DNS_R_UNKNOWNSERVICE (ISC_RESULTCLASS_DNS + 62) +#define DNS_R_RECOVERABLE (ISC_RESULTCLASS_DNS + 63) +#define DNS_R_UNKNOWNOPT (ISC_RESULTCLASS_DNS + 64) +#define DNS_R_UNEXPECTEDID (ISC_RESULTCLASS_DNS + 65) +#define DNS_R_SEENINCLUDE (ISC_RESULTCLASS_DNS + 66) +#define DNS_R_NOTEXACT (ISC_RESULTCLASS_DNS + 67) +#define DNS_R_BLACKHOLED (ISC_RESULTCLASS_DNS + 68) +#define DNS_R_BADALG (ISC_RESULTCLASS_DNS + 69) +#define DNS_R_METATYPE (ISC_RESULTCLASS_DNS + 70) +#define DNS_R_CNAMEANDOTHER (ISC_RESULTCLASS_DNS + 71) +#define DNS_R_SINGLETON (ISC_RESULTCLASS_DNS + 72) +#define DNS_R_HINTNXRRSET (ISC_RESULTCLASS_DNS + 73) +#define DNS_R_NOMASTERFILE (ISC_RESULTCLASS_DNS + 74) +#define DNS_R_UNKNOWNPROTO (ISC_RESULTCLASS_DNS + 75) +#define DNS_R_CLOCKSKEW (ISC_RESULTCLASS_DNS + 76) +#define DNS_R_BADIXFR (ISC_RESULTCLASS_DNS + 77) +#define DNS_R_NOTAUTHORITATIVE (ISC_RESULTCLASS_DNS + 78) +#define DNS_R_NOVALIDKEY (ISC_RESULTCLASS_DNS + 79) +#define DNS_R_OBSOLETE (ISC_RESULTCLASS_DNS + 80) +#define DNS_R_FROZEN (ISC_RESULTCLASS_DNS + 81) +#define DNS_R_UNKNOWNFLAG (ISC_RESULTCLASS_DNS + 82) +#define DNS_R_EXPECTEDRESPONSE (ISC_RESULTCLASS_DNS + 83) +#define DNS_R_NOVALIDDS (ISC_RESULTCLASS_DNS + 84) +#define DNS_R_NSISADDRESS (ISC_RESULTCLASS_DNS + 85) +#define DNS_R_REMOTEFORMERR (ISC_RESULTCLASS_DNS + 86) +#define DNS_R_TRUNCATEDTCP (ISC_RESULTCLASS_DNS + 87) +#define DNS_R_LAME (ISC_RESULTCLASS_DNS + 88) +#define DNS_R_UNEXPECTEDRCODE (ISC_RESULTCLASS_DNS + 89) +#define DNS_R_UNEXPECTEDOPCODE (ISC_RESULTCLASS_DNS + 90) +#define DNS_R_CHASEDSSERVERS (ISC_RESULTCLASS_DNS + 91) +#define DNS_R_EMPTYNAME (ISC_RESULTCLASS_DNS + 92) +#define DNS_R_EMPTYWILD (ISC_RESULTCLASS_DNS + 93) +#define DNS_R_BADBITMAP (ISC_RESULTCLASS_DNS + 94) +#define DNS_R_FROMWILDCARD (ISC_RESULTCLASS_DNS + 95) +#define DNS_R_BADOWNERNAME (ISC_RESULTCLASS_DNS + 96) +#define DNS_R_BADNAME (ISC_RESULTCLASS_DNS + 97) +#define DNS_R_DYNAMIC (ISC_RESULTCLASS_DNS + 98) +#define DNS_R_UNKNOWNCOMMAND (ISC_RESULTCLASS_DNS + 99) +#define DNS_R_MUSTBESECURE (ISC_RESULTCLASS_DNS + 100) +#define DNS_R_COVERINGNSEC (ISC_RESULTCLASS_DNS + 101) +#define DNS_R_MXISADDRESS (ISC_RESULTCLASS_DNS + 102) +#define DNS_R_DUPLICATE (ISC_RESULTCLASS_DNS + 103) +#define DNS_R_INVALIDNSEC3 (ISC_RESULTCLASS_DNS + 104) +#define DNS_R_NOTMASTER (ISC_RESULTCLASS_DNS + 105) +#define DNS_R_BROKENCHAIN (ISC_RESULTCLASS_DNS + 106) +#define DNS_R_EXPIRED (ISC_RESULTCLASS_DNS + 107) +#define DNS_R_NOTDYNAMIC (ISC_RESULTCLASS_DNS + 108) +#define DNS_R_BADEUI (ISC_RESULTCLASS_DNS + 109) +#define DNS_R_NTACOVERED (ISC_RESULTCLASS_DNS + 110) +#define DNS_R_BADCDS (ISC_RESULTCLASS_DNS + 111) +#define DNS_R_BADCDNSKEY (ISC_RESULTCLASS_DNS + 112) +#define DNS_R_OPTERR (ISC_RESULTCLASS_DNS + 113) +#define DNS_R_BADDNSTAP (ISC_RESULTCLASS_DNS + 114) +#define DNS_R_BADTSIG (ISC_RESULTCLASS_DNS + 115) +#define DNS_R_BADSIG0 (ISC_RESULTCLASS_DNS + 116) +#define DNS_R_TOOMANYRECORDS (ISC_RESULTCLASS_DNS + 117) +#define DNS_R_VERIFYFAILURE (ISC_RESULTCLASS_DNS + 118) +#define DNS_R_ATZONETOP (ISC_RESULTCLASS_DNS + 119) +#define DNS_R_NOKEYMATCH (ISC_RESULTCLASS_DNS + 120) +#define DNS_R_TOOMANYKEYS (ISC_RESULTCLASS_DNS + 121) +#define DNS_R_KEYNOTACTIVE (ISC_RESULTCLASS_DNS + 122) +#define DNS_R_NSEC3ITERRANGE (ISC_RESULTCLASS_DNS + 123) +#define DNS_R_NSEC3SALTRANGE (ISC_RESULTCLASS_DNS + 124) +#define DNS_R_NSEC3BADALG (ISC_RESULTCLASS_DNS + 125) +#define DNS_R_NSEC3RESALT (ISC_RESULTCLASS_DNS + 126) +#define DNS_R_INCONSISTENTRR (ISC_RESULTCLASS_DNS + 127) + +#define DNS_R_NRESULTS 128 /*%< Number of results */ + +/* + * DNS wire format rcodes. + * + * By making these their own class we can easily convert them into the + * wire-format rcode value simply by masking off the resultclass. + */ +#define DNS_R_NOERROR (ISC_RESULTCLASS_DNSRCODE + 0) +#define DNS_R_FORMERR (ISC_RESULTCLASS_DNSRCODE + 1) +#define DNS_R_SERVFAIL (ISC_RESULTCLASS_DNSRCODE + 2) +#define DNS_R_NXDOMAIN (ISC_RESULTCLASS_DNSRCODE + 3) +#define DNS_R_NOTIMP (ISC_RESULTCLASS_DNSRCODE + 4) +#define DNS_R_REFUSED (ISC_RESULTCLASS_DNSRCODE + 5) +#define DNS_R_YXDOMAIN (ISC_RESULTCLASS_DNSRCODE + 6) +#define DNS_R_YXRRSET (ISC_RESULTCLASS_DNSRCODE + 7) +#define DNS_R_NXRRSET (ISC_RESULTCLASS_DNSRCODE + 8) +#define DNS_R_NOTAUTH (ISC_RESULTCLASS_DNSRCODE + 9) +#define DNS_R_NOTZONE (ISC_RESULTCLASS_DNSRCODE + 10) +#define DNS_R_RCODE11 (ISC_RESULTCLASS_DNSRCODE + 11) +#define DNS_R_RCODE12 (ISC_RESULTCLASS_DNSRCODE + 12) +#define DNS_R_RCODE13 (ISC_RESULTCLASS_DNSRCODE + 13) +#define DNS_R_RCODE14 (ISC_RESULTCLASS_DNSRCODE + 14) +#define DNS_R_RCODE15 (ISC_RESULTCLASS_DNSRCODE + 15) +#define DNS_R_BADVERS (ISC_RESULTCLASS_DNSRCODE + 16) + +#define DNS_R_NRCODERESULTS 17 /*%< Number of rcode results */ + +#define DNS_RESULT_ISRCODE(result) \ + (ISC_RESULTCLASS_INCLASS(ISC_RESULTCLASS_DNSRCODE, (result))) + +ISC_LANG_BEGINDECLS + +const char *dns_result_totext(isc_result_t); + +void +dns_result_register(void); + +dns_rcode_t +dns_result_torcode(isc_result_t result); + +ISC_LANG_ENDDECLS + +#endif /* DNS_RESULT_H */ diff --git a/lib/dns/include/dns/rootns.h b/lib/dns/include/dns/rootns.h new file mode 100644 index 0000000..140538b --- /dev/null +++ b/lib/dns/include/dns/rootns.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. + */ + +#ifndef DNS_ROOTNS_H +#define DNS_ROOTNS_H 1 + +/*! \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 + +#endif /* DNS_ROOTNS_H */ diff --git a/lib/dns/include/dns/rpz.h b/lib/dns/include/dns/rpz.h new file mode 100644 index 0000000..f0cc578 --- /dev/null +++ b/lib/dns/include/dns/rpz.h @@ -0,0 +1,435 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RPZ_H +#define DNS_RPZ_H 1 + +#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 { + isc_refcount_t refs; + 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/waiting */ + bool updaterunning; /* there is an update running */ + 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 currently working + * on */ + dns_dbiterator_t *updbit; /* iterator to use when updating */ + isc_ht_t *newnodes; /* entries in zone being updated */ + bool db_registered; /* is the notify event + * registered? */ + 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; + unsigned int min_ns_labels; + dns_rpz_num_t num_zones; +}; + +/* + * Response policy zones known to a view. + */ +struct dns_rpz_zones { + 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; + + isc_mem_t *mctx; + isc_taskmgr_t *taskmgr; + isc_timermgr_t *timermgr; + isc_task_t *updater; + isc_refcount_t refs; + isc_refcount_t irefs; + /* + * 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; + + 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(dns_rpz_zones_t **rpzsp, char *rps_cstr, size_t rps_cstr_size, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr); + +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_attach_rpzs(dns_rpz_zones_t *source, dns_rpz_zones_t **target); + +void +dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp); + +isc_result_t +dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num) ISC_DEPRECATED; + +isc_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp, + dns_rpz_num_t rpz_num) ISC_DEPRECATED; + +isc_result_t +dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + const dns_name_t *name); + +void +dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + const dns_name_t *name); + +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 + +#endif /* DNS_RPZ_H */ diff --git a/lib/dns/include/dns/rriterator.h b/lib/dns/include/dns/rriterator.h new file mode 100644 index 0000000..2294d69 --- /dev/null +++ b/lib/dns/include/dns/rriterator.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RRITERATOR_H +#define DNS_RRITERATOR_H 1 + +/***** +***** 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 + +#endif /* DNS_RRITERATOR_H */ diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h new file mode 100644 index 0000000..fabefa3 --- /dev/null +++ b/lib/dns/include/dns/rrl.h @@ -0,0 +1,272 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RRL_H +#define DNS_RRL_H 1 + +/* + * 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 + +#endif /* DNS_RRL_H */ diff --git a/lib/dns/include/dns/sdb.h b/lib/dns/include/dns/sdb.h new file mode 100644 index 0000000..578d7cc --- /dev/null +++ b/lib/dns/include/dns/sdb.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_SDB_H +#define DNS_SDB_H 1 + +/***** +***** 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 master 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 + +#endif /* DNS_SDB_H */ diff --git a/lib/dns/include/dns/sdlz.h b/lib/dns/include/dns/sdlz.h new file mode 100644 index 0000000..6a06c98 --- /dev/null +++ b/lib/dns/include/dns/sdlz.h @@ -0,0 +1,359 @@ +/* + * 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 */ + +#ifndef SDLZ_H +#define SDLZ_H 1 + +#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 master 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 + +#endif /* SDLZ_H */ diff --git a/lib/dns/include/dns/secalg.h b/lib/dns/include/dns/secalg.h new file mode 100644 index 0000000..6ca61cb --- /dev/null +++ b/lib/dns/include/dns/secalg.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. + */ + +#ifndef DNS_SECALG_H +#define DNS_SECALG_H 1 + +/*! \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 + +#endif /* DNS_SECALG_H */ diff --git a/lib/dns/include/dns/secproto.h b/lib/dns/include/dns/secproto.h new file mode 100644 index 0000000..7eccfab --- /dev/null +++ b/lib/dns/include/dns/secproto.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. + */ + +#ifndef DNS_SECPROTO_H +#define DNS_SECPROTO_H 1 + +/*! \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 + +#endif /* DNS_SECPROTO_H */ diff --git a/lib/dns/include/dns/soa.h b/lib/dns/include/dns/soa.h new file mode 100644 index 0000000..a80a132 --- /dev/null +++ b/lib/dns/include/dns/soa.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. + */ + +#ifndef DNS_SOA_H +#define DNS_SOA_H 1 + +/***** +***** 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 + +#endif /* DNS_SOA_H */ diff --git a/lib/dns/include/dns/ssu.h b/lib/dns/include/dns/ssu.h new file mode 100644 index 0000000..11d40f3 --- /dev/null +++ b/lib/dns/include/dns/ssu.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. + */ + +#ifndef DNS_SSU_H +#define DNS_SSU_H 1 + +/*! \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_max = 15, /* max value */ + + dns_ssumatchtype_dlz = 16 /* intentionally higher than _max */ +} dns_ssumatchtype_t; + +isc_result_t +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 + */ + +isc_result_t +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. + */ + +isc_result_t +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_rdatatype_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, const dns_aclenv_t *env, dns_rdatatype_t type, + const dst_key_t *key); +/*%< + * 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_rdatatype_t **types); + +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 + +#endif /* DNS_SSU_H */ diff --git a/lib/dns/include/dns/stats.h b/lib/dns/include/dns/stats.h new file mode 100644 index 0000000..fd1697e --- /dev/null +++ b/lib/dns/include/dns/stats.h @@ -0,0 +1,826 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_STATS_H +#define DNS_STATS_H 1 + +/*! \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_nextitem = 43, + dns_resstatscounter_priming = 44, + dns_resstatscounter_max = 45, + + /* + * 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_max = 7, + + /*% + * 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) + */ +LIBDNS_EXTERNAL_DATA 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 + +#endif /* DNS_STATS_H */ diff --git a/lib/dns/include/dns/tcpmsg.h b/lib/dns/include/dns/tcpmsg.h new file mode 100644 index 0000000..df98cd7 --- /dev/null +++ b/lib/dns/include/dns/tcpmsg.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TCPMSG_H +#define DNS_TCPMSG_H 1 + +/*! \file dns/tcpmsg.h */ + +#include + +#include +#include +#include + +typedef struct dns_tcpmsg { + /* private (don't touch!) */ + unsigned int magic; + uint16_t size; + isc_buffer_t buffer; + unsigned int maxsize; + isc_mem_t *mctx; + isc_socket_t *sock; + isc_task_t *task; + isc_taskaction_t action; + void *arg; + isc_event_t event; + /* public (read-only) */ + isc_result_t result; + isc_sockaddr_t address; +} dns_tcpmsg_t; + +ISC_LANG_BEGINDECLS + +void +dns_tcpmsg_init(isc_mem_t *mctx, isc_socket_t *sock, dns_tcpmsg_t *tcpmsg); +/*%< + * Associate a tcp message state with a given memory context and + * TCP socket. + * + * Requires: + * + *\li "mctx" and "sock" be non-NULL and valid types. + * + *\li "sock" be a read/write TCP socket. + * + *\li "tcpmsg" be non-NULL and an uninitialized or invalidated structure. + * + * Ensures: + * + *\li "tcpmsg" is a valid structure. + */ + +void +dns_tcpmsg_setmaxsize(dns_tcpmsg_t *tcpmsg, unsigned int maxsize); +/*%< + * Set the maximum packet size to "maxsize" + * + * Requires: + * + *\li "tcpmsg" be valid. + * + *\li 512 <= "maxsize" <= 65536 + */ + +isc_result_t +dns_tcpmsg_readmessage(dns_tcpmsg_t *tcpmsg, isc_task_t *task, + isc_taskaction_t action, void *arg); +/*%< + * Schedule an event to be delivered when a DNS message is readable, or + * when an error occurs on the socket. + * + * Requires: + * + *\li "tcpmsg" be valid. + * + *\li "task", "taskaction", and "arg" be valid. + * + * Returns: + * + *\li ISC_R_SUCCESS -- no error + *\li Anything that the isc_socket_recv() call can return. XXXMLG + * + * Notes: + * + *\li The event delivered is a fully generic event. It will contain no + * actual data. The sender will be a pointer to the dns_tcpmsg_t. + * The result code inside that structure should be checked to see + * what the final result was. + */ + +void +dns_tcpmsg_cancelread(dns_tcpmsg_t *tcpmsg); +/*%< + * Cancel a readmessage() call. The event will still be posted with a + * CANCELED result code. + * + * Requires: + * + *\li "tcpmsg" be valid. + */ + +void +dns_tcpmsg_keepbuffer(dns_tcpmsg_t *tcpmsg, isc_buffer_t *buffer); +/*%< + * If a dns buffer is to be kept between calls, this function marks the + * internal state-machine buffer as invalid, and copies all the contents + * of the state into "buffer". + * + * Requires: + * + *\li "tcpmsg" be valid. + * + *\li "buffer" be non-NULL. + */ + +void +dns_tcpmsg_invalidate(dns_tcpmsg_t *tcpmsg); +/*%< + * Clean up all allocated state, and invalidate the structure. + * + * Requires: + * + *\li "tcpmsg" be valid. + * + * Ensures: + * + *\li "tcpmsg" is invalidated and disassociated with all memory contexts, + * sockets, etc. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_TCPMSG_H */ diff --git a/lib/dns/include/dns/time.h b/lib/dns/include/dns/time.h new file mode 100644 index 0000000..62e0fa0 --- /dev/null +++ b/lib/dns/include/dns/time.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TIME_H +#define DNS_TIME_H 1 + +/*! \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 + +#endif /* DNS_TIME_H */ diff --git a/lib/dns/include/dns/timer.h b/lib/dns/include/dns/timer.h new file mode 100644 index 0000000..96fcd3b --- /dev/null +++ b/lib/dns/include/dns/timer.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. + */ + +#ifndef DNS_TIMER_H +#define DNS_TIMER_H 1 + +/*! \file dns/timer.h */ + +/*** + *** Imports + ***/ + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_timer_setidle(isc_timer_t *timer, unsigned int maxtime, + unsigned int idletime, bool purge); +/*%< + * Convenience function for setting up simple, one-second-granularity + * idle timers as used by zone transfers. + * \brief + * Set the timer 'timer' to go off after 'idletime' seconds of inactivity, + * or after 'maxtime' at the very latest. Events are purged iff + * 'purge' is true. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_TIMER_H */ diff --git a/lib/dns/include/dns/tkey.h b/lib/dns/include/dns/tkey.h new file mode 100644 index 0000000..23d95ae --- /dev/null +++ b/lib/dns/include/dns/tkey.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TKEY_H +#define DNS_TKEY_H 1 + +/*! \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 + +#endif /* DNS_TKEY_H */ diff --git a/lib/dns/include/dns/tsec.h b/lib/dns/include/dns/tsec.h new file mode 100644 index 0000000..5ea7b8a --- /dev/null +++ b/lib/dns/include/dns/tsec.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TSEC_H +#define DNS_TSEC_H 1 + +/***** +***** 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 + +#endif /* DNS_TSEC_H */ diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h new file mode 100644 index 0000000..c908fa1 --- /dev/null +++ b/lib/dns/include/dns/tsig.h @@ -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. + */ + +#ifndef DNS_TSIG_H +#define DNS_TSIG_H 1 + +/*! \file dns/tsig.h */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +/* + * Algorithms. + */ +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacmd5_name; +#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_gssapi_name; +#define DNS_TSIG_GSSAPI_NAME dns_tsig_gssapi_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_gssapims_name; +#define DNS_TSIG_GSSAPIMS_NAME dns_tsig_gssapims_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha1_name; +#define DNS_TSIG_HMACSHA1_NAME dns_tsig_hmacsha1_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha224_name; +#define DNS_TSIG_HMACSHA224_NAME dns_tsig_hmacsha224_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha256_name; +#define DNS_TSIG_HMACSHA256_NAME dns_tsig_hmacsha256_name +LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_tsig_hmacsha384_name; +#define DNS_TSIG_HMACSHA384_NAME dns_tsig_hmacsha384_name +LIBDNS_EXTERNAL_DATA 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 'ring', 'name' and 'tkey' are not NULL + * + * 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 + +#endif /* DNS_TSIG_H */ diff --git a/lib/dns/include/dns/ttl.h b/lib/dns/include/dns/ttl.h new file mode 100644 index 0000000..2d718f4 --- /dev/null +++ b/lib/dns/include/dns/ttl.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TTL_H +#define DNS_TTL_H 1 + +/*! \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 + +#endif /* DNS_TTL_H */ diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h new file mode 100644 index 0000000..641d81f --- /dev/null +++ b/lib/dns/include/dns/types.h @@ -0,0 +1,438 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_TYPES_H +#define DNS_TYPES_H 1 + +/*! \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_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 struct dns_dbtable dns_dbtable_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_dispatchevent dns_dispatchevent_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_portlist dns_portlist_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; + +/* + * If we are not using GSSAPI, define the types we use as opaque types here. + */ +#ifndef GSSAPI +typedef struct not_defined_gss_cred_id *gss_cred_id_t; +typedef struct not_defined_gss_ctx *gss_ctx_id_t; +#endif /* ifndef GSSAPI */ +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_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_map = 3 +} 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); + +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 isc_result_t (*dns_deserializefunc_t)(void *, FILE *, off_t); + +typedef void (*dns_nseclog_t)(void *val, int, const char *, ...); + +#endif /* DNS_TYPES_H */ diff --git a/lib/dns/include/dns/update.h b/lib/dns/include/dns/update.h new file mode 100644 index 0000000..aca9e71 --- /dev/null +++ b/lib/dns/include/dns/update.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_UPDATE_H +#define DNS_UPDATE_H 1 + +/*! \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 + +#endif /* DNS_UPDATE_H */ diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h new file mode 100644 index 0000000..b435cd6 --- /dev/null +++ b/lib/dns/include/dns/validator.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. + */ + +#ifndef DNS_VALIDATOR_H +#define DNS_VALIDATOR_H 1 + +/***** +***** 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 + +#endif /* DNS_VALIDATOR_H */ diff --git a/lib/dns/include/dns/version.h b/lib/dns/include/dns/version.h new file mode 100644 index 0000000..0f5ec7e --- /dev/null +++ b/lib/dns/include/dns/version.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. + */ + +/*! \file dns/version.h */ + +#ifndef DNS_VERSION_H +#define DNS_VERSION_H 1 + +#include + +LIBDNS_EXTERNAL_DATA extern const char dns_version[]; +LIBDNS_EXTERNAL_DATA extern const char dns_major[]; +LIBDNS_EXTERNAL_DATA extern const char dns_mapapi[]; + +#endif /* DNS_VERSION_H */ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h new file mode 100644 index 0000000..d6e7f10 --- /dev/null +++ b/lib/dns/include/dns/view.h @@ -0,0 +1,1359 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_VIEW_H +#define DNS_VIEW_H 1 + +/***** +***** 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 + +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_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; + 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 + +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_socketmgr_t *socketmgr, 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', 'socketmgr', '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_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_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. + */ + +isc_result_t +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 + */ + +isc_result_t +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 master 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. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_VIEW_H */ diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h new file mode 100644 index 0000000..2c0f530 --- /dev/null +++ b/lib/dns/include/dns/xfrin.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_XFRIN_H +#define DNS_XFRIN_H 1 + +/***** +***** Module Info +*****/ + +/*! \file dns/xfrin.h + * \brief + * Incoming zone transfers (AXFR + IXFR). + */ + +/*** + *** Imports + ***/ + +#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 *masteraddr, + const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp, + dns_tsigkey_t *tsigkey, isc_mem_t *mctx, + isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, + isc_task_t *task, dns_xfrindone_t done, + dns_xfrin_ctx_t **xfrp); +/*%< + * Attempt to start an incoming zone transfer of 'zone' + * from 'masteraddr', 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 guaranteed to be + * called in the context of 'task', 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 + +#endif /* DNS_XFRIN_H */ diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h new file mode 100644 index 0000000..4bdc936 --- /dev/null +++ b/lib/dns/include/dns/zone.h @@ -0,0 +1,2726 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ZONE_H +#define DNS_ZONE_H 1 + +/*! \file dns/zone.h */ + +/*** + *** Imports + ***/ + +#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. + */ + +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. + */ + +isc_result_t +dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries, + uint32_t count); +isc_result_t +dns_zone_setprimarieswithkeys(dns_zone_t *zone, const isc_sockaddr_t *primaries, + dns_name_t **keynames, uint32_t count); +/*%< + * Set the list of master 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 + */ + +isc_result_t +dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals, + dns_name_t **keynames, 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 + */ + +isc_result_t +dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify, + uint32_t count); +isc_result_t +dns_zone_setalsonotifywithkeys(dns_zone_t *zone, const isc_sockaddr_t *notify, + dns_name_t **keynames, uint32_t count); +isc_result_t +dns_zone_setalsonotifydscpkeys(dns_zone_t *zone, const isc_sockaddr_t *notify, + const isc_dscp_t *dscps, dns_name_t **keynames, + 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_setxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp); +isc_result_t +dns_zone_setaltxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the transfer/alt-transfer source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +isc_dscp_t +dns_zone_getxfrsource4dscp(dns_zone_t *zone); +isc_dscp_t +dns_zone_getaltxfrsource4dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the transfer/alt-transfer source. + * + * 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_dscp_t +dns_zone_getxfrsource6dscp(dns_zone_t *zone); +isc_dscp_t +dns_zone_getaltxfrsource6dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the transfer/alt-transfer source. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp); +isc_result_t +dns_zone_setaltxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the transfer/alt-transfer source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +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_dscp_t +dns_zone_getparentalsrc4dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the IPv4 parental source. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setparentalsrc4dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the IPv4 parental source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +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_dscp_t +dns_zone_getparentalsrc6dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the IPv6 parental source. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setparentalsrc6dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the IPv6 parental source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +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_dscp_t +dns_zone_getnotifysrc4dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the IPv4 notify source. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setnotifysrc4dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the IPv4 notify source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +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. + */ + +isc_dscp_t +dns_zone_getnotifysrc6dscp(dns_zone_t *zone); +/*%/ + * Get the DSCP value associated with the IPv6 notify source. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +isc_result_t +dns_zone_setnotifysrc6dscp(dns_zone_t *zone, isc_dscp_t dscp); +/*%< + * Set the DSCP value associated with the IPv6 notify source. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + *\li #ISC_R_SUCCESS + */ + +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 (master/slave/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 master or a + * slave 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 master 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_socketmgr_t *socketmgr, + 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'. + */ + +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_getttransfersin(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_getttransfersperns(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_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 slave 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 (slave) 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 (slave) 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'. + */ + +#endif /* DNS_ZONE_H */ diff --git a/lib/dns/include/dns/zonekey.h b/lib/dns/include/dns/zonekey.h new file mode 100644 index 0000000..f03f0a5 --- /dev/null +++ b/lib/dns/include/dns/zonekey.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ZONEKEY_H +#define DNS_ZONEKEY_H 1 + +/*! \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 + +#endif /* DNS_ZONEKEY_H */ 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..2964fc9 --- /dev/null +++ b/lib/dns/include/dns/zt.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_ZT_H +#define DNS_ZT_H 1 + +/*! \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_flushanddetach(dns_zt_t **ztp); +/*%< + * Detach the given zonetable, if the reference count goes to zero the + * zonetable will be flushed and then freed. In either case 'ztp' is + * set to NULL. + * + * 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 master 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 + +#endif /* DNS_ZT_H */ diff --git a/lib/dns/include/dst/Makefile.in b/lib/dns/include/dst/Makefile.in new file mode 100644 index 0000000..e4dad59 --- /dev/null +++ b/lib/dns/include/dst/Makefile.in @@ -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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +HEADERS = dst.h gssapi.h result.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/dst + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/dst || exit 1; \ + done + +uninstall:: + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/dst/$$i || exit 1; \ + done diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h new file mode 100644 index 0000000..3185e9f --- /dev/null +++ b/lib/dns/include/dst/dst.h @@ -0,0 +1,1227 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DST_DST_H +#define DST_DST_H 1 + +/*! \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 */ +#define DST_ALG_UNKNOWN 0 +#define DST_ALG_RSA 1 /* Used for parsing RSASHA1, RSASHA256 and RSASHA512 */ +#define DST_ALG_RSAMD5 1 +#define DST_ALG_DH 2 +#define DST_ALG_DSA 3 +#define DST_ALG_ECC 4 +#define DST_ALG_RSASHA1 5 +#define DST_ALG_NSEC3DSA 6 +#define DST_ALG_NSEC3RSASHA1 7 +#define DST_ALG_RSASHA256 8 +#define DST_ALG_RSASHA512 10 +#define DST_ALG_ECCGOST 12 +#define DST_ALG_ECDSA256 13 +#define DST_ALG_ECDSA384 14 +#define DST_ALG_ED25519 15 +#define DST_ALG_ED448 16 +#define DST_ALG_HMACMD5 157 +#define DST_ALG_GSSAPI 160 +#define DST_ALG_HMACSHA1 161 /* XXXMPA */ +#define DST_ALG_HMACSHA224 162 /* XXXMPA */ +#define DST_ALG_HMACSHA256 163 /* XXXMPA */ +#define DST_ALG_HMACSHA384 164 /* XXXMPA */ +#define DST_ALG_HMACSHA512 165 /* XXXMPA */ +#define DST_ALG_INDIRECT 252 +#define DST_ALG_PRIVATE 254 +#define DST_MAX_ALGS 256 + +/*% 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 + +/* 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. + * + * 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 + * + * 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. + */ + +ISC_LANG_ENDDECLS + +#endif /* DST_DST_H */ diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h new file mode 100644 index 0000000..e783f20 --- /dev/null +++ b/lib/dns/include/dst/gssapi.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DST_GSSAPI_H +#define DST_GSSAPI_H 1 + +/*! \file dst/gssapi.h */ + +#include +#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 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 + +#endif /* DST_GSSAPI_H */ diff --git a/lib/dns/include/dst/result.h b/lib/dns/include/dst/result.h new file mode 100644 index 0000000..b909f55 --- /dev/null +++ b/lib/dns/include/dst/result.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. + */ + +#ifndef DST_RESULT_H +#define DST_RESULT_H 1 + +/*! \file dst/result.h */ + +#include +#include + +/* + * Nothing in this file truly depends on , but the + * DST result codes are considered to be publicly derived from + * the ISC result codes, so including this file buys you the ISC_R_ + * namespace too. + */ +#include /* Contractual promise. */ + +#define DST_R_UNSUPPORTEDALG (ISC_RESULTCLASS_DST + 0) +#define DST_R_CRYPTOFAILURE (ISC_RESULTCLASS_DST + 1) +/* compat */ +#define DST_R_OPENSSLFAILURE DST_R_CRYPTOFAILURE +#define DST_R_NOCRYPTO (ISC_RESULTCLASS_DST + 2) +#define DST_R_NULLKEY (ISC_RESULTCLASS_DST + 3) +#define DST_R_INVALIDPUBLICKEY (ISC_RESULTCLASS_DST + 4) +#define DST_R_INVALIDPRIVATEKEY (ISC_RESULTCLASS_DST + 5) +/* 6 is unused */ +#define DST_R_WRITEERROR (ISC_RESULTCLASS_DST + 7) +#define DST_R_INVALIDPARAM (ISC_RESULTCLASS_DST + 8) +/* 9 is unused */ +/* 10 is unused */ +#define DST_R_SIGNFAILURE (ISC_RESULTCLASS_DST + 11) +/* 12 is unused */ +/* 13 is unused */ +#define DST_R_VERIFYFAILURE (ISC_RESULTCLASS_DST + 14) +#define DST_R_NOTPUBLICKEY (ISC_RESULTCLASS_DST + 15) +#define DST_R_NOTPRIVATEKEY (ISC_RESULTCLASS_DST + 16) +#define DST_R_KEYCANNOTCOMPUTESECRET (ISC_RESULTCLASS_DST + 17) +#define DST_R_COMPUTESECRETFAILURE (ISC_RESULTCLASS_DST + 18) +#define DST_R_NORANDOMNESS (ISC_RESULTCLASS_DST + 19) +#define DST_R_BADKEYTYPE (ISC_RESULTCLASS_DST + 20) +#define DST_R_NOENGINE (ISC_RESULTCLASS_DST + 21) +#define DST_R_EXTERNALKEY (ISC_RESULTCLASS_DST + 22) + +#define DST_R_NRESULTS 23 /* Number of results */ + +ISC_LANG_BEGINDECLS + +const char *dst_result_totext(isc_result_t); + +void +dst_result_register(void); + +ISC_LANG_ENDDECLS + +#endif /* DST_RESULT_H */ diff --git a/lib/dns/ipkeylist.c b/lib/dns/ipkeylist.c new file mode 100644 index 0000000..07d2c12 --- /dev/null +++ b/lib/dns/ipkeylist.c @@ -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. + */ + +#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->dscps = NULL; + ipkl->keys = 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->dscps != NULL) { + isc_mem_put(mctx, ipkl->dscps, + ipkl->allocated * sizeof(isc_dscp_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->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->dscps != NULL) { + memmove(dst->dscps, src->dscps, + src->count * sizeof(isc_dscp_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->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; + isc_dscp_t *dscps = NULL; + dns_name_t **keys = 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)); + dscps = isc_mem_get(mctx, n * sizeof(isc_dscp_t)); + keys = 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->dscps != NULL) { + memmove(dscps, ipkl->dscps, + ipkl->allocated * sizeof(isc_dscp_t)); + isc_mem_put(mctx, ipkl->dscps, + ipkl->allocated * sizeof(isc_dscp_t)); + } + ipkl->dscps = dscps; + memset(&ipkl->dscps[ipkl->allocated], 0, + (n - ipkl->allocated) * sizeof(isc_dscp_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->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, dscps, n * sizeof(isc_dscp_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..b586528 --- /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 + +/*! \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_copynf(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(__FILE__, __LINE__, "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 (this is + * necessary on WIN32). + */ + 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..09c6810 --- /dev/null +++ b/lib/dns/kasp.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. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +isc_result_t +dns_kasp_create(isc_mem_t *mctx, const char *name, dns_kasp_t **kaspp) { + dns_kasp_t *kasp; + + REQUIRE(name != NULL); + REQUIRE(kaspp != NULL && *kaspp == NULL); + + kasp = isc_mem_get(mctx, sizeof(*kasp)); + kasp->mctx = NULL; + isc_mem_attach(mctx, &kasp->mctx); + + kasp->name = isc_mem_strdup(mctx, name); + isc_mutex_init(&kasp->lock); + kasp->frozen = false; + + isc_refcount_init(&kasp->references, 1); + + ISC_LINK_INIT(kasp, link); + + kasp->signatures_refresh = DNS_KASP_SIG_REFRESH; + kasp->signatures_validity = DNS_KASP_SIG_VALIDITY; + kasp->signatures_validity_dnskey = DNS_KASP_SIG_VALIDITY_DNSKEY; + + ISC_LIST_INIT(kasp->keys); + + kasp->dnskey_ttl = DNS_KASP_KEY_TTL; + kasp->publish_safety = DNS_KASP_PUBLISH_SAFETY; + kasp->retire_safety = DNS_KASP_RETIRE_SAFETY; + kasp->purge_keys = DNS_KASP_PURGE_KEYS; + + kasp->zone_max_ttl = DNS_KASP_ZONE_MAXTTL; + kasp->zone_propagation_delay = DNS_KASP_ZONE_PROPDELAY; + + kasp->parent_ds_ttl = DNS_KASP_DS_TTL; + kasp->parent_propagation_delay = DNS_KASP_PARENT_PROPDELAY; + + kasp->nsec3 = false; + + kasp->magic = DNS_KASP_MAGIC; + *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) { + REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(kasp->frozen); + + 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..fe0af0e --- /dev/null +++ b/lib/dns/keymgr.c @@ -0,0 +1,2628 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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; \ + if (dst_key_getstate((key), (state), &s) == ISC_R_NOTFOUND) { \ + dst_key_setstate((key), (state), (target)); \ + dst_key_settime((key), (timing), time); \ + } \ + } 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) { + /* ZSK: Iret = Dsgn + Dprp + TTLsig */ + zsk_remove = retire + dns_kasp_zonemaxttl(kasp) + + 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 = ksk_remove > zsk_remove ? 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; + zrrsig_present = published + dns_kasp_zonemaxttl(kasp) + + 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. + */ + syncpub2 = pub + dns_kasp_zonemaxttl(kasp) + + dns_kasp_publishsafety(kasp) + + dns_kasp_zonepropagationdelay(kasp); + } + + syncpub = syncpub1 > syncpub2 ? 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; + + /* + * 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 + dns_kasp_zonemaxttl(kasp) + + 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 zone_ttl = dns_kasp_zonemaxttl(kasp); + zone_ttl += dns_kasp_zonepropagationdelay(kasp); + if ((active + zone_ttl) <= 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 zone_ttl = dns_kasp_zonemaxttl(kasp); + zone_ttl += dns_kasp_zonepropagationdelay(kasp); + if ((retire + zone_ttl) <= 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 == 0 || 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)); + } + + /* 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..e5786f1 --- /dev/null +++ b/lib/dns/keytable.c @@ -0,0 +1,943 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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_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); + } 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); + } 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) { + REQUIRE(ds != NULL); + REQUIRE(!initial || managed); + + return (insert(keytable, managed, initial, name, ds)); +} + +isc_result_t +dns_keytable_marksecure(dns_keytable_t *keytable, const dns_name_t *name) { + return (insert(keytable, true, false, name, NULL)); +} + +isc_result_t +dns_keytable_delete(dns_keytable_t *keytable, const dns_name_t *keyname) { + 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); + } 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/lib.c b/lib/dns/lib.c new file mode 100644 index 0000000..e8eab73 --- /dev/null +++ b/lib/dns/lib.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/*** + *** Globals + ***/ + +LIBDNS_EXTERNAL_DATA unsigned int dns_pps = 0U; + +/*** + *** Functions + ***/ + +static isc_once_t init_once = ISC_ONCE_INIT; +static isc_mem_t *dns_g_mctx = NULL; +static dns_dbimplementation_t *dbimp = NULL; +static bool initialize_done = false; +static isc_refcount_t references; + +static void +initialize(void) { + isc_result_t result; + + REQUIRE(!initialize_done); + + isc_refcount_init(&references, 0); + + isc_mem_create(&dns_g_mctx); + dns_result_register(); + result = dns_ecdb_register(dns_g_mctx, &dbimp); + if (result != ISC_R_SUCCESS) { + goto cleanup_mctx; + } + + result = dst_lib_init(dns_g_mctx, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup_db; + } + + initialize_done = true; + return; + +cleanup_db: + if (dbimp != NULL) { + dns_ecdb_unregister(&dbimp); + } +cleanup_mctx: + if (dns_g_mctx != NULL) { + isc_mem_detach(&dns_g_mctx); + } +} + +isc_result_t +dns_lib_init(void) { + isc_result_t result; + + /* + * Since this routine is expected to be used by a normal application, + * it should be better to return an error, instead of an emergency + * abort, on any failure. + */ + result = isc_once_do(&init_once, initialize); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (!initialize_done) { + return (ISC_R_FAILURE); + } + + isc_refcount_increment0(&references); + + return (ISC_R_SUCCESS); +} + +void +dns_lib_shutdown(void) { + if (isc_refcount_decrement(&references) == 1) { + dst_lib_destroy(); + + isc_refcount_destroy(&references); + + if (dbimp != NULL) { + dns_ecdb_unregister(&dbimp); + } + if (dns_g_mctx != NULL) { + isc_mem_detach(&dns_g_mctx); + } + } +} diff --git a/lib/dns/log.c b/lib/dns/log.c new file mode 100644 index 0000000..af8a7d9 --- /dev/null +++ b/lib/dns/log.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 . + */ +LIBDNS_EXTERNAL_DATA 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 }, + { NULL, 0 } +}; + +/*% + * When adding a new module, be sure to add the appropriate + * \#define to . + */ +LIBDNS_EXTERNAL_DATA 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 } +}; + +LIBDNS_EXTERNAL_DATA 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..5d3ee70 --- /dev/null +++ b/lib/dns/lookup.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +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 isc_result_t +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; + + return (ISC_R_SUCCESS); +} + +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; + bool want_restart; + bool send_event; + dns_name_t *name, *fname, *prefix; + 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); + + result = ISC_R_SUCCESS; + 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 = dns_fixedname_name(&event->foundname); + dns_resolver_destroyfetch(&lookup->fetch); + INSIST(event->rdataset == &lookup->rdataset); + INSIST(event->sigrdataset == &lookup->sigrdataset); + } else { + fname = NULL; /* Silence compiler warning. */ + } + + /* + * If we've been canceled, forget about the result. + */ + if (lookup->canceled) { + result = ISC_R_CANCELED; + } + + switch (result) { + case ISC_R_SUCCESS: + result = 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_copynf(&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_copynf(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/mapapi b/lib/dns/mapapi new file mode 100644 index 0000000..1b502d3 --- /dev/null +++ b/lib/dns/mapapi @@ -0,0 +1,16 @@ +# This value should be increased whenever changing the structure of +# any object that will appear in a type 'map' master file (which +# contains a working memory image of an RBT database), as loading +# an incorrect memory image produces an inconsistent and probably +# nonfunctional database. These structures include but are not +# necessarily limited to dns_masterrawheader, rbtdb_file_header, +# rbt_file_header, dns_rbtdb, dns_rbt, dns_rbtnode, rdatasetheader. +# +# Err on the side of caution: if anything in the RBTDB is changed, +# bump the value. Making map files unreadable protects the system +# from instability; it's a feature not a bug. +# +# Whenever releasing a new major release of BIND9, set this value +# back to 1.0 when releasing the first alpha. Map files are *never* +# compatible across major releases. +MAPAPI=3.0 diff --git a/lib/dns/master.c b/lib/dns/master.c new file mode 100644 index 0000000..fc56107 --- /dev/null +++ b/lib/dns/master.c @@ -0,0 +1,3264 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 +openfile_map(dns_loadctx_t *lctx, const char *master_file); + +static isc_result_t +load_map(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, \ + dns_result_totext(result)); \ + else \ + LOGIT(result) + +#define LOGIT(result) \ + if (result == ISC_R_NOMEMORY) \ + (*callbacks->error)(callbacks, "dns_master_load: %s", \ + dns_result_totext(result)); \ + else \ + (*callbacks->error)(callbacks, "%s: %s:%lu: %s", \ + "dns_master_load", source, line, \ + dns_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_SLAVE) == 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(__FILE__, __LINE__, + "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; + case dns_masterformat_map: + lctx->openfile = openfile_map; + lctx->load = load_map; + 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", + dns_result_totext(result)); + } else { + (*callbacks->error)(callbacks, "$GENERATE: %s:%lu: %s", source, + line, dns_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(__FILE__, __LINE__, + "%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(__FILE__, __LINE__, + "%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(__FILE__, __LINE__, + "%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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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, + dns_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, + dns_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 = dns_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 && + lctx->format != dns_masterformat_map) + { + 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(__FILE__, __LINE__, + "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 %s)", + lctx->format == dns_masterformat_map ? "map" + : "ra" + "w"); + 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(__FILE__, __LINE__, + "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_map(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(__FILE__, __LINE__, + "isc_stdio_open() failed: %s", + isc_result_totext(result)); + } + + return (result); +} + +/* + * Load a map format file, using mmap() to access RBT trees directly + */ +static isc_result_t +load_map(dns_loadctx_t *lctx) { + isc_result_t result = ISC_R_SUCCESS; + dns_rdatacallbacks_t *callbacks; + + REQUIRE(DNS_LCTX_VALID(lctx)); + + callbacks = lctx->callbacks; + + if (lctx->first) { + result = load_header(lctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = (*callbacks->deserialize)( + callbacks->deserialize_private, lctx->f, + sizeof(dns_masterrawheader_t)); + } + + return (result); +} + +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(__FILE__, __LINE__, + "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", + dns_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", + dns_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, dns_result_totext(result)); + } else { + (*error)(callbacks, "%s: %s: %s", + "dns_master_load", namebuf, + dns_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..7561061 --- /dev/null +++ b/lib/dns/masterdump.c @@ -0,0 +1,2133 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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; + +LIBDNS_EXTERNAL_DATA 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 +}; + +LIBDNS_EXTERNAL_DATA 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 +}; + +LIBDNS_EXTERNAL_DATA const dns_master_style_t dns_master_style_full = { + DNS_STYLEFLAG_COMMENT | DNS_STYLEFLAG_RESIGN, + 46, + 46, + 46, + 64, + 120, + 8, + UINT_MAX +}; + +LIBDNS_EXTERNAL_DATA 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 +}; + +LIBDNS_EXTERNAL_DATA 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 +}; + +LIBDNS_EXTERNAL_DATA 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 + }; + +LIBDNS_EXTERNAL_DATA 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(). + */ +LIBDNS_EXTERNAL_DATA 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). + */ +LIBDNS_EXTERNAL_DATA 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. + */ +LIBDNS_EXTERNAL_DATA 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 + */ +LIBDNS_EXTERNAL_DATA 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_copynf(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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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_copynf(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); +} + +static isc_result_t +dump_rdatasets_map(isc_mem_t *mctx, const dns_name_t *name, + dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx, + isc_buffer_t *buffer, FILE *f) { + UNUSED(mctx); + UNUSED(name); + UNUSED(rdsiter); + UNUSED(ctx); + UNUSED(buffer); + UNUSED(f); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * 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; + case dns_masterformat_map: + dctx->dumpsets = dump_rdatasets_map; + break; + default: + UNREACHABLE(); + } + + result = totext_ctx_init(style, NULL, &dctx->tctx); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "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: + case dns_masterformat_map: + 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)); + + /* + * Fast format is not currently written incrementally, + * so we make the call to dns_db_serialize() here. + * If the database is anything other than an rbtdb, + * this should result in not implemented + */ + if (dctx->format == dns_masterformat_map) { + result = dns_db_serialize(dctx->db, dctx->version, dctx->f); + goto cleanup; + } + + 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(__FILE__, __LINE__, + "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..09645c2 --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,4748 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 /* 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); + if (msgblock == NULL) { + return (NULL); + } + + 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); + if (msgblock == NULL) { + return (NULL); + } + + ISC_LIST_APPEND(msg->rdatalists, msgblock, link); + + rdatalist = msgblock_get(msgblock, dns_rdatalist_t); + } +out: + if (rdatalist != NULL) { + 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); + if (msgblock == NULL) { + return (NULL); + } + + 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); + if (msg->sig0name != NULL) { + dns_message_puttempname(msg, &msg->sig0name); + } + msg->sig0 = NULL; + msg->sig0name = NULL; + } +} + +/* + * 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; + } + + /* + * 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 (ISC_UNLIKELY(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); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * 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) + { + 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); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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; + } +} + +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); + } + 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; + } + + 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->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 (ISC_UNLIKELY(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); + if (fn == NULL) { + return (ISC_R_NOMEMORY); + } + *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); + if (*item == NULL) { + return (ISC_R_NOMEMORY); + } + + 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); + if (*item == NULL) { + return (ISC_R_NOMEMORY); + } + + 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); + if (*item == NULL) { + return (ISC_R_NOMEMORY); + } + + 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, const 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; + msg->order_arg.env = env; + msg->order_arg.acl = 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; + } +} diff --git a/lib/dns/name.c b/lib/dns/name.c new file mode 100644 index 0000000..96f95b3 --- /dev/null +++ b/lib/dns/name.c @@ -0,0 +1,2692 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); +LIBDNS_EXTERNAL_DATA 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); + +LIBDNS_EXTERNAL_DATA const dns_name_t *dns_wildcardname = &wild; + +/* + * dns_name_t to text post-conversion procedure. + */ +ISC_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); + } + + /* + * RFC292/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; + } + + /* + * RFC292/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 (ISC_UNLIKELY(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 (ISC_LIKELY(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 (ISC_LIKELY(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 (ISC_LIKELY(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 (ISC_UNLIKELY(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 (ISC_LIKELY(l-- > 0)) { + count = *label1++; + if (count != *label2++) { + return (false); + } + + INSIST(count <= 63); /* no bitstring support */ + + /* Loop unrolled for performance */ + while (ISC_LIKELY(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 (ISC_LIKELY(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 (ISC_UNLIKELY(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 (ISC_LIKELY(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(__FILE__, __LINE__, "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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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(__FILE__, __LINE__, + "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 (ISC_LIKELY(offset != length)) { + INSIST(nlabels < 128); + offsets[nlabels++] = offset; + count = *ndata; + INSIST(count <= 63); + offset += count + 1; + ndata += count + 1; + INSIST(offset <= length); + if (ISC_UNLIKELY(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 *name, isc_buffer_t *source, + dns_decompress_t *dctx, unsigned int options, + isc_buffer_t *target) { + unsigned char *cdata, *ndata; + unsigned int cused; /* Bytes of compressed name data used */ + unsigned int nused, labels, n, nmax; + unsigned int current, new_current, biggest_pointer; + bool done; + fw_state state = fw_start; + unsigned int c; + unsigned char *offsets; + dns_offsets_t odata; + bool downcase; + bool seen_pointer; + + /* + * Copy the possibly-compressed name at source into target, + * decompressing it. Loop prevention is performed by checking + * the new pointer against biggest_pointer. + */ + + REQUIRE(VALID_NAME(name)); + 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(dctx != NULL); + REQUIRE(BINDABLE(name)); + + INIT_OFFSETS(name, offsets, odata); + + /* + * Make 'name' empty in case of failure. + */ + MAKE_EMPTY(name); + + /* + * Initialize things to make the compiler happy; they're not required. + */ + n = 0; + new_current = 0; + + /* + * Set up. + */ + labels = 0; + done = false; + + ndata = isc_buffer_used(target); + nused = 0; + seen_pointer = false; + + /* + * Find the maximum number of uncompressed target name + * bytes we are willing to generate. This is the smaller + * of the available target buffer length and the + * maximum legal domain name length (255). + */ + nmax = isc_buffer_availablelength(target); + if (nmax > DNS_NAME_MAXWIRE) { + nmax = DNS_NAME_MAXWIRE; + } + + cdata = isc_buffer_current(source); + cused = 0; + + current = source->current; + biggest_pointer = current; + + /* + * Note: The following code is not optimized for speed, but + * rather for correctness. Speed will be addressed in the future. + */ + + while (current < source->active && !done) { + c = *cdata++; + current++; + if (!seen_pointer) { + cused++; + } + + switch (state) { + case fw_start: + if (c < 64) { + offsets[labels] = nused; + labels++; + if (nused + c + 1 > nmax) { + goto full; + } + nused += c + 1; + *ndata++ = c; + if (c == 0) { + done = true; + } + n = c; + state = fw_ordinary; + } else if (c >= 128 && c < 192) { + /* + * 14 bit local compression pointer. + * Local compression is no longer an + * IETF draft. + */ + return (DNS_R_BADLABELTYPE); + } else if (c >= 192) { + /* + * Ordinary 14-bit pointer. + */ + if ((dctx->allowed & DNS_COMPRESS_GLOBAL14) == + 0) + { + return (DNS_R_DISALLOWED); + } + new_current = c & 0x3F; + state = fw_newcurrent; + } else { + return (DNS_R_BADLABELTYPE); + } + break; + case fw_ordinary: + if (downcase) { + c = maptolower[c]; + } + *ndata++ = c; + n--; + if (n == 0) { + state = fw_start; + } + break; + case fw_newcurrent: + new_current *= 256; + new_current += c; + if (new_current >= biggest_pointer) { + return (DNS_R_BADPOINTER); + } + biggest_pointer = new_current; + current = new_current; + cdata = (unsigned char *)source->base + current; + seen_pointer = true; + state = fw_start; + break; + default: + FATAL_ERROR(__FILE__, __LINE__, "Unknown state %d", + state); + /* Does not return. */ + } + } + + if (!done) { + return (ISC_R_UNEXPECTEDEND); + } + + name->ndata = (unsigned char *)target->base + target->used; + name->labels = labels; + name->length = nused; + name->attributes |= DNS_NAMEATTR_ABSOLUTE; + + isc_buffer_forward(source, cused); + isc_buffer_add(target, name->length); + + return (ISC_R_SUCCESS); + +full: + if (nmax == DNS_NAME_MAXWIRE) { + /* + * The name did not fit even though we had a buffer + * big enough to fit a maximum-length name. + */ + return (DNS_R_NAMETOOLONG); + } else { + /* + * The name might fit if only the caller could give us a + * big enough buffer. + */ + return (ISC_R_NOSPACE); + } +} + +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 (ISC_UNLIKELY(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 && ISC_UNLIKELY(go >= 0x4000)) { + gf = false; + } + + /* + * Will the compression pointer reduce the message size? + */ + if (gf && (gp.length + 2) >= name->length) { + gf = false; + } + + if (gf) { + if (ISC_UNLIKELY(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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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); +} + +static isc_result_t +name_copy(const dns_name_t *source, dns_name_t *dest, isc_buffer_t *target) { + unsigned char *ndata = NULL; + + /* + * Make dest a copy of source. + */ + + REQUIRE(BINDABLE(dest)); + + /* + * Set up. + */ + if (target->length - target->used < source->length) { + return (ISC_R_NOSPACE); + } + + ndata = (unsigned char *)target->base + target->used; + 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); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_copy(const dns_name_t *source, dns_name_t *dest, + isc_buffer_t *target) { + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(dest)); + REQUIRE(target != NULL); + + return (name_copy(source, dest, target)); +} + +void +dns_name_copynf(const dns_name_t *source, dns_name_t *dest) { + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(dest)); + REQUIRE(dest->buffer != NULL); + + isc_buffer_clear(dest->buffer); + RUNTIME_CHECK(name_copy(source, dest, dest->buffer) == ISC_R_SUCCESS); +} + +/* + * 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..3089c9e --- /dev/null +++ b/lib/dns/nsec.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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, 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_RSASHA1) + { + 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); + + (*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); +} diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c new file mode 100644 index 0000000..7f68580 --- /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_copynf(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_copynf(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_copynf(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..c6725c7 --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_copynf(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; + + /* + * Skip "validate-except" entries. + */ + if (n->expiry != 0xffffffffU) { + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + 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); + 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_dump(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + isc_buffer_t *text = NULL; + int len = 4096; + + isc_buffer_allocate(ntatable->view->mctx, &text, len); + + result = dns_ntatable_totext(ntatable, NULL, &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 NTA 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); +} + +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..4878f57 --- /dev/null +++ b/lib/dns/openssl_link.c @@ -0,0 +1,214 @@ +/* + * 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) +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + +#if !defined(OPENSSL_NO_ENGINE) +static ENGINE *e = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + +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) + 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; + } + /* This will init the engine. */ + if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { + result = DST_R_NOENGINE; + goto cleanup_rm; + } + } + + return (ISC_R_SUCCESS); +cleanup_rm: + if (e != NULL) { + ENGINE_free(e); + } + e = NULL; +#else + UNUSED(engine); +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + return (result); +} + +void +dst__openssl_destroy(void) { +#if !defined(OPENSSL_NO_ENGINE) + if (e != NULL) { + ENGINE_free(e); + } + e = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) */ +} + +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, *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_line_data(&file, &line, &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) +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) */ + +/*! \file */ diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c new file mode 100644 index 0000000..f514931 --- /dev/null +++ b/lib/dns/openssldh_link.c @@ -0,0 +1,815 @@ +/* + * 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 "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.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" + +static BIGNUM *bn2 = NULL, *bn768 = NULL, *bn1024 = NULL, *bn1536 = NULL; + +#if !HAVE_DH_GET0_KEY +/* + * DH_get0_key, DH_set0_key, DH_get0_pqg and DH_set0_pqg + * are from OpenSSL 1.1.0. + */ +static 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; + } +} + +static 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); +} + +static 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; + } +} + +static 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); +} + +#define DH_clear_flags(d, f) (d)->flags &= ~(f) + +#endif /* !HAVE_DH_GET0_KEY */ + +static isc_result_t +openssldh_computesecret(const dst_key_t *pub, const dst_key_t *priv, + isc_buffer_t *secret) { + DH *dhpub, *dhpriv; + const BIGNUM *pub_key = NULL; + int ret; + isc_region_t r; + unsigned int len; + + REQUIRE(pub->keydata.dh != NULL); + REQUIRE(priv->keydata.dh != NULL); + + dhpub = pub->keydata.dh; + dhpriv = priv->keydata.dh; + + len = DH_size(dhpriv); + isc_buffer_availableregion(secret, &r); + if (r.length < len) { + return (ISC_R_NOSPACE); + } + + DH_get0_key(dhpub, &pub_key, NULL); + ret = DH_compute_key(r.base, pub_key, dhpriv); + if (ret <= 0) { + return (dst__openssl_toresult2("DH_compute_key", + DST_R_COMPUTESECRETFAILURE)); + } + isc_buffer_add(secret, len); + return (ISC_R_SUCCESS); +} + +static bool +openssldh_compare(const dst_key_t *key1, const dst_key_t *key2) { + 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; + + 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); + + if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0 || + BN_cmp(pub_key1, pub_key2) != 0) + { + return (false); + } + + if (priv_key1 != NULL || priv_key2 != NULL) { + if (priv_key1 == NULL || priv_key2 == NULL) { + return (false); + } + if (BN_cmp(priv_key1, priv_key2) != 0) { + return (false); + } + } + return (true); +} + +static bool +openssldh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { + DH *dh1, *dh2; + const BIGNUM *p1 = NULL, *g1 = NULL, *p2 = NULL, *g2 = NULL; + + 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); + + if (BN_cmp(p1, p2) != 0 || BN_cmp(g1, g2) != 0) { + return (false); + } + return (true); +} + +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); +} + +static isc_result_t +openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) { + DH *dh = NULL; + BN_GENCB *cb; +#if !HAVE_BN_GENCB_NEW + BN_GENCB _cb; +#endif /* !HAVE_BN_GENCB_NEW */ + union { + void *dptr; + void (*fptr)(int); + } u; + + if (generator == 0) { + if (key->key_size == 768 || key->key_size == 1024 || + key->key_size == 1536) + { + BIGNUM *p, *g; + dh = DH_new(); + 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 (dh == NULL || p == NULL || g == NULL) { + if (dh != NULL) { + DH_free(dh); + } + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + DH_set0_pqg(dh, p, NULL, g); + } else { + generator = 2; + } + } + + if (generator != 0) { + dh = DH_new(); + if (dh == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + cb = BN_GENCB_new(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + if (cb == NULL) { + DH_free(dh); + return (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)) + { + DH_free(dh); + BN_GENCB_free(cb); + return (dst__openssl_toresult2("DH_generate_parameters_" + "ex", + DST_R_OPENSSLFAILURE)); + } + BN_GENCB_free(cb); + cb = NULL; + } + + if (DH_generate_key(dh) == 0) { + DH_free(dh); + return (dst__openssl_toresult2("DH_generate_key", + DST_R_OPENSSLFAILURE)); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); + key->keydata.dh = dh; + + return (ISC_R_SUCCESS); +} + +static bool +openssldh_isprivate(const dst_key_t *key) { + DH *dh = key->keydata.dh; + const BIGNUM *priv_key = NULL; + + DH_get0_key(dh, NULL, &priv_key); + return (dh != NULL && priv_key != NULL); +} + +static void +openssldh_destroy(dst_key_t *key) { + DH *dh = key->keydata.dh; + + if (dh == NULL) { + return; + } + + DH_free(dh); + key->keydata.dh = NULL; +} + +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) { + DH *dh; + const BIGNUM *pub_key = NULL, *p = NULL, *g = NULL; + isc_region_t r; + uint16_t dnslen, plen, glen, publen; + + REQUIRE(key->keydata.dh != NULL); + + dh = key->keydata.dh; + + isc_buffer_availableregion(data, &r); + + DH_get0_pqg(dh, &p, NULL, &g); + 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); + } + DH_get0_key(dh, &pub_key, NULL); + publen = BN_num_bytes(pub_key); + dnslen = plen + glen + publen + 6; + if (r.length < (unsigned int)dnslen) { + return (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); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssldh_fromdns(dst_key_t *key, isc_buffer_t *data) { + DH *dh; + BIGNUM *pub_key = NULL, *p = NULL, *g = NULL; + 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); + } + + dh = DH_new(); + if (dh == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); + + /* + * Read the prime length. 1 & 2 are table entries, > 16 means a + * prime follows, otherwise an error. + */ + if (r.length < 2) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + plen = uint16_fromregion(&r); + if (plen < 16 && plen != 1 && plen != 2) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + if (r.length < plen) { + DH_free(dh); + return (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: + DH_free(dh); + return (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) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + glen = uint16_fromregion(&r); + if (r.length < glen) { + DH_free(dh); + return (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) { + DH_free(dh); + BN_free(g); + return (DST_R_INVALIDPUBLICKEY); + } + } + } else { + if (glen == 0) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + g = BN_bin2bn(r.base, glen, NULL); + } + isc_region_consume(&r, glen); + + if (p == NULL || g == NULL) { + DH_free(dh); + if (p != NULL) { + BN_free(p); + } + if (g != NULL) { + BN_free(g); + } + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + DH_set0_pqg(dh, p, NULL, g); + + if (r.length < 2) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + publen = uint16_fromregion(&r); + if (r.length < publen) { + DH_free(dh); + return (DST_R_INVALIDPUBLICKEY); + } + pub_key = BN_bin2bn(r.base, publen, NULL); + if (pub_key == NULL) { + DH_free(dh); + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } +#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 */ + DH_set0_key(dh, pub_key, NULL); +#endif /* LIBRESSL_VERSION_NUMBER */ + isc_region_consume(&r, publen); + + key->key_size = BN_num_bits(p); + + isc_buffer_forward(data, plen + glen + publen + 6); + + key->keydata.dh = dh; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssldh_tofile(const dst_key_t *key, const char *directory) { + int i; + DH *dh; + const BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL; + dst_private_t priv; + unsigned char *bufs[4]; + isc_result_t result; + + if (key->keydata.dh == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + return (DST_R_EXTERNALKEY); + } + + dh = key->keydata.dh; + DH_get0_key(dh, &pub_key, &priv_key); + DH_get0_pqg(dh, &p, NULL, &g); + + memset(bufs, 0, sizeof(bufs)); + for (i = 0; i < 4; i++) { + bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(p)); + } + + i = 0; + + priv.elements[i].tag = TAG_DH_PRIME; + priv.elements[i].length = BN_num_bytes(p); + 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); + 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); + 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); + BN_bn2bin(pub_key, bufs[i]); + priv.elements[i].data = bufs[i]; + i++; + + priv.nelements = i; + result = dst__privstruct_writefile(key, &priv, directory); + + for (i = 0; i < 4; i++) { + if (bufs[i] == NULL) { + break; + } + isc_mem_put(key->mctx, bufs[i], BN_num_bytes(p)); + } + 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; + DH *dh = NULL; + BIGNUM *pub_key = NULL, *priv_key = NULL, *p = NULL, *g = NULL; + isc_mem_t *mctx; +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + + 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); + } + + dh = DH_new(); + if (dh == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + DH_clear_flags(dh, DH_FLAG_CACHE_MONT_P); + key->keydata.dh = dh; + + 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; + break; + case TAG_DH_GENERATOR: + g = bn; + break; + case TAG_DH_PRIVATE: + priv_key = bn; + break; + case TAG_DH_PUBLIC: + pub_key = bn; + break; + } + } + dst__privstruct_free(&priv, mctx); + DH_set0_key(dh, pub_key, priv_key); + DH_set0_pqg(dh, p, NULL, g); + + key->key_size = BN_num_bits(p); + return (ISC_R_SUCCESS); + +err: + 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_free(priv_key); + } + 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..e9f5185 --- /dev/null +++ b/lib/dns/opensslecdsa_link.c @@ -0,0 +1,905 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 !USE_PKCS11 + +#include + +#include +#include +#include +#include +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif + +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.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 !HAVE_ECDSA_SIG_GET0 +/* From OpenSSL 1.1 */ +static 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; + } +} + +static 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 */ + +static isc_result_t +opensslecdsa_createctx(dst_key_t *key, dst_context_t *dctx) { + 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); + + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) { + return (ISC_R_NOMEMORY); + } + if (dctx->key->key_alg == DST_ALG_ECDSA256) { + type = EVP_sha256(); + } else { + type = EVP_sha384(); + } + + 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 +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); + + 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) { + 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); + + 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 int +BN_bn2bin_fixed(const BIGNUM *bn, unsigned char *buf, int size) { + int bytes = size - BN_num_bytes(bn); + + 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; + ECDSA_SIG *ecdsasig; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; + EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); + unsigned int dgstlen, siglen; + unsigned char digest[EVP_MAX_MD_SIZE]; + const BIGNUM *r, *s; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + if (eckey == NULL) { + return (ISC_R_FAILURE); + } + + 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_DigestFinal_ex(evp_md_ctx, digest, &dgstlen)) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE)); + } + + ecdsasig = ECDSA_do_sign(digest, dgstlen, eckey); + if (ecdsasig == NULL) { + DST_RET(dst__openssl_toresult3(dctx->category, "ECDSA_do_sign", + DST_R_SIGNFAILURE)); + } + 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: + EC_KEY_free(eckey); + 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; + EVP_PKEY *pkey = key->keydata.pkey; + EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); + unsigned int dgstlen, siglen; + unsigned char digest[EVP_MAX_MD_SIZE]; + BIGNUM *r = NULL, *s = NULL; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + if (eckey == NULL) { + return (ISC_R_FAILURE); + } + + 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); + } + + if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &dgstlen)) { + DST_RET(dst__openssl_toresult3( + dctx->category, "EVP_DigestFinal_ex", ISC_R_FAILURE)); + } + + ecdsasig = ECDSA_SIG_new(); + if (ecdsasig == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + r = BN_bin2bn(cp, siglen / 2, NULL); + cp += siglen / 2; + s = BN_bin2bn(cp, siglen / 2, NULL); + ECDSA_SIG_set0(ecdsasig, r, s); + /* cp += siglen / 2; */ + + status = ECDSA_do_verify(digest, dgstlen, ecdsasig, eckey); + 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, "ECDSA_do_verify", + DST_R_VERIFYFAILURE); + break; + } + +err: + if (ecdsasig != NULL) { + ECDSA_SIG_free(ecdsasig); + } + EC_KEY_free(eckey); + return (ret); +} + +static bool +opensslecdsa_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; + EC_KEY *eckey1 = NULL; + EC_KEY *eckey2 = NULL; + const BIGNUM *priv1, *priv2; + + if (pkey1 == NULL && pkey2 == NULL) { + return (true); + } else if (pkey1 == NULL || pkey2 == NULL) { + return (false); + } + + eckey1 = EVP_PKEY_get1_EC_KEY(pkey1); + eckey2 = EVP_PKEY_get1_EC_KEY(pkey2); + if (eckey1 == NULL && eckey2 == NULL) { + DST_RET(true); + } else if (eckey1 == NULL || eckey2 == NULL) { + DST_RET(false); + } + + status = EVP_PKEY_cmp(pkey1, pkey2); + if (status != 1) { + DST_RET(false); + } + + priv1 = EC_KEY_get0_private_key(eckey1); + priv2 = EC_KEY_get0_private_key(eckey2); + if (priv1 != NULL || priv2 != NULL) { + if (priv1 == NULL || priv2 == NULL) { + DST_RET(false); + } + if (BN_cmp(priv1, priv2) != 0) { + DST_RET(false); + } + } + ret = true; + +err: + if (eckey1 != NULL) { + EC_KEY_free(eckey1); + } + if (eckey2 != NULL) { + EC_KEY_free(eckey2); + } + return (ret); +} + +static isc_result_t +opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + isc_result_t ret; + EVP_PKEY *pkey; + EC_KEY *eckey = NULL; + 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; + } + + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) { + return (dst__openssl_toresult2("EC_KEY_new_by_curve_name", + DST_R_OPENSSLFAILURE)); + } + + if (EC_KEY_generate_key(eckey) != 1) { + DST_RET(dst__openssl_toresult2("EC_KEY_generate_key", + DST_R_OPENSSLFAILURE)); + } + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { + EVP_PKEY_free(pkey); + DST_RET(ISC_R_FAILURE); + } + key->keydata.pkey = pkey; + ret = ISC_R_SUCCESS; + +err: + EC_KEY_free(eckey); + return (ret); +} + +static bool +opensslecdsa_isprivate(const dst_key_t *key) { + bool ret; + EVP_PKEY *pkey = key->keydata.pkey; + EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(pkey); + + ret = (eckey != NULL && EC_KEY_get0_private_key(eckey) != NULL); + if (eckey != NULL) { + EC_KEY_free(eckey); + } + return (ret); +} + +static void +opensslecdsa_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + + 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; + EC_KEY *eckey = NULL; + isc_region_t r; + int len; + unsigned char *cp; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + eckey = EVP_PKEY_get1_EC_KEY(pkey); + if (eckey == NULL) { + return (dst__openssl_toresult(ISC_R_FAILURE)); + } + len = i2o_ECPublicKey(eckey, NULL); + /* skip form */ + len--; + + isc_buffer_availableregion(data, &r); + if (r.length < (unsigned int)len) { + DST_RET(ISC_R_NOSPACE); + } + cp = buf; + if (!i2o_ECPublicKey(eckey, &cp)) { + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } + memmove(r.base, buf + 1, len); + isc_buffer_add(data, len); + ret = ISC_R_SUCCESS; + +err: + EC_KEY_free(eckey); + return (ret); +} + +static isc_result_t +opensslecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_result_t ret; + EVP_PKEY *pkey; + EC_KEY *eckey = NULL; + isc_region_t r; + int group_nid; + unsigned int len; + const unsigned char *cp; + unsigned char buf[DNS_KEY_ECDSA384SIZE + 1]; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + if (key->key_alg == DST_ALG_ECDSA256) { + len = DNS_KEY_ECDSA256SIZE; + group_nid = NID_X9_62_prime256v1; + } else { + len = DNS_KEY_ECDSA384SIZE; + group_nid = NID_secp384r1; + } + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + if (r.length < len) { + return (DST_R_INVALIDPUBLICKEY); + } + + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) { + return (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(ISC_R_NOMEMORY); + } + if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { + EVP_PKEY_free(pkey); + DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); + } + + isc_buffer_forward(data, len); + key->keydata.pkey = pkey; + key->key_size = len * 4; + ret = ISC_R_SUCCESS; + +err: + if (eckey != NULL) { + EC_KEY_free(eckey); + } + return (ret); +} + +static isc_result_t +opensslecdsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + EVP_PKEY *pkey; + EC_KEY *eckey = NULL; + const BIGNUM *privkey; + dst_private_t priv; + unsigned char *buf = NULL; + unsigned short i; + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + pkey = key->keydata.pkey; + eckey = EVP_PKEY_get1_EC_KEY(pkey); + if (eckey == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + privkey = EC_KEY_get0_private_key(eckey); + if (privkey == NULL) { + ret = dst__openssl_toresult(DST_R_OPENSSLFAILURE); + goto err; + } + + 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: + EC_KEY_free(eckey); + if (buf != NULL) { + isc_mem_put(key->mctx, buf, BN_num_bytes(privkey)); + } + return (ret); +} + +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); +} + +static isc_result_t +load_privkey_from_privstruct(EC_KEY *eckey, dst_private_t *priv) { + BIGNUM *privkey = BN_bin2bn(priv->elements[0].data, + priv->elements[0].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 (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); +} + +static isc_result_t +finalize_eckey(dst_key_t *key, EC_KEY *eckey, const char *engine, + const char *label) { + isc_result_t result = ISC_R_SUCCESS; + EVP_PKEY *pkey = NULL; + + result = eckey_to_pkey(eckey, &pkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + + key->keydata.pkey = pkey; + + 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 (ISC_R_SUCCESS); +} + +static isc_result_t +dst__key_to_eckey(dst_key_t *key, EC_KEY **eckey) { + REQUIRE(eckey != NULL && *eckey == NULL); + + int group_nid; + 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); +} + +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 result = ISC_R_SUCCESS; + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; + const char *engine = NULL; + const char *label = NULL; + int i, privkey_index = -1; + bool finalize_key = false; + + /* read private key file */ + result = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, key->mctx, + &priv); + if (result != ISC_R_SUCCESS) { + goto end; + } + + if (key->external) { + if (priv.nelements != 0 || pub == NULL) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + goto end; + } + + 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) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + + if (label != NULL) { + result = opensslecdsa_fromlabel(key, engine, label, NULL); + if (result != ISC_R_SUCCESS) { + goto end; + } + + eckey = EVP_PKEY_get1_EC_KEY(key->keydata.pkey); + if (eckey == NULL) { + result = dst__openssl_toresult(DST_R_OPENSSLFAILURE); + goto end; + } + + } else { + result = dst__key_to_eckey(key, &eckey); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = load_privkey_from_privstruct(eckey, &priv); + if (result != ISC_R_SUCCESS) { + goto end; + } + + finalize_key = true; + } + + if (pub != NULL && pub->keydata.pkey != NULL) { + pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey); + } + + if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + + if (finalize_key) { + result = finalize_eckey(key, eckey, engine, label); + } + +end: + if (pubeckey != NULL) { + EC_KEY_free(pubeckey); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } + dst__privstruct_free(&priv, key->mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (result); +} + +static isc_result_t +opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { +#if !defined(OPENSSL_NO_ENGINE) + isc_result_t ret = ISC_R_SUCCESS; + ENGINE *e; + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; + EVP_PKEY *pkey = NULL; + EVP_PKEY *pubkey = NULL; + int group_nid = 0; + + UNUSED(pin); + + if (engine == NULL || label == NULL) { + return (DST_R_NOENGINE); + } + e = dst__openssl_getengine(engine); + if (e == NULL) { + return (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) { + return (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. */ + pubkey = ENGINE_load_public_key(e, label, NULL, NULL); + if (pubkey == NULL) { + DST_RET(dst__openssl_toresult2("ENGINE_load_public_key", + DST_R_OPENSSLFAILURE)); + } + /* Check base id, group nid */ + if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + pubeckey = EVP_PKEY_get1_EC_KEY(pubkey); + 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_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 (pubkey != NULL) { + EVP_PKEY_free(pubkey); + } + 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 +} + +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); +} + +#endif /* !USE_PKCS11 */ diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c new file mode 100644 index 0000000..295b8b8 --- /dev/null +++ b/lib/dns/openssleddsa_link.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if !USE_PKCS11 + +#if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 + +#include + +#include +#include +#include +#include +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + +#include +#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 (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_cmp(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_cmp(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) + 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) */ + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) */ +} + +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 */ + +#endif /* !USE_PKCS11 */ + +/*! \file */ diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c new file mode 100644 index 0000000..1f2a2e0 --- /dev/null +++ b/lib/dns/opensslrsa_link.c @@ -0,0 +1,1405 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if !USE_PKCS11 + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + +/* + * Limit the size of public exponents. + */ +#ifndef RSA_MAX_PUBEXP_BITS +#define RSA_MAX_PUBEXP_BITS 35 +#endif /* ifndef RSA_MAX_PUBEXP_BITS */ + +/* + * We don't use configure for windows so enforce the OpenSSL version + * here. Unlike with configure we don't support overriding this test. + */ +#if defined(WIN32) && (OPENSSL_VERSION_NUMBER < 0x10000000L) +#error Please upgrade OpenSSL to 1.0.0 or greater. +#endif /* if defined(WIN32) && (OPENSSL_VERSION_NUMBER < 0x10000000L) */ + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +#if !HAVE_RSA_SET0_KEY +/* From OpenSSL 1.1.0 */ +static 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_free(r->d); + r->d = d; + } + + return (1); +} + +static 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_free(r->p); + r->p = p; + } + if (q != NULL) { + BN_free(r->q); + r->q = q; + } + + return (1); +} + +static 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_free(r->dmp1); + r->dmp1 = dmp1; + } + if (dmq1 != NULL) { + BN_free(r->dmq1); + r->dmq1 = dmq1; + } + if (iqmp != NULL) { + BN_free(r->iqmp); + r->iqmp = iqmp; + } + + return (1); +} + +static 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; + } +} + +static 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; + } +} + +static 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; + } +} + +static int +RSA_test_flags(const RSA *r, int flags) { + return (r->flags & flags); +} + +#endif /* !HAVE_RSA_SET0_KEY */ + +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->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 (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 = dctx->ctxdata.evp_md_ctx; + + 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); + + 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 = dctx->ctxdata.evp_md_ctx; + + 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); + + 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 = dctx->key; + isc_region_t r; + unsigned int siglen = 0; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; + + 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); + + 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 = dctx->key; + int status = 0; + const BIGNUM *e = NULL; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; + RSA *rsa; + int bits; + + 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); + + 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); + 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) { + int status; + RSA *rsa1 = NULL, *rsa2 = NULL; + const BIGNUM *n1 = NULL, *n2 = NULL; + const BIGNUM *e1 = NULL, *e2 = NULL; + const BIGNUM *d1 = NULL, *d2 = NULL; + const BIGNUM *p1 = NULL, *p2 = NULL; + const BIGNUM *q1 = NULL, *q2 = NULL; + EVP_PKEY *pkey1, *pkey2; + + pkey1 = key1->keydata.pkey; + pkey2 = key2->keydata.pkey; + /* + * The pkey reference will keep these around after + * the RSA_free() call. + */ + if (pkey1 != NULL) { + rsa1 = EVP_PKEY_get1_RSA(pkey1); + RSA_free(rsa1); + } + if (pkey2 != NULL) { + rsa2 = EVP_PKEY_get1_RSA(pkey2); + RSA_free(rsa2); + } + + if (rsa1 == NULL && rsa2 == NULL) { + return (true); + } else if (rsa1 == NULL || rsa2 == NULL) { + return (false); + } + + RSA_get0_key(rsa1, &n1, &e1, &d1); + RSA_get0_key(rsa2, &n2, &e2, &d2); + status = BN_cmp(n1, n2) || BN_cmp(e1, e2); + + if (status != 0) { + return (false); + } + + if (RSA_test_flags(rsa1, RSA_FLAG_EXT_PKEY) != 0 || + RSA_test_flags(rsa2, RSA_FLAG_EXT_PKEY) != 0) + { + if (RSA_test_flags(rsa1, RSA_FLAG_EXT_PKEY) == 0 || + RSA_test_flags(rsa2, RSA_FLAG_EXT_PKEY) == 0) + { + return (false); + } + /* + * Can't compare private parameters, BTW does it make sense? + */ + return (true); + } + + if (d1 != NULL || d2 != NULL) { + if (d1 == NULL || d2 == NULL) { + return (false); + } + RSA_get0_factors(rsa1, &p1, &q1); + RSA_get0_factors(rsa2, &p2, &q2); + status = BN_cmp(d1, d2) || BN_cmp(p1, p2) || BN_cmp(q1, q2); + + if (status != 0) { + return (false); + } + } + return (true); +} + +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); +} + +static isc_result_t +opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { + isc_result_t ret = DST_R_OPENSSLFAILURE; + union { + void *dptr; + void (*fptr)(int); + } u; + RSA *rsa = RSA_new(); + BIGNUM *e = BN_new(); +#if !HAVE_BN_GENCB_NEW + BN_GENCB _cb; +#endif /* !HAVE_BN_GENCB_NEW */ + BN_GENCB *cb = BN_GENCB_new(); + EVP_PKEY *pkey = EVP_PKEY_new(); + + /* + * 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) { + goto err; + } + break; + case DST_ALG_RSASHA256: + /* From RFC 5702 */ + if ((key->key_size < 512) || (key->key_size > 4096)) { + goto err; + } + break; + case DST_ALG_RSASHA512: + /* From RFC 5702 */ + if ((key->key_size < 1024) || (key->key_size > 4096)) { + goto err; + } + break; + default: + UNREACHABLE(); + } + + if (rsa == NULL || e == NULL || cb == NULL) { + goto err; + } + if (pkey == NULL) { + goto err; + } + if (!EVP_PKEY_set1_RSA(pkey, rsa)) { + goto err; + } + + 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 (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)) { + BN_free(e); + BN_GENCB_free(cb); + cb = NULL; + key->keydata.pkey = pkey; + + RSA_free(rsa); + return (ISC_R_SUCCESS); + } + ret = dst__openssl_toresult2("RSA_generate_key_ex", + DST_R_OPENSSLFAILURE); + +err: + if (pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + if (e != NULL) { + BN_free(e); + e = NULL; + } + if (rsa != NULL) { + RSA_free(rsa); + rsa = NULL; + } + if (cb != NULL) { + BN_GENCB_free(cb); + cb = NULL; + } + return (dst__openssl_toresult(ret)); +} + +static bool +opensslrsa_isprivate(const dst_key_t *key) { + const BIGNUM *d = NULL; + RSA *rsa = EVP_PKEY_get1_RSA(key->keydata.pkey); + INSIST(rsa != NULL); + RSA_free(rsa); + /* key->keydata.pkey still has a reference so rsa is still valid. */ + if (rsa != NULL && RSA_test_flags(rsa, RSA_FLAG_EXT_PKEY) != 0) { + return (true); + } + RSA_get0_key(rsa, NULL, NULL, &d); + return (rsa != NULL && d != NULL); +} + +static void +opensslrsa_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + 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; + RSA *rsa; + EVP_PKEY *pkey; + const BIGNUM *e = NULL, *n = NULL; + + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + RSA_get0_key(rsa, &n, &e, NULL); + 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); + + isc_buffer_availableregion(data, &r); + + 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); + } + + RSA_get0_key(rsa, &n, &e, NULL); + BN_bn2bin(e, r.base); + isc_region_consume(&r, e_bytes); + BN_bn2bin(n, r.base); + + isc_buffer_add(data, e_bytes + mod_bytes); + + ret = ISC_R_SUCCESS; +err: + RSA_free(rsa); + return (ret); +} + +static isc_result_t +opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + RSA *rsa; + isc_region_t r; + unsigned int e_bytes; + unsigned int length; + EVP_PKEY *pkey; + BIGNUM *e = NULL, *n = NULL; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + length = r.length; + + rsa = RSA_new(); + if (rsa == NULL) { + return (dst__openssl_toresult(ISC_R_NOMEMORY)); + } + + if (r.length < 1) { + RSA_free(rsa); + return (DST_R_INVALIDPUBLICKEY); + } + e_bytes = *r.base; + isc_region_consume(&r, 1); + + if (e_bytes == 0) { + if (r.length < 2) { + RSA_free(rsa); + return (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) { + RSA_free(rsa); + return (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) { + RSA_free(rsa); + 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_free(rsa); + return (ISC_R_NOMEMORY); + } + key->key_size = BN_num_bits(n); + + isc_buffer_forward(data, length); + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + RSA_free(rsa); + return (ISC_R_NOMEMORY); + } + if (!EVP_PKEY_set1_RSA(pkey, rsa)) { + EVP_PKEY_free(pkey); + RSA_free(rsa); + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + key->keydata.pkey = pkey; + RSA_free(rsa); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslrsa_tofile(const dst_key_t *key, const char *directory) { + int i; + RSA *rsa; + dst_private_t priv; + unsigned char *bufs[8]; + isc_result_t result; + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + const BIGNUM *p = NULL, *q = NULL; + const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + rsa = EVP_PKEY_get1_RSA(key->keydata.pkey); + if (rsa == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + memset(bufs, 0, sizeof(bufs)); + + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + + for (i = 0; i < 8; i++) { + bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(n)); + } + + i = 0; + + priv.elements[i].tag = TAG_RSA_MODULUS; + priv.elements[i].length = BN_num_bytes(n); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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; + result = dst__privstruct_writefile(key, &priv, directory); + + RSA_free(rsa); + for (i = 0; i < 8; i++) { + if (bufs[i] == NULL) { + break; + } + isc_mem_put(key->mctx, bufs[i], BN_num_bytes(n)); + } + return (result); +} + +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. + */ + RSA_get0_key(rsa, &n1, &e1, NULL); + if (pub != 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); +} + +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; + RSA *rsa = NULL, *pubrsa = NULL; +#if !defined(OPENSSL_NO_ENGINE) + ENGINE *ep = NULL; + const BIGNUM *ex = NULL; +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + isc_mem_t *mctx = key->mctx; + 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; + + /* 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) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + if (pub == NULL) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + key->key_size = pub->key_size; + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ISC_R_SUCCESS); + } + + if (pub != NULL && pub->keydata.pkey != NULL) { + pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey); + } + + 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 is stored in a HSM? + * See if we can fetch it. + */ + if (label != NULL) { +#if !defined(OPENSSL_NO_ENGINE) + if (engine == NULL) { + DST_RET(DST_R_NOENGINE); + } + ep = dst__openssl_getengine(engine); + if (ep == NULL) { + DST_RET(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_R_INVALIDPRIVATEKEY); + } + RSA_get0_key(rsa, NULL, &ex, NULL); + if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + RSA_free(rsa); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ISC_R_SUCCESS); +#else /* if !defined(OPENSSL_NO_ENGINE) */ + DST_RET(DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) */ + } + + 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)) { + DST_RET(ISC_R_FAILURE); + } + key->keydata.pkey = pkey; + + 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; + } + } + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + 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_free(d); + } + } + if (RSA_set0_factors(rsa, p, q) == 0) { + if (p != NULL) { + BN_free(p); + } + if (q != NULL) { + BN_free(q); + } + } + if (RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 0) { + if (dmp1 != NULL) { + BN_free(dmp1); + } + if (dmq1 != NULL) { + BN_free(dmq1); + } + if (iqmp != NULL) { + BN_free(iqmp); + } + } + + if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + key->key_size = BN_num_bits(n); + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + RSA_free(rsa); + + return (ISC_R_SUCCESS); + +err: + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (rsa != NULL) { + RSA_free(rsa); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + 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) + ENGINE *e = NULL; + isc_result_t ret; + EVP_PKEY *pkey = 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_R_NOENGINE); + } + pkey = ENGINE_load_public_key(e, label, NULL, NULL); + if (pkey != NULL) { + pubrsa = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); + 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", + 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_R_INVALIDPRIVATEKEY); + } + RSA_get0_key(rsa, NULL, &ex, NULL); + if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + key->key_size = EVP_PKEY_bits(pkey); + key->keydata.pkey = pkey; + RSA_free(rsa); + return (ISC_R_SUCCESS); + +err: + if (rsa != NULL) { + RSA_free(rsa); + } + if (pubrsa != NULL) { + RSA_free(pubrsa); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + return (ret); +#else /* if !defined(OPENSSL_NO_ENGINE) */ + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif /* if !defined(OPENSSL_NO_ENGINE) */ +} + +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; + RSA *rsa = NULL; + + 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); + } + + 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)); + } + + /* + * 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 (rsa != NULL) { + RSA_free(rsa); + } + 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); +} + +#endif /* !USE_PKCS11 */ + +/*! \file */ diff --git a/lib/dns/order.c b/lib/dns/order.c new file mode 100644 index 0000000..94e5f81 --- /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_copynf(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..d07933d --- /dev/null +++ b/lib/dns/peer.c @@ -0,0 +1,919 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/*% + * 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 NOTIFY_DSCP_BIT 10 +#define TRANSFER_DSCP_BIT 11 +#define QUERY_DSCP_BIT 12 +#define REQUEST_EXPIRE_BIT 13 +#define EDNS_VERSION_BIT 14 +#define FORCE_TCP_BIT 15 +#define SERVER_PADDING_BIT 16 +#define REQUEST_TCP_KEEPALIVE_BIT 17 + +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_setnotifydscp(dns_peer_t *peer, isc_dscp_t dscp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscp < 64); + + peer->notify_dscp = dscp; + DNS_BIT_SET(NOTIFY_DSCP_BIT, &peer->bitflags); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getnotifydscp(dns_peer_t *peer, isc_dscp_t *dscpp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscpp != NULL); + + if (DNS_BIT_CHECK(NOTIFY_DSCP_BIT, &peer->bitflags)) { + *dscpp = peer->notify_dscp; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_peer_settransferdscp(dns_peer_t *peer, isc_dscp_t dscp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscp < 64); + + peer->transfer_dscp = dscp; + DNS_BIT_SET(TRANSFER_DSCP_BIT, &peer->bitflags); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_gettransferdscp(dns_peer_t *peer, isc_dscp_t *dscpp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscpp != NULL); + + if (DNS_BIT_CHECK(TRANSFER_DSCP_BIT, &peer->bitflags)) { + *dscpp = peer->transfer_dscp; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_peer_setquerydscp(dns_peer_t *peer, isc_dscp_t dscp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscp < 64); + + peer->query_dscp = dscp; + DNS_BIT_SET(QUERY_DSCP_BIT, &peer->bitflags); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_peer_getquerydscp(dns_peer_t *peer, isc_dscp_t *dscpp) { + REQUIRE(DNS_PEER_VALID(peer)); + REQUIRE(dscpp != NULL); + + if (DNS_BIT_CHECK(QUERY_DSCP_BIT, &peer->bitflags)) { + *dscpp = peer->query_dscp; + return (ISC_R_SUCCESS); + } + 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/pkcs11.c b/lib/dns/pkcs11.c new file mode 100644 index 0000000..4eb90b9 --- /dev/null +++ b/lib/dns/pkcs11.c @@ -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. + */ + +#if USE_PKCS11 + +#include + +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_pkcs11.h" + +isc_result_t +dst__pkcs11_toresult(const char *funcname, const char *file, int line, + isc_result_t fallback, CK_RV rv) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_CRYPTO, + ISC_LOG_WARNING, "%s:%d: %s: Error = 0x%.8lX\n", file, + line, funcname, rv); + if (rv == CKR_HOST_MEMORY) { + return (ISC_R_NOMEMORY); + } + return (fallback); +} + +#endif /* USE_PKCS11 */ +/*! \file */ diff --git a/lib/dns/pkcs11ecdsa_link.c b/lib/dns/pkcs11ecdsa_link.c new file mode 100644 index 0000000..2806c3b --- /dev/null +++ b/lib/dns/pkcs11ecdsa_link.c @@ -0,0 +1,1153 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 USE_PKCS11 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +/* + * FIPS 186-3 ECDSA keys: + * mechanisms: + * CKM_ECDSA, + * CKM_EC_KEY_PAIR_GEN + * domain parameters: + * CKA_EC_PARAMS (choice with OID namedCurve) + * public keys: + * object class CKO_PUBLIC_KEY + * key type CKK_EC + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_EC_POINT (point Q) + * private keys: + * object class CKO_PRIVATE_KEY + * key type CKK_EC + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_VALUE (big int d) + * point format: 0x04 (octet-string) <2*size+1> 0x4 (uncompressed) + */ + +#define TAG_OCTECT_STRING 0x04 +#define UNCOMPRESSED 0x04 + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static void +pkcs11ecdsa_destroy(dst_key_t *key); + +static isc_result_t +pkcs11ecdsa_createctx(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { 0, NULL, 0 }; + CK_SLOT_ID slotid; + pk11_context_t *pk11_ctx; + pk11_object_t *ec = key->keydata.pkey; + isc_result_t ret; + + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + REQUIRE(ec != NULL); + + if (dctx->key->key_alg == DST_ALG_ECDSA256) { + mech.mechanism = CKM_SHA256; + } else { + mech.mechanism = CKM_SHA384; + } + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) { + slotid = ec->slot; + } else { + slotid = pk11_get_best_token(OP_ECDSA); + } + ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon, + NULL, slotid); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + PK11_RET(pkcs_C_DigestInit, (pk11_ctx->session, &mech), ISC_R_FAILURE); + dctx->ctxdata.pk11_ctx = pk11_ctx; + return (ISC_R_SUCCESS); + +err: + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static void +pkcs11ecdsa_destroyctx(dst_context_t *dctx) { + CK_BYTE garbage[ISC_SHA384_DIGESTLENGTH]; + CK_ULONG len = ISC_SHA384_DIGESTLENGTH; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + + if (pk11_ctx != NULL) { + (void)pkcs_C_DigestFinal(pk11_ctx->session, garbage, &len); + memset(garbage, 0, sizeof(garbage)); + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + } +} + +static isc_result_t +pkcs11ecdsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + CK_RV rv; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + REQUIRE(dctx->key->key_alg == DST_ALG_ECDSA256 || + dctx->key->key_alg == DST_ALG_ECDSA384); + + PK11_CALL(pkcs_C_DigestUpdate, + (pk11_ctx->session, (CK_BYTE_PTR)data->base, + (CK_ULONG)data->length), + ISC_R_FAILURE); + + return (ret); +} + +static isc_result_t +pkcs11ecdsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + CK_RV rv; + CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 }, + { CKA_VALUE, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_BYTE digest[ISC_SHA384_DIGESTLENGTH]; + CK_ULONG dgstlen; + CK_ULONG siglen; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *ec = key->keydata.pkey; + isc_region_t r; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + REQUIRE(ec != NULL); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + dgstlen = ISC_SHA256_DIGESTLENGTH; + siglen = DNS_SIG_ECDSA256SIZE; + break; + case DST_ALG_ECDSA384: + siglen = DNS_SIG_ECDSA384SIZE; + dgstlen = ISC_SHA384_DIGESTLENGTH; + break; + default: + UNREACHABLE(); + } + + PK11_RET(pkcs_C_DigestFinal, (pk11_ctx->session, digest, &dgstlen), + ISC_R_FAILURE); + + isc_buffer_availableregion(sig, &r); + if (r.length < siglen) { + DST_RET(ISC_R_NOSPACE); + } + + if (ec->ontoken && (ec->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = ec->ontoken; + pk11_ctx->object = ec->object; + goto token_key; + } + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_EC_PARAMS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_VALUE: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey), + ISC_R_FAILURE); + +token_key: + + PK11_RET(pkcs_C_SignInit, + (pk11_ctx->session, &mech, + pk11_ctx->ontoken ? pk11_ctx->object : hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_Sign, + (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)r.base, + &siglen), + DST_R_SIGNFAILURE); + + isc_buffer_add(sig, (unsigned int)siglen); + +err: + + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + memset(keyTemplate[i].pValue, 0, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + + return (ret); +} + +static isc_result_t +pkcs11ecdsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + CK_RV rv; + CK_MECHANISM mech = { CKM_ECDSA, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 }, + { CKA_EC_POINT, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_BYTE digest[ISC_SHA384_DIGESTLENGTH]; + CK_ULONG dgstlen; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *ec = key->keydata.pkey; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + REQUIRE(ec != NULL); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + dgstlen = ISC_SHA256_DIGESTLENGTH; + break; + case DST_ALG_ECDSA384: + dgstlen = ISC_SHA384_DIGESTLENGTH; + break; + default: + UNREACHABLE(); + } + + PK11_RET(pkcs_C_DigestFinal, (pk11_ctx->session, digest, &dgstlen), + ISC_R_FAILURE); + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_EC_PARAMS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_EC_POINT: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_Verify, + (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)sig->base, + (CK_ULONG)sig->length), + DST_R_VERIFYFAILURE); + +err: + + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + memset(keyTemplate[i].pValue, 0, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + + return (ret); +} + +static bool +pkcs11ecdsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *ec1, *ec2; + CK_ATTRIBUTE *attr1, *attr2; + + ec1 = key1->keydata.pkey; + ec2 = key2->keydata.pkey; + + if ((ec1 == NULL) && (ec2 == NULL)) { + return (true); + } else if ((ec1 == NULL) || (ec2 == NULL)) { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_EC_PARAMS); + attr2 = pk11_attribute_bytype(ec2, CKA_EC_PARAMS); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_EC_POINT); + attr2 = pk11_attribute_bytype(ec2, CKA_EC_POINT); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_VALUE); + attr2 = pk11_attribute_bytype(ec2, CKA_VALUE); + if (((attr1 != NULL) || (attr2 != NULL)) && + ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen))) + { + return (false); + } + + if (!ec1->ontoken && !ec2->ontoken) { + return (true); + } else if (ec1->ontoken || ec2->ontoken || (ec1->object != ec2->object)) + { + return (false); + } + + return (true); +} + +#define SETCURVE() \ + switch (key->key_alg) { \ + case DST_ALG_ECDSA256: \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(PK11_ECC_PRIME256V1)); \ + memmove(attr->pValue, PK11_ECC_PRIME256V1, \ + sizeof(PK11_ECC_PRIME256V1)); \ + attr->ulValueLen = sizeof(PK11_ECC_PRIME256V1); \ + break; \ + case DST_ALG_ECDSA384: \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(PK11_ECC_SECP384R1)); \ + memmove(attr->pValue, PK11_ECC_SECP384R1, \ + sizeof(PK11_ECC_SECP384R1)); \ + attr->ulValueLen = sizeof(PK11_ECC_SECP384R1); \ + break; \ + default: \ + UNREACHABLE(); \ + } + +#define FREECURVE() \ + if (attr->pValue != NULL) { \ + memset(attr->pValue, 0, attr->ulValueLen); \ + isc_mem_put(key->mctx, attr->pValue, attr->ulValueLen); \ + attr->pValue = NULL; \ + } + +static isc_result_t +pkcs11ecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_EC_KEY_PAIR_GEN, NULL, 0 }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_ATTRIBUTE pubTemplate[] = { + { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 } + }; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privClass = CKO_PRIVATE_KEY; + CK_ATTRIBUTE privTemplate[] = { + { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *ec; + pk11_context_t *pk11_ctx; + isc_result_t ret; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + UNUSED(unused); + UNUSED(callback); + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, false, NULL, + pk11_get_best_token(OP_ECDSA)); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3); + memset(ec->repr, 0, sizeof(*attr) * 3); + ec->attrcnt = 3; + + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_EC_POINT; + attr[2].type = CKA_VALUE; + + attr = &pubTemplate[5]; + SETCURVE(); + + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)6, + privTemplate, (CK_ULONG)7, &pub, &priv), + DST_R_CRYPTOFAILURE); + + attr = &pubTemplate[5]; + FREECURVE(); + + attr = ec->repr; + SETCURVE(); + + attr++; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + + attr++; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + break; + case DST_ALG_ECDSA384: + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +err: + pkcs11ecdsa_destroy(key); + if (priv != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + } + if (pub != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static bool +pkcs11ecdsa_isprivate(const dst_key_t *key) { + pk11_object_t *ec = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (ec == NULL) { + return (false); + } + attr = pk11_attribute_bytype(ec, CKA_VALUE); + return (attr != NULL || ec->ontoken); +} + +static void +pkcs11ecdsa_destroy(dst_key_t *key) { + pk11_object_t *ec = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (ec == NULL) { + return; + } + + INSIST((ec->object == CK_INVALID_HANDLE) || ec->ontoken); + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_LABEL: + case CKA_ID: + case CKA_EC_PARAMS: + case CKA_EC_POINT: + case CKA_VALUE: + FREECURVE(); + break; + } + } + if (ec->repr != NULL) { + memset(ec->repr, 0, ec->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, ec->repr, ec->attrcnt * sizeof(*attr)); + } + memset(ec, 0, sizeof(*ec)); + isc_mem_put(key->mctx, ec, sizeof(*ec)); + key->keydata.pkey = NULL; +} + +static isc_result_t +pkcs11ecdsa_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *ec; + isc_region_t r; + unsigned int len; + CK_ATTRIBUTE *attr; + + REQUIRE(key->keydata.pkey != NULL); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + len = DNS_KEY_ECDSA256SIZE; + break; + case DST_ALG_ECDSA384: + len = DNS_KEY_ECDSA384SIZE; + break; + default: + UNREACHABLE(); + } + + ec = key->keydata.pkey; + attr = pk11_attribute_bytype(ec, CKA_EC_POINT); + if ((attr == NULL) || (attr->ulValueLen != len + 3) || + (((CK_BYTE_PTR)attr->pValue)[0] != TAG_OCTECT_STRING) || + (((CK_BYTE_PTR)attr->pValue)[1] != len + 1) || + (((CK_BYTE_PTR)attr->pValue)[2] != UNCOMPRESSED)) + { + return (ISC_R_FAILURE); + } + + isc_buffer_availableregion(data, &r); + if (r.length < len) { + return (ISC_R_NOSPACE); + } + memmove(r.base, (CK_BYTE_PTR)attr->pValue + 3, len); + isc_buffer_add(data, len); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11ecdsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *ec; + isc_region_t r; + unsigned int len; + CK_ATTRIBUTE *attr; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + switch (key->key_alg) { + case DST_ALG_ECDSA256: + len = DNS_KEY_ECDSA256SIZE; + break; + case DST_ALG_ECDSA384: + len = DNS_KEY_ECDSA384SIZE; + break; + default: + UNREACHABLE(); + } + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + if (r.length != len) { + return (DST_R_INVALIDPUBLICKEY); + } + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + ec->attrcnt = 2; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + SETCURVE(); + + attr++; + attr->type = CKA_EC_POINT; + attr->pValue = isc_mem_get(key->mctx, len + 3); + ((CK_BYTE_PTR)attr->pValue)[0] = TAG_OCTECT_STRING; + ((CK_BYTE_PTR)attr->pValue)[1] = len + 1; + ((CK_BYTE_PTR)attr->pValue)[2] = UNCOMPRESSED; + memmove((CK_BYTE_PTR)attr->pValue + 3, r.base, len); + attr->ulValueLen = len + 3; + + isc_buffer_forward(data, len); + key->keydata.pkey = ec; + key->key_size = len * 4; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11ecdsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + pk11_object_t *ec; + dst_private_t priv; + unsigned char *buf = NULL; + unsigned int i = 0; + CK_ATTRIBUTE *attr; + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + ec = key->keydata.pkey; + attr = pk11_attribute_bytype(ec, CKA_VALUE); + if (attr != NULL) { + buf = isc_mem_get(key->mctx, attr->ulValueLen); + priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY; + priv.elements[i].length = (unsigned short)attr->ulValueLen; + memmove(buf, attr->pValue, attr->ulValueLen); + priv.elements[i].data = buf; + i++; + } + + if (key->engine != NULL) { + priv.elements[i].tag = TAG_ECDSA_ENGINE; + priv.elements[i].length = 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 = strlen(key->label) + 1; + priv.elements[i].data = (unsigned char *)key->label; + i++; + } + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + + if (buf != NULL) { + memset(buf, 0, attr->ulValueLen); + isc_mem_put(key->mctx, buf, attr->ulValueLen); + } + return (ret); +} + +static isc_result_t +pkcs11ecdsa_fetch(dst_key_t *key, const char *engine, const char *label, + dst_key_t *pub) { + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *pubattr; + pk11_object_t *ec; + pk11_object_t *pubec; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + + if (label == NULL) { + return (DST_R_NOENGINE); + } + + ec = key->keydata.pkey; + pubec = pub->keydata.pkey; + + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(ec->repr, 0, sizeof(*attr) * 2); + ec->attrcnt = 2; + attr = ec->repr; + + attr->type = CKA_EC_PARAMS; + pubattr = pk11_attribute_bytype(pubec, CKA_EC_PARAMS); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + attr++; + + attr->type = CKA_EC_POINT; + pubattr = pk11_attribute_bytype(pubec, CKA_EC_POINT); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_ECDSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon, + NULL, ec->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(ec, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(ec, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + return (ISC_R_SUCCESS); + +err: + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + return (ret); +} + +static isc_result_t +pkcs11ecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + pk11_object_t *ec = NULL; + CK_ATTRIBUTE *attr, *pattr; + isc_mem_t *mctx = key->mctx; + unsigned int i; + const char *engine = NULL, *label = NULL; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + if ((pub == NULL) || (pub->keydata.pkey == NULL)) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + if (key->external) { + if (priv.nelements != 0) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + key->key_size = pub->key_size; + + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + + return (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; + default: + break; + } + } + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + + /* Is this key is stored in a HSM? See if we can fetch it. */ + if ((label != NULL) || (engine != NULL)) { + ret = pkcs11ecdsa_fetch(key, engine, label, pub); + if (ret != ISC_R_SUCCESS) { + goto err; + } + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + return (ret); + } + + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3); + memset(ec->repr, 0, sizeof(*attr) * 3); + ec->attrcnt = 3; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_PARAMS); + INSIST(pattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen); + memmove(attr->pValue, pattr->pValue, pattr->ulValueLen); + attr->ulValueLen = pattr->ulValueLen; + + attr++; + attr->type = CKA_EC_POINT; + pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_POINT); + INSIST(pattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen); + memmove(attr->pValue, pattr->pValue, pattr->ulValueLen); + attr->ulValueLen = pattr->ulValueLen; + + attr++; + attr->type = CKA_VALUE; + attr->pValue = isc_mem_get(key->mctx, priv.elements[0].length); + memmove(attr->pValue, priv.elements[0].data, priv.elements[0].length); + attr->ulValueLen = priv.elements[0].length; + + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + switch (key->key_alg) { + case DST_ALG_ECDSA256: + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + break; + case DST_ALG_ECDSA384: + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +err: + pkcs11ecdsa_destroy(key); + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + return (ret); +} + +static isc_result_t +pkcs11ecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { + CK_RV rv; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + pk11_object_t *ec; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + unsigned int i; + + UNUSED(pin); + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + key->keydata.pkey = ec; + + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(ec->repr, 0, sizeof(*attr) * 2); + ec->attrcnt = 2; + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_EC_POINT; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_ECDSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_ECDSA, true, false, ec->reqlogon, + NULL, ec->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(ec, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(ec, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + attr = ec->repr; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + for (i = 0; i <= 1; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + + keyClass = CKO_PRIVATE_KEY; + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + switch (key->key_alg) { + case DST_ALG_ECDSA256: + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + break; + case DST_ALG_ECDSA384: + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + break; + default: + UNREACHABLE(); + } + + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + return (ISC_R_SUCCESS); + +err: + pkcs11ecdsa_destroy(key); + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + return (ret); +} + +static dst_func_t pkcs11ecdsa_functions = { + pkcs11ecdsa_createctx, + NULL, /*%< createctx2 */ + pkcs11ecdsa_destroyctx, + pkcs11ecdsa_adddata, + pkcs11ecdsa_sign, + pkcs11ecdsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + pkcs11ecdsa_compare, + NULL, /*%< paramcompare */ + pkcs11ecdsa_generate, + pkcs11ecdsa_isprivate, + pkcs11ecdsa_destroy, + pkcs11ecdsa_todns, + pkcs11ecdsa_fromdns, + pkcs11ecdsa_tofile, + pkcs11ecdsa_parse, + NULL, /*%< cleanup */ + pkcs11ecdsa_fromlabel, + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11ecdsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + *funcp = &pkcs11ecdsa_functions; + } + return (ISC_R_SUCCESS); +} + +#endif /* USE_PKCS11 */ diff --git a/lib/dns/pkcs11eddsa_link.c b/lib/dns/pkcs11eddsa_link.c new file mode 100644 index 0000000..a6aff28 --- /dev/null +++ b/lib/dns/pkcs11eddsa_link.c @@ -0,0 +1,1124 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 USE_PKCS11 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +/* + * FIPS 186-3 EDDSA keys: + * mechanisms: + * CKM_EDDSA, + * CKM_EC_EDWARDS_KEY_PAIR_GEN + * domain parameters: + * CKA_EC_PARAMS (choice with OID namedCurve) + * public keys: + * object class CKO_PUBLIC_KEY + * key type CKK_EC_EDWARDS + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_EC_POINT (big int A) + * private keys: + * object class CKO_PRIVATE_KEY + * key type CKK_EC_EDWARDS + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_VALUE (big int k) + * point format: 0x04 (octet-string) + */ + +#define TAG_OCTECT_STRING 0x04 + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static void +pkcs11eddsa_destroy(dst_key_t *key); + +static isc_result_t +pkcs11eddsa_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, 16); + isc_buffer_setautorealloc(buf, true); + dctx->ctxdata.generic = buf; + + return (ISC_R_SUCCESS); +} + +static void +pkcs11eddsa_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 +pkcs11eddsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + 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); + INSIST(result == ISC_R_SUCCESS); + + return (result); +} + +static isc_result_t +pkcs11eddsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + CK_RV rv; + CK_MECHANISM mech = { CKM_EDDSA, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC_EDWARDS; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 }, + { CKA_VALUE, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_ULONG siglen; + CK_SLOT_ID slotid; + pk11_context_t *pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *ec = key->keydata.pkey; + isc_region_t t; + isc_region_t r; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + REQUIRE(ec != NULL); + + switch (key->key_alg) { + case DST_ALG_ED25519: + siglen = DNS_SIG_ED25519SIZE; + break; + case DST_ALG_ED448: + siglen = DNS_SIG_ED448SIZE; + break; + default: + UNREACHABLE(); + } + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) { + slotid = ec->slot; + } else { + slotid = pk11_get_best_token(OP_EDDSA); + } + ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon, + NULL, slotid); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + isc_buffer_availableregion(sig, &r); + if (r.length < siglen) { + DST_RET(ISC_R_NOSPACE); + } + + if (ec->ontoken && (ec->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = ec->ontoken; + pk11_ctx->object = ec->object; + goto token_key; + } + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_EC_PARAMS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_VALUE: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey), + ISC_R_FAILURE); + +token_key: + + PK11_RET(pkcs_C_SignInit, + (pk11_ctx->session, &mech, + pk11_ctx->ontoken ? pk11_ctx->object : hKey), + ISC_R_FAILURE); + + isc_buffer_usedregion(buf, &t); + + PK11_RET(pkcs_C_Sign, + (pk11_ctx->session, (CK_BYTE_PTR)t.base, (CK_ULONG)t.length, + (CK_BYTE_PTR)r.base, &siglen), + DST_R_SIGNFAILURE); + + isc_buffer_add(sig, (unsigned int)siglen); + +err: + + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + memset(keyTemplate[i].pValue, 0, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + isc_buffer_free(&buf); + dctx->ctxdata.generic = NULL; + + return (ret); +} + +static isc_result_t +pkcs11eddsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; + CK_RV rv; + CK_MECHANISM mech = { CKM_EDDSA, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC_EDWARDS; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 }, + { CKA_EC_POINT, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_SLOT_ID slotid; + pk11_context_t *pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *ec = key->keydata.pkey; + isc_region_t t; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + REQUIRE(ec != NULL); + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) { + slotid = ec->slot; + } else { + slotid = pk11_get_best_token(OP_EDDSA); + } + ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon, + NULL, slotid); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_EC_PARAMS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_EC_POINT: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey), + ISC_R_FAILURE); + + isc_buffer_usedregion(buf, &t); + + PK11_RET(pkcs_C_Verify, + (pk11_ctx->session, (CK_BYTE_PTR)t.base, (CK_ULONG)t.length, + (CK_BYTE_PTR)sig->base, (CK_ULONG)sig->length), + DST_R_VERIFYFAILURE); + +err: + + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + memset(keyTemplate[i].pValue, 0, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + isc_buffer_free(&buf); + dctx->ctxdata.generic = NULL; + + return (ret); +} + +static bool +pkcs11eddsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *ec1, *ec2; + CK_ATTRIBUTE *attr1, *attr2; + + ec1 = key1->keydata.pkey; + ec2 = key2->keydata.pkey; + + if ((ec1 == NULL) && (ec2 == NULL)) { + return (true); + } else if ((ec1 == NULL) || (ec2 == NULL)) { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_EC_PARAMS); + attr2 = pk11_attribute_bytype(ec2, CKA_EC_PARAMS); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_EC_POINT); + attr2 = pk11_attribute_bytype(ec2, CKA_EC_POINT); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(ec1, CKA_VALUE); + attr2 = pk11_attribute_bytype(ec2, CKA_VALUE); + if (((attr1 != NULL) || (attr2 != NULL)) && + ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen))) + { + return (false); + } + + if (!ec1->ontoken && !ec2->ontoken) { + return (true); + } else if (ec1->ontoken || ec2->ontoken || (ec1->object != ec2->object)) + { + return (false); + } + + return (true); +} + +#define SETCURVE() \ + switch (key->key_alg) { \ + case DST_ALG_ED25519: \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(PK11_ECX_ED25519)); \ + memmove(attr->pValue, PK11_ECX_ED25519, \ + sizeof(PK11_ECX_ED25519)); \ + attr->ulValueLen = sizeof(PK11_ECX_ED25519); \ + break; \ + case DST_ALG_ED448: \ + attr->pValue = isc_mem_get(key->mctx, sizeof(PK11_ECX_ED448)); \ + memmove(attr->pValue, PK11_ECX_ED448, sizeof(PK11_ECX_ED448)); \ + attr->ulValueLen = sizeof(PK11_ECX_ED448); \ + break; \ + default: \ + UNREACHABLE(); \ + } + +#define FREECURVE() \ + if (attr->pValue != NULL) { \ + memset(attr->pValue, 0, attr->ulValueLen); \ + isc_mem_put(key->mctx, attr->pValue, attr->ulValueLen); \ + attr->pValue = NULL; \ + } + +static isc_result_t +pkcs11eddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_EC_EDWARDS_KEY_PAIR_GEN, NULL, 0 }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC_EDWARDS; + CK_ATTRIBUTE pubTemplate[] = { + { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_EC_PARAMS, NULL, 0 } + }; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE privClass = CKO_PRIVATE_KEY; + CK_ATTRIBUTE privTemplate[] = { + { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *ec; + pk11_context_t *pk11_ctx; + isc_result_t ret; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + UNUSED(unused); + UNUSED(callback); + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, false, NULL, + pk11_get_best_token(OP_EDDSA)); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3); + memset(ec->repr, 0, sizeof(*attr) * 3); + ec->attrcnt = 3; + + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_EC_POINT; + attr[2].type = CKA_VALUE; + + attr = &pubTemplate[5]; + SETCURVE(); + + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)6, + privTemplate, (CK_ULONG)7, &pub, &priv), + DST_R_CRYPTOFAILURE); + + attr = &pubTemplate[5]; + FREECURVE(); + + attr = ec->repr; + SETCURVE(); + + attr++; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + + attr++; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + switch (key->key_alg) { + case DST_ALG_ED25519: + key->key_size = DNS_KEY_ED25519SIZE * 8; + break; + case DST_ALG_ED448: + key->key_size = DNS_KEY_ED448SIZE * 8; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +err: + pkcs11eddsa_destroy(key); + if (priv != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + } + if (pub != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + } + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static bool +pkcs11eddsa_isprivate(const dst_key_t *key) { + pk11_object_t *ec = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (ec == NULL) { + return (false); + } + attr = pk11_attribute_bytype(ec, CKA_VALUE); + return (attr != NULL || ec->ontoken); +} + +static void +pkcs11eddsa_destroy(dst_key_t *key) { + pk11_object_t *ec = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (ec == NULL) { + return; + } + + INSIST((ec->object == CK_INVALID_HANDLE) || ec->ontoken); + + for (attr = pk11_attribute_first(ec); attr != NULL; + attr = pk11_attribute_next(ec, attr)) + { + switch (attr->type) { + case CKA_LABEL: + case CKA_ID: + case CKA_EC_PARAMS: + case CKA_EC_POINT: + case CKA_VALUE: + FREECURVE(); + break; + } + } + if (ec->repr != NULL) { + memset(ec->repr, 0, ec->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, ec->repr, ec->attrcnt * sizeof(*attr)); + } + memset(ec, 0, sizeof(*ec)); + isc_mem_put(key->mctx, ec, sizeof(*ec)); + key->keydata.pkey = NULL; +} + +static isc_result_t +pkcs11eddsa_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *ec; + isc_region_t r; + unsigned int len; + CK_ATTRIBUTE *attr; + + REQUIRE(key->keydata.pkey != NULL); + + switch (key->key_alg) { + case DST_ALG_ED25519: + len = DNS_KEY_ED25519SIZE; + break; + case DST_ALG_ED448: + len = DNS_KEY_ED448SIZE; + break; + default: + UNREACHABLE(); + } + + ec = key->keydata.pkey; + attr = pk11_attribute_bytype(ec, CKA_EC_POINT); + if ((attr == NULL) || (attr->ulValueLen != len + 2) || + (((CK_BYTE_PTR)attr->pValue)[0] != TAG_OCTECT_STRING) || + (((CK_BYTE_PTR)attr->pValue)[1] != len)) + { + return (ISC_R_FAILURE); + } + + isc_buffer_availableregion(data, &r); + if (r.length < len) { + return (ISC_R_NOSPACE); + } + memmove(r.base, (CK_BYTE_PTR)attr->pValue + 2, len); + isc_buffer_add(data, len); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11eddsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *ec; + isc_region_t r; + unsigned int len; + CK_ATTRIBUTE *attr; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + switch (key->key_alg) { + case DST_ALG_ED25519: + len = DNS_KEY_ED25519SIZE; + break; + case DST_ALG_ED448: + len = DNS_KEY_ED448SIZE; + break; + default: + UNREACHABLE(); + } + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + if (r.length != len) { + return (DST_R_INVALIDPUBLICKEY); + } + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + ec->attrcnt = 2; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + SETCURVE(); + + attr++; + attr->type = CKA_EC_POINT; + attr->pValue = isc_mem_get(key->mctx, len + 2); + ((CK_BYTE_PTR)attr->pValue)[0] = TAG_OCTECT_STRING; + ((CK_BYTE_PTR)attr->pValue)[1] = len; + memmove((CK_BYTE_PTR)attr->pValue + 2, r.base, len); + attr->ulValueLen = len + 2; + + isc_buffer_forward(data, len); + key->keydata.pkey = ec; + key->key_size = len * 8; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11eddsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + pk11_object_t *ec; + dst_private_t priv; + unsigned char *buf = NULL; + unsigned int i = 0; + CK_ATTRIBUTE *attr; + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + ec = key->keydata.pkey; + attr = pk11_attribute_bytype(ec, CKA_VALUE); + if (attr != NULL) { + buf = isc_mem_get(key->mctx, attr->ulValueLen); + priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY; + priv.elements[i].length = (unsigned short)attr->ulValueLen; + memmove(buf, attr->pValue, attr->ulValueLen); + priv.elements[i].data = buf; + i++; + } + + if (key->engine != NULL) { + priv.elements[i].tag = TAG_EDDSA_ENGINE; + priv.elements[i].length = 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 = strlen(key->label) + 1; + priv.elements[i].data = (unsigned char *)key->label; + i++; + } + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + + if (buf != NULL) { + memset(buf, 0, attr->ulValueLen); + isc_mem_put(key->mctx, buf, attr->ulValueLen); + } + return (ret); +} + +static isc_result_t +pkcs11eddsa_fetch(dst_key_t *key, const char *engine, const char *label, + dst_key_t *pub) { + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_EC_EDWARDS; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *pubattr; + pk11_object_t *ec; + pk11_object_t *pubec; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + + if (label == NULL) { + return (DST_R_NOENGINE); + } + + ec = key->keydata.pkey; + pubec = pub->keydata.pkey; + + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(ec->repr, 0, sizeof(*attr) * 2); + ec->attrcnt = 2; + attr = ec->repr; + + attr->type = CKA_EC_PARAMS; + pubattr = pk11_attribute_bytype(pubec, CKA_EC_PARAMS); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + attr++; + + attr->type = CKA_EC_POINT; + pubattr = pk11_attribute_bytype(pubec, CKA_EC_POINT); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_EDDSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon, + NULL, ec->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(ec, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(ec, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + return (ISC_R_SUCCESS); + +err: + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + return (ret); +} + +static isc_result_t +pkcs11eddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + pk11_object_t *ec = NULL; + CK_ATTRIBUTE *attr, *pattr; + isc_mem_t *mctx = key->mctx; + unsigned int i; + const char *engine = NULL, *label = NULL; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + + if ((pub == NULL) || (pub->keydata.pkey == NULL)) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + if (key->external) { + if (priv.nelements != 0) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + key->key_size = pub->key_size; + + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + + return (ISC_R_SUCCESS); + } + + 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; + default: + break; + } + } + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + + /* Is this key is stored in a HSM? See if we can fetch it. */ + if ((label != NULL) || (engine != NULL)) { + ret = pkcs11eddsa_fetch(key, engine, label, pub); + if (ret != ISC_R_SUCCESS) { + goto err; + } + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + return (ret); + } + + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 3); + memset(ec->repr, 0, sizeof(*attr) * 3); + ec->attrcnt = 3; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_PARAMS); + INSIST(pattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen); + memmove(attr->pValue, pattr->pValue, pattr->ulValueLen); + attr->ulValueLen = pattr->ulValueLen; + + attr++; + attr->type = CKA_EC_POINT; + pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_EC_POINT); + INSIST(pattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen); + memmove(attr->pValue, pattr->pValue, pattr->ulValueLen); + attr->ulValueLen = pattr->ulValueLen; + + attr++; + attr->type = CKA_VALUE; + attr->pValue = isc_mem_get(key->mctx, priv.elements[0].length); + memmove(attr->pValue, priv.elements[0].data, priv.elements[0].length); + attr->ulValueLen = priv.elements[0].length; + + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + switch (key->key_alg) { + case DST_ALG_ED25519: + key->key_size = DNS_KEY_ED25519SIZE * 8; + break; + case DST_ALG_ED448: + key->key_size = DNS_KEY_ED448SIZE * 8; + break; + default: + UNREACHABLE(); + } + + return (ISC_R_SUCCESS); + +err: + pkcs11eddsa_destroy(key); + dst__privstruct_free(&priv, mctx); + memset(&priv, 0, sizeof(priv)); + return (ret); +} + +static isc_result_t +pkcs11eddsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { + CK_RV rv; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EC_EDWARDS; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + pk11_object_t *ec; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + unsigned int i; + + UNUSED(pin); + + ec = isc_mem_get(key->mctx, sizeof(*ec)); + memset(ec, 0, sizeof(*ec)); + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + key->keydata.pkey = ec; + + ec->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(ec->repr, 0, sizeof(*attr) * 2); + ec->attrcnt = 2; + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_EC_POINT; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_EDDSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_EDDSA, true, false, ec->reqlogon, + NULL, ec->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(ec, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(ec, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + attr = ec->repr; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + for (i = 0; i <= 1; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + + keyClass = CKO_PRIVATE_KEY; + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &ec->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + switch (key->key_alg) { + case DST_ALG_ED25519: + key->key_size = DNS_KEY_ED25519SIZE * 8; + break; + case DST_ALG_ED448: + key->key_size = DNS_KEY_ED448SIZE * 8; + break; + default: + UNREACHABLE(); + } + + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + return (ISC_R_SUCCESS); + +err: + pkcs11eddsa_destroy(key); + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + return (ret); +} + +static dst_func_t pkcs11eddsa_functions = { + pkcs11eddsa_createctx, + NULL, /*%< createctx2 */ + pkcs11eddsa_destroyctx, + pkcs11eddsa_adddata, + pkcs11eddsa_sign, + pkcs11eddsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + pkcs11eddsa_compare, + NULL, /*%< paramcompare */ + pkcs11eddsa_generate, + pkcs11eddsa_isprivate, + pkcs11eddsa_destroy, + pkcs11eddsa_todns, + pkcs11eddsa_fromdns, + pkcs11eddsa_tofile, + pkcs11eddsa_parse, + NULL, /*%< cleanup */ + pkcs11eddsa_fromlabel, + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11eddsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) { + *funcp = &pkcs11eddsa_functions; + } + return (ISC_R_SUCCESS); +} + +#endif /* USE_PKCS11 */ diff --git a/lib/dns/pkcs11rsa_link.c b/lib/dns/pkcs11rsa_link.c new file mode 100644 index 0000000..b439dd7 --- /dev/null +++ b/lib/dns/pkcs11rsa_link.c @@ -0,0 +1,2115 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 USE_PKCS11 + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +/* + * Limit the size of public exponents. + */ +#ifndef RSA_MAX_PUBEXP_BITS +#define RSA_MAX_PUBEXP_BITS 35 +#endif /* ifndef RSA_MAX_PUBEXP_BITS */ + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static void +pkcs11rsa_destroy(dst_key_t *key); + +#ifndef PK11_RSA_PKCS_REPLACE + +static isc_result_t +pkcs11rsa_createctx_sign(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { 0, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_MODULUS, NULL, 0 }, + { CKA_PUBLIC_EXPONENT, NULL, 0 }, + { CKA_PRIVATE_EXPONENT, NULL, 0 }, + { CKA_PRIME_1, NULL, 0 }, + { CKA_PRIME_2, NULL, 0 }, + { CKA_EXPONENT_1, NULL, 0 }, + { CKA_EXPONENT_2, NULL, 0 }, + { CKA_COEFFICIENT, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_SLOT_ID slotid; + pk11_object_t *rsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + 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); + + /* + * 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(); + } + + rsa = key->keydata.pkey; + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (rsa->ontoken) { + slotid = rsa->slot; + } else { + slotid = pk11_get_best_token(OP_RSA); + } + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon, + NULL, slotid); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + if (rsa->ontoken && (rsa->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = rsa->ontoken; + pk11_ctx->object = rsa->object; + goto token_key; + } + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + case CKA_PUBLIC_EXPONENT: + INSIST(keyTemplate[7].type == attr->type); + keyTemplate[7].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[7].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[7].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIVATE_EXPONENT: + INSIST(keyTemplate[8].type == attr->type); + keyTemplate[8].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[8].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[8].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIME_1: + INSIST(keyTemplate[9].type == attr->type); + keyTemplate[9].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[9].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[9].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIME_2: + INSIST(keyTemplate[10].type == attr->type); + keyTemplate[10].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[10].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[10].ulValueLen = attr->ulValueLen; + break; + case CKA_EXPONENT_1: + INSIST(keyTemplate[11].type == attr->type); + keyTemplate[11].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[11].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[11].ulValueLen = attr->ulValueLen; + break; + case CKA_EXPONENT_2: + INSIST(keyTemplate[12].type == attr->type); + keyTemplate[12].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[12].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[12].ulValueLen = attr->ulValueLen; + break; + case CKA_COEFFICIENT: + INSIST(keyTemplate[13].type == attr->type); + keyTemplate[13].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[13].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[13].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)14, + &pk11_ctx->object), + ISC_R_FAILURE); + +token_key: + + switch (dctx->key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + mech.mechanism = CKM_SHA1_RSA_PKCS; + break; + case DST_ALG_RSASHA256: + mech.mechanism = CKM_SHA256_RSA_PKCS; + break; + case DST_ALG_RSASHA512: + mech.mechanism = CKM_SHA512_RSA_PKCS; + break; + default: + UNREACHABLE(); + } + + PK11_RET(pkcs_C_SignInit, (pk11_ctx->session, &mech, pk11_ctx->object), + ISC_R_FAILURE); + + dctx->ctxdata.pk11_ctx = pk11_ctx; + + for (i = 6; i <= 13; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + + return (ISC_R_SUCCESS); + +err: + if (!pk11_ctx->ontoken && (pk11_ctx->object != CK_INVALID_HANDLE)) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, pk11_ctx->object); + } + for (i = 6; i <= 13; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static isc_result_t +pkcs11rsa_createctx_verify(dst_key_t *key, unsigned int maxbits, + dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { 0, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_MODULUS, NULL, 0 }, + { CKA_PUBLIC_EXPONENT, NULL, 0 }, + }; + CK_ATTRIBUTE *attr; + pk11_object_t *rsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + 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); + REQUIRE(maxbits <= RSA_MAX_PUBEXP_BITS); + + /* + * 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(); + } + + rsa = key->keydata.pkey; + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon, + NULL, pk11_get_best_token(OP_RSA)); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + unsigned int bits; + + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_PUBLIC_EXPONENT: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + ret = pk11_numbits(attr->pValue, attr->ulValueLen, + &bits); + if (ret != ISC_R_SUCCESS || + (bits > maxbits && maxbits != 0)) + { + DST_RET(DST_R_VERIFYFAILURE); + } + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, + &pk11_ctx->object), + ISC_R_FAILURE); + + switch (dctx->key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + mech.mechanism = CKM_SHA1_RSA_PKCS; + break; + case DST_ALG_RSASHA256: + mech.mechanism = CKM_SHA256_RSA_PKCS; + break; + case DST_ALG_RSASHA512: + mech.mechanism = CKM_SHA512_RSA_PKCS; + break; + default: + UNREACHABLE(); + } + + PK11_RET(pkcs_C_VerifyInit, + (pk11_ctx->session, &mech, pk11_ctx->object), ISC_R_FAILURE); + + dctx->ctxdata.pk11_ctx = pk11_ctx; + + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + + return (ISC_R_SUCCESS); + +err: + if (!pk11_ctx->ontoken && (pk11_ctx->object != CK_INVALID_HANDLE)) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, pk11_ctx->object); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static isc_result_t +pkcs11rsa_createctx(dst_key_t *key, dst_context_t *dctx) { + if (dctx->use == DO_SIGN) { + return (pkcs11rsa_createctx_sign(key, dctx)); + } else { + return (pkcs11rsa_createctx_verify(key, 0U, dctx)); + } +} + +static isc_result_t +pkcs11rsa_createctx2(dst_key_t *key, int maxbits, dst_context_t *dctx) { + if (dctx->use == DO_SIGN) { + return (pkcs11rsa_createctx_sign(key, dctx)); + } else { + return (pkcs11rsa_createctx_verify(key, (unsigned)maxbits, + dctx)); + } +} + +static void +pkcs11rsa_destroyctx(dst_context_t *dctx) { + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + + if (pk11_ctx != NULL) { + if (!pk11_ctx->ontoken && + (pk11_ctx->object != CK_INVALID_HANDLE)) + { + (void)pkcs_C_DestroyObject(pk11_ctx->session, + pk11_ctx->object); + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + } +} + +static isc_result_t +pkcs11rsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + CK_RV rv; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + if (dctx->use == DO_SIGN) { + PK11_CALL(pkcs_C_SignUpdate, + (pk11_ctx->session, (CK_BYTE_PTR)data->base, + (CK_ULONG)data->length), + ISC_R_FAILURE); + } else { + PK11_CALL(pkcs_C_VerifyUpdate, + (pk11_ctx->session, (CK_BYTE_PTR)data->base, + (CK_ULONG)data->length), + ISC_R_FAILURE); + } + return (ret); +} + +static isc_result_t +pkcs11rsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + CK_RV rv; + CK_ULONG siglen = 0; + isc_region_t r; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + PK11_RET(pkcs_C_SignFinal, (pk11_ctx->session, NULL, &siglen), + DST_R_SIGNFAILURE); + + isc_buffer_availableregion(sig, &r); + + if (r.length < (unsigned int)siglen) { + return (ISC_R_NOSPACE); + } + + PK11_RET(pkcs_C_SignFinal, + (pk11_ctx->session, (CK_BYTE_PTR)r.base, &siglen), + DST_R_SIGNFAILURE); + + isc_buffer_add(sig, (unsigned int)siglen); + +err: + return (ret); +} + +static isc_result_t +pkcs11rsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + CK_RV rv; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + PK11_CALL(pkcs_C_VerifyFinal, + (pk11_ctx->session, (CK_BYTE_PTR)sig->base, + (CK_ULONG)sig->length), + DST_R_VERIFYFAILURE); + return (ret); +} + +#else /* ifndef PK11_RSA_PKCS_REPLACE */ + +/* + * CKM__RSA_PKCS mechanisms are not available so fall back + * to CKM_RSA_PKCS and do the EMSA-PKCS#1-v1.5 encapsulation by hand. + */ + +CK_BYTE md5_der[] = { 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10 }; +CK_BYTE sha1_der[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, + 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; +CK_BYTE sha256_der[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 0x01, 0x05, 0x00, 0x04, 0x20 }; +CK_BYTE sha512_der[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 0x03, 0x05, 0x00, 0x04, 0x40 }; +#define MAX_DER_SIZE 19 +#define MIN_PKCS1_PADLEN 11 + +static isc_result_t +pkcs11rsa_createctx(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { 0, NULL, 0 }; + CK_SLOT_ID slotid; + pk11_object_t *rsa = key->keydata.pkey; + pk11_context_t *pk11_ctx; + isc_result_t ret; + + 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); + REQUIRE(rsa != NULL); + + /* + * 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(); + } + + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + mech.mechanism = CKM_SHA_1; + break; + case DST_ALG_RSASHA256: + mech.mechanism = CKM_SHA256; + break; + case DST_ALG_RSASHA512: + mech.mechanism = CKM_SHA512; + break; + default: + UNREACHABLE(); + } + + pk11_ctx = isc_mem_get(dctx->mctx, sizeof(*pk11_ctx)); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (rsa->ontoken) { + slotid = rsa->slot; + } else { + slotid = pk11_get_best_token(OP_RSA); + } + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon, + NULL, slotid); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + PK11_RET(pkcs_C_DigestInit, (pk11_ctx->session, &mech), ISC_R_FAILURE); + dctx->ctxdata.pk11_ctx = pk11_ctx; + return (ISC_R_SUCCESS); + +err: + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static void +pkcs11rsa_destroyctx(dst_context_t *dctx) { + CK_BYTE garbage[ISC_SHA512_DIGESTLENGTH]; + CK_ULONG len = ISC_SHA512_DIGESTLENGTH; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + + if (pk11_ctx != NULL) { + (void)pkcs_C_DigestFinal(pk11_ctx->session, garbage, &len); + isc_safe_memwipe(garbage, sizeof(garbage)); + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + } +} + +static isc_result_t +pkcs11rsa_adddata(dst_context_t *dctx, const isc_region_t *data) { + CK_RV rv; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + PK11_CALL(pkcs_C_DigestUpdate, + (pk11_ctx->session, (CK_BYTE_PTR)data->base, + (CK_ULONG)data->length), + ISC_R_FAILURE); + + return (ret); +} + +static isc_result_t +pkcs11rsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + CK_RV rv; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_MODULUS, NULL, 0 }, + { CKA_PUBLIC_EXPONENT, NULL, 0 }, + { CKA_PRIVATE_EXPONENT, NULL, 0 }, + { CKA_PRIME_1, NULL, 0 }, + { CKA_PRIME_2, NULL, 0 }, + { CKA_EXPONENT_1, NULL, 0 }, + { CKA_EXPONENT_2, NULL, 0 }, + { CKA_COEFFICIENT, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + CK_BYTE digest[MAX_DER_SIZE + ISC_SHA512_DIGESTLENGTH]; + CK_BYTE *der; + CK_ULONG derlen; + CK_ULONG hashlen; + CK_ULONG dgstlen; + CK_ULONG siglen = 0; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *rsa = key->keydata.pkey; + isc_region_t r; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + 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); + REQUIRE(rsa != NULL); + + /* + * 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(); + } + + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + der = sha1_der; + derlen = sizeof(sha1_der); + hashlen = ISC_SHA1_DIGESTLENGTH; + break; + case DST_ALG_RSASHA256: + der = sha256_der; + derlen = sizeof(sha256_der); + hashlen = ISC_SHA256_DIGESTLENGTH; + break; + case DST_ALG_RSASHA512: + der = sha512_der; + derlen = sizeof(sha512_der); + hashlen = ISC_SHA512_DIGESTLENGTH; + break; + default: + UNREACHABLE(); + } + dgstlen = derlen + hashlen; + INSIST(dgstlen <= sizeof(digest)); + memmove(digest, der, derlen); + + PK11_RET(pkcs_C_DigestFinal, + (pk11_ctx->session, digest + derlen, &hashlen), + DST_R_SIGNFAILURE); + + isc_buffer_availableregion(sig, &r); + if (r.length < (unsigned int)dgstlen + MIN_PKCS1_PADLEN) { + return (ISC_R_NOSPACE); + } + + if (rsa->ontoken && (rsa->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = rsa->ontoken; + pk11_ctx->object = rsa->object; + goto token_key; + } + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + case CKA_PUBLIC_EXPONENT: + INSIST(keyTemplate[7].type == attr->type); + keyTemplate[7].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[7].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[7].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIVATE_EXPONENT: + INSIST(keyTemplate[8].type == attr->type); + keyTemplate[8].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[8].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[8].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIME_1: + INSIST(keyTemplate[9].type == attr->type); + keyTemplate[9].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[9].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[9].ulValueLen = attr->ulValueLen; + break; + case CKA_PRIME_2: + INSIST(keyTemplate[10].type == attr->type); + keyTemplate[10].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[10].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[10].ulValueLen = attr->ulValueLen; + break; + case CKA_EXPONENT_1: + INSIST(keyTemplate[11].type == attr->type); + keyTemplate[11].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[11].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[11].ulValueLen = attr->ulValueLen; + break; + case CKA_EXPONENT_2: + INSIST(keyTemplate[12].type == attr->type); + keyTemplate[12].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[12].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[12].ulValueLen = attr->ulValueLen; + break; + case CKA_COEFFICIENT: + INSIST(keyTemplate[13].type == attr->type); + keyTemplate[13].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[13].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[13].ulValueLen = attr->ulValueLen; + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)14, &hKey), + ISC_R_FAILURE); + +token_key: + + PK11_RET(pkcs_C_SignInit, + (pk11_ctx->session, &mech, + pk11_ctx->ontoken ? pk11_ctx->object : hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_Sign, + (pk11_ctx->session, digest, dgstlen, NULL, &siglen), + DST_R_SIGNFAILURE); + + if (r.length < (unsigned int)siglen) { + return (ISC_R_NOSPACE); + } + + PK11_RET(pkcs_C_Sign, + (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)r.base, + &siglen), + DST_R_SIGNFAILURE); + + isc_buffer_add(sig, (unsigned int)siglen); + +err: + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 6; i <= 13; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + + return (ret); +} + +static isc_result_t +pkcs11rsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + CK_RV rv; + CK_MECHANISM mech = { CKM_RSA_PKCS, NULL, 0 }; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE keyTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_MODULUS, NULL, 0 }, + { CKA_PUBLIC_EXPONENT, NULL, 0 }, + }; + CK_ATTRIBUTE *attr; + CK_BYTE digest[MAX_DER_SIZE + ISC_SHA512_DIGESTLENGTH]; + CK_BYTE *der; + CK_ULONG derlen; + CK_ULONG hashlen; + CK_ULONG dgstlen; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + dst_key_t *key = dctx->key; + pk11_object_t *rsa = key->keydata.pkey; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int i; + + 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); + REQUIRE(rsa != NULL); + + switch (key->key_alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + der = sha1_der; + derlen = sizeof(sha1_der); + hashlen = ISC_SHA1_DIGESTLENGTH; + break; + case DST_ALG_RSASHA256: + der = sha256_der; + derlen = sizeof(sha256_der); + hashlen = ISC_SHA256_DIGESTLENGTH; + break; + case DST_ALG_RSASHA512: + der = sha512_der; + derlen = sizeof(sha512_der); + hashlen = ISC_SHA512_DIGESTLENGTH; + break; + default: + UNREACHABLE(); + } + dgstlen = derlen + hashlen; + INSIST(dgstlen <= sizeof(digest)); + memmove(digest, der, derlen); + + PK11_RET(pkcs_C_DigestFinal, + (pk11_ctx->session, digest + derlen, &hashlen), + DST_R_SIGNFAILURE); + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + unsigned int bits; + + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_PUBLIC_EXPONENT: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + ret = pk11_numbits(attr->pValue, attr->ulValueLen, + &bits); + if (ret != ISC_R_SUCCESS || bits > RSA_MAX_PUBEXP_BITS) + { + DST_RET(DST_R_VERIFYFAILURE); + } + break; + } + } + pk11_ctx->object = CK_INVALID_HANDLE; + pk11_ctx->ontoken = false; + PK11_RET(pkcs_C_CreateObject, + (pk11_ctx->session, keyTemplate, (CK_ULONG)7, &hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_VerifyInit, (pk11_ctx->session, &mech, hKey), + ISC_R_FAILURE); + + PK11_RET(pkcs_C_Verify, + (pk11_ctx->session, digest, dgstlen, (CK_BYTE_PTR)sig->base, + (CK_ULONG)sig->length), + DST_R_VERIFYFAILURE); + +err: + if (hKey != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, hKey); + } + for (i = 5; i <= 6; i++) { + if (keyTemplate[i].pValue != NULL) { + { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(dctx->mctx, keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + } + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(dctx->mctx, pk11_ctx, sizeof(*pk11_ctx)); + dctx->ctxdata.pk11_ctx = NULL; + + return (ret); +} +#endif /* ifndef PK11_RSA_PKCS_REPLACE */ + +static bool +pkcs11rsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *rsa1, *rsa2; + CK_ATTRIBUTE *attr1, *attr2; + + rsa1 = key1->keydata.pkey; + rsa2 = key2->keydata.pkey; + + if ((rsa1 == NULL) && (rsa2 == NULL)) { + return (true); + } else if ((rsa1 == NULL) || (rsa2 == NULL)) { + return (false); + } + + attr1 = pk11_attribute_bytype(rsa1, CKA_MODULUS); + attr2 = pk11_attribute_bytype(rsa2, CKA_MODULUS); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(rsa1, CKA_PUBLIC_EXPONENT); + attr2 = pk11_attribute_bytype(rsa2, CKA_PUBLIC_EXPONENT); + if ((attr1 == NULL) && (attr2 == NULL)) { + return (true); + } else if ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen)) + { + return (false); + } + + attr1 = pk11_attribute_bytype(rsa1, CKA_PRIVATE_EXPONENT); + attr2 = pk11_attribute_bytype(rsa2, CKA_PRIVATE_EXPONENT); + if (((attr1 != NULL) || (attr2 != NULL)) && + ((attr1 == NULL) || (attr2 == NULL) || + (attr1->ulValueLen != attr2->ulValueLen) || + !isc_safe_memequal(attr1->pValue, attr2->pValue, + attr1->ulValueLen))) + { + return (false); + } + + if (!rsa1->ontoken && !rsa2->ontoken) { + return (true); + } else if (rsa1->ontoken || rsa2->ontoken || + (rsa1->object != rsa2->object)) + { + return (false); + } + + return (true); +} + +static isc_result_t +pkcs11rsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL, 0 }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_ULONG bits = 0; + CK_BYTE pubexp[5]; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE pubTemplate[] = { + { CKA_CLASS, &pubClass, (CK_ULONG)sizeof(pubClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_VERIFY, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_MODULUS_BITS, &bits, (CK_ULONG)sizeof(bits) }, + { CKA_PUBLIC_EXPONENT, &pubexp, (CK_ULONG)sizeof(pubexp) } + }; + CK_OBJECT_HANDLE priv = CK_INVALID_HANDLE; + CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY; + CK_ATTRIBUTE privTemplate[] = { + { CKA_CLASS, &privClass, (CK_ULONG)sizeof(privClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG)sizeof(falsevalue) }, + { CKA_EXTRACTABLE, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_SIGN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + }; + CK_ATTRIBUTE *attr; + pk11_object_t *rsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + UNUSED(callback); + + /* + * 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) { + return (ISC_R_FAILURE); + } + break; + case DST_ALG_RSASHA256: + /* From RFC 5702 */ + if ((key->key_size < 512) || (key->key_size > 4096)) { + return (ISC_R_FAILURE); + } + break; + case DST_ALG_RSASHA512: + /* From RFC 5702 */ + if ((key->key_size < 1024) || (key->key_size > 4096)) { + return (ISC_R_FAILURE); + } + break; + default: + UNREACHABLE(); + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, false, NULL, + pk11_get_best_token(OP_RSA)); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + bits = key->key_size; + if (exp == 0) { + /* RSA_F4 0x10001 */ + pubexp[0] = 1; + pubexp[1] = 0; + pubexp[2] = 1; + pubTemplate[6].ulValueLen = 3; + } else { + /* F5 0x100000001 */ + pubexp[0] = 1; + pubexp[1] = 0; + pubexp[2] = 0; + pubexp[3] = 0; + pubexp[4] = 1; + pubTemplate[6].ulValueLen = 5; + } + + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, pubTemplate, (CK_ULONG)7, + privTemplate, (CK_ULONG)7, &pub, &priv), + DST_R_CRYPTOFAILURE); + + rsa = isc_mem_get(key->mctx, sizeof(*rsa)); + memset(rsa, 0, sizeof(*rsa)); + key->keydata.pkey = rsa; + rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 8); + memset(rsa->repr, 0, sizeof(*attr) * 8); + rsa->attrcnt = 8; + + attr = rsa->repr; + attr[0].type = CKA_MODULUS; + attr[1].type = CKA_PUBLIC_EXPONENT; + attr[2].type = CKA_PRIVATE_EXPONENT; + attr[3].type = CKA_PRIME_1; + attr[4].type = CKA_PRIME_2; + attr[5].type = CKA_EXPONENT_1; + attr[6].type = CKA_EXPONENT_2; + attr[7].type = CKA_COEFFICIENT; + + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 2), + DST_R_CRYPTOFAILURE); + for (i = 0; i <= 1; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, pub, attr, 2), + DST_R_CRYPTOFAILURE); + + attr += 2; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 6), + DST_R_CRYPTOFAILURE); + for (i = 0; i <= 5; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, priv, attr, 6), + DST_R_CRYPTOFAILURE); + + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ISC_R_SUCCESS); + +err: + pkcs11rsa_destroy(key); + if (priv != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, priv); + } + if (pub != CK_INVALID_HANDLE) { + (void)pkcs_C_DestroyObject(pk11_ctx->session, pub); + } + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ret); +} + +static bool +pkcs11rsa_isprivate(const dst_key_t *key) { + pk11_object_t *rsa = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (rsa == NULL) { + return (false); + } + attr = pk11_attribute_bytype(rsa, CKA_PRIVATE_EXPONENT); + return (attr != NULL || rsa->ontoken); +} + +static void +pkcs11rsa_destroy(dst_key_t *key) { + pk11_object_t *rsa = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (rsa == NULL) { + return; + } + + INSIST((rsa->object == CK_INVALID_HANDLE) || rsa->ontoken); + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + switch (attr->type) { + case CKA_LABEL: + case CKA_ID: + case CKA_MODULUS: + case CKA_PUBLIC_EXPONENT: + case CKA_PRIVATE_EXPONENT: + case CKA_PRIME_1: + case CKA_PRIME_2: + case CKA_EXPONENT_1: + case CKA_EXPONENT_2: + case CKA_COEFFICIENT: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, attr->pValue, + attr->ulValueLen); + } + break; + } + } + if (rsa->repr != NULL) { + isc_safe_memwipe(rsa->repr, rsa->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, rsa->repr, rsa->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(rsa, sizeof(*rsa)); + isc_mem_put(key->mctx, rsa, sizeof(*rsa)); + key->keydata.pkey = NULL; +} + +static isc_result_t +pkcs11rsa_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *rsa; + CK_ATTRIBUTE *attr; + isc_region_t r; + unsigned int e_bytes = 0, mod_bytes = 0; + CK_BYTE *exponent = NULL, *modulus = NULL; + + REQUIRE(key->keydata.pkey != NULL); + + rsa = key->keydata.pkey; + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + switch (attr->type) { + case CKA_PUBLIC_EXPONENT: + exponent = (CK_BYTE *)attr->pValue; + e_bytes = (unsigned int)attr->ulValueLen; + break; + case CKA_MODULUS: + modulus = (CK_BYTE *)attr->pValue; + mod_bytes = (unsigned int)attr->ulValueLen; + break; + } + } + REQUIRE((exponent != NULL) && (modulus != NULL)); + + isc_buffer_availableregion(data, &r); + + if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */ + if (r.length < 1) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint8(data, (uint8_t)e_bytes); + isc_region_consume(&r, 1); + } else { + if (r.length < 3) { + return (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) { + return (ISC_R_NOSPACE); + } + + memmove(r.base, exponent, e_bytes); + isc_region_consume(&r, e_bytes); + memmove(r.base, modulus, mod_bytes); + + isc_buffer_add(data, e_bytes + mod_bytes); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11rsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *rsa; + isc_region_t r; + unsigned int e_bytes, mod_bytes; + CK_BYTE *exponent = NULL, *modulus = NULL; + CK_ATTRIBUTE *attr; + unsigned int length; + unsigned int bits; + isc_result_t ret = ISC_R_SUCCESS; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + length = r.length; + + rsa = isc_mem_get(key->mctx, sizeof(*rsa)); + + memset(rsa, 0, sizeof(*rsa)); + + 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); + } + exponent = r.base; + isc_region_consume(&r, e_bytes); + modulus = r.base; + mod_bytes = r.length; + + ret = pk11_numbits(modulus, mod_bytes, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + key->key_size = bits; + + isc_buffer_forward(data, length); + + rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(rsa->repr, 0, sizeof(*attr) * 2); + rsa->attrcnt = 2; + attr = rsa->repr; + attr[0].type = CKA_MODULUS; + attr[0].pValue = isc_mem_get(key->mctx, mod_bytes); + memmove(attr[0].pValue, modulus, mod_bytes); + attr[0].ulValueLen = (CK_ULONG)mod_bytes; + attr[1].type = CKA_PUBLIC_EXPONENT; + attr[1].pValue = isc_mem_get(key->mctx, e_bytes); + memmove(attr[1].pValue, exponent, e_bytes); + attr[1].ulValueLen = (CK_ULONG)e_bytes; + + key->keydata.pkey = rsa; + + return (ISC_R_SUCCESS); +err: + isc_safe_memwipe(rsa, sizeof(*rsa)); + isc_mem_put(key->mctx, rsa, sizeof(*rsa)); + return (ret); +} + +static isc_result_t +pkcs11rsa_tofile(const dst_key_t *key, const char *directory) { + int i; + pk11_object_t *rsa; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *modulus = NULL, *exponent = NULL; + CK_ATTRIBUTE *d = NULL, *p = NULL, *q = NULL; + CK_ATTRIBUTE *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL; + dst_private_t priv; + unsigned char *bufs[10]; + isc_result_t result; + + if (key->keydata.pkey == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + rsa = key->keydata.pkey; + + for (attr = pk11_attribute_first(rsa); attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + { + switch (attr->type) { + case CKA_MODULUS: + modulus = attr; + break; + case CKA_PUBLIC_EXPONENT: + exponent = attr; + break; + case CKA_PRIVATE_EXPONENT: + d = attr; + break; + case CKA_PRIME_1: + p = attr; + break; + case CKA_PRIME_2: + q = attr; + break; + case CKA_EXPONENT_1: + dmp1 = attr; + break; + case CKA_EXPONENT_2: + dmq1 = attr; + break; + case CKA_COEFFICIENT: + iqmp = attr; + break; + } + } + if ((modulus == NULL) || (exponent == NULL)) { + return (DST_R_NULLKEY); + } + + memset(bufs, 0, sizeof(bufs)); + + for (i = 0; i < 10; i++) { + bufs[i] = isc_mem_get(key->mctx, modulus->ulValueLen); + memset(bufs[i], 0, modulus->ulValueLen); + } + + i = 0; + + priv.elements[i].tag = TAG_RSA_MODULUS; + priv.elements[i].length = (unsigned short)modulus->ulValueLen; + memmove(bufs[i], modulus->pValue, modulus->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_RSA_PUBLICEXPONENT; + priv.elements[i].length = (unsigned short)exponent->ulValueLen; + memmove(bufs[i], exponent->pValue, exponent->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + if (d != NULL) { + priv.elements[i].tag = TAG_RSA_PRIVATEEXPONENT; + priv.elements[i].length = (unsigned short)d->ulValueLen; + memmove(bufs[i], d->pValue, d->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + } + + if (p != NULL) { + priv.elements[i].tag = TAG_RSA_PRIME1; + priv.elements[i].length = (unsigned short)p->ulValueLen; + memmove(bufs[i], p->pValue, p->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + } + + if (q != NULL) { + priv.elements[i].tag = TAG_RSA_PRIME2; + priv.elements[i].length = (unsigned short)q->ulValueLen; + memmove(bufs[i], q->pValue, q->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + } + + if (dmp1 != NULL) { + priv.elements[i].tag = TAG_RSA_EXPONENT1; + priv.elements[i].length = (unsigned short)dmp1->ulValueLen; + memmove(bufs[i], dmp1->pValue, dmp1->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + } + + if (dmq1 != NULL) { + priv.elements[i].tag = TAG_RSA_EXPONENT2; + priv.elements[i].length = (unsigned short)dmq1->ulValueLen; + memmove(bufs[i], dmq1->pValue, dmq1->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + } + + if (iqmp != NULL) { + priv.elements[i].tag = TAG_RSA_COEFFICIENT; + priv.elements[i].length = (unsigned short)iqmp->ulValueLen; + memmove(bufs[i], iqmp->pValue, iqmp->ulValueLen); + 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; + result = dst__privstruct_writefile(key, &priv, directory); + for (i = 0; i < 10; i++) { + if (bufs[i] == NULL) { + break; + } + isc_safe_memwipe(bufs[i], modulus->ulValueLen); + isc_mem_put(key->mctx, bufs[i], modulus->ulValueLen); + } + return (result); +} + +static isc_result_t +pkcs11rsa_fetch(dst_key_t *key, const char *engine, const char *label, + dst_key_t *pub) { + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *pubattr; + pk11_object_t *rsa; + pk11_object_t *pubrsa; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + unsigned int bits; + + if (label == NULL) { + return (DST_R_NOENGINE); + } + + rsa = key->keydata.pkey; + pubrsa = pub->keydata.pkey; + + rsa->object = CK_INVALID_HANDLE; + rsa->ontoken = true; + rsa->reqlogon = true; + rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(rsa->repr, 0, sizeof(*attr) * 2); + rsa->attrcnt = 2; + attr = rsa->repr; + + attr->type = CKA_MODULUS; + pubattr = pk11_attribute_bytype(pubrsa, CKA_MODULUS); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + attr++; + + attr->type = CKA_PUBLIC_EXPONENT; + pubattr = pk11_attribute_bytype(pubrsa, CKA_PUBLIC_EXPONENT); + INSIST(pubattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + + ret = pk11_parse_uri(rsa, label, key->mctx, OP_RSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon, + NULL, rsa->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(rsa, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(rsa, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &rsa->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + attr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(attr != NULL); + ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + key->key_size = bits; + + return (ISC_R_SUCCESS); + +err: + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + + return (ret); +} + +static isc_result_t +rsa_check(pk11_object_t *rsa, pk11_object_t *pubrsa) { + CK_ATTRIBUTE *pubattr, *privattr; + CK_BYTE *priv_exp = NULL, *priv_mod = NULL; + CK_BYTE *pub_exp = NULL, *pub_mod = NULL; + unsigned int priv_explen = 0, priv_modlen = 0; + unsigned int pub_explen = 0, pub_modlen = 0; + + REQUIRE(rsa != NULL && pubrsa != NULL); + + privattr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(privattr != NULL); + priv_exp = privattr->pValue; + priv_explen = privattr->ulValueLen; + + pubattr = pk11_attribute_bytype(pubrsa, CKA_PUBLIC_EXPONENT); + INSIST(pubattr != NULL); + pub_exp = pubattr->pValue; + pub_explen = pubattr->ulValueLen; + + if (priv_exp != NULL) { + if (priv_explen != pub_explen) { + return (DST_R_INVALIDPRIVATEKEY); + } + if (!isc_safe_memequal(priv_exp, pub_exp, pub_explen)) { + return (DST_R_INVALIDPRIVATEKEY); + } + } else { + privattr->pValue = pub_exp; + privattr->ulValueLen = pub_explen; + pubattr->pValue = NULL; + pubattr->ulValueLen = 0; + } + + if (privattr->pValue == NULL) { + return (DST_R_INVALIDPRIVATEKEY); + } + + privattr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(privattr != NULL); + priv_mod = privattr->pValue; + priv_modlen = privattr->ulValueLen; + + pubattr = pk11_attribute_bytype(pubrsa, CKA_MODULUS); + INSIST(pubattr != NULL); + pub_mod = pubattr->pValue; + pub_modlen = pubattr->ulValueLen; + + if (priv_mod != NULL) { + if (priv_modlen != pub_modlen) { + return (DST_R_INVALIDPRIVATEKEY); + } + if (!isc_safe_memequal(priv_mod, pub_mod, pub_modlen)) { + return (DST_R_INVALIDPRIVATEKEY); + } + } else { + privattr->pValue = pub_mod; + privattr->ulValueLen = pub_modlen; + pubattr->pValue = NULL; + pubattr->ulValueLen = 0; + } + + if (privattr->pValue == NULL) { + return (DST_R_INVALIDPRIVATEKEY); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11rsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + int i; + pk11_object_t *rsa; + CK_ATTRIBUTE *attr; + isc_mem_t *mctx = key->mctx; + const char *engine = NULL, *label = NULL; + unsigned int bits; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_RSA, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + 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; + key->key_size = pub->key_size; + + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ISC_R_SUCCESS); + } + + 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; + } + } + rsa = isc_mem_get(key->mctx, sizeof(*rsa)); + memset(rsa, 0, sizeof(*rsa)); + key->keydata.pkey = rsa; + + /* Is this key is stored in a HSM? See if we can fetch it. */ + if ((label != NULL) || (engine != NULL)) { + ret = pkcs11rsa_fetch(key, engine, label, pub); + if (ret != ISC_R_SUCCESS) { + goto err; + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); + } + + rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 8); + memset(rsa->repr, 0, sizeof(*attr) * 8); + rsa->attrcnt = 8; + attr = rsa->repr; + attr[0].type = CKA_MODULUS; + attr[1].type = CKA_PUBLIC_EXPONENT; + attr[2].type = CKA_PRIVATE_EXPONENT; + attr[3].type = CKA_PRIME_1; + attr[4].type = CKA_PRIME_2; + attr[5].type = CKA_EXPONENT_1; + attr[6].type = CKA_EXPONENT_2; + attr[7].type = CKA_COEFFICIENT; + + for (i = 0; i < priv.nelements; i++) { + CK_BYTE *bn; + + switch (priv.elements[i].tag) { + case TAG_RSA_ENGINE: + continue; + case TAG_RSA_LABEL: + continue; + default: + bn = isc_mem_get(key->mctx, priv.elements[i].length); + memmove(bn, priv.elements[i].data, + priv.elements[i].length); + } + + switch (priv.elements[i].tag) { + case TAG_RSA_MODULUS: + attr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_PUBLICEXPONENT: + attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_PRIVATEEXPONENT: + attr = pk11_attribute_bytype(rsa, CKA_PRIVATE_EXPONENT); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_PRIME1: + attr = pk11_attribute_bytype(rsa, CKA_PRIME_1); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_PRIME2: + attr = pk11_attribute_bytype(rsa, CKA_PRIME_2); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_EXPONENT1: + attr = pk11_attribute_bytype(rsa, CKA_EXPONENT_1); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_EXPONENT2: + attr = pk11_attribute_bytype(rsa, CKA_EXPONENT_2); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_RSA_COEFFICIENT: + attr = pk11_attribute_bytype(rsa, CKA_COEFFICIENT); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + } + } + + if (rsa_check(rsa, pub->keydata.pkey) != ISC_R_SUCCESS) { + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + + attr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(attr != NULL); + ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + key->key_size = bits; + + attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(attr != NULL); + + ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + if (bits > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ISC_R_SUCCESS); + +err: + pkcs11rsa_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static isc_result_t +pkcs11rsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { + CK_RV rv; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_RSA; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, &keyClass, (CK_ULONG)sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG)sizeof(keyType) }, + { CKA_TOKEN, &truevalue, (CK_ULONG)sizeof(truevalue) }, + { CKA_LABEL, NULL, 0 } + }; + CK_ULONG cnt; + CK_ATTRIBUTE *attr; + pk11_object_t *rsa; + pk11_context_t *pk11_ctx = NULL; + isc_result_t ret; + unsigned int i; + unsigned int bits; + + UNUSED(pin); + + rsa = isc_mem_get(key->mctx, sizeof(*rsa)); + memset(rsa, 0, sizeof(*rsa)); + rsa->object = CK_INVALID_HANDLE; + rsa->ontoken = true; + rsa->reqlogon = true; + key->keydata.pkey = rsa; + + rsa->repr = isc_mem_get(key->mctx, sizeof(*attr) * 2); + memset(rsa->repr, 0, sizeof(*attr) * 2); + rsa->attrcnt = 2; + attr = rsa->repr; + attr[0].type = CKA_MODULUS; + attr[1].type = CKA_PUBLIC_EXPONENT; + + ret = pk11_parse_uri(rsa, label, key->mctx, OP_RSA); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + pk11_ctx = isc_mem_get(key->mctx, sizeof(*pk11_ctx)); + ret = pk11_get_session(pk11_ctx, OP_RSA, true, false, rsa->reqlogon, + NULL, rsa->slot); + if (ret != ISC_R_SUCCESS) { + goto err; + } + + attr = pk11_attribute_bytype(rsa, CKA_LABEL); + if (attr == NULL) { + attr = pk11_attribute_bytype(rsa, CKA_ID); + INSIST(attr != NULL); + searchTemplate[3].type = CKA_ID; + } + searchTemplate[3].pValue = attr->pValue; + searchTemplate[3].ulValueLen = attr->ulValueLen; + + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &hKey, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + attr = rsa->repr; + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + for (i = 0; i <= 1; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + + keyClass = CKO_PRIVATE_KEY; + PK11_RET(pkcs_C_FindObjectsInit, + (pk11_ctx->session, searchTemplate, (CK_ULONG)4), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_FindObjects, + (pk11_ctx->session, &rsa->object, (CK_ULONG)1, &cnt), + DST_R_CRYPTOFAILURE); + (void)pkcs_C_FindObjectsFinal(pk11_ctx->session); + if (cnt == 0) { + DST_RET(ISC_R_NOTFOUND); + } + if (cnt > 1) { + DST_RET(ISC_R_EXISTS); + } + + if (engine != NULL) { + key->engine = isc_mem_strdup(key->mctx, engine); + } + + key->label = isc_mem_strdup(key->mctx, label); + + attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(attr != NULL); + + ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + if (bits > RSA_MAX_PUBEXP_BITS) { + DST_RET(ISC_R_RANGE); + } + + attr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(attr != NULL); + ret = pk11_numbits(attr->pValue, attr->ulValueLen, &bits); + if (ret != ISC_R_SUCCESS) { + goto err; + } + key->key_size = bits; + + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + + return (ISC_R_SUCCESS); + +err: + pkcs11rsa_destroy(key); + if (pk11_ctx != NULL) { + pk11_return_session(pk11_ctx); + isc_safe_memwipe(pk11_ctx, sizeof(*pk11_ctx)); + isc_mem_put(key->mctx, pk11_ctx, sizeof(*pk11_ctx)); + } + + return (ret); +} + +static dst_func_t pkcs11rsa_functions = { + pkcs11rsa_createctx, +#ifndef PK11_RSA_PKCS_REPLACE + pkcs11rsa_createctx2, +#else /* ifndef PK11_RSA_PKCS_REPLACE */ + NULL, /*%< createctx2 */ +#endif /* ifndef PK11_RSA_PKCS_REPLACE */ + pkcs11rsa_destroyctx, + pkcs11rsa_adddata, + pkcs11rsa_sign, + pkcs11rsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + pkcs11rsa_compare, + NULL, /*%< paramcompare */ + pkcs11rsa_generate, + pkcs11rsa_isprivate, + pkcs11rsa_destroy, + pkcs11rsa_todns, + pkcs11rsa_fromdns, + pkcs11rsa_tofile, + pkcs11rsa_parse, + NULL, /*%< cleanup */ + pkcs11rsa_fromlabel, + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11rsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + + if (*funcp == NULL) { + *funcp = &pkcs11rsa_functions; + } + return (ISC_R_SUCCESS); +} + +#endif /* USE_PKCS11 */ diff --git a/lib/dns/portlist.c b/lib/dns/portlist.c new file mode 100644 index 0000000..c1d523c --- /dev/null +++ b/lib/dns/portlist.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. + */ + +/*! \file */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DNS_PORTLIST_MAGIC ISC_MAGIC('P', 'L', 'S', 'T') +#define DNS_VALID_PORTLIST(p) ISC_MAGIC_VALID(p, DNS_PORTLIST_MAGIC) + +typedef struct dns_element { + in_port_t port; + uint16_t flags; +} dns_element_t; + +struct dns_portlist { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + isc_mutex_t lock; + dns_element_t *list; + unsigned int allocated; + unsigned int active; +}; + +#define DNS_PL_INET 0x0001 +#define DNS_PL_INET6 0x0002 +#define DNS_PL_ALLOCATE 16 + +static int +compare(const void *arg1, const void *arg2) { + const dns_element_t *e1 = (const dns_element_t *)arg1; + const dns_element_t *e2 = (const dns_element_t *)arg2; + + if (e1->port < e2->port) { + return (-1); + } + if (e1->port > e2->port) { + return (1); + } + return (0); +} + +isc_result_t +dns_portlist_create(isc_mem_t *mctx, dns_portlist_t **portlistp) { + dns_portlist_t *portlist; + + REQUIRE(portlistp != NULL && *portlistp == NULL); + + portlist = isc_mem_get(mctx, sizeof(*portlist)); + isc_mutex_init(&portlist->lock); + isc_refcount_init(&portlist->refcount, 1); + portlist->list = NULL; + portlist->allocated = 0; + portlist->active = 0; + portlist->mctx = NULL; + isc_mem_attach(mctx, &portlist->mctx); + portlist->magic = DNS_PORTLIST_MAGIC; + *portlistp = portlist; + return (ISC_R_SUCCESS); +} + +static dns_element_t * +find_port(dns_element_t *list, unsigned int len, in_port_t port) { + unsigned int xtry = len / 2; + unsigned int min = 0; + unsigned int max = len - 1; + unsigned int last = len; + + for (;;) { + if (list[xtry].port == port) { + return (&list[xtry]); + } + if (port > list[xtry].port) { + if (xtry == max) { + break; + } + min = xtry; + xtry = xtry + (max - xtry + 1) / 2; + INSIST(xtry <= max); + if (xtry == last) { + break; + } + last = min; + } else { + if (xtry == min) { + break; + } + max = xtry; + xtry = xtry - (xtry - min + 1) / 2; + INSIST(xtry >= min); + if (xtry == last) { + break; + } + last = max; + } + } + return (NULL); +} + +isc_result_t +dns_portlist_add(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + isc_result_t result; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET) { + el->flags |= DNS_PL_INET; + } else { + el->flags |= DNS_PL_INET6; + } + result = ISC_R_SUCCESS; + goto unlock; + } + } + + if (portlist->allocated <= portlist->active) { + unsigned int allocated; + allocated = portlist->allocated + DNS_PL_ALLOCATE; + el = isc_mem_get(portlist->mctx, sizeof(*el) * allocated); + if (portlist->list != NULL) { + memmove(el, portlist->list, + portlist->allocated * sizeof(*el)); + isc_mem_put(portlist->mctx, portlist->list, + portlist->allocated * sizeof(*el)); + } + portlist->list = el; + portlist->allocated = allocated; + } + portlist->list[portlist->active].port = port; + if (af == AF_INET) { + portlist->list[portlist->active].flags = DNS_PL_INET; + } else { + portlist->list[portlist->active].flags = DNS_PL_INET6; + } + portlist->active++; + qsort(portlist->list, portlist->active, sizeof(*el), compare); + result = ISC_R_SUCCESS; +unlock: + UNLOCK(&portlist->lock); + return (result); +} + +void +dns_portlist_remove(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET) { + el->flags &= ~DNS_PL_INET; + } else { + el->flags &= ~DNS_PL_INET6; + } + if (el->flags == 0) { + *el = portlist->list[portlist->active]; + portlist->active--; + qsort(portlist->list, portlist->active, + sizeof(*el), compare); + } + } + } + UNLOCK(&portlist->lock); +} + +bool +dns_portlist_match(dns_portlist_t *portlist, int af, in_port_t port) { + dns_element_t *el; + bool result = false; + + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(af == AF_INET || af == AF_INET6); + LOCK(&portlist->lock); + if (portlist->active != 0) { + el = find_port(portlist->list, portlist->active, port); + if (el != NULL) { + if (af == AF_INET && (el->flags & DNS_PL_INET) != 0) { + result = true; + } + if (af == AF_INET6 && (el->flags & DNS_PL_INET6) != 0) { + result = true; + } + } + } + UNLOCK(&portlist->lock); + return (result); +} + +void +dns_portlist_attach(dns_portlist_t *portlist, dns_portlist_t **portlistp) { + REQUIRE(DNS_VALID_PORTLIST(portlist)); + REQUIRE(portlistp != NULL && *portlistp == NULL); + + isc_refcount_increment(&portlist->refcount); + *portlistp = portlist; +} + +void +dns_portlist_detach(dns_portlist_t **portlistp) { + REQUIRE(portlistp != NULL && DNS_VALID_PORTLIST(*portlistp)); + dns_portlist_t *portlist = *portlistp; + *portlistp = NULL; + + if (isc_refcount_decrement(&portlist->refcount) == 1) { + portlist->magic = 0; + isc_refcount_destroy(&portlist->refcount); + if (portlist->list != NULL) { + isc_mem_put(portlist->mctx, portlist->list, + portlist->allocated * + sizeof(*portlist->list)); + } + isc_mutex_destroy(&portlist->lock); + isc_mem_putanddetach(&portlist->mctx, portlist, + sizeof(*portlist)); + } +} 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..d5d18b8 --- /dev/null +++ b/lib/dns/rbt.c @@ -0,0 +1,3808 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/*% + * 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 +#define ALIGNMENT_SIZE 8U /* see lib/isc/mem.c */ + +#include + +#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_MIN_BITS 4 +#define RBT_HASH_MAX_BITS 32 +#define RBT_HASH_OVERCOMMIT 3 +#define RBT_HASH_BUCKETSIZE 4096 /* FIXME: What would be a good value here? */ + +#ifdef RBT_MEM_TEST +#undef RBT_HASH_SIZE +#define RBT_HASH_SIZE 2 /*%< To give the reallocation code a workout. */ +#endif /* ifdef RBT_MEM_TEST */ + +#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; + uint16_t hashbits; + uint16_t maxhashbits; + dns_rbtnode_t **hashtable; + void *mmap_location; +}; + +#define RED 0 +#define BLACK 1 + +/* + * This is the header for map-format RBT images. It is populated, + * and then written, as the LAST thing done to the file before returning. + * Writing this last (with zeros in the header area initially) will ensure + * that the header is only valid when the RBT image is also valid. + */ +typedef struct file_header file_header_t; + +/* Pad to 32 bytes */ +static char FILE_VERSION[32] = "\0"; + +/* Header length, always the same size regardless of structure size */ +#define HEADER_LENGTH 1024 + +struct file_header { + char version1[32]; + uint64_t first_node_offset; /* usually 1024 */ + /* + * information about the system on which the map file was generated + * will be used to tell if we can load the map file or not + */ + uint32_t ptrsize; + unsigned int bigendian : 1; /* big or little endian system */ + unsigned int rdataset_fixed : 1; /* compiled with + * --enable-rrset-fixed + */ + unsigned int nodecount; /* shadow from rbt structure */ + uint64_t crc; + char version2[32]; /* repeated; must match version1 */ +}; + +/* + * The following declarations are for the serialization of an RBT: + * + * step one: write out a zeroed header of 1024 bytes + * step two: walk the tree in a depth-first, left-right-down order, writing + * out the nodes, reserving space as we go, correcting addresses to point + * at the proper offset in the file, and setting a flag for each pointer to + * indicate that it is a reference to a location in the file, rather than in + * memory. + * step three: write out the header, adding the information that will be + * needed to re-create the tree object itself. + * + * The RBTDB object will do this three times, once for each of the three + * RBT objects it contains. + * + * Note: 'file' must point an actual open file that can be mmapped + * and fseeked, not to a pipe or stream + */ + +static isc_result_t +dns_rbt_zero_header(FILE *file); + +static isc_result_t +write_header(FILE *file, dns_rbt_t *rbt, uint64_t first_node_offset, + uint64_t crc); + +static bool +match_header_version(file_header_t *header); + +static isc_result_t +serialize_node(FILE *file, dns_rbtnode_t *node, uintptr_t left, uintptr_t right, + uintptr_t down, uintptr_t parent, uintptr_t data, uint64_t *crc); + +static isc_result_t +serialize_nodes(FILE *file, dns_rbtnode_t *node, uintptr_t parent, + dns_rbtdatawriter_t datawriter, void *writer_arg, + uintptr_t *where, uint64_t *crc); + +#define ADJUST_ADDRESS(address, relative, header) \ + if (address != NULL && header != NULL) { \ + address += relative * (uintptr_t)header; \ + } +/* + * The following functions allow you to get the actual address of a pointer + * without having to use an if statement to check to see if that address is + * relative or not + */ +static dns_rbtnode_t * +getparent(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->parent); + + ADJUST_ADDRESS(adjusted_address, node->parent_is_relative, header); + + return ((dns_rbtnode_t *)adjusted_address); +} + +static dns_rbtnode_t * +getleft(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->left); + + ADJUST_ADDRESS(adjusted_address, node->left_is_relative, header); + + return ((dns_rbtnode_t *)adjusted_address); +} + +static dns_rbtnode_t * +getright(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->right); + + ADJUST_ADDRESS(adjusted_address, node->right_is_relative, header); + + return ((dns_rbtnode_t *)adjusted_address); +} + +static dns_rbtnode_t * +getdown(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->down); + + ADJUST_ADDRESS(adjusted_address, node->down_is_relative, header); + + return ((dns_rbtnode_t *)adjusted_address); +} + +static dns_rbtnode_t * +getdata(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->data); + + ADJUST_ADDRESS(adjusted_address, node->data_is_relative, header); + + return ((dns_rbtnode_t *)adjusted_address); +} + +/*% + * 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 +#define inline +/* + * 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); +} + +static void +hexdump(const char *desc, unsigned char *data, size_t size) { + char hexdump[BUFSIZ * 2 + 1]; + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + size_t bytes; + + fprintf(stderr, "%s: ", desc); + do { + isc_buffer_init(&b, hexdump, sizeof(hexdump)); + r.base = data; + r.length = bytes = (size > BUFSIZ) ? BUFSIZ : size; + result = isc_hex_totext(&r, 0, "", &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(&b, 0); + fprintf(stderr, "%s", hexdump); + data += bytes; + size -= bytes; + } while (size > 0); + fprintf(stderr, "\n"); +} +#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)); +} + +static void +fixup_uppernodes_helper(dns_rbtnode_t *node, dns_rbtnode_t *uppernode) { + if (node == NULL) { + return; + } + + UPPERNODE(node) = uppernode; + + fixup_uppernodes_helper(LEFT(node), uppernode); + fixup_uppernodes_helper(RIGHT(node), uppernode); + fixup_uppernodes_helper(DOWN(node), node); +} + +/* + * This function is used to fixup uppernode members of all dns_rbtnodes + * after deserialization. + */ +static void +fixup_uppernodes(dns_rbt_t *rbt) { + fixup_uppernodes_helper(rbt->root, NULL); +} + +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 isc_result_t +inithash(dns_rbt_t *rbt); + +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 +rehash(dns_rbt_t *rbt, uint32_t newbits); +static void +maybe_rehash(dns_rbt_t *rbt, size_t size); + +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 isc_result_t +treefix(dns_rbt_t *rbt, void *base, size_t size, dns_rbtnode_t *n, + const dns_name_t *name, dns_rbtdatafixer_t datafixer, void *fixer_arg, + uint64_t *crc); + +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); + +static isc_result_t +dns_rbt_zero_header(FILE *file) { + /* + * Write out a zeroed header as a placeholder. Doing this ensures + * that the file will not read while it is partially written, should + * writing fail or be interrupted. + */ + char buffer[HEADER_LENGTH]; + isc_result_t result; + + memset(buffer, 0, HEADER_LENGTH); + result = isc_stdio_write(buffer, 1, HEADER_LENGTH, file, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = fflush(file); + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (ISC_R_SUCCESS); +} + +static isc_once_t once = ISC_ONCE_INIT; + +static void +init_file_version(void) { + int n; + + memset(FILE_VERSION, 0, sizeof(FILE_VERSION)); + n = snprintf(FILE_VERSION, sizeof(FILE_VERSION), "RBT Image %s %s", + dns_major, dns_mapapi); + INSIST(n > 0 && (unsigned int)n < sizeof(FILE_VERSION)); +} + +/* + * Write out the real header, including NodeDump version information + * and the offset of the first node. + * + * Any information stored in the rbt object itself should be stored + * here. + */ +static isc_result_t +write_header(FILE *file, dns_rbt_t *rbt, uint64_t first_node_offset, + uint64_t crc) { + file_header_t header; + isc_result_t result; + off_t location; + + RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS); + + memset(&header, 0, sizeof(file_header_t)); + memmove(header.version1, FILE_VERSION, sizeof(header.version1)); + memmove(header.version2, FILE_VERSION, sizeof(header.version2)); + header.first_node_offset = first_node_offset; + header.ptrsize = (uint32_t)sizeof(void *); + header.bigendian = (1 == htonl(1)) ? 1 : 0; + +#ifdef DNS_RDATASET_FIXED + header.rdataset_fixed = 1; +#else /* ifdef DNS_RDATASET_FIXED */ + header.rdataset_fixed = 0; +#endif /* ifdef DNS_RDATASET_FIXED */ + + header.nodecount = rbt->nodecount; + + header.crc = crc; + + CHECK(isc_stdio_tell(file, &location)); + location = dns_rbt_serialize_align(location); + CHECK(isc_stdio_seek(file, location, SEEK_SET)); + CHECK(isc_stdio_write(&header, 1, sizeof(file_header_t), file, NULL)); + CHECK(fflush(file)); + + /* Ensure we are always at the end of the file. */ + CHECK(isc_stdio_seek(file, 0, SEEK_END)); + +cleanup: + return (result); +} + +static bool +match_header_version(file_header_t *header) { + RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS); + + if (memcmp(header->version1, FILE_VERSION, sizeof(header->version1)) != + 0 || + memcmp(header->version2, FILE_VERSION, sizeof(header->version1)) != + 0) + { + return (false); + } + + return (true); +} + +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); +} + +static isc_result_t +serialize_node(FILE *file, dns_rbtnode_t *node, uintptr_t left, uintptr_t right, + uintptr_t down, uintptr_t parent, uintptr_t data, + uint64_t *crc) { + isc_result_t result; + dns_rbtnode_t temp_node; + off_t file_position; + unsigned char *node_data = NULL; + size_t datasize; +#ifdef DEBUG + dns_name_t nodename; +#endif /* ifdef DEBUG */ + + INSIST(node != NULL); + + CHECK(isc_stdio_tell(file, &file_position)); + file_position = dns_rbt_serialize_align(file_position); + CHECK(isc_stdio_seek(file, file_position, SEEK_SET)); + + temp_node = *node; + temp_node.down_is_relative = 0; + temp_node.left_is_relative = 0; + temp_node.right_is_relative = 0; + temp_node.parent_is_relative = 0; + temp_node.data_is_relative = 0; + temp_node.is_mmapped = 1; + + /* + * If the next node is not NULL, calculate the next node's location + * in the file. Note that this will have to change when the data + * structure changes, and it also assumes that we always write the + * nodes out in list order (which we currently do.) + */ + if (temp_node.parent != NULL) { + temp_node.parent = (dns_rbtnode_t *)(parent); + temp_node.parent_is_relative = 1; + } + if (temp_node.left != NULL) { + temp_node.left = (dns_rbtnode_t *)(left); + temp_node.left_is_relative = 1; + } + if (temp_node.right != NULL) { + temp_node.right = (dns_rbtnode_t *)(right); + temp_node.right_is_relative = 1; + } + if (temp_node.down != NULL) { + temp_node.down = (dns_rbtnode_t *)(down); + temp_node.down_is_relative = 1; + } + if (temp_node.data != NULL) { + temp_node.data = (dns_rbtnode_t *)(data); + temp_node.data_is_relative = 1; + } + + temp_node.fullnamelen = dns__rbtnode_namelen(node); + + node_data = (unsigned char *)node + sizeof(dns_rbtnode_t); + datasize = NODE_SIZE(node) - sizeof(dns_rbtnode_t); + + CHECK(isc_stdio_write(&temp_node, 1, sizeof(dns_rbtnode_t), file, + NULL)); + CHECK(isc_stdio_write(node_data, 1, datasize, file, NULL)); + +#ifdef DEBUG + dns_name_init(&nodename, NULL); + NODENAME(node, &nodename); + fprintf(stderr, "serialize "); + dns_name_print(&nodename, stderr); + fprintf(stderr, "\n"); + hexdump("node header", (unsigned char *)&temp_node, + sizeof(dns_rbtnode_t)); + hexdump("node data", node_data, datasize); +#endif /* ifdef DEBUG */ + + isc_crc64_update(crc, (const uint8_t *)&temp_node, + sizeof(dns_rbtnode_t)); + isc_crc64_update(crc, (const uint8_t *)node_data, datasize); + +cleanup: + return (result); +} + +static isc_result_t +serialize_nodes(FILE *file, dns_rbtnode_t *node, uintptr_t parent, + dns_rbtdatawriter_t datawriter, void *writer_arg, + uintptr_t *where, uint64_t *crc) { + uintptr_t left = 0, right = 0, down = 0, data = 0; + off_t location = 0, offset_adjust; + isc_result_t result; + + if (node == NULL) { + if (where != NULL) { + *where = 0; + } + return (ISC_R_SUCCESS); + } + + /* Reserve space for current node. */ + CHECK(isc_stdio_tell(file, &location)); + location = dns_rbt_serialize_align(location); + CHECK(isc_stdio_seek(file, location, SEEK_SET)); + + offset_adjust = dns_rbt_serialize_align(location + NODE_SIZE(node)); + CHECK(isc_stdio_seek(file, offset_adjust, SEEK_SET)); + + /* + * Serialize the rest of the tree. + * + * WARNING: A change in the order (from left, right, down) + * will break the way the crc hash is computed. + */ + CHECK(serialize_nodes(file, getleft(node, NULL), location, datawriter, + writer_arg, &left, crc)); + CHECK(serialize_nodes(file, getright(node, NULL), location, datawriter, + writer_arg, &right, crc)); + CHECK(serialize_nodes(file, getdown(node, NULL), location, datawriter, + writer_arg, &down, crc)); + + if (node->data != NULL) { + off_t ret; + + CHECK(isc_stdio_tell(file, &ret)); + ret = dns_rbt_serialize_align(ret); + CHECK(isc_stdio_seek(file, ret, SEEK_SET)); + data = ret; + + datawriter(file, node->data, writer_arg, crc); + } + + /* Seek back to reserved space. */ + CHECK(isc_stdio_seek(file, location, SEEK_SET)); + + /* Serialize the current node. */ + CHECK(serialize_node(file, node, left, right, down, parent, data, crc)); + + /* Ensure we are always at the end of the file. */ + CHECK(isc_stdio_seek(file, 0, SEEK_END)); + + if (where != NULL) { + *where = (uintptr_t)location; + } + +cleanup: + return (result); +} + +off_t +dns_rbt_serialize_align(off_t target) { + off_t offset = target % 8; + + if (offset == 0) { + return (target); + } else { + return (target + 8 - offset); + } +} + +isc_result_t +dns_rbt_serialize_tree(FILE *file, dns_rbt_t *rbt, + dns_rbtdatawriter_t datawriter, void *writer_arg, + off_t *offset) { + isc_result_t result; + off_t header_position, node_position, end_position; + uint64_t crc; + + REQUIRE(file != NULL); + + CHECK(isc_file_isplainfilefd(fileno(file))); + + isc_crc64_init(&crc); + + CHECK(isc_stdio_tell(file, &header_position)); + + /* Write dummy header */ + CHECK(dns_rbt_zero_header(file)); + + /* Serialize nodes */ + CHECK(isc_stdio_tell(file, &node_position)); + CHECK(serialize_nodes(file, rbt->root, 0, datawriter, writer_arg, NULL, + &crc)); + + CHECK(isc_stdio_tell(file, &end_position)); + if (node_position == end_position) { + CHECK(isc_stdio_seek(file, header_position, SEEK_SET)); + *offset = 0; + return (ISC_R_SUCCESS); + } + + isc_crc64_final(&crc); +#ifdef DEBUG + hexdump("serializing CRC", (unsigned char *)&crc, sizeof(crc)); +#endif /* ifdef DEBUG */ + + /* Serialize header */ + CHECK(isc_stdio_seek(file, header_position, SEEK_SET)); + CHECK(write_header(file, rbt, HEADER_LENGTH, crc)); + + /* Ensure we are always at the end of the file. */ + CHECK(isc_stdio_seek(file, 0, SEEK_END)); + *offset = dns_rbt_serialize_align(header_position); + +cleanup: + return (result); +} + +#define CONFIRM(a) \ + do { \ + if (!(a)) { \ + result = ISC_R_INVALIDFILE; \ + goto cleanup; \ + } \ + } while (0); + +static isc_result_t +treefix(dns_rbt_t *rbt, void *base, size_t filesize, dns_rbtnode_t *n, + const dns_name_t *name, dns_rbtdatafixer_t datafixer, void *fixer_arg, + uint64_t *crc) { + isc_result_t result = ISC_R_SUCCESS; + dns_fixedname_t fixed; + dns_name_t nodename, *fullname = NULL; + unsigned char *node_data = NULL; + dns_rbtnode_t header; + size_t nodemax = filesize - sizeof(dns_rbtnode_t); + size_t datasize; + + if (n == NULL) { + return (ISC_R_SUCCESS); + } + +#define CHECK_ALIGNMENT(n) \ + (((uintptr_t)n & ~((uintptr_t)ALIGNMENT_SIZE - 1)) == (uintptr_t)n) + + CONFIRM((void *)n >= base); + CONFIRM((size_t)((char *)n - (char *)base) <= nodemax); + CONFIRM(CHECK_ALIGNMENT(n)); + CONFIRM(DNS_RBTNODE_VALID(n)); + + dns_name_init(&nodename, NULL); + NODENAME(n, &nodename); + + fullname = &nodename; + CONFIRM(dns_name_isvalid(fullname)); + + if (!dns_name_isabsolute(&nodename)) { + fullname = dns_fixedname_initname(&fixed); + CHECK(dns_name_concatenate(&nodename, name, fullname, NULL)); + } + + /* memorize header contents prior to fixup */ + memmove(&header, n, sizeof(header)); + + if (n->left_is_relative) { + CONFIRM(n->left <= (dns_rbtnode_t *)nodemax); + n->left = getleft(n, rbt->mmap_location); + n->left_is_relative = 0; + CONFIRM(CHECK_ALIGNMENT(n->left)); + CONFIRM(DNS_RBTNODE_VALID(n->left)); + } else { + CONFIRM(n->left == NULL); + } + + if (n->right_is_relative) { + CONFIRM(n->right <= (dns_rbtnode_t *)nodemax); + n->right = getright(n, rbt->mmap_location); + n->right_is_relative = 0; + CONFIRM(CHECK_ALIGNMENT(n->right)); + CONFIRM(DNS_RBTNODE_VALID(n->right)); + } else { + CONFIRM(n->right == NULL); + } + + if (n->down_is_relative) { + CONFIRM(n->down <= (dns_rbtnode_t *)nodemax); + n->down = getdown(n, rbt->mmap_location); + n->down_is_relative = 0; + CONFIRM(n->down > (dns_rbtnode_t *)n); + CONFIRM(CHECK_ALIGNMENT(n->down)); + CONFIRM(DNS_RBTNODE_VALID(n->down)); + } else { + CONFIRM(n->down == NULL); + } + + if (n->parent_is_relative) { + CONFIRM(n->parent <= (dns_rbtnode_t *)nodemax); + n->parent = getparent(n, rbt->mmap_location); + n->parent_is_relative = 0; + CONFIRM(n->parent < (dns_rbtnode_t *)n); + CONFIRM(CHECK_ALIGNMENT(n->parent)); + CONFIRM(DNS_RBTNODE_VALID(n->parent)); + } else { + CONFIRM(n->parent == NULL); + } + + if (n->data_is_relative) { + CONFIRM(n->data <= (void *)filesize); + n->data = getdata(n, rbt->mmap_location); + n->data_is_relative = 0; + CONFIRM(n->data > (void *)n); + CONFIRM(CHECK_ALIGNMENT(n->data)); + } else { + CONFIRM(n->data == NULL); + } + + hash_node(rbt, n, fullname); + + /* a change in the order (from left, right, down) will break hashing*/ + if (n->left != NULL) { + CHECK(treefix(rbt, base, filesize, n->left, name, datafixer, + fixer_arg, crc)); + } + if (n->right != NULL) { + CHECK(treefix(rbt, base, filesize, n->right, name, datafixer, + fixer_arg, crc)); + } + if (n->down != NULL) { + CHECK(treefix(rbt, base, filesize, n->down, fullname, datafixer, + fixer_arg, crc)); + } + + if (datafixer != NULL && n->data != NULL) { + CHECK(datafixer(n, base, filesize, fixer_arg, crc)); + } + + rbt->nodecount++; + node_data = (unsigned char *)n + sizeof(dns_rbtnode_t); + datasize = NODE_SIZE(n) - sizeof(dns_rbtnode_t); + +#ifdef DEBUG + fprintf(stderr, "deserialize "); + dns_name_print(&nodename, stderr); + fprintf(stderr, "\n"); + hexdump("node header", (unsigned char *)&header, sizeof(dns_rbtnode_t)); + hexdump("node data", node_data, datasize); +#endif /* ifdef DEBUG */ + isc_crc64_update(crc, (const uint8_t *)&header, sizeof(dns_rbtnode_t)); + isc_crc64_update(crc, (const uint8_t *)node_data, datasize); + +cleanup: + return (result); +} + +isc_result_t +dns_rbt_deserialize_tree(void *base_address, size_t filesize, + off_t header_offset, isc_mem_t *mctx, + dns_rbtdeleter_t deleter, void *deleter_arg, + dns_rbtdatafixer_t datafixer, void *fixer_arg, + dns_rbtnode_t **originp, dns_rbt_t **rbtp) { + isc_result_t result = ISC_R_SUCCESS; + file_header_t *header; + dns_rbt_t *rbt = NULL; + uint64_t crc; + unsigned int host_big_endian; + + REQUIRE(originp == NULL || *originp == NULL); + REQUIRE(rbtp != NULL && *rbtp == NULL); + + isc_crc64_init(&crc); + + CHECK(dns_rbt_create(mctx, deleter, deleter_arg, &rbt)); + + rbt->mmap_location = base_address; + + header = (file_header_t *)((char *)base_address + header_offset); + if (!match_header_version(header)) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + +#ifdef DNS_RDATASET_FIXED + if (header->rdataset_fixed != 1) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + +#else /* ifdef DNS_RDATASET_FIXED */ + if (header->rdataset_fixed != 0) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } +#endif /* ifdef DNS_RDATASET_FIXED */ + + if (header->ptrsize != (uint32_t)sizeof(void *)) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + host_big_endian = (1 == htonl(1)); + if (header->bigendian != host_big_endian) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + /* Copy other data items from the header into our rbt. */ + rbt->root = (dns_rbtnode_t *)((char *)base_address + header_offset + + header->first_node_offset); + + if ((header->nodecount * sizeof(dns_rbtnode_t)) > filesize) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + maybe_rehash(rbt, header->nodecount); + + CHECK(treefix(rbt, base_address, filesize, rbt->root, dns_rootname, + datafixer, fixer_arg, &crc)); + + isc_crc64_final(&crc); +#ifdef DEBUG + hexdump("deserializing CRC", (unsigned char *)&crc, sizeof(crc)); +#endif /* ifdef DEBUG */ + + /* Check file hash */ + if (header->crc != crc) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + if (header->nodecount != rbt->nodecount) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + fixup_uppernodes(rbt); + + *rbtp = rbt; + if (originp != NULL) { + *originp = rbt->root; + } + +cleanup: + if (result != ISC_R_SUCCESS && rbt != NULL) { + rbt->root = NULL; + rbt->nodecount = 0; + dns_rbt_destroy(&rbt); + } + + return (result); +} + +/* + * 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) { + isc_result_t result; + 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->mctx = NULL; + isc_mem_attach(mctx, &rbt->mctx); + rbt->data_deleter = deleter; + rbt->deleter_arg = deleter_arg; + rbt->root = NULL; + rbt->nodecount = 0; + rbt->hashtable = NULL; + rbt->hashbits = 0; + rbt->maxhashbits = RBT_HASH_MAX_BITS; + rbt->mmap_location = NULL; + + result = inithash(rbt); + if (result != ISC_R_SUCCESS) { + isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt)); + return (result); + } + + 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); + + rbt->mmap_location = NULL; + + if (rbt->hashtable != NULL) { + size_t size = HASHSIZE(rbt->hashbits) * sizeof(dns_rbtnode_t *); + isc_mem_put(rbt->mctx, rbt->hashtable, size); + } + + 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)); + + return (1 << rbt->hashbits); +} + +isc_result_t +dns_rbt_adjusthashsize(dns_rbt_t *rbt, size_t size) { + REQUIRE(VALID_RBT(rbt)); + + if (size > 0) { + /* + * Setting a new, finite size limit was requested for the RBT. + * Estimate how many hash table slots are needed for the + * requested size and how many bits would be needed to index + * those hash table slots, then rehash the RBT if necessary. + * Note that the hash table can only grow, it is not shrunk if + * the requested size limit is lower than the current one. + */ + size_t newsize = size / RBT_HASH_BUCKETSIZE; + rbt->maxhashbits = rehash_bits(rbt, newsize); + maybe_rehash(rbt, newsize); + } else { + /* + * Setting an infinite size limit was requested for the RBT. + * Increase the maximum allowed number of hash table slots to + * 2^32, which enables the hash table to grow as nodes are + * added to the RBT without immediately preallocating 2^32 hash + * table slots. + */ + rbt->maxhashbits = RBT_HASH_MAX_BITS; + } + + return (ISC_R_SUCCESS); +} + +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_copynf(&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 (ISC_UNLIKELY(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 (ISC_LIKELY(child != NULL)); + + if (ISC_LIKELY(result == ISC_R_SUCCESS)) { + result = create_node(rbt->mctx, add_name, &new_current); + } + + if (ISC_LIKELY(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; + + 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 (ISC_UNLIKELY(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 (ISC_LIKELY(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 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: + /* + * 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); + hash = dns_name_fullhash(&hash_name, false); + dns_name_getlabelsequence(search_name, + nlabels - tlabels, tlabels, + &hash_name); + + /* + * Walk all the nodes in the hash bucket pointed + * by the computed hash value. + */ + for (hnode = rbt->hashtable[hash_32(hash, + rbt->hashbits)]; + hnode != NULL; hnode = hnode->hashnext) + { + dns_name_t hnode_name; + + if (ISC_LIKELY(hash != 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 (ISC_LIKELY(get_upper_node(hnode) != + up_current)) + { + continue; + } + + dns_name_init(&hnode_name, NULL); + NODENAME(hnode, &hnode_name); + if (ISC_LIKELY(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 (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, "", + dns_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->is_mmapped = 0; + node->down_is_relative = 0; + node->left_is_relative = 0; + node->right_is_relative = 0; + node->parent_is_relative = 0; + node->data_is_relative = 0; + 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); + HASHNEXT(node) = rbt->hashtable[hash]; + + rbt->hashtable[hash] = node; +} + +/* + * Initialize hash table + */ +static isc_result_t +inithash(dns_rbt_t *rbt) { + size_t size; + + rbt->hashbits = RBT_HASH_MIN_BITS; + size = HASHSIZE(rbt->hashbits) * sizeof(dns_rbtnode_t *); + rbt->hashtable = isc_mem_get(rbt->mctx, size); + memset(rbt->hashtable, 0, size); + + return (ISC_R_SUCCESS); +} + +static uint32_t +rehash_bits(dns_rbt_t *rbt, size_t newcount) { + uint32_t newbits = rbt->hashbits; + + while (newcount >= HASHSIZE(newbits) && newbits < RBT_HASH_MAX_BITS) { + newbits += 1; + } + + return (newbits); +} + +/* + * Rebuild the hashtable to reduce the load factor + */ +static void +rehash(dns_rbt_t *rbt, uint32_t newbits) { + uint32_t oldbits; + size_t oldsize; + dns_rbtnode_t **oldtable; + size_t newsize; + + REQUIRE(rbt->hashbits <= rbt->maxhashbits); + REQUIRE(newbits <= rbt->maxhashbits); + + oldbits = rbt->hashbits; + oldsize = HASHSIZE(oldbits); + oldtable = rbt->hashtable; + + rbt->hashbits = newbits; + newsize = HASHSIZE(rbt->hashbits); + rbt->hashtable = isc_mem_get(rbt->mctx, + newsize * sizeof(dns_rbtnode_t *)); + memset(rbt->hashtable, 0, newsize * sizeof(dns_rbtnode_t *)); + + for (size_t i = 0; i < oldsize; i++) { + dns_rbtnode_t *node; + dns_rbtnode_t *nextnode; + for (node = oldtable[i]; node != NULL; node = nextnode) { + uint32_t hash = hash_32(HASHVAL(node), rbt->hashbits); + nextnode = HASHNEXT(node); + HASHNEXT(node) = rbt->hashtable[hash]; + rbt->hashtable[hash] = node; + } + } + + isc_mem_put(rbt->mctx, oldtable, oldsize * sizeof(dns_rbtnode_t *)); +} + +static void +maybe_rehash(dns_rbt_t *rbt, size_t newcount) { + uint32_t newbits = rehash_bits(rbt, newcount); + if (rbt->hashbits < newbits && newbits <= rbt->maxhashbits) { + rehash(rbt, newbits); + } +} + +/* + * 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 (rbt->nodecount >= (HASHSIZE(rbt->hashbits) * RBT_HASH_OVERCOMMIT)) { + 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 *node) { + uint32_t bucket; + dns_rbtnode_t *bucket_node; + + REQUIRE(DNS_RBTNODE_VALID(node)); + + bucket = hash_32(HASHVAL(node), rbt->hashbits); + bucket_node = rbt->hashtable[bucket]; + + if (bucket_node == node) { + rbt->hashtable[bucket] = HASHNEXT(node); + } else { + while (HASHNEXT(bucket_node) != node) { + INSIST(HASHNEXT(bucket_node) != NULL); + bucket_node = HASHNEXT(bucket_node); + } + HASHNEXT(bucket_node) = HASHNEXT(node); + } +} + +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)) { + /* cppcheck-suppress nullPointerRedundantCheck symbolName=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); + + /* cppcheck-suppress nullPointerRedundantCheck + * symbolName=sibling */ + 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); + + /* cppcheck-suppress nullPointerRedundantCheck + * symbolName=sibling */ + 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; + + if (node->is_mmapped == 0) { + 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); + } + } + + /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */ + 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); + } + + /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */ + if (!check_black_distance_helper(LEFT(node), &dl)) { + return (false); + } + + /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */ + if (!check_black_distance_helper(RIGHT(node), &dr)) { + return (false); + } + + /* cppcheck-suppress nullPointerRedundantCheck symbolName=node */ + 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, "Relative pointers: %s%s%s%s%s\n", + (n->parent_is_relative == 1 ? " P" : ""), + (n->right_is_relative == 1 ? " R" : ""), + (n->left_is_relative == 1 ? " L" : ""), + (n->down_is_relative == 1 ? " D" : ""), + (n->data_is_relative == 1 ? " T" : "")); + + 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); + /* + * Don't use IS_RED(root) as it tests for 'root != NULL' + * and cppcheck produces false positives. + */ + 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++; + + /* + * Don't use IS_RED(root) as it tests for 'root != NULL' + * and cppcheck produces false positives. + */ + 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); + + /* + * Don't use IS_RED(root) as cppcheck produces false positives. + */ + 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_copynf(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..ee06c51 --- /dev/null +++ b/lib/dns/rbtdb.c @@ -0,0 +1,10667 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#ifndef WIN32 +#include +#else /* ifndef WIN32 */ +#define PROT_READ 0x01 +#define PROT_WRITE 0x02 +#define MAP_PRIVATE 0x0002 +#define MAP_FAILED ((void *)-1) +#endif /* ifndef WIN32 */ + +#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) + +/* + * This is the map file header for RBTDB images. It is populated, and then + * written, as the LAST thing done to the file. Writing this last (with + * zeros in the header area initially) will ensure that the header is only + * valid when the RBTDB image is also valid. + */ +typedef struct rbtdb_file_header rbtdb_file_header_t; + +/* Header length, always the same size regardless of structure size */ +#define RBTDB_HEADER_LENGTH 1024 + +struct rbtdb_file_header { + char version1[32]; + uint32_t ptrsize; + unsigned int bigendian : 1; + uint64_t tree; + uint64_t nsec; + uint64_t nsec3; + + char version2[32]; /* repeated; must match version1 */ +}; + +/*% + * 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 is_mmapped : 1; + unsigned int next_is_relative : 1; + unsigned int node_is_relative : 1; + 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. + */ + +#undef IGNORE /* WIN32 winbase.h defines this. */ + +#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 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; + + /* + * Base values for the mmap() code. + */ + void *mmap_location; + size_t mmap_size; + + /* 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); + +static bool +match_header_version(rbtdb_file_header_t *header); + +/* Pad to 32 bytes */ +static char FILE_VERSION[32] = "\0"; + +/*% + * '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. + */ + +/* + * Debugging routines + */ +#ifdef DEBUG +static void +hexdump(const char *desc, unsigned char *data, size_t size) { + char hexdump[BUFSIZ * 2 + 1]; + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + size_t bytes; + + fprintf(stderr, "%s: ", desc); + do { + isc_buffer_init(&b, hexdump, sizeof(hexdump)); + r.base = data; + r.length = bytes = (size > BUFSIZ) ? BUFSIZ : size; + result = isc_hex_totext(&r, 0, "", &b); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(&b, 0); + fprintf(stderr, "%s", hexdump); + data += bytes; + size -= bytes; + } while (size > 0); + fprintf(stderr, "\n"); +} +#endif /* ifdef DEBUG */ + +/* 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 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); + + if (rbtdb->mmap_location != NULL) { + isc_file_munmap(rbtdb->mmap_location, (size_t)rbtdb->mmap_size); + } + + 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; + h->is_mmapped = 0; + h->next_is_relative = 0; + h->node_is_relative = 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 */ +} + +/* + * Update the copied values of 'next' and 'node' if they are relative. + */ +static void +update_newheader(rdatasetheader_t *newh, rdatasetheader_t *old) { + char *p; + + if (old->next_is_relative) { + p = (char *)old; + p += (uintptr_t)old->next; + newh->next = (rdatasetheader_t *)p; + } + if (old->node_is_relative) { + p = (char *)old; + p += (uintptr_t)old->node; + newh->node = (dns_rbtnode_t *)p; + } + 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)); + } + + if (rdataset->is_mmapped == 1) { + return; + } + + 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: + /* + * Though this may be wasteful, it has to be done before + * node is deleted. + */ + name = dns_fixedname_initname(&fname); + dns_rbt_fullnamefromnode(node, name); + + result = dns_rbt_deletenode(rbtdb->tree, node, false); + break; + case DNS_RBT_NSEC_HAS_NSEC: + 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_copynf(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 + rbtdb->serve_stale_ttl; + /* + * 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 + rbtdb->serve_stale_ttl; + 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; + + /* + * 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_copynf(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_copynf(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(__FILE__, __LINE__, "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 + + search->rbtdb->serve_stale_ttl; + /* + * 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_copynf(&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); +} + +static isc_result_t +find_coveringnsec(rbtdb_search_t *search, dns_dbnode_t **nodep, + isc_stdtime_t now, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + dns_rbtnode_t *node; + rdatasetheader_t *header, *header_next, *header_prev; + rdatasetheader_t *found, *foundsig; + bool empty_node; + isc_result_t result; + dns_fixedname_t fname, forigin; + dns_name_t *name, *origin; + rbtdb_rdatatype_t matchtype, sigmatchtype; + nodelock_t *lock; + isc_rwlocktype_t locktype; + dns_rbtnodechain_t chain; + + chain = search->chain; + + matchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_nsec, 0); + sigmatchtype = RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, + dns_rdatatype_nsec); + + do { + node = NULL; + name = dns_fixedname_initname(&fname); + origin = dns_fixedname_initname(&forigin); + result = dns_rbtnodechain_current(&chain, name, origin, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + locktype = isc_rwlocktype_read; + lock = &(search->rbtdb->node_locks[node->locknum].lock); + NODE_LOCK(lock, locktype); + found = NULL; + foundsig = 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)) + { + continue; + } + if (NONEXISTENT(header) || + RBTDB_RDATATYPE_BASE(header->type) == 0) + { + header_prev = header; + continue; + } + /* + * Don't stop on provable noqname / RRSIG. + */ + if (header->noqname == NULL && + RBTDB_RDATATYPE_BASE(header->type) != + dns_rdatatype_rrsig) + { + empty_node = false; + } + if (header->type == matchtype) { + found = header; + } else if (header->type == sigmatchtype) { + foundsig = header; + } + header_prev = header; + } + if (found != NULL) { + result = dns_name_concatenate(name, origin, foundname, + NULL); + if (result != ISC_R_SUCCESS) { + goto unlock_node; + } + 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); + *nodep = node; + result = DNS_R_COVERINGNSEC; + } else if (!empty_node) { + result = ISC_R_NOTFOUND; + } else { + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + } + unlock_node: + NODE_UNLOCK(lock, locktype); + } while (empty_node && result == ISC_R_SUCCESS); + 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 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; + 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 ((search.options & DNS_DBFIND_COVERINGNSEC) != 0) { + result = find_coveringnsec(&search, 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 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); + 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; + } + + /* + * 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_copynf(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_copynf(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 + rbtdb->serve_stale_ttl <= + 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 + rbtdb->serve_stale_ttl < + 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 += rbtdb->serve_stale_ttl; + } + + 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 +rbt_datafixer(dns_rbtnode_t *rbtnode, void *base, size_t filesize, void *arg, + uint64_t *crc) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)arg; + rdatasetheader_t *header; + unsigned char *limit = ((unsigned char *)base) + filesize; + + REQUIRE(rbtnode != NULL); + REQUIRE(VALID_RBTDB(rbtdb)); + + for (header = rbtnode->data; header != NULL; header = header->next) { + unsigned char *p = (unsigned char *)header; + size_t size = dns_rdataslab_size(p, sizeof(*header)); + isc_crc64_update(crc, p, size); +#ifdef DEBUG + hexdump("hashing header", p, sizeof(rdatasetheader_t)); + hexdump("hashing slab", p + sizeof(rdatasetheader_t), + size - sizeof(rdatasetheader_t)); +#endif /* ifdef DEBUG */ + header->serial = 1; + header->is_mmapped = 1; + header->node = rbtnode; + header->node_is_relative = 0; + + if (RESIGN(header) && + (header->resign != 0 || header->resign_lsb != 0)) + { + int idx = header->node->locknum; + isc_heap_insert(rbtdb->heaps[idx], header); + } + + if (header->next != NULL) { + size_t cooked = dns_rbt_serialize_align(size); + if ((uintptr_t)header->next != + (p - (unsigned char *)base) + cooked) + { + return (ISC_R_INVALIDFILE); + } + header->next = (rdatasetheader_t *)(p + cooked); + header->next_is_relative = 0; + if ((header->next < (rdatasetheader_t *)base) || + (header->next > (rdatasetheader_t *)limit)) + { + return (ISC_R_INVALIDFILE); + } + } + + update_recordsandxfrsize(true, rbtdb->current_version, header, + rbtnode->fullnamelen); + } + + /* We're done deserializing; clear fullnamelen */ + rbtnode->fullnamelen = 0; + + return (ISC_R_SUCCESS); +} + +/* + * Load the RBT database from the image in 'f' + */ +static isc_result_t +deserialize(void *arg, FILE *f, off_t offset) { + isc_result_t result; + rbtdb_load_t *loadctx = arg; + dns_rbtdb_t *rbtdb = loadctx->rbtdb; + rbtdb_file_header_t *header; + int fd; + off_t filesize = 0; + char *base; + dns_rbt_t *tree = NULL, *nsec = NULL, *nsec3 = NULL; + int protect, flags; + dns_rbtnode_t *origin_node = NULL; + + REQUIRE(VALID_RBTDB(rbtdb)); + + /* + * TODO CKB: since this is read-write (had to be to add nodes later) + * we will need to lock the file or the nodes in it before modifying + * the nodes in the file. + */ + + /* Map in the whole file in one go */ + fd = fileno(f); + isc_file_getsizefd(fd, &filesize); + protect = PROT_READ | PROT_WRITE; + flags = MAP_PRIVATE; +#ifdef MAP_FILE + flags |= MAP_FILE; +#endif /* ifdef MAP_FILE */ + + base = isc_file_mmap(NULL, filesize, protect, flags, fd, 0); + if (base == NULL || base == MAP_FAILED) { + return (ISC_R_FAILURE); + } + + header = (rbtdb_file_header_t *)(base + offset); + if (!match_header_version(header)) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + if (header->tree != 0) { + result = dns_rbt_deserialize_tree( + base, filesize, (off_t)header->tree, rbtdb->common.mctx, + delete_callback, rbtdb, rbt_datafixer, rbtdb, NULL, + &tree); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rbt_findnode(tree, &rbtdb->common.origin, NULL, + &origin_node, NULL, + DNS_RBTFIND_EMPTYDATA, NULL, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + if (header->nsec != 0) { + result = dns_rbt_deserialize_tree( + base, filesize, (off_t)header->nsec, rbtdb->common.mctx, + delete_callback, rbtdb, rbt_datafixer, rbtdb, NULL, + &nsec); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + if (header->nsec3 != 0) { + result = dns_rbt_deserialize_tree( + base, filesize, (off_t)header->nsec3, + rbtdb->common.mctx, delete_callback, rbtdb, + rbt_datafixer, rbtdb, NULL, &nsec3); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + /* + * We have a successfully loaded all the rbt trees now update + * rbtdb to use them. + */ + + rbtdb->mmap_location = base; + rbtdb->mmap_size = (size_t)filesize; + + if (tree != NULL) { + dns_rbt_destroy(&rbtdb->tree); + rbtdb->tree = tree; + rbtdb->origin_node = origin_node; + } + + if (nsec != NULL) { + dns_rbt_destroy(&rbtdb->nsec); + rbtdb->nsec = nsec; + } + + if (nsec3 != NULL) { + dns_rbt_destroy(&rbtdb->nsec3); + rbtdb->nsec3 = nsec3; + } + + return (ISC_R_SUCCESS); + +cleanup: + if (tree != NULL) { + dns_rbt_destroy(&tree); + } + if (nsec != NULL) { + dns_rbt_destroy(&nsec); + } + if (nsec3 != NULL) { + dns_rbt_destroy(&nsec3); + } + isc_file_munmap(base, (size_t)filesize); + 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; + callbacks->deserialize = deserialize; + callbacks->deserialize_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; + callbacks->deserialize = NULL; + callbacks->deserialize_private = NULL; + + isc_mem_put(rbtdb->common.mctx, loadctx, sizeof(*loadctx)); + + return (ISC_R_SUCCESS); +} + +/* + * helper function to handle writing out the rdataset data pointed to + * by the void *data pointer in the dns_rbtnode + */ +static isc_result_t +rbt_datawriter(FILE *rbtfile, unsigned char *data, void *arg, uint64_t *crc) { + rbtdb_version_t *version = (rbtdb_version_t *)arg; + rbtdb_serial_t serial; + rdatasetheader_t newheader; + rdatasetheader_t *header = (rdatasetheader_t *)data, *next; + off_t where; + size_t cooked, size; + unsigned char *p; + isc_result_t result = ISC_R_SUCCESS; + char pad[sizeof(char *)]; + uintptr_t off; + + REQUIRE(rbtfile != NULL); + REQUIRE(data != NULL); + REQUIRE(version != NULL); + + serial = version->serial; + + for (; header != NULL; header = next) { + next = header->next; + do { + if (header->serial <= serial && !IGNORE(header)) { + if (NONEXISTENT(header)) { + header = NULL; + } + break; + } else { + header = header->down; + } + } while (header != NULL); + + if (header == NULL) { + continue; + } + + CHECK(isc_stdio_tell(rbtfile, &where)); + size = dns_rdataslab_size((unsigned char *)header, + sizeof(rdatasetheader_t)); + + p = (unsigned char *)header; + memmove(&newheader, p, sizeof(rdatasetheader_t)); + newheader.down = NULL; + newheader.next = NULL; + off = where; + if ((off_t)off != where) { + return (ISC_R_RANGE); + } + newheader.node = (dns_rbtnode_t *)off; + newheader.node_is_relative = 1; + newheader.serial = 1; + + /* + * Round size up to the next pointer sized offset so it + * will be properly aligned when read back in. + */ + cooked = dns_rbt_serialize_align(size); + if (next != NULL) { + newheader.next = (rdatasetheader_t *)(off + cooked); + newheader.next_is_relative = 1; + } + +#ifdef DEBUG + hexdump("writing header", (unsigned char *)&newheader, + sizeof(rdatasetheader_t)); + hexdump("writing slab", p + sizeof(rdatasetheader_t), + size - sizeof(rdatasetheader_t)); +#endif /* ifdef DEBUG */ + isc_crc64_update(crc, (unsigned char *)&newheader, + sizeof(rdatasetheader_t)); + CHECK(isc_stdio_write(&newheader, sizeof(rdatasetheader_t), 1, + rbtfile, NULL)); + + isc_crc64_update(crc, p + sizeof(rdatasetheader_t), + size - sizeof(rdatasetheader_t)); + CHECK(isc_stdio_write(p + sizeof(rdatasetheader_t), + size - sizeof(rdatasetheader_t), 1, + rbtfile, NULL)); + /* + * Pad to force alignment. + */ + if (size != (size_t)cooked) { + memset(pad, 0, sizeof(pad)); + CHECK(isc_stdio_write(pad, cooked - size, 1, rbtfile, + NULL)); + } + } + +failure: + return (result); +} + +/* + * Write out a zeroed header as a placeholder. Doing this ensures + * that the file will not read while it is partially written, should + * writing fail or be interrupted. + */ +static isc_result_t +rbtdb_zero_header(FILE *rbtfile) { + char buffer[RBTDB_HEADER_LENGTH]; + isc_result_t result; + + memset(buffer, 0, RBTDB_HEADER_LENGTH); + result = isc_stdio_write(buffer, 1, RBTDB_HEADER_LENGTH, rbtfile, NULL); + fflush(rbtfile); + + return (result); +} + +static isc_once_t once = ISC_ONCE_INIT; + +static void +init_file_version(void) { + int n; + + memset(FILE_VERSION, 0, sizeof(FILE_VERSION)); + n = snprintf(FILE_VERSION, sizeof(FILE_VERSION), "RBTDB Image %s %s", + dns_major, dns_mapapi); + INSIST(n > 0 && (unsigned int)n < sizeof(FILE_VERSION)); +} + +/* + * Write the file header out, recording the locations of the three + * RBT's used in the rbtdb: tree, nsec, and nsec3, and including NodeDump + * version information and any information stored in the rbtdb object + * itself that should be stored here. + */ +static isc_result_t +rbtdb_write_header(FILE *rbtfile, off_t tree_location, off_t nsec_location, + off_t nsec3_location) { + rbtdb_file_header_t header; + isc_result_t result; + + RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS); + + memset(&header, 0, sizeof(rbtdb_file_header_t)); + memmove(header.version1, FILE_VERSION, sizeof(header.version1)); + memmove(header.version2, FILE_VERSION, sizeof(header.version2)); + header.ptrsize = (uint32_t)sizeof(void *); + header.bigendian = (1 == htonl(1)) ? 1 : 0; + header.tree = (uint64_t)tree_location; + header.nsec = (uint64_t)nsec_location; + header.nsec3 = (uint64_t)nsec3_location; + result = isc_stdio_write(&header, 1, sizeof(rbtdb_file_header_t), + rbtfile, NULL); + fflush(rbtfile); + + return (result); +} + +static bool +match_header_version(rbtdb_file_header_t *header) { + RUNTIME_CHECK(isc_once_do(&once, init_file_version) == ISC_R_SUCCESS); + + if (memcmp(header->version1, FILE_VERSION, sizeof(header->version1)) != + 0 || + memcmp(header->version2, FILE_VERSION, sizeof(header->version1)) != + 0) + { + return (false); + } + + return (true); +} + +static isc_result_t +serialize(dns_db_t *db, dns_dbversion_t *ver, FILE *rbtfile) { + rbtdb_version_t *version = (rbtdb_version_t *)ver; + dns_rbtdb_t *rbtdb; + isc_result_t result; + off_t tree_location, nsec_location, nsec3_location, header_location; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(rbtfile != NULL); + + /* Ensure we're writing to a plain file */ + CHECK(isc_file_isplainfilefd(fileno(rbtfile))); + + /* + * first, write out a zeroed header to store rbtdb information + * + * then for each of the three trees, store the current position + * in the file and call dns_rbt_serialize_tree + * + * finally, write out the rbtdb header, storing the locations of the + * rbtheaders + * + * NOTE: need to do something better with the return codes, &= will + * not work. + */ + CHECK(isc_stdio_tell(rbtfile, &header_location)); + CHECK(rbtdb_zero_header(rbtfile)); + CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->tree, rbt_datawriter, + version, &tree_location)); + CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->nsec, rbt_datawriter, + version, &nsec_location)); + CHECK(dns_rbt_serialize_tree(rbtfile, rbtdb->nsec3, rbt_datawriter, + version, &nsec3_location)); + + CHECK(isc_stdio_seek(rbtfile, header_location, SEEK_SET)); + CHECK(rbtdb_write_header(rbtfile, tree_location, nsec_location, + nsec3_location)); +failure: + return (result); +} + +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_rbtdb_t *rbtdb; + unsigned int count; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + count = dns_rbt_nodecount(rbtdb->tree); + 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 isc_result_t +adjusthashsize(dns_db_t *db, size_t size) { + isc_result_t result; + dns_rbtdb_t *rbtdb; + + rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + result = dns_rbt_adjusthashsize(rbtdb->tree, size); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + + return (result); +} + +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, + serialize, + 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, + adjusthashsize }; + +static dns_dbmethods_t cache_methods = { attach, + detach, + beginload, + endload, + NULL, /* serialize */ + 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, + adjusthashsize }; + +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 + rbtdb->serve_stale_ttl; + + /* + * 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_copynf(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 (ISC_LIKELY(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 (ISC_LIKELY(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 (ISC_LIKELY(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) { + 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; + + /* + * 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_copynf(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_copynf(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 (ISC_UNLIKELY(result != ISC_R_SUCCESS)) { + goto no_glue; + } + + dns_name_copynf(gluename, name); + + if (dns_rdataset_isassociated(&ge->rdataset_a)) { + result = dns_message_gettemprdataset(msg, &rdataset_a); + if (ISC_UNLIKELY(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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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 (ISC_UNLIKELY(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 (ISC_LIKELY(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, 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..54f0b44 --- /dev/null +++ b/lib/dns/rbtdb.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 DNS_RBTDB_H +#define DNS_RBTDB_H 1 + +#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 + +#endif /* DNS_RBTDB_H */ diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c new file mode 100644 index 0000000..4beb5a2 --- /dev/null +++ b/lib/dns/rcode.c @@ -0,0 +1,588 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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..a283831 --- /dev/null +++ b/lib/dns/rdata.c @@ -0,0 +1,2365 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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, dns_additionaldatafunc_t add, void *arg + +#define CALL_ADDLDATA rdata, 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 isc_result_t +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); + } + return (ISC_R_SUCCESS); +} + +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, 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), + dns_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, + dns_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, + dns_result_totext(result)); + break; + case isc_tokentype_eof: + (*callback)(callbacks, "%s: %s:%lu: near eof: %s", + "dns_rdata_fromtext", name, line, + dns_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, + dns_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), + dns_result_totext(result)); + break; + default: + (*callback)(callbacks, "%s: %s:%lu: %s", + "dns_rdata_fromtext", name, line, + dns_result_totext(result)); + break; + } + } else { + (*callback)(callbacks, "dns_rdata_fromtext: %s:%lu: %s", name, + line, dns_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..4c6b715 --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.c @@ -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. + */ + +#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); + RETERR(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(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..3df1d82 --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.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. + */ + +#ifndef ANY_255_TSIG_250_H +#define ANY_255_TSIG_250_H 1 + +/*% 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; + +#endif /* ANY_255_TSIG_250_H */ 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..4814a02 --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.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. + */ + +/* 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); + RETERR(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(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..231665d --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.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. + */ + +/* by Bjorn.Victor@it.uu.se, 2005-05-07 */ +/* Based on generic/mx_15.h */ + +#ifndef CH_3_A_1_H +#define CH_3_A_1_H 1 + +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; + +#endif /* CH_3_A_1_H */ diff --git a/lib/dns/rdata/generic/afsdb_18.c b/lib/dns/rdata/generic/afsdb_18.c new file mode 100644 index 0000000..2c4eb27 --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + + RETERR(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); + + 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)); +} + +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..878ca9d --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.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. + */ + +#ifndef GENERIC_AFSDB_18_H +#define GENERIC_AFSDB_18_H 1 + +/*! + * \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; + +#endif /* GENERIC_AFSDB_18_H */ diff --git a/lib/dns/rdata/generic/amtrelay_260.c b/lib/dns/rdata/generic/amtrelay_260.c new file mode 100644 index 0000000..6938c43 --- /dev/null +++ b/lib/dns/rdata/generic/amtrelay_260.c @@ -0,0 +1,471 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..48a3fbb --- /dev/null +++ b/lib/dns/rdata/generic/amtrelay_260.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. + */ + +#ifndef GENERIC_AMTRELAY_260_H +#define GENERIC_AMTRELAY_260_H 1 + +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; + +#endif /* GENERIC_AMTRELAY_260_H */ diff --git a/lib/dns/rdata/generic/avc_258.c b/lib/dns/rdata/generic/avc_258.c new file mode 100644 index 0000000..34d0048 --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..c158764 --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.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. + */ + +#ifndef GENERIC_AVC_258_H +#define GENERIC_AVC_258_H 1 + +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. + */ +#endif /* GENERIC_AVC_258_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..0e6170b --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.c @@ -0,0 +1,627 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..ff1c605 --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.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. + */ + +#ifndef GENERIC_CAA_257_H +#define GENERIC_CAA_257_H 1 + +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; + +#endif /* GENERIC_CAA_257_H */ diff --git a/lib/dns/rdata/generic/cdnskey_60.c b/lib/dns/rdata/generic/cdnskey_60.c new file mode 100644 index 0000000..672bf12 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.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. + */ + +/* 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(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..a1a6e57 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.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. + */ + +#ifndef GENERIC_CDNSKEY_60_H +#define GENERIC_CDNSKEY_60_H 1 + +/* CDNSKEY records have the same RDATA fields as DNSKEY records. */ +typedef struct dns_rdata_key dns_rdata_cdnskey_t; + +#endif /* GENERIC_CDNSKEY_60_H */ diff --git a/lib/dns/rdata/generic/cds_59.c b/lib/dns/rdata/generic/cds_59.c new file mode 100644 index 0000000..4b3a79b --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..31996b1 --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.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. + */ + +#ifndef GENERIC_CDS_59_H +#define GENERIC_CDS_59_H 1 + +/* CDS records have the same RDATA fields as DS records. */ +typedef struct dns_rdata_ds dns_rdata_cds_t; + +#endif /* GENERIC_CDS_59_H */ diff --git a/lib/dns/rdata/generic/cert_37.c b/lib/dns/rdata/generic/cert_37.c new file mode 100644 index 0000000..4bd8c0a --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..3fd305e --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.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. + */ + +#ifndef GENERIC_CERT_37_H +#define GENERIC_CERT_37_H 1 + +/*% 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; + +#endif /* GENERIC_CERT_37_H */ diff --git a/lib/dns/rdata/generic/cname_5.c b/lib/dns/rdata/generic/cname_5.c new file mode 100644 index 0000000..a7248cd --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.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_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); + RETERR(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(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..a44dfbe --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.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. + */ + +#ifndef GENERIC_CNAME_5_H +#define GENERIC_CNAME_5_H 1 + +typedef struct dns_rdata_cname { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t cname; +} dns_rdata_cname_t; + +#endif /* GENERIC_CNAME_5_H */ diff --git a/lib/dns/rdata/generic/csync_62.c b/lib/dns/rdata/generic/csync_62.c new file mode 100644 index 0000000..f988729 --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..ecc2202 --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.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. + */ + +#ifndef GENERIC_CSYNC_62_H +#define GENERIC_CSYNC_62_H 1 + +/*! + * \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; + +#endif /* GENERIC_CSYNC_62_H */ diff --git a/lib/dns/rdata/generic/dlv_32769.c b/lib/dns/rdata/generic/dlv_32769.c new file mode 100644 index 0000000..f45b196 --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..bf3734e --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.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. + */ + +/* draft-ietf-dnsext-delegation-signer-05.txt */ +#ifndef GENERIC_DLV_32769_H +#define GENERIC_DLV_32769_H 1 + +typedef struct dns_rdata_ds dns_rdata_dlv_t; + +#endif /* GENERIC_DLV_32769_H */ diff --git a/lib/dns/rdata/generic/dname_39.c b/lib/dns/rdata/generic/dname_39.c new file mode 100644 index 0000000..87f417c --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.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. + */ + +/* 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); + RETERR(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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_dname); + + 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..b1cc198 --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.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. + */ + +#ifndef GENERIC_DNAME_39_H +#define GENERIC_DNAME_39_H 1 + +/*! + * \brief per RFC2672 */ + +typedef struct dns_rdata_dname { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t dname; +} dns_rdata_dname_t; + +#endif /* GENERIC_DNAME_39_H */ diff --git a/lib/dns/rdata/generic/dnskey_48.c b/lib/dns/rdata/generic/dnskey_48.c new file mode 100644 index 0000000..c609ff0 --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.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. + */ + +/* 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(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..385d5e0 --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.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. + */ + +#ifndef GENERIC_DNSKEY_48_H +#define GENERIC_DNSKEY_48_H 1 + +/*! + * \brief per RFC2535 + */ + +typedef struct dns_rdata_key dns_rdata_dnskey_t; + +#endif /* GENERIC_DNSKEY_48_H */ diff --git a/lib/dns/rdata/generic/doa_259.c b/lib/dns/rdata/generic/doa_259.c new file mode 100644 index 0000000..e35f2ca --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_doa); + + 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..92999a1 --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.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. + */ + +#ifndef GENERIC_DOA_259_H +#define GENERIC_DOA_259_H 1 + +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; + +#endif /* GENERIC_DOA_259_H */ diff --git a/lib/dns/rdata/generic/ds_43.c b/lib/dns/rdata/generic/ds_43.c new file mode 100644 index 0000000..0b6fa0a --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.c @@ -0,0 +1,385 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..26ef13e --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.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. + */ + +#ifndef GENERIC_DS_43_H +#define GENERIC_DS_43_H 1 + +/*! + * \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; + +#endif /* GENERIC_DS_43_H */ diff --git a/lib/dns/rdata/generic/eui48_108.c b/lib/dns/rdata/generic/eui48_108.c new file mode 100644 index 0000000..7926708 --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.c @@ -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. + */ + +#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(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..2ce2706 --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.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. + */ + +/* */ +#ifndef GENERIC_EUI48_108_H +#define GENERIC_EUI48_108_H 1 + +typedef struct dns_rdata_eui48 { + dns_rdatacommon_t common; + unsigned char eui48[6]; +} dns_rdata_eui48_t; + +#endif /* GENERIC_EUI48_10k_H */ diff --git a/lib/dns/rdata/generic/eui64_109.c b/lib/dns/rdata/generic/eui64_109.c new file mode 100644 index 0000000..5cb788a --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..0783197 --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.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. + */ + +/* */ +#ifndef GENERIC_EUI64_109_H +#define GENERIC_EUI64_109_H 1 + +typedef struct dns_rdata_eui64 { + dns_rdatacommon_t common; + unsigned char eui64[8]; +} dns_rdata_eui64_t; + +#endif /* GENERIC_EUI64_10k_H */ diff --git a/lib/dns/rdata/generic/gpos_27.c b/lib/dns/rdata/generic/gpos_27.c new file mode 100644 index 0000000..9d02779 --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.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. + */ + +/* 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(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..08b6b84 --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.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. + */ + +#ifndef GENERIC_GPOS_27_H +#define GENERIC_GPOS_27_H 1 + +/*! + * \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; + +#endif /* GENERIC_GPOS_27_H */ diff --git a/lib/dns/rdata/generic/hinfo_13.c b/lib/dns/rdata/generic/hinfo_13.c new file mode 100644 index 0000000..d483839 --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_HINFO_13_C +#define RDATA_GENERIC_HINFO_13_C + +#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(add); + UNUSED(arg); + UNUSED(rdata); + + 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)); +} +#endif /* RDATA_GENERIC_HINFO_13_C */ diff --git a/lib/dns/rdata/generic/hinfo_13.h b/lib/dns/rdata/generic/hinfo_13.h new file mode 100644 index 0000000..294b4bf --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.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. + */ + +#ifndef GENERIC_HINFO_13_H +#define GENERIC_HINFO_13_H 1 + +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; + +#endif /* GENERIC_HINFO_13_H */ diff --git a/lib/dns/rdata/generic/hip_55.c b/lib/dns/rdata/generic/hip_55.c new file mode 100644 index 0000000..5c9041a --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +#ifndef RDATA_GENERIC_HIP_5_C +#define RDATA_GENERIC_HIP_5_C + +#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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_hip); + + 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)); +} + +#endif /* RDATA_GENERIC_HIP_5_C */ diff --git a/lib/dns/rdata/generic/hip_55.h b/lib/dns/rdata/generic/hip_55.h new file mode 100644 index 0000000..4b852fa --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.h @@ -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. + */ + +#ifndef GENERIC_HIP_5_H +#define GENERIC_HIP_5_H 1 + +/* 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 *); + +#endif /* GENERIC_HIP_5_H */ diff --git a/lib/dns/rdata/generic/ipseckey_45.c b/lib/dns/rdata/generic/ipseckey_45.c new file mode 100644 index 0000000..bc9d457 --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.c @@ -0,0 +1,526 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..bdace0e --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.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. + */ + +#ifndef GENERIC_IPSECKEY_45_H +#define GENERIC_IPSECKEY_45_H 1 + +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; + +#endif /* GENERIC_IPSECKEY_45_H */ diff --git a/lib/dns/rdata/generic/isdn_20.c b/lib/dns/rdata/generic/isdn_20.c new file mode 100644 index 0000000..3fd756a --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..17bb155 --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.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. + */ + +#ifndef GENERIC_ISDN_20_H +#define GENERIC_ISDN_20_H 1 + +/*! + * \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; + +#endif /* GENERIC_ISDN_20_H */ diff --git a/lib/dns/rdata/generic/key_25.c b/lib/dns/rdata/generic/key_25.c new file mode 100644 index 0000000..a94ebc2 --- /dev/null +++ b/lib/dns/rdata/generic/key_25.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..c528224 --- /dev/null +++ b/lib/dns/rdata/generic/key_25.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. + */ + +#ifndef GENERIC_KEY_25_H +#define GENERIC_KEY_25_H 1 + +/*! + * \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; + +#endif /* GENERIC_KEY_25_H */ diff --git a/lib/dns/rdata/generic/keydata_65533.c b/lib/dns/rdata/generic/keydata_65533.c new file mode 100644 index 0000000..46ae5dd --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.c @@ -0,0 +1,462 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..5ad0715 --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.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. + */ + +#ifndef GENERIC_KEYDATA_65533_H +#define GENERIC_KEYDATA_65533_H 1 + +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; + +#endif /* GENERIC_KEYDATA_65533_H */ diff --git a/lib/dns/rdata/generic/l32_105.c b/lib/dns/rdata/generic/l32_105.c new file mode 100644 index 0000000..07cbc0f --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.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_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(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..8bf2a5c --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.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. + */ + +/* */ +#ifndef GENERIC_L32_105_H +#define GENERIC_L32_105_H 1 + +typedef struct dns_rdata_l32 { + dns_rdatacommon_t common; + uint16_t pref; + struct in_addr l32; +} dns_rdata_l32_t; + +#endif /* GENERIC_L32_105_H */ diff --git a/lib/dns/rdata/generic/l64_106.c b/lib/dns/rdata/generic/l64_106.c new file mode 100644 index 0000000..c5daf07 --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..314e583 --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.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. + */ + +/* */ +#ifndef GENERIC_L64_106_H +#define GENERIC_L64_106_H 1 + +typedef struct dns_rdata_l64 { + dns_rdatacommon_t common; + uint16_t pref; + unsigned char l64[8]; +} dns_rdata_l64_t; + +#endif /* GENERIC_L64_106_H */ diff --git a/lib/dns/rdata/generic/loc_29.c b/lib/dns/rdata/generic/loc_29.c new file mode 100644 index 0000000..f2a08b5 --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.c @@ -0,0 +1,838 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..c523785 --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.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. + */ + +#ifndef GENERIC_LOC_29_H +#define GENERIC_LOC_29_H 1 + +/*! + * \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; + +#endif /* GENERIC_LOC_29_H */ diff --git a/lib/dns/rdata/generic/lp_107.c b/lib/dns/rdata/generic/lp_107.c new file mode 100644 index 0000000..50b2e29 --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + 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); + if (result != ISC_R_SUCCESS) { + return (result); + } + return ((add)(arg, &name, dns_rdatatype_l64)); +} + +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..71cd196 --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.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. + */ + +/* */ +#ifndef GENERIC_LP_107_H +#define GENERIC_LP_107_H 1 + +typedef struct dns_rdata_lp { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t pref; + dns_name_t lp; +} dns_rdata_lp_t; + +#endif /* GENERIC_LP_107_H */ diff --git a/lib/dns/rdata/generic/mb_7.c b/lib/dns/rdata/generic/mb_7.c new file mode 100644 index 0000000..a3923eb --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a)); +} + +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..ae7e2e2 --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.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. + */ + +/* */ +#ifndef GENERIC_MB_7_H +#define GENERIC_MB_7_H 1 + +typedef struct dns_rdata_mb { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mb; +} dns_rdata_mb_t; + +#endif /* GENERIC_MB_7_H */ diff --git a/lib/dns/rdata/generic/md_3.c b/lib/dns/rdata/generic/md_3.c new file mode 100644 index 0000000..9e2b778 --- /dev/null +++ b/lib/dns/rdata/generic/md_3.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_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); + RETERR(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); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a)); +} + +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..643bd03 --- /dev/null +++ b/lib/dns/rdata/generic/md_3.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. + */ + +/* */ +#ifndef GENERIC_MD_3_H +#define GENERIC_MD_3_H 1 + +typedef struct dns_rdata_md { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t md; +} dns_rdata_md_t; + +#endif /* GENERIC_MD_3_H */ diff --git a/lib/dns/rdata/generic/mf_4.c b/lib/dns/rdata/generic/mf_4.c new file mode 100644 index 0000000..30ea010 --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.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. + */ + +#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); + RETERR(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); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a)); +} + +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..6a4b514 --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.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. + */ + +/* */ +#ifndef GENERIC_MF_4_H +#define GENERIC_MF_4_H 1 + +typedef struct dns_rdata_mf { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mf; +} dns_rdata_mf_t; + +#endif /* GENERIC_MF_4_H */ diff --git a/lib/dns/rdata/generic/mg_8.c b/lib/dns/rdata/generic/mg_8.c new file mode 100644 index 0000000..183e428 --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + 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..815b46a --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.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. + */ + +/* */ +#ifndef GENERIC_MG_8_H +#define GENERIC_MG_8_H 1 + +typedef struct dns_rdata_mg { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mg; +} dns_rdata_mg_t; + +#endif /* GENERIC_MG_8_H */ diff --git a/lib/dns/rdata/generic/minfo_14.c b/lib/dns/rdata/generic/minfo_14.c new file mode 100644 index 0000000..e396cff --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + isc_result_t result; + + 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); + RETERR(name_duporclone(&name, mctx, &minfo->rmailbox)); + isc_region_consume(®ion, name_length(&name)); + + dns_name_fromregion(&name, ®ion); + dns_name_init(&minfo->emailbox, NULL); + result = name_duporclone(&name, mctx, &minfo->emailbox); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + minfo->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&minfo->rmailbox, mctx); + } + return (ISC_R_NOMEMORY); +} + +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(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..f2017e6 --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.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. + */ + +/* */ +#ifndef GENERIC_MINFO_14_H +#define GENERIC_MINFO_14_H 1 + +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; + +#endif /* GENERIC_MINFO_14_H */ diff --git a/lib/dns/rdata/generic/mr_9.c b/lib/dns/rdata/generic/mr_9.c new file mode 100644 index 0000000..aff5da3 --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.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_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); + RETERR(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(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..6855933 --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.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. + */ + +/* */ +#ifndef GENERIC_MR_9_H +#define GENERIC_MR_9_H 1 + +typedef struct dns_rdata_mr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t mr; +} dns_rdata_mr_t; + +#endif /* GENERIC_MR_9_H */ diff --git a/lib/dns/rdata/generic/mx_15.c b/lib/dns/rdata/generic/mx_15.c new file mode 100644 index 0000000..ff6637c --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + 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); + 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)); +} + +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..14f9189 --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.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. + */ + +/* */ +#ifndef GENERIC_MX_15_H +#define GENERIC_MX_15_H 1 + +typedef struct dns_rdata_mx { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t pref; + dns_name_t mx; +} dns_rdata_mx_t; + +#endif /* GENERIC_MX_15_H */ diff --git a/lib/dns/rdata/generic/naptr_35.c b/lib/dns/rdata/generic/naptr_35.c new file mode 100644 index 0000000..3f43e0e --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.c @@ -0,0 +1,740 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + isc_result_t result; + 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); + result = name_duporclone(&name, mctx, &naptr->replacement); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + 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); + + /* + * 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)); + } + + 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..d221823 --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.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. + */ + +#ifndef GENERIC_NAPTR_35_H +#define GENERIC_NAPTR_35_H 1 + +/*! + * \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; + +#endif /* GENERIC_NAPTR_35_H */ diff --git a/lib/dns/rdata/generic/nid_104.c b/lib/dns/rdata/generic/nid_104.c new file mode 100644 index 0000000..0297da7 --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..cab4330 --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.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. + */ + +/* */ +#ifndef GENERIC_NID_104_H +#define GENERIC_NID_104_H 1 + +typedef struct dns_rdata_nid { + dns_rdatacommon_t common; + uint16_t pref; + unsigned char nid[8]; +} dns_rdata_nid_t; + +#endif /* GENERIC_NID_104_H */ diff --git a/lib/dns/rdata/generic/ninfo_56.c b/lib/dns/rdata/generic/ninfo_56.c new file mode 100644 index 0000000..3fcc582 --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..0630ff2 --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.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. + */ + +/* */ +#ifndef GENERIC_NINFO_56_H +#define GENERIC_NINFO_56_H 1 + +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 *); + +#endif /* GENERIC_NINFO_16_H */ diff --git a/lib/dns/rdata/generic/ns_2.c b/lib/dns/rdata/generic/ns_2.c new file mode 100644 index 0000000..265f3bb --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + dns_name_init(&name, offsets); + dns_rdata_toregion(rdata, ®ion); + dns_name_fromregion(&name, ®ion); + + return ((add)(arg, &name, dns_rdatatype_a)); +} + +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..3cb9e65 --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.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. + */ + +/* */ +#ifndef GENERIC_NS_2_H +#define GENERIC_NS_2_H 1 + +typedef struct dns_rdata_ns { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t name; +} dns_rdata_ns_t; + +#endif /* GENERIC_NS_2_H */ diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c new file mode 100644 index 0000000..c0d81a6 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.c @@ -0,0 +1,424 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..e773437 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.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. + */ + +#ifndef GENERIC_NSEC3_50_H +#define GENERIC_NSEC3_50_H 1 + +/*! + * \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 + +#endif /* GENERIC_NSEC3_50_H */ diff --git a/lib/dns/rdata/generic/nsec3param_51.c b/lib/dns/rdata/generic/nsec3param_51.c new file mode 100644 index 0000000..1d08d72 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.c @@ -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. + */ + +/* + * 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(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..2ff3029 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.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. + */ + +#ifndef GENERIC_NSEC3PARAM_51_H +#define GENERIC_NSEC3PARAM_51_H 1 + +/*! + * \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; + +#endif /* GENERIC_NSEC3PARAM_51_H */ diff --git a/lib/dns/rdata/generic/nsec_47.c b/lib/dns/rdata/generic/nsec_47.c new file mode 100644 index 0000000..03aab8c --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.c @@ -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. + */ + +/* 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); + RETERR(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(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..98cb788 --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.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. + */ + +#ifndef GENERIC_NSEC_47_H +#define GENERIC_NSEC_47_H 1 + +/*! + * \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; + +#endif /* GENERIC_NSEC_47_H */ diff --git a/lib/dns/rdata/generic/null_10.c b/lib/dns/rdata/generic/null_10.c new file mode 100644 index 0000000..c5884bf --- /dev/null +++ b/lib/dns/rdata/generic/null_10.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_null); + + 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..cc17e23 --- /dev/null +++ b/lib/dns/rdata/generic/null_10.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. + */ + +/* */ +#ifndef GENERIC_NULL_10_H +#define GENERIC_NULL_10_H 1 + +typedef struct dns_rdata_null { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t length; + unsigned char *data; +} dns_rdata_null_t; + +#endif /* GENERIC_NULL_10_H */ diff --git a/lib/dns/rdata/generic/nxt_30.c b/lib/dns/rdata/generic/nxt_30.c new file mode 100644 index 0000000..5a3dcb5 --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..a7cae69 --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.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. + */ + +#ifndef GENERIC_NXT_30_H +#define GENERIC_NXT_30_H 1 + +/*! + * \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; + +#endif /* GENERIC_NXT_30_H */ diff --git a/lib/dns/rdata/generic/openpgpkey_61.c b/lib/dns/rdata/generic/openpgpkey_61.c new file mode 100644 index 0000000..719c081 --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..b78923b --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.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. + */ + +#ifndef GENERIC_OPENPGPKEY_61_H +#define GENERIC_OPENPGPKEY_61_H 1 + +typedef struct dns_rdata_openpgpkey { + dns_rdatacommon_t common; + isc_mem_t *mctx; + uint16_t length; + unsigned char *keyring; +} dns_rdata_openpgpkey_t; + +#endif /* GENERIC_OPENPGPKEY_61_H */ diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c new file mode 100644 index 0000000..8aa7b52 --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.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. + */ + +/* 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(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..a5e4c0d --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.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. + */ + +#ifndef GENERIC_OPT_41_H +#define GENERIC_OPT_41_H 1 + +/*! + * \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 *); + +#endif /* GENERIC_OPT_41_H */ diff --git a/lib/dns/rdata/generic/proforma.c b/lib/dns/rdata/generic/proforma.c new file mode 100644 index 0000000..9d97699 --- /dev/null +++ b/lib/dns/rdata/generic/proforma.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. + */ + +#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 == #); + + (void)add; + (void)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..5610c06 --- /dev/null +++ b/lib/dns/rdata/generic/proforma.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. + */ + +/* */ +#ifndef GENERIC_PROFORMA_H +#define GENERIC_PROFORMA_H 1 + +typedef struct dns_rdata_ #{ + dns_rdatacommon_t common; + isc_mem_t *mctx; /* if required */ + /* type & class specific elements */ +} +dns_rdata_ #_t; + +#endif /* GENERIC_PROFORMA_H */ diff --git a/lib/dns/rdata/generic/ptr_12.c b/lib/dns/rdata/generic/ptr_12.c new file mode 100644 index 0000000..5d39300 --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.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_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); + RETERR(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(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..310a4ea --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.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. + */ + +/* */ +#ifndef GENERIC_PTR_12_H +#define GENERIC_PTR_12_H 1 + +typedef struct dns_rdata_ptr { + dns_rdatacommon_t common; + isc_mem_t *mctx; + dns_name_t ptr; +} dns_rdata_ptr_t; + +#endif /* GENERIC_PTR_12_H */ diff --git a/lib/dns/rdata/generic/rkey_57.c b/lib/dns/rdata/generic/rkey_57.c new file mode 100644 index 0000000..06d8495 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..f5665f6 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.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. + */ + +#ifndef GENERIC_RKEY_57_H +#define GENERIC_RKEY_57_H 1 + +typedef struct dns_rdata_key dns_rdata_rkey_t; + +#endif /* GENERIC_RKEY_57_H */ diff --git a/lib/dns/rdata/generic/rp_17.c b/lib/dns/rdata/generic/rp_17.c new file mode 100644 index 0000000..3abe55d --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_result_t result; + 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); + RETERR(name_duporclone(&name, mctx, &rp->mail)); + isc_region_consume(®ion, name_length(&name)); + dns_name_fromregion(&name, ®ion); + dns_name_init(&rp->text, NULL); + result = name_duporclone(&name, mctx, &rp->text); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + rp->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&rp->mail, mctx); + } + return (ISC_R_NOMEMORY); +} + +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(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..824b576 --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.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. + */ + +#ifndef GENERIC_RP_17_H +#define GENERIC_RP_17_H 1 + +/*! + * \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; + +#endif /* GENERIC_RP_17_H */ diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c new file mode 100644 index 0000000..8e22030 --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.c @@ -0,0 +1,639 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..3d4d7a5 --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.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. + */ + +#ifndef GENERIC_DNSSIG_46_H +#define GENERIC_DNSSIG_46_H 1 + +/*! + * \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; + +#endif /* GENERIC_DNSSIG_46_H */ diff --git a/lib/dns/rdata/generic/rt_21.c b/lib/dns/rdata/generic/rt_21.c new file mode 100644 index 0000000..1396b4d --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.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. + */ + +/* 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); + RETERR(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); + + 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); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = (add)(arg, &name, dns_rdatatype_isdn); + if (result != ISC_R_SUCCESS) { + return (result); + } + return ((add)(arg, &name, dns_rdatatype_a)); +} + +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..24a6529 --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.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. + */ + +#ifndef GENERIC_RT_21_H +#define GENERIC_RT_21_H 1 + +/*! + * \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; + +#endif /* GENERIC_RT_21_H */ diff --git a/lib/dns/rdata/generic/sig_24.c b/lib/dns/rdata/generic/sig_24.c new file mode 100644 index 0000000..8424992 --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.c @@ -0,0 +1,590 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..5e5db8c --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.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. + */ + +#ifndef GENERIC_SIG_24_H +#define GENERIC_SIG_24_H 1 + +/*! + * \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; + +#endif /* GENERIC_SIG_24_H */ diff --git a/lib/dns/rdata/generic/sink_40.c b/lib/dns/rdata/generic/sink_40.c new file mode 100644 index 0000000..b4ad0b9 --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.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. + */ + +#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(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..b2956b7 --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.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. + */ + +#ifndef GENERIC_SINK_40_H +#define GENERIC_SINK_40_H 1 + +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; + +#endif /* GENERIC_SINK_40_H */ diff --git a/lib/dns/rdata/generic/smimea_53.c b/lib/dns/rdata/generic/smimea_53.c new file mode 100644 index 0000000..ecdfea3 --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..527f596 --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.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. + */ + +#ifndef GENERIC_SMIMEA_53_H +#define GENERIC_SMIMEA_53_H 1 + +typedef struct dns_rdata_tlsa dns_rdata_smimea_t; + +#endif /* GENERIC_SMIMEA_53_H */ diff --git a/lib/dns/rdata/generic/soa_6.c b/lib/dns/rdata/generic/soa_6.c new file mode 100644 index 0000000..70c9cd3 --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.c @@ -0,0 +1,452 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + isc_result_t result; + + 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); + RETERR(name_duporclone(&name, mctx, &soa->origin)); + + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&soa->contact, NULL); + result = name_duporclone(&name, mctx, &soa->contact); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + 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); + +cleanup: + if (mctx != NULL) { + dns_name_free(&soa->origin, mctx); + } + return (ISC_R_NOMEMORY); +} + +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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_soa); + + 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..ba5f000 --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.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. + */ + +/* */ +#ifndef GENERIC_SOA_6_H +#define GENERIC_SOA_6_H 1 + +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; + +#endif /* GENERIC_SOA_6_H */ diff --git a/lib/dns/rdata/generic/spf_99.c b/lib/dns/rdata/generic/spf_99.c new file mode 100644 index 0000000..1df3e75 --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.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_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(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..1c98259 --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.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. + */ + +#ifndef GENERIC_SPF_99_H +#define GENERIC_SPF_99_H 1 + +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. + */ +#endif /* GENERIC_SPF_99_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..9ca97b4 --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..61d5a00 --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.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. + */ + +/*! + * \brief Per RFC 4255 */ + +#ifndef GENERIC_SSHFP_44_H +#define GENERIC_SSHFP_44_H 1 + +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; + +#endif /* GENERIC_SSHFP_44_H */ diff --git a/lib/dns/rdata/generic/ta_32768.c b/lib/dns/rdata/generic/ta_32768.c new file mode 100644 index 0000000..eeab50b --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..7ea6e40 --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.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. + */ + +#ifndef GENERIC_TA_32768_H +#define GENERIC_TA_32768_H 1 + +/* + * TA records are identical to DS records. + */ +typedef struct dns_rdata_ds dns_rdata_ta_t; + +#endif /* GENERIC_TA_32768_H */ diff --git a/lib/dns/rdata/generic/talink_58.c b/lib/dns/rdata/generic/talink_58.c new file mode 100644 index 0000000..4460ffb --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + isc_result_t result; + + 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); + RETERR(name_duporclone(&name, mctx, &talink->prev)); + + dns_name_fromregion(&name, ®ion); + isc_region_consume(®ion, name_length(&name)); + dns_name_init(&talink->next, NULL); + result = name_duporclone(&name, mctx, &talink->next); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + talink->mctx = mctx; + return (ISC_R_SUCCESS); + +cleanup: + if (mctx != NULL) { + dns_name_free(&talink->prev, mctx); + } + return (ISC_R_NOMEMORY); +} + +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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_talink); + + 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..08ea600 --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.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. + */ + +/* + * http://www.iana.org/assignments/dns-parameters/TALINK/talink-completed-template + */ + +#ifndef GENERIC_TALINK_58_H +#define GENERIC_TALINK_58_H 1 + +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; + +#endif /* GENERIC_TALINK_58_H */ diff --git a/lib/dns/rdata/generic/tkey_249.c b/lib/dns/rdata/generic/tkey_249.c new file mode 100644 index 0000000..7d3ea0e --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.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. + */ + +/* 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); + RETERR(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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_tkey); + + 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..eed3c68 --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.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. + */ + +#ifndef GENERIC_TKEY_249_H +#define GENERIC_TKEY_249_H 1 + +/*! + * \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; + +#endif /* GENERIC_TKEY_249_H */ diff --git a/lib/dns/rdata/generic/tlsa_52.c b/lib/dns/rdata/generic/tlsa_52.c new file mode 100644 index 0000000..ddd437b --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..a95b882 --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.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. + */ + +#ifndef GENERIC_TLSA_52_H +#define GENERIC_TLSA_52_H 1 + +/*! + * \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; + +#endif /* GENERIC_TLSA_52_H */ diff --git a/lib/dns/rdata/generic/txt_16.c b/lib/dns/rdata/generic/txt_16.c new file mode 100644 index 0000000..c4d338b --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.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_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(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..d94eb70 --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.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. + */ + +/* */ +#ifndef GENERIC_TXT_16_H +#define GENERIC_TXT_16_H 1 + +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 *); + +#endif /* GENERIC_TXT_16_H */ diff --git a/lib/dns/rdata/generic/uri_256.c b/lib/dns/rdata/generic/uri_256.c new file mode 100644 index 0000000..a3c61d8 --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.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. + */ + +#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(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..8786eff --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.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. + */ + +#ifndef GENERIC_URI_256_H +#define GENERIC_URI_256_H 1 + +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; + +#endif /* GENERIC_URI_256_H */ diff --git a/lib/dns/rdata/generic/x25_19.c b/lib/dns/rdata/generic/x25_19.c new file mode 100644 index 0000000..e8ed4dd --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..5f8981e --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.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. + */ + +#ifndef GENERIC_X25_19_H +#define GENERIC_X25_19_H 1 + +/*! + * \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; + +#endif /* GENERIC_X25_19_H */ diff --git a/lib/dns/rdata/generic/zonemd_63.c b/lib/dns/rdata/generic/zonemd_63.c new file mode 100644 index 0000000..949507e --- /dev/null +++ b/lib/dns/rdata/generic/zonemd_63.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..02adc14 --- /dev/null +++ b/lib/dns/rdata/generic/zonemd_63.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. + */ + +#ifndef GENERIC_ZONEMD_63_H +#define GENERIC_ZONEMD_63_H 1 + +/* 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; + +#endif /* GENERIC_ZONEMD_63_H */ 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..8943e7e --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.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_HS_4_A_1_C +#define RDATA_HS_4_A_1_C + +#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(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)); +} + +#endif /* RDATA_HS_4_A_1_C */ 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..1bd8c5d --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.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. + */ + +/* */ +#ifndef HS_4_A_1_H +#define HS_4_A_1_H 1 + +typedef struct dns_rdata_hs_a { + dns_rdatacommon_t common; + struct in_addr in_addr; +} dns_rdata_hs_a_t; + +#endif /* HS_4_A_1_H */ 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..65c0a69 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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(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..bd83092 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.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. + */ + +#ifndef IN_1_A6_38_H +#define IN_1_A6_38_H 1 + +/*! + * \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; + +#endif /* IN_1_A6_38_H */ 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..b474aa7 --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.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_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(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..aefc4f3 --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.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. + */ + +/* */ +#ifndef IN_1_A_1_H +#define IN_1_A_1_H 1 + +typedef struct dns_rdata_in_a { + dns_rdatacommon_t common; + struct in_addr in_addr; +} dns_rdata_in_a_t; + +#endif /* IN_1_A_1_H */ 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..d60175a --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..b7310b2 --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.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. + */ + +#ifndef IN_1_AAAA_28_H +#define IN_1_AAAA_28_H 1 + +/*! + * \brief Per RFC1886 */ + +typedef struct dns_rdata_in_aaaa { + dns_rdatacommon_t common; + struct in6_addr in6_addr; +} dns_rdata_in_aaaa_t; + +#endif /* IN_1_AAAA_28_H */ 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..96372e0 --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + + (void)add; + (void)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..603fd3e --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 IN_1_APL_42_H +#define IN_1_APL_42_H 1 + +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); + +#endif /* IN_1_APL_42_H */ 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..c00defd --- /dev/null +++ b/lib/dns/rdata/in_1/atma_34.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..abb5457 --- /dev/null +++ b/lib/dns/rdata/in_1/atma_34.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. + */ + +#ifndef IN_1_ATMA_22_H +#define IN_1_ATMA_22_H 1 + +/*! + * \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; + +#endif /* IN_1_ATMA_22_H */ 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..7d36d50 --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.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. + */ + +/* 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(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..071ea41 --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.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. + */ + +/* */ +#ifndef IN_1_DHCID_49_H +#define IN_1_DHCID_49_H 1 + +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; + +#endif /* IN_1_DHCID_49_H */ 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..104837c --- /dev/null +++ b/lib/dns/rdata/in_1/eid_31.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..879bcf5 --- /dev/null +++ b/lib/dns/rdata/in_1/eid_31.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. + */ + +#ifndef IN_1_EID_31_H +#define IN_1_EID_31_H 1 + +/*! + * \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; + +#endif /* IN_1_EID_31_H */ 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..0581645 --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_HTTPS_65_C +#define RDATA_IN_1_HTTPS_65_C + +#define RRTYPE_HTTPS_ATTRIBUTES 0 + +/* + * 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); +} + +#endif /* RDATA_IN_1_HTTPS_65_C */ 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..a5967a3 --- /dev/null +++ b/lib/dns/rdata/in_1/https_65.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. + */ + +#ifndef IN_1_HTTPS_65_H +#define IN_1_HTTPS_65_H 1 + +/*! + * \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 *); + +#endif /* IN_1_HTTPS_65_H */ 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..508def3 --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.c @@ -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. + */ + +/* 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); + RETERR(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); + + 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)); +} + +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..b71aadc --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.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. + */ + +#ifndef IN_1_KX_36_H +#define IN_1_KX_36_H 1 + +/*! + * \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; + +#endif /* IN_1_KX_36_H */ 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..6f1e3d6 --- /dev/null +++ b/lib/dns/rdata/in_1/nimloc_32.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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(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..02ca047 --- /dev/null +++ b/lib/dns/rdata/in_1/nimloc_32.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. + */ + +#ifndef IN_1_NIMLOC_32_H +#define IN_1_NIMLOC_32_H 1 + +/*! + * \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; + +#endif /* IN_1_NIMLOC_32_H */ 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..326e878 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.c @@ -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. + */ + +/* 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); + RETERR(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(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..ef1ee60 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.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. + */ + +#ifndef IN_1_NSAP_PTR_23_H +#define IN_1_NSAP_PTR_23_H 1 + +/*! + * \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; + +#endif /* IN_1_NSAP_PTR_23_H */ 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..5ccb793 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.c @@ -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. + */ + +/* 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(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..f65c913 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.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. + */ + +#ifndef IN_1_NSAP_22_H +#define IN_1_NSAP_22_H 1 + +/*! + * \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; + +#endif /* IN_1_NSAP_22_H */ 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..9f6a21a --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.c @@ -0,0 +1,379 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + isc_result_t result; + + 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); + RETERR(name_duporclone(&name, mctx, &px->map822)); + isc_region_consume(®ion, name_length(&px->map822)); + + dns_name_init(&px->mapx400, NULL); + result = name_duporclone(&name, mctx, &px->mapx400); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + px->mctx = mctx; + return (result); + +cleanup: + dns_name_free(&px->map822, mctx); + return (ISC_R_NOMEMORY); +} + +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(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..1fcbd36 --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.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. + */ + +#ifndef IN_1_PX_26_H +#define IN_1_PX_26_H 1 + +/*! + * \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; + +#endif /* IN_1_PX_26_H */ 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..6e24867 --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + RETERR(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); + + 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); + 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)); +} + +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..554180a --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.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. + */ + +#ifndef IN_1_SRV_33_H +#define IN_1_SRV_33_H 1 + +/*! + * \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; + +#endif /* IN_1_SRV_33_H */ 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..a87473f --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.c @@ -0,0 +1,1268 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 0 + +#define SVCB_MAN_KEY 0 +#define SVCB_ALPN_KEY 1 +#define SVCB_NO_DEFAULT_ALPN_KEY 2 + +/* + * 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; + +#ifndef ARRAYSIZE +/* defined in winnt.h */ +#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x)) +#endif + + for (i = 0; i < ARRAYSIZE(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 < ARRAYSIZE(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 < ARRAYSIZE(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 < ARRAYSIZE(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)); + + RETERR(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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +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..e4ddc64 --- /dev/null +++ b/lib/dns/rdata/in_1/svcb_64.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. + */ + +#ifndef IN_1_SVCB_64_H +#define IN_1_SVCB_64_H 1 + +/*! + * \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 *); + +#endif /* IN_1_SVCB_64_H */ 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..bf9f830 --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/* + * Redefine CHECK here so cppcheck "sees" the define. + */ +#ifndef CHECK +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) +#endif /* ifndef CHECK */ + +#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); +} + +#ifdef _WIN32 +#include +#include +#include +#endif /* ifdef _WIN32 */ + +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); + +#ifdef _WIN32 + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(2, 0); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + return (ISC_R_FAILURE); + } + } +#endif /* ifdef _WIN32 */ + + /* + * 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: +#ifdef _WIN32 + WSACleanup(); +#endif /* ifdef _WIN32 */ + + 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) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_wks); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + + 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..9a926b1 --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.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. + */ + +#ifndef IN_1_WKS_11_H +#define IN_1_WKS_11_H 1 + +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; + +#endif /* IN_1_WKS_11_H */ diff --git a/lib/dns/rdata/rdatastructpre.h b/lib/dns/rdata/rdatastructpre.h new file mode 100644 index 0000000..eb96ba8 --- /dev/null +++ b/lib/dns/rdata/rdatastructpre.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. + */ + +#ifndef DNS_RDATASTRUCT_H +#define DNS_RDATASTRUCT_H 1 + +#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..f242908 --- /dev/null +++ b/lib/dns/rdata/rdatastructsuf.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. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_RDATASTRUCT_H */ 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..adc4496 --- /dev/null +++ b/lib/dns/rdatalist_p.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. + */ + +#ifndef DNS_RDATALIST_P_H +#define DNS_RDATALIST_P_H + +/*! \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 + +#endif /* DNS_RDATALIST_P_H */ diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c new file mode 100644 index 0000000..221d7f8 --- /dev/null +++ b/lib/dns/rdataset.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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]); +} + +#define DNS_RDATASET_COUNT_UNDEFINED UINT32_MAX + +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 (ISC_LIKELY(want_random)) { + seed = isc_random32(); + } + + if (ISC_UNLIKELY(want_cyclic) && + (rdataset->count != DNS_RDATASET_COUNT_UNDEFINED)) + { + j = rdataset->count % count; + } + + for (i = 0; i < count; i++) { + if (ISC_LIKELY(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_copynf(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, + 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, 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..2783c1c --- /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 /* Required for HP/UX (and others?) */ +#include + +#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..2e21ca6 --- /dev/null +++ b/lib/dns/request.c @@ -0,0 +1,1545 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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_mutex_t lock; + isc_mem_t *mctx; + + /* locked */ + int32_t eref; + int32_t iref; + isc_timermgr_t *timermgr; + isc_socketmgr_t *socketmgr; + isc_taskmgr_t *taskmgr; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatchv4; + dns_dispatch_t *dispatchv6; + 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; + 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; + isc_timer_t *timer; + dns_requestmgr_t *requestmgr; + isc_buffer_t *tsig; + dns_tsigkey_t *tsigkey; + isc_event_t ctlevent; + bool canceling; /* ctlevent outstanding */ + isc_sockaddr_t destaddr; + unsigned int udpcount; + isc_dscp_t dscp; +}; + +#define DNS_REQUEST_F_CONNECTING 0x0001 +#define DNS_REQUEST_F_SENDING 0x0002 +#define DNS_REQUEST_F_CANCELED \ + 0x0004 /*%< ctlevent received, or otherwise \ + * synchronously canceled */ +#define DNS_REQUEST_F_TIMEDOUT 0x0008 /*%< canceled due to a timeout */ +#define DNS_REQUEST_F_TCP 0x0010 /*%< This request used TCP */ +#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) +#define DNS_REQUEST_TIMEDOUT(r) (((r)->flags & DNS_REQUEST_F_TIMEDOUT) != 0) + +/*** + *** Forward + ***/ + +static void +mgr_destroy(dns_requestmgr_t *requestmgr); +static void +mgr_shutdown(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_senddone(isc_task_t *task, isc_event_t *event); +static void +req_response(isc_task_t *task, isc_event_t *event); +static void +req_timeout(isc_task_t *task, isc_event_t *event); +static isc_socket_t * +req_getsocket(dns_request_t *request); +static void +req_connected(isc_task_t *task, isc_event_t *event); +static void +req_sendevent(dns_request_t *request, isc_result_t result); +static void +req_cancel(dns_request_t *request); +static void +req_destroy(dns_request_t *request); +static void +req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); +static void +do_cancel(isc_task_t *task, isc_event_t *event); + +/*** + *** Public + ***/ + +isc_result_t +dns_requestmgr_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, + isc_socketmgr_t *socketmgr, 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; + unsigned int dispattr; + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create"); + + REQUIRE(requestmgrp != NULL && *requestmgrp == NULL); + REQUIRE(timermgr != NULL); + REQUIRE(socketmgr != NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(dispatchmgr != NULL); + + if (dispatchv4 != NULL) { + dispattr = dns_dispatch_getattributes(dispatchv4); + REQUIRE((dispattr & DNS_DISPATCHATTR_UDP) != 0); + } + if (dispatchv6 != NULL) { + dispattr = dns_dispatch_getattributes(dispatchv6); + REQUIRE((dispattr & DNS_DISPATCHATTR_UDP) != 0); + } + + requestmgr = isc_mem_get(mctx, sizeof(*requestmgr)); + + isc_mutex_init(&requestmgr->lock); + + for (i = 0; i < DNS_REQUEST_NLOCKS; i++) { + isc_mutex_init(&requestmgr->locks[i]); + } + requestmgr->timermgr = timermgr; + requestmgr->socketmgr = socketmgr; + requestmgr->taskmgr = taskmgr; + requestmgr->dispatchmgr = dispatchmgr; + requestmgr->dispatchv4 = NULL; + if (dispatchv4 != NULL) { + dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4); + } + requestmgr->dispatchv6 = NULL; + if (dispatchv6 != NULL) { + dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6); + } + requestmgr->mctx = NULL; + isc_mem_attach(mctx, &requestmgr->mctx); + requestmgr->eref = 1; /* implicit attach */ + requestmgr->iref = 0; + ISC_LIST_INIT(requestmgr->whenshutdown); + ISC_LIST_INIT(requestmgr->requests); + requestmgr->exiting = false; + requestmgr->hash = 0; + 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 (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) { + REQUIRE(VALID_REQUESTMGR(requestmgr)); + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_shutdown: %p", requestmgr); + + LOCK(&requestmgr->lock); + mgr_shutdown(requestmgr); + UNLOCK(&requestmgr->lock); +} + +static void +mgr_shutdown(dns_requestmgr_t *requestmgr) { + dns_request_t *request; + + /* + * Caller holds lock. + */ + if (!requestmgr->exiting) { + requestmgr->exiting = true; + for (request = ISC_LIST_HEAD(requestmgr->requests); + request != NULL; request = ISC_LIST_NEXT(request, link)) + { + dns_request_cancel(request); + } + if (requestmgr->iref == 0) { + INSIST(ISC_LIST_EMPTY(requestmgr->requests)); + send_shutdown_events(requestmgr); + } + } +} + +static void +requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) { + /* + * Locked by caller. + */ + + REQUIRE(VALID_REQUESTMGR(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + REQUIRE(!source->exiting); + + source->iref++; + *targetp = source; + + req_log(ISC_LOG_DEBUG(3), "requestmgr_attach: %p: eref %d iref %d", + source, source->eref, source->iref); +} + +static void +requestmgr_detach(dns_requestmgr_t **requestmgrp) { + dns_requestmgr_t *requestmgr; + bool need_destroy = false; + + REQUIRE(requestmgrp != NULL); + requestmgr = *requestmgrp; + *requestmgrp = NULL; + REQUIRE(VALID_REQUESTMGR(requestmgr)); + + LOCK(&requestmgr->lock); + INSIST(requestmgr->iref > 0); + requestmgr->iref--; + + req_log(ISC_LOG_DEBUG(3), "requestmgr_detach: %p: eref %d iref %d", + requestmgr, requestmgr->eref, requestmgr->iref); + + if (requestmgr->iref == 0 && requestmgr->exiting) { + INSIST(ISC_LIST_HEAD(requestmgr->requests) == NULL); + send_shutdown_events(requestmgr); + if (requestmgr->eref == 0) { + need_destroy = true; + } + } + UNLOCK(&requestmgr->lock); + + if (need_destroy) { + mgr_destroy(requestmgr); + } +} + +void +dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) { + REQUIRE(VALID_REQUESTMGR(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + REQUIRE(!source->exiting); + + LOCK(&source->lock); + source->eref++; + *targetp = source; + UNLOCK(&source->lock); + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_attach: %p: eref %d iref %d", + source, source->eref, source->iref); +} + +void +dns_requestmgr_detach(dns_requestmgr_t **requestmgrp) { + dns_requestmgr_t *requestmgr; + bool need_destroy = false; + + REQUIRE(requestmgrp != NULL); + requestmgr = *requestmgrp; + *requestmgrp = NULL; + REQUIRE(VALID_REQUESTMGR(requestmgr)); + + LOCK(&requestmgr->lock); + INSIST(requestmgr->eref > 0); + requestmgr->eref--; + + req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_detach: %p: eref %d iref %d", + requestmgr, requestmgr->eref, requestmgr->iref); + + if (requestmgr->eref == 0 && requestmgr->iref == 0) { + INSIST(requestmgr->exiting && + ISC_LIST_HEAD(requestmgr->requests) == NULL); + need_destroy = true; + } + UNLOCK(&requestmgr->lock); + + if (need_destroy) { + mgr_destroy(requestmgr); + } +} + +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"); + + REQUIRE(requestmgr->eref == 0); + REQUIRE(requestmgr->iref == 0); + + 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); + } + 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 isc_result_t +req_send(dns_request_t *request, isc_task_t *task, + const isc_sockaddr_t *address) { + isc_region_t r; + isc_socket_t *sock; + isc_socketevent_t *sendevent; + isc_result_t result; + + req_log(ISC_LOG_DEBUG(3), "req_send: request %p", request); + + REQUIRE(VALID_REQUEST(request)); + sock = req_getsocket(request); + isc_buffer_usedregion(request->query, &r); + /* + * We could connect the socket when we are using an exclusive dispatch + * as we do in resolver.c, but we prefer implementation simplicity + * at this moment. + */ + sendevent = isc_socket_socketevent(request->mctx, sock, + ISC_SOCKEVENT_SENDDONE, req_senddone, + request); + if (sendevent == NULL) { + return (ISC_R_NOMEMORY); + } + if (request->dscp == -1) { + sendevent->attributes &= ~ISC_SOCKEVENTATTR_DSCP; + sendevent->dscp = 0; + } else { + sendevent->attributes |= ISC_SOCKEVENTATTR_DSCP; + sendevent->dscp = request->dscp; + } + + request->flags |= DNS_REQUEST_F_SENDING; + result = isc_socket_sendto2(sock, &r, task, address, NULL, sendevent, + 0); + INSIST(result == ISC_R_SUCCESS); + return (result); +} + +static isc_result_t +new_request(isc_mem_t *mctx, dns_request_t **requestp) { + dns_request_t *request; + + request = isc_mem_get(mctx, sizeof(*request)); + + /* + * Zero structure. + */ + request->magic = 0; + request->mctx = NULL; + request->flags = 0; + ISC_LINK_INIT(request, link); + request->query = NULL; + request->answer = NULL; + request->event = NULL; + request->dispatch = NULL; + request->dispentry = NULL; + request->timer = NULL; + request->requestmgr = NULL; + request->tsig = NULL; + request->tsigkey = NULL; + request->dscp = -1; + ISC_EVENT_INIT(&request->ctlevent, sizeof(request->ctlevent), 0, NULL, + DNS_EVENT_REQUESTCONTROL, do_cancel, request, NULL, NULL, + NULL); + request->canceling = false; + request->udpcount = 0; + + 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; + int match; + bool drop = false; + char netaddrstr[ISC_NETADDR_FORMATSIZE]; + + blackhole = dns_dispatchmgr_getblackhole(dispatchmgr); + if (blackhole != NULL) { + isc_netaddr_fromsockaddr(&netaddr, destaddr); + if (dns_acl_match(&netaddr, NULL, blackhole, NULL, &match, + NULL) == ISC_R_SUCCESS && + match > 0) + { + drop = true; + } + } + if (drop) { + isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr)); + req_log(ISC_LOG_DEBUG(10), "blackholed address %s", netaddrstr); + } + return (drop); +} + +static isc_result_t +create_tcp_dispatch(bool newtcp, bool share, dns_requestmgr_t *requestmgr, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, isc_dscp_t dscp, + bool *connected, dns_dispatch_t **dispatchp) { + isc_result_t result; + isc_socket_t *sock = NULL; + isc_sockaddr_t src; + unsigned int attrs; + isc_sockaddr_t bind_any; + + if (!newtcp && share) { + result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr, + srcaddr, connected, 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 %s TCP " + "connection to %s", + *connected ? "existing" : "pending", peer); + return (result); + } + } else if (!newtcp) { + result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr, + srcaddr, NULL, dispatchp); + if (result == ISC_R_SUCCESS) { + char peer[ISC_SOCKADDR_FORMATSIZE]; + + *connected = true; + isc_sockaddr_format(destaddr, peer, sizeof(peer)); + req_log(ISC_LOG_DEBUG(1), + "attached to existing TCP " + "connection to %s", + peer); + return (result); + } + } + + result = isc_socket_create(requestmgr->socketmgr, + isc_sockaddr_pf(destaddr), + isc_sockettype_tcp, &sock); + if (result != ISC_R_SUCCESS) { + return (result); + } +#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT + if (srcaddr == NULL) { + isc_sockaddr_anyofpf(&bind_any, isc_sockaddr_pf(destaddr)); + result = isc_socket_bind(sock, &bind_any, 0); + } else { + src = *srcaddr; + isc_sockaddr_setport(&src, 0); + result = isc_socket_bind(sock, &src, 0); + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } +#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */ + + attrs = 0; + attrs |= DNS_DISPATCHATTR_TCP; + if (isc_sockaddr_pf(destaddr) == AF_INET) { + attrs |= DNS_DISPATCHATTR_IPV4; + } else { + attrs |= DNS_DISPATCHATTR_IPV6; + } + attrs |= DNS_DISPATCHATTR_MAKEQUERY; + + isc_socket_dscp(sock, dscp); + result = dns_dispatch_createtcp( + requestmgr->dispatchmgr, sock, requestmgr->taskmgr, srcaddr, + destaddr, 4096, 32768, 32768, 16411, 16433, attrs, dispatchp); +cleanup: + isc_socket_detach(&sock); + return (result); +} + +static isc_result_t +find_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; + unsigned int attrs, attrmask; + + 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); + } + attrs = 0; + attrs |= DNS_DISPATCHATTR_UDP; + switch (isc_sockaddr_pf(srcaddr)) { + case PF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + break; + + case PF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + break; + + default: + return (ISC_R_NOTIMPLEMENTED); + } + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + return (dns_dispatch_getudp(requestmgr->dispatchmgr, + requestmgr->socketmgr, requestmgr->taskmgr, + srcaddr, 4096, 32768, 32768, 16411, 16433, + attrs, attrmask, dispatchp)); +} + +static isc_result_t +get_dispatch(bool tcp, bool newtcp, bool share, dns_requestmgr_t *requestmgr, + const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr, + isc_dscp_t dscp, bool *connected, dns_dispatch_t **dispatchp) { + isc_result_t result; + + if (tcp) { + result = create_tcp_dispatch(newtcp, share, requestmgr, srcaddr, + destaddr, dscp, connected, + dispatchp); + } else { + result = find_udp_dispatch(requestmgr, srcaddr, destaddr, + dispatchp); + } + return (result); +} + +static isc_result_t +set_timer(isc_timer_t *timer, unsigned int timeout, unsigned int udpresend) { + isc_time_t expires; + isc_interval_t interval; + isc_result_t result; + isc_timertype_t timertype; + + isc_interval_set(&interval, timeout, 0); + result = isc_time_nowplusinterval(&expires, &interval); + isc_interval_set(&interval, udpresend, 0); + + timertype = udpresend != 0 ? isc_timertype_limited : isc_timertype_once; + if (result == ISC_R_SUCCESS) { + result = isc_timer_reset(timer, timertype, &expires, &interval, + false); + } + 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, isc_dscp_t dscp, + 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_task_t *tclone = NULL; + isc_socket_t *sock = NULL; + isc_result_t result; + isc_mem_t *mctx; + dns_messageid_t id; + bool tcp = false; + bool newtcp = false; + bool share = false; + isc_region_t r; + bool connected = false; + 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); + 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 (isblackholed(requestmgr->dispatchmgr, destaddr)) { + return (DNS_R_BLACKHOLED); + } + + request = NULL; + result = new_request(mctx, &request); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (udptimeout == 0 && udpretries != 0) { + udptimeout = timeout / (udpretries + 1); + if (udptimeout == 0) { + udptimeout = 1; + } + } + request->udpcount = udpretries; + request->dscp = dscp; + + /* + * Create timer now. We will set it below once. + */ + result = isc_timer_create(requestmgr->timermgr, isc_timertype_inactive, + NULL, NULL, task, req_timeout, request, + &request->timer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + request->event = (dns_requestevent_t *)isc_event_allocate( + mctx, task, DNS_EVENT_REQUESTDONE, action, arg, + sizeof(dns_requestevent_t)); + isc_task_attach(task, &tclone); + 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; + } + share = (options & DNS_REQUESTOPT_SHARE); + +again: + result = get_dispatch(tcp, newtcp, share, requestmgr, srcaddr, destaddr, + dscp, &connected, &request->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if ((options & DNS_REQUESTOPT_FIXEDID) != 0) { + id = (r.base[0] << 8) | r.base[1]; + dispopt |= DNS_DISPATCHOPT_FIXEDID; + } + + result = dns_dispatch_addresponse( + request->dispatch, dispopt, destaddr, task, req_response, + request, &id, &request->dispentry, requestmgr->socketmgr); + if (result != ISC_R_SUCCESS) { + if ((options & DNS_REQUESTOPT_FIXEDID) != 0 && !newtcp) { + newtcp = true; + connected = false; + dns_dispatch_detach(&request->dispatch); + goto again; + } + goto cleanup; + } + + sock = req_getsocket(request); + INSIST(sock != NULL); + + isc_buffer_allocate(mctx, &request->query, r.length + (tcp ? 2 : 0)); + if (tcp) { + isc_buffer_putuint16(request->query, (uint16_t)r.length); + } + result = isc_buffer_copyregion(request->query, &r); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Add message ID. */ + isc_buffer_usedregion(request->query, &r); + if (tcp) { + isc_region_consume(&r, 2); + } + r.base[0] = (id >> 8) & 0xff; + r.base[1] = id & 0xff; + + LOCK(&requestmgr->lock); + if (requestmgr->exiting) { + UNLOCK(&requestmgr->lock); + result = ISC_R_SHUTTINGDOWN; + goto cleanup; + } + requestmgr_attach(requestmgr, &request->requestmgr); + request->hash = mgr_gethash(requestmgr); + ISC_LIST_APPEND(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + + result = set_timer(request->timer, timeout, tcp ? 0 : udptimeout); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + + request->destaddr = *destaddr; + if (tcp && !connected) { + result = isc_socket_connect(sock, destaddr, task, req_connected, + request); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + request->flags |= DNS_REQUEST_F_CONNECTING | DNS_REQUEST_F_TCP; + } else { + result = req_send(request, task, connected ? NULL : destaddr); + 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); + +cleanup: + if (tclone != NULL) { + isc_task_detach(&tclone); + } + req_destroy(request); + req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: failed %s", + dns_result_totext(result)); + return (result); +} + +isc_result_t +dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message, + const isc_sockaddr_t *address, unsigned int options, + dns_tsigkey_t *key, unsigned int timeout, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp) { + return (dns_request_createvia(requestmgr, message, NULL, address, -1, + options, key, timeout, 0, 0, task, action, + arg, requestp)); +} + +isc_result_t +dns_request_createvia(dns_requestmgr_t *requestmgr, dns_message_t *message, + const isc_sockaddr_t *srcaddr, + const isc_sockaddr_t *destaddr, isc_dscp_t dscp, + 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_task_t *tclone = NULL; + isc_socket_t *sock = NULL; + isc_result_t result; + isc_mem_t *mctx; + dns_messageid_t id; + bool tcp; + bool share; + bool settsigkey = true; + 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); + + mctx = requestmgr->mctx; + + req_log(ISC_LOG_DEBUG(3), "dns_request_createvia"); + + if (srcaddr != NULL && + isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr)) + { + return (ISC_R_FAMILYMISMATCH); + } + + if (isblackholed(requestmgr->dispatchmgr, destaddr)) { + return (DNS_R_BLACKHOLED); + } + + request = NULL; + result = new_request(mctx, &request); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (udptimeout == 0 && udpretries != 0) { + udptimeout = timeout / (udpretries + 1); + if (udptimeout == 0) { + udptimeout = 1; + } + } + request->udpcount = udpretries; + request->dscp = dscp; + + /* + * Create timer now. We will set it below once. + */ + result = isc_timer_create(requestmgr->timermgr, isc_timertype_inactive, + NULL, NULL, task, req_timeout, request, + &request->timer); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + request->event = (dns_requestevent_t *)isc_event_allocate( + mctx, task, DNS_EVENT_REQUESTDONE, action, arg, + sizeof(dns_requestevent_t)); + isc_task_attach(task, &tclone); + request->event->ev_sender = task; + request->event->request = request; + request->event->result = ISC_R_FAILURE; + if (key != NULL) { + dns_tsigkey_attach(key, &request->tsigkey); + } + +use_tcp: + tcp = ((options & DNS_REQUESTOPT_TCP) != 0); + share = ((options & DNS_REQUESTOPT_SHARE) != 0); + result = get_dispatch(tcp, false, share, requestmgr, srcaddr, destaddr, + dscp, &connected, &request->dispatch); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_dispatch_addresponse( + request->dispatch, 0, destaddr, task, req_response, request, + &id, &request->dispentry, requestmgr->socketmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + sock = req_getsocket(request); + INSIST(sock != NULL); + + message->id = id; + if (settsigkey) { + result = dns_message_settsigkey(message, request->tsigkey); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = req_render(message, &request->query, options, mctx); + if (result == DNS_R_USETCP && (options & DNS_REQUESTOPT_TCP) == 0) { + /* + * Try again using TCP. + */ + dns_message_renderreset(message); + dns_dispatch_removeresponse(&request->dispentry, NULL); + dns_dispatch_detach(&request->dispatch); + sock = NULL; + options |= DNS_REQUESTOPT_TCP; + settsigkey = false; + goto use_tcp; + } + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_getquerytsig(message, mctx, &request->tsig); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + LOCK(&requestmgr->lock); + if (requestmgr->exiting) { + UNLOCK(&requestmgr->lock); + result = ISC_R_SHUTTINGDOWN; + goto cleanup; + } + requestmgr_attach(requestmgr, &request->requestmgr); + request->hash = mgr_gethash(requestmgr); + ISC_LIST_APPEND(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + + result = set_timer(request->timer, timeout, tcp ? 0 : udptimeout); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + + request->destaddr = *destaddr; + if (tcp && !connected) { + result = isc_socket_connect(sock, destaddr, task, req_connected, + request); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + request->flags |= DNS_REQUEST_F_CONNECTING | DNS_REQUEST_F_TCP; + } else { + result = req_send(request, task, connected ? NULL : destaddr); + if (result != ISC_R_SUCCESS) { + goto unlink; + } + } + + req_log(ISC_LOG_DEBUG(3), "dns_request_createvia: request %p", request); + *requestp = request; + return (ISC_R_SUCCESS); + +unlink: + LOCK(&requestmgr->lock); + ISC_LIST_UNLINK(requestmgr->requests, request, link); + UNLOCK(&requestmgr->lock); + +cleanup: + if (tclone != NULL) { + isc_task_detach(&tclone); + } + req_destroy(request); + req_log(ISC_LOG_DEBUG(3), "dns_request_createvia: failed %s", + dns_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; + bool tcp = false; + 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) { + tcp = true; + } else if (r.length > 512) { + result = DNS_R_USETCP; + goto cleanup; + } + isc_buffer_allocate(mctx, &buf2, r.length + (tcp ? 2 : 0)); + if (tcp) { + isc_buffer_putuint16(buf2, (uint16_t)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); +} + +/* + * If this request is no longer waiting for events, + * send the completion event. This will ultimately + * cause the request to be destroyed. + * + * Requires: + * 'request' is locked by the caller. + */ +static void +send_if_done(dns_request_t *request, isc_result_t result) { + if (request->event != NULL && !request->canceling) { + req_sendevent(request, result); + } +} + +/* + * Handle the control event. + */ +static void +do_cancel(isc_task_t *task, isc_event_t *event) { + dns_request_t *request = event->ev_arg; + UNUSED(task); + INSIST(event->ev_type == DNS_EVENT_REQUESTCONTROL); + LOCK(&request->requestmgr->locks[request->hash]); + request->canceling = false; + if (!DNS_REQUEST_CANCELED(request)) { + req_cancel(request); + } + send_if_done(request, ISC_R_CANCELED); + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +void +dns_request_cancel(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "dns_request_cancel: request %p", request); + + REQUIRE(VALID_REQUEST(request)); + + LOCK(&request->requestmgr->locks[request->hash]); + if (!request->canceling && !DNS_REQUEST_CANCELED(request)) { + isc_event_t *ev = &request->ctlevent; + isc_task_send(request->event->ev_sender, &ev); + request->canceling = true; + } + 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); + INSIST(!DNS_REQUEST_CONNECTING(request)); + INSIST(!DNS_REQUEST_SENDING(request)); + UNLOCK(&request->requestmgr->locks[request->hash]); + UNLOCK(&request->requestmgr->lock); + + /* + * These should have been cleaned up by req_cancel() before + * the completion event was sent. + */ + INSIST(!ISC_LINK_LINKED(request, link)); + INSIST(request->dispentry == NULL); + INSIST(request->dispatch == NULL); + INSIST(request->timer == NULL); + + req_destroy(request); +} + +/*** + *** Private: request. + ***/ + +static isc_socket_t * +req_getsocket(dns_request_t *request) { + unsigned int dispattr; + isc_socket_t *sock; + + dispattr = dns_dispatch_getattributes(request->dispatch); + if ((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + INSIST(request->dispentry != NULL); + sock = dns_dispatch_getentrysocket(request->dispentry); + } else { + sock = dns_dispatch_getsocket(request->dispatch); + } + + return (sock); +} + +static void +req_connected(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + isc_result_t result; + dns_request_t *request = event->ev_arg; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); + REQUIRE(VALID_REQUEST(request)); + REQUIRE(DNS_REQUEST_CONNECTING(request)); + + req_log(ISC_LOG_DEBUG(3), "req_connected: request %p", request); + + result = sevent->result; + isc_event_free(&event); + + LOCK(&request->requestmgr->locks[request->hash]); + request->flags &= ~DNS_REQUEST_F_CONNECTING; + + if (DNS_REQUEST_CANCELED(request)) { + /* + * Send delayed event. + */ + if (DNS_REQUEST_TIMEDOUT(request)) { + send_if_done(request, ISC_R_TIMEDOUT); + } else { + send_if_done(request, ISC_R_CANCELED); + } + } else { + dns_dispatch_starttcp(request->dispatch); + if (result == ISC_R_SUCCESS) { + result = req_send(request, task, NULL); + } + + if (result != ISC_R_SUCCESS) { + req_cancel(request); + send_if_done(request, ISC_R_CANCELED); + } + } + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +static void +req_senddone(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + dns_request_t *request = event->ev_arg; + isc_result_t result = sevent->result; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE); + REQUIRE(VALID_REQUEST(request)); + REQUIRE(DNS_REQUEST_SENDING(request)); + + req_log(ISC_LOG_DEBUG(3), "req_senddone: request %p", request); + + UNUSED(task); + + isc_event_free(&event); + + LOCK(&request->requestmgr->locks[request->hash]); + request->flags &= ~DNS_REQUEST_F_SENDING; + + if (DNS_REQUEST_CANCELED(request)) { + /* + * Send delayed event. + */ + if (DNS_REQUEST_TIMEDOUT(request)) { + send_if_done(request, ISC_R_TIMEDOUT); + } else { + send_if_done(request, ISC_R_CANCELED); + } + } else if (result != ISC_R_SUCCESS) { + req_cancel(request); + send_if_done(request, ISC_R_CANCELED); + } + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +static void +req_response(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_request_t *request = event->ev_arg; + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + isc_region_t r; + + REQUIRE(VALID_REQUEST(request)); + REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + + UNUSED(task); + + req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request, + dns_result_totext(devent->result)); + + LOCK(&request->requestmgr->locks[request->hash]); + result = devent->result; + if (result != ISC_R_SUCCESS) { + goto done; + } + + /* + * Copy buffer to request. + */ + isc_buffer_usedregion(&devent->buffer, &r); + isc_buffer_allocate(request->mctx, &request->answer, r.length); + result = isc_buffer_copyregion(request->answer, &r); + if (result != ISC_R_SUCCESS) { + isc_buffer_free(&request->answer); + } +done: + /* + * Cleanup. + */ + dns_dispatch_removeresponse(&request->dispentry, &devent); + req_cancel(request); + /* + * Send completion event. + */ + send_if_done(request, result); + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +static void +req_timeout(isc_task_t *task, isc_event_t *event) { + dns_request_t *request = event->ev_arg; + isc_result_t result; + isc_eventtype_t ev_type = event->ev_type; + + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "req_timeout: request %p", request); + + UNUSED(task); + + isc_event_free(&event); + + LOCK(&request->requestmgr->locks[request->hash]); + if (ev_type == ISC_TIMEREVENT_TICK && request->udpcount-- != 0) { + if (!DNS_REQUEST_SENDING(request)) { + result = req_send(request, task, &request->destaddr); + if (result != ISC_R_SUCCESS) { + req_cancel(request); + send_if_done(request, result); + } + } + } else { + request->flags |= DNS_REQUEST_F_TIMEDOUT; + req_cancel(request); + send_if_done(request, ISC_R_TIMEDOUT); + } + UNLOCK(&request->requestmgr->locks[request->hash]); +} + +static void +req_sendevent(dns_request_t *request, isc_result_t result) { + isc_task_t *task; + + REQUIRE(VALID_REQUEST(request)); + + 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_destroy(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "req_destroy: request %p", request); + + 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_removeresponse(&request->dispentry, NULL); + } + if (request->dispatch != NULL) { + dns_dispatch_detach(&request->dispatch); + } + if (request->timer != NULL) { + isc_timer_destroy(&request->timer); + } + if (request->tsig != NULL) { + isc_buffer_free(&request->tsig); + } + if (request->tsigkey != NULL) { + dns_tsigkey_detach(&request->tsigkey); + } + if (request->requestmgr != NULL) { + requestmgr_detach(&request->requestmgr); + } + isc_mem_putanddetach(&request->mctx, request, sizeof(*request)); +} + +/* + * Stop the current request. Must be called from the request's task. + */ +static void +req_cancel(dns_request_t *request) { + isc_socket_t *sock; + unsigned int dispattr; + + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "req_cancel: request %p", request); + + /* + * Lock held by caller. + */ + request->flags |= DNS_REQUEST_F_CANCELED; + + if (request->timer != NULL) { + isc_timer_destroy(&request->timer); + } + dispattr = dns_dispatch_getattributes(request->dispatch); + sock = NULL; + if (DNS_REQUEST_CONNECTING(request) || DNS_REQUEST_SENDING(request)) { + if ((dispattr & DNS_DISPATCHATTR_EXCLUSIVE) != 0) { + if (request->dispentry != NULL) { + sock = dns_dispatch_getentrysocket( + request->dispentry); + } + } else { + sock = dns_dispatch_getsocket(request->dispatch); + } + if (DNS_REQUEST_CONNECTING(request) && sock != NULL) { + isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_CONNECT); + } + if (DNS_REQUEST_SENDING(request) && sock != NULL) { + isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND); + } + } + if (request->dispentry != NULL) { + dns_dispatch_removeresponse(&request->dispentry, NULL); + } + dns_dispatch_detach(&request->dispatch); +} + +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..a97aaa8 --- /dev/null +++ b/lib/dns/resolver.c @@ -0,0 +1,12095 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +#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(m); \ + } while (0) +#define FCTXTRACE2(m1, m2) \ + do { \ + UNUSED(m1); \ + UNUSED(m2); \ + } while (0) +#define FCTXTRACE3(m1, res) \ + do { \ + UNUSED(m1); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE4(m1, m2, res) \ + do { \ + UNUSED(m1); \ + UNUSED(m2); \ + UNUSED(res); \ + } while (0) +#define FCTXTRACE5(m1, m2, v) \ + do { \ + 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 */ + +#define US_PER_SEC 1000000U +#define US_PER_MSEC 1000U +/* + * 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_MSEC) + +/* + * 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 + +/* Number of hash buckets for zone counters */ +#ifndef RES_DOMAIN_BUCKETS +#define RES_DOMAIN_BUCKETS 523 +#endif /* ifndef RES_DOMAIN_BUCKETS */ +#define RES_NOBUCKET 0xffffffff + +/*% + * Maximum EDNS0 input packet size. + */ +#define RECV_BUFFER_SIZE 4096 /* XXXRTH Constant. */ + +/*% + * 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; + fetchctx_t *fctx; + dns_message_t *rmessage; + isc_mem_t *mctx; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatch; + bool exclusivesocket; + dns_adbaddrinfo_t *addrinfo; + isc_socket_t *tcpsocket; + 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; + isc_socketevent_t sendevent; + isc_dscp_t dscp; + int ednsversion; + unsigned int options; + isc_sockeventattr_t attributes; + unsigned int sends; + unsigned int connects; + 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; + +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_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; + + /* Atomic */ + isc_refcount_t references; + + /*% Locked by appropriate bucket lock. */ + fetchstate state; + 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_name_t domain; + dns_rdataset_t nameservers; + atomic_uint_fast32_t attributes; + isc_timer_t *timer; + isc_timer_t *timer_try_stale; + isc_time_t expires; + isc_time_t expires_try_stale; + 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_LIST(struct tried) edns512; + 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_name_t qminname; + dns_rdatatype_t qmintype; + dns_fetch_t *qminfetch; + dns_rdataset_t qminrrset; + dns_name_t qmindcname; + dns_fixedname_t fwdfname; + dns_name_t *fwdname; + + /*% + * The number of events we're waiting for. + */ + unsigned int 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_name_t nsname; + dns_fetch_t *nsfetch; + dns_rdataset_t nsrrset; + + /*% + * Number of queries that reference this context. + */ + unsigned int nqueries; /* Bucket lock. */ + + /*% + * The reason to print when logging a successful + * response to a query. + */ + const char *reason; + + /*% + * 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; + dns_messageid_t id; + 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; + 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; + isc_mem_t *mctx; +} fctxbucket_t; + +typedef struct fctxcount fctxcount_t; +struct fctxcount { + dns_fixedname_t fdname; + 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_mem_t *mctx; + 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_socketmgr_t *socketmgr; + 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; + bool exclusivev4; + dns_dispatchset_t *dispatches6; + isc_dscp_t querydscp4; + isc_dscp_t querydscp6; + bool exclusivev6; + unsigned int nbuckets; + fctxbucket_t *buckets; + zonebucket_t *dbuckets; + uint32_t lame_ttl; + ISC_LIST(alternate_t) alternates; + uint16_t udpsize; +#if USE_ALGLOCK + isc_rwlock_t alglock; +#endif /* if USE_ALGLOCK */ + dns_rbt_t *algorithms; + dns_rbt_t *digests; +#if USE_MBSLOCK + isc_rwlock_t mbslock; +#endif /* if USE_MBSLOCK */ + 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; + unsigned int 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. These must not conflict with DNS_FETCHOPT_NOEDNS0 + * (0x008) which we also use as an addrinfo flag. + */ +#define FCTX_ADDRINFO_MARK 0x00001 +#define FCTX_ADDRINFO_FORWARDER 0x01000 +#define FCTX_ADDRINFO_EDNSOK 0x04000 +#define FCTX_ADDRINFO_NOCOOKIE 0x08000 +#define FCTX_ADDRINFO_BADCOOKIE 0x10000 +#define FCTX_ADDRINFO_DUALSTACK 0x20000 + +#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() { + 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 unsigned char underscore_data[] = "\001_"; +static unsigned char underscore_offsets[] = { 0 }; +static const dns_name_t underscore_name = + DNS_NAME_INITNONABSOLUTE(underscore_data, underscore_offsets); + +static void +destroy(dns_resolver_t *res); +static void +empty_bucket(dns_resolver_t *res); +static isc_result_t +resquery_send(resquery_t *query); +static void +resquery_response(isc_task_t *task, isc_event_t *event); +static void +resquery_connected(isc_task_t *task, isc_event_t *event); +static void +fctx_try(fetchctx_t *fctx, bool retrying, bool badcache); +static isc_result_t +fctx_minimize_qname(fetchctx_t *fctx); +static void +fctx_destroy(fetchctx_t *fctx); +static bool +fctx_unlink(fetchctx_t *fctx); +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 +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); +static void +fctx_increference(fetchctx_t *fctx); +static bool +fctx_decreference(fetchctx_t *fctx); +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 { + isc_task_t *task; + dns_dispatchevent_t *devent; + resquery_t *query; + fetchctx_t *fctx; + isc_result_t result; + 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(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query, + fetchctx_t *fctx, 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 void +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 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 = NULL; + isc_result_t result; + + if (SHUTTINGDOWN(fctx)) { + return (ISC_R_SHUTTINGDOWN); + } + + valarg = isc_mem_get(fctx->mctx, sizeof(*valarg)); + *valarg = (dns_valarg_t){ .fctx = fctx, .addrinfo = addrinfo }; + + 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); + 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); + 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 isc_result_t +fctx_starttimer(fetchctx_t *fctx) { + /* + * Start the lifetime timer for fctx. + * + * This is also used for stopping the idle timer; in that + * case we must purge events already posted to ensure that + * no further idle events are delivered. + */ + return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, + NULL, true)); +} + +static isc_result_t +fctx_starttimer_trystale(fetchctx_t *fctx) { + /* + * Start the stale-answer-client-timeout timer for fctx. + */ + + return (isc_timer_reset(fctx->timer_try_stale, isc_timertype_once, + &fctx->expires_try_stale, 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(__FILE__, __LINE__, "isc_timer_reset(): %s", + isc_result_totext(result)); + } +} + +static void +fctx_stoptimer_trystale(fetchctx_t *fctx) { + isc_result_t result; + + if (fctx->timer_try_stale != NULL) { + result = isc_timer_reset(fctx->timer_try_stale, + isc_timertype_inactive, NULL, NULL, + true); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_reset(): %s", + isc_result_totext(result)); + } + } +} + +static isc_result_t +fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) { + /* + * Start the idle timer for fctx. The lifetime timer continues + * to be in effect. + */ + return (isc_timer_reset(fctx->timer, isc_timertype_once, &fctx->expires, + interval, false)); +} + +/* + * Stopping the idle timer is equivalent to calling fctx_starttimer(), but + * we use fctx_stopidletimer for readability in the code below. + */ +#define fctx_stopidletimer fctx_starttimer + +static void +resquery_destroy(resquery_t **queryp) { + dns_resolver_t *res; + bool empty; + resquery_t *query; + fetchctx_t *fctx; + unsigned int bucket; + + REQUIRE(queryp != NULL); + query = *queryp; + *queryp = NULL; + REQUIRE(!ISC_LINK_LINKED(query, link)); + + INSIST(query->tcpsocket == NULL); + + fctx = query->fctx; + res = fctx->res; + bucket = fctx->bucketnum; + + LOCK(&res->buckets[bucket].lock); + fctx->nqueries--; + empty = fctx_decreference(query->fctx); + UNLOCK(&res->buckets[bucket].lock); + + if (query->rmessage != NULL) { + dns_message_detach(&query->rmessage); + } + + query->magic = 0; + isc_mem_put(query->mctx, query, sizeof(*query)); + + if (empty) { + empty_bucket(res); + } +} + +/*% + * 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, query->udpsize); + } else { + dns_adb_timeout(fctx->adb, query->addrinfo); + } +} + +static void +fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp, + isc_time_t *finish, bool no_response, bool age_untried) { + fetchctx_t *fctx; + resquery_t *query; + unsigned int rtt, rttms; + unsigned int factor; + dns_adbfind_t *find; + dns_adbaddrinfo_t *addrinfo; + isc_socket_t *sock; + isc_stdtime_t now; + + query = *queryp; + fctx = query->fctx; + + FCTXTRACE("cancelquery"); + + REQUIRE(!RESQUERY_CANCELED(query)); + + 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 / 1000; + 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 socket events. If they exist, cancel + * them and let the event handlers finish the cleanup. The resolver + * only needs to worry about managing the connect and send events; + * the dispatcher manages the recv events. + */ + if (RESQUERY_CONNECTING(query)) { + /* + * Cancel the connect. + */ + if (query->tcpsocket != NULL) { + isc_socket_cancel(query->tcpsocket, NULL, + ISC_SOCKCANCEL_CONNECT); + } else if (query->dispentry != NULL) { + INSIST(query->exclusivesocket); + sock = dns_dispatch_getentrysocket(query->dispentry); + if (sock != NULL) { + isc_socket_cancel(sock, NULL, + ISC_SOCKCANCEL_CONNECT); + } + } + } + if (RESQUERY_SENDING(query)) { + /* + * Cancel the pending send. + */ + if (query->exclusivesocket && query->dispentry != NULL) { + sock = dns_dispatch_getentrysocket(query->dispentry); + } else { + sock = dns_dispatch_getsocket(query->dispatch); + } + if (sock != NULL) { + isc_socket_cancel(sock, NULL, ISC_SOCKCANCEL_SEND); + } + } + + if (query->dispentry != NULL) { + dns_dispatch_removeresponse(&query->dispentry, deventp); + } + + 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->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + + if (!(RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query))) { + /* + * It's safe to destroy the query now. + */ + resquery_destroy(&query); + } +} + +static void +fctx_cancelqueries(fetchctx_t *fctx, bool no_response, bool age_untried) { + resquery_t *query, *next_query; + + FCTXTRACE("cancelqueries"); + + for (query = ISC_LIST_HEAD(fctx->queries); query != NULL; + query = next_query) + { + next_query = ISC_LIST_NEXT(query, link); + fctx_cancelquery(&query, NULL, NULL, no_response, age_untried); + } +} + +static void +fctx_cleanupfinds(fetchctx_t *fctx) { + dns_adbfind_t *find, *next_find; + + 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->find = NULL; +} + +static void +fctx_cleanupaltfinds(fetchctx_t *fctx) { + dns_adbfind_t *find, *next_find; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + 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->altfind = NULL; +} + +static void +fctx_cleanupforwaddrs(fetchctx_t *fctx) { + dns_adbaddrinfo_t *addr, *next_addr; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + 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); + } +} + +static void +fctx_cleanupaltaddrs(fetchctx_t *fctx) { + dns_adbaddrinfo_t *addr, *next_addr; + + REQUIRE(ISC_LIST_EMPTY(fctx->queries)); + + 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_stopqueries(fetchctx_t *fctx, bool no_response, bool age_untried) { + FCTXTRACE("stopqueries"); + fctx_cancelqueries(fctx, no_response, age_untried); + fctx_stoptimer(fctx); + fctx_stoptimer_trystale(fctx); +} + +static void +fctx_cleanupall(fetchctx_t *fctx) { + fctx_cleanupfinds(fctx); + fctx_cleanupaltfinds(fctx); + fctx_cleanupforwaddrs(fctx); + fctx_cleanupaltaddrs(fctx); +} + +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; + fctxcount_t *counter; + unsigned int bucketnum; + + REQUIRE(fctx != NULL); + REQUIRE(fctx->res != NULL); + + INSIST(fctx->dbucketnum == RES_NOBUCKET); + bucketnum = dns_name_fullhash(&fctx->domain, false) % + RES_DOMAIN_BUCKETS; + + dbucket = &fctx->res->dbuckets[bucketnum]; + + 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(dbucket->mctx, sizeof(fctxcount_t)); + { + ISC_LINK_INIT(counter, link); + counter->count = 1; + counter->logged = 0; + counter->allowed = 1; + counter->dropped = 0; + counter->domain = + dns_fixedname_initname(&counter->fdname); + dns_name_copynf(&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 = bucketnum; + } + + return (result); +} + +static void +fcount_decr(fetchctx_t *fctx) { + zonebucket_t *dbucket; + fctxcount_t *counter; + + 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(dbucket->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); + 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); + } + + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event)); + count++; + } + + 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 +log_edns(fetchctx_t *fctx) { + char domainbuf[DNS_NAME_FORMATSIZE]; + + if (fctx->reason == NULL) { + return; + } + + /* + * We do not know if fctx->domain is the actual domain the record + * lives in or a parent domain so we have a '?' after it. + */ + dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_EDNS_DISABLED, + DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + "success resolving '%s' (in '%s'?) after %s", fctx->info, + domainbuf, fctx->reason); +} + +static void +fctx_done(fetchctx_t *fctx, isc_result_t result, int line) { + dns_resolver_t *res; + bool no_response = false; + bool age_untried = false; + + REQUIRE(line >= 0); + + FCTXTRACE("done"); + + res = fctx->res; + + if (result == ISC_R_SUCCESS) { + /*% + * Log any deferred EDNS timeout messages. + */ + log_edns(fctx); + no_response = true; + 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)); + } + } else if (result == ISC_R_TIMEDOUT) { + age_untried = true; + } + + fctx->qmin_warning = ISC_R_SUCCESS; + fctx->reason = NULL; + + fctx_stopqueries(fctx, no_response, age_untried); + + LOCK(&res->buckets[fctx->bucketnum].lock); + + fctx->state = fetchstate_done; + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + fctx_sendevents(fctx, result, line); + + UNLOCK(&res->buckets[fctx->bucketnum].lock); +} + +static void +process_sendevent(resquery_t *query, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + bool destroy_query = false; + bool retry = false; + isc_result_t result; + fetchctx_t *fctx; + + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + if (query->sends == 0 && query->connects == 0) { + /* + * This query was canceled while the + * isc_socket_sendto/connect() was in progress. + */ + if (query->tcpsocket != NULL) { + isc_socket_detach(&query->tcpsocket); + } + destroy_query = true; + } + } else { + switch (sevent->result) { + case ISC_R_SUCCESS: + break; + + case ISC_R_HOSTUNREACH: + case ISC_R_NETUNREACH: + case ISC_R_NOPERM: + case ISC_R_ADDRNOTAVAIL: + case ISC_R_CONNREFUSED: + FCTXTRACE3("query canceled in sendevent(): " + "no route to host; no response", + sevent->result); + + /* + * No route to remote. + */ + add_bad(fctx, query->rmessage, query->addrinfo, + sevent->result, badns_unreachable); + fctx_cancelquery(&query, NULL, NULL, true, false); + retry = true; + break; + + default: + FCTXTRACE3("query canceled in sendevent() due to " + "unexpected event result; responding", + sevent->result); + + fctx_cancelquery(&query, NULL, NULL, false, false); + break; + } + } + + if (event->ev_type == ISC_SOCKEVENT_CONNECT) { + isc_event_free(&event); + } + + if (retry) { + /* + * Behave as if the idle timer has expired. For TCP + * this may not actually reflect the latest timer. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, true, false); + } + } + + if (destroy_query) { + resquery_destroy(&query); + } +} + +static void +resquery_udpconnected(isc_task_t *task, isc_event_t *event) { + resquery_t *query = event->ev_arg; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); + + QTRACE("udpconnected"); + + UNUSED(task); + + INSIST(RESQUERY_CONNECTING(query)); + + query->connects--; + + process_sendevent(query, event); +} + +static void +resquery_senddone(isc_task_t *task, isc_event_t *event) { + resquery_t *query = event->ev_arg; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE); + + QTRACE("senddone"); + + /* + * XXXRTH + * + * Currently we don't wait for the senddone event before retrying + * a query. This means that if we get really behind, we may end + * up doing extra work! + */ + + UNUSED(task); + + INSIST(RESQUERY_SENDING(query)); + + query->sends--; + + process_sendevent(query, event); +} + +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; + unsigned int us; + + us = fctx->res->retryinterval * 1000; + /* + * 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 ever wait for more than 10 seconds. + */ + 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 * 1000); +} + +static isc_result_t +fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + unsigned int options) { + dns_resolver_t *res; + isc_task_t *task; + isc_result_t result; + resquery_t *query; + isc_sockaddr_t addr; + bool have_addr = false; + unsigned int srtt; + isc_dscp_t dscp = -1; + unsigned int bucketnum; + + FCTXTRACE("query"); + + res = fctx->res; + task = res->buckets[fctx->bucketnum].task; + + 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 += 1000000; + } + + /* + * A forwarder needs to make multiple queries. Give it at least + * a second to do these in. + */ + if (ISFORWARDER(addrinfo) && srtt < 1000000) { + srtt = 1000000; + } + + fctx_setretryinterval(fctx, srtt); + result = fctx_startidletimer(fctx, &fctx->interval); + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(ISC_LIST_EMPTY(fctx->validators)); + + query = isc_mem_get(fctx->mctx, sizeof(*query)); + query->rmessage = NULL; + dns_message_create(fctx->mctx, DNS_MESSAGE_INTENTPARSE, + &query->rmessage); + query->mctx = fctx->mctx; + query->options = options; + query->attributes = 0; + query->sends = 0; + query->connects = 0; + query->dscp = addrinfo->dscp; + query->udpsize = 0; + /* + * Note that the caller MUST guarantee that 'addrinfo' will remain + * valid until this query is canceled. + */ + query->addrinfo = addrinfo; + 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. + */ + query->dispatchmgr = res->dispatchmgr; + query->dispatch = NULL; + query->exclusivesocket = false; + query->tcpsocket = NULL; + 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_getquerydscp(peer, &dscp); + if (result == ISC_R_SUCCESS) { + query->dscp = dscp; + } + result = dns_peer_getforcetcp(peer, &usetcp); + if (result == ISC_R_SUCCESS && usetcp) { + query->options |= DNS_FETCHOPT_TCP; + } + } + } + + dscp = -1; + 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); + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case PF_INET6: + result = dns_dispatch_getlocaladdress( + res->dispatches6->dispatches[0], &addr); + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + break; + } + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + } + isc_sockaddr_setport(&addr, 0); + if (query->dscp == -1) { + query->dscp = dscp; + } + + result = isc_socket_create(res->socketmgr, pf, + isc_sockettype_tcp, + &query->tcpsocket); + if (result != ISC_R_SUCCESS) { + goto cleanup_query; + } + +#ifndef BROKEN_TCP_BIND_BEFORE_CONNECT + result = isc_socket_bind(query->tcpsocket, &addr, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_socket; + } +#endif /* ifndef BROKEN_TCP_BIND_BEFORE_CONNECT */ + + /* + * A dispatch will be created once the connect succeeds. + */ + } else { + if (have_addr) { + unsigned int attrs, attrmask; + attrs = DNS_DISPATCHATTR_UDP; + switch (isc_sockaddr_pf(&addr)) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_query; + } + attrmask = DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + result = dns_dispatch_getudp( + res->dispatchmgr, res->socketmgr, res->taskmgr, + &addr, 4096, 20000, 32768, 16411, 16433, attrs, + attrmask, &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); + query->exclusivesocket = res->exclusivev4; + dscp = dns_resolver_getquerydscp4(fctx->res); + break; + case PF_INET6: + dns_dispatch_attach( + dns_resolver_dispatchv6(res), + &query->dispatch); + query->exclusivesocket = res->exclusivev6; + dscp = dns_resolver_getquerydscp6(fctx->res); + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup_query; + } + } + + if (query->dscp == -1) { + query->dscp = dscp; + } + /* + * 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); + } + + query->dispentry = NULL; + query->fctx = fctx; /* reference added by caller */ + query->tsig = NULL; + query->tsigkey = NULL; + ISC_LINK_INIT(query, link); + query->magic = QUERY_MAGIC; + + if ((query->options & DNS_FETCHOPT_TCP) != 0) { + /* + * Connect to the remote server. + * + * XXXRTH Should we attach to the socket? + */ + if (query->dscp != -1) { + isc_socket_dscp(query->tcpsocket, query->dscp); + } + result = isc_socket_connect(query->tcpsocket, + &addrinfo->sockaddr, task, + resquery_connected, query); + if (result != ISC_R_SUCCESS) { + goto cleanup_socket; + } + query->connects++; + QTRACE("connecting via TCP"); + } else { + if (dns_adbentry_overquota(addrinfo->entry)) { + goto cleanup_dispatch; + } + + /* Inform the ADB that we're starting a UDP fetch */ + dns_adb_beginudpfetch(fctx->adb, addrinfo); + + result = resquery_send(query); + if (result != ISC_R_SUCCESS) { + goto cleanup_udpfetch; + } + } + + fctx->querysent++; + + ISC_LIST_APPEND(fctx->queries, query, link); + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + fctx->nqueries++; + UNLOCK(&res->buckets[bucketnum].lock); + if (isc_sockaddr_pf(&addrinfo->sockaddr) == 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); + } + + return (ISC_R_SUCCESS); + +cleanup_socket: + isc_socket_detach(&query->tcpsocket); + +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: + if (query->dispatch != NULL) { + dns_dispatch_detach(&query->dispatch); + } + +cleanup_query: + if (query->connects == 0) { + query->magic = 0; + dns_message_detach(&query->rmessage); + isc_mem_put(fctx->mctx, query, sizeof(*query)); + } + + RUNTIME_CHECK(fctx_stopidletimer(fctx) == ISC_R_SUCCESS); + + 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 struct tried * +triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + for (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL; + tried = ISC_LIST_NEXT(tried, link)) + { + if (isc_sockaddr_equal(&tried->addr, address)) { + return (tried); + } + } + + return (NULL); +} + +static void +add_triededns512(fetchctx_t *fctx, isc_sockaddr_t *address) { + struct tried *tried; + + tried = triededns512(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->edns512, 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 isc_socket_t * +query2sock(const resquery_t *query) { + if (query->exclusivesocket) { + return (dns_dispatch_getentrysocket(query->dispentry)); + } else { + return (dns_dispatch_getsocket(query->dispatch)); + } +} + +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) { + fetchctx_t *fctx; + isc_result_t result; + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_region_t r; + dns_resolver_t *res; + isc_task_t *task; + isc_socket_t *sock; + isc_buffer_t tcpbuffer; + isc_sockaddr_t *address; + isc_buffer_t *buffer; + isc_netaddr_t ipaddr; + dns_tsigkey_t *tsigkey = NULL; + dns_peer_t *peer = NULL; + bool useedns; + dns_compress_t cctx; + bool cleanup_cctx = false; + 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 */ + + fctx = query->fctx; + QTRACE("send"); + + res = fctx->res; + task = res->buckets[fctx->bucketnum].task; + address = NULL; + + if (tcp) { + /* + * Reserve space for the TCP message length. + */ + isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data)); + isc_buffer_init(&query->buffer, query->data + 2, + sizeof(query->data) - 2); + buffer = &tcpbuffer; + } else { + isc_buffer_init(&query->buffer, query->data, + sizeof(query->data)); + buffer = &query->buffer; + } + + 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; + } + + /* + * Get a query id from the dispatch. + */ + result = dns_dispatch_addresponse(query->dispatch, 0, + &query->addrinfo->sockaddr, task, + resquery_response, query, &query->id, + &query->dispentry, res->socketmgr); + 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; + + result = dns_message_renderbegin(fctx->qmessage, &cctx, &query->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; + } + + peer = NULL; + 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 & DNS_FETCHOPT_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, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_NOEDNS0); + } + + /* Sync NOEDNS0 flag in addrinfo->flags and options now. */ + if ((query->addrinfo->flags & DNS_FETCHOPT_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 ((tried = triededns(fctx, sockaddr)) != NULL) { + if (tried->count == 1U) { + hint = dns_adb_getudpsize(fctx->adb, + query->addrinfo); + } else if (tried->count >= 2U) { + query->options |= DNS_FETCHOPT_EDNS512; + fctx->reason = "reducing the advertised EDNS " + "UDP packet size to 512 octets"; + } + } + } + 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 & DNS_FETCHOPT_NOEDNS0) == 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; + + if ((flags & FCTX_ADDRINFO_EDNSOK) != 0 && + (query->options & DNS_FETCHOPT_EDNS512) == 0) + { + udpsize = dns_adb_probesize(fctx->adb, + query->addrinfo, + fctx->timeouts); + if (udpsize > res->udpsize) { + udpsize = res->udpsize; + } + } + + if (peer != NULL) { + (void)dns_peer_getudpsize(peer, &udpsize); + } + + if (udpsize == 0U && res->udpsize == 512U) { + udpsize = 512; + } + + /* + * Was the size forced to 512 in the configuration? + */ + if (udpsize == 512U) { + query->options |= DNS_FETCHOPT_EDNS512; + } + + /* + * We have talked to this server before. + */ + if (hint != 0U) { + udpsize = hint; + } + + /* + * We know nothing about the peer's capabilities + * so start with minimal EDNS UDP size. + */ + if (udpsize == 0U) { + udpsize = 512; + } + + 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; + } + + if (udpsize > 512U) { + add_triededns(fctx, &query->addrinfo->sockaddr); + } + + if (udpsize == 512U) { + add_triededns512(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; + } + } + + /* + * If using TCP, write the length of the message at the beginning + * of the buffer. + */ + if (tcp) { + isc_buffer_usedregion(&query->buffer, &r); + isc_buffer_putuint16(&tcpbuffer, (uint16_t)r.length); + isc_buffer_add(&tcpbuffer, r.length); + } + + /* + * 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); + + sock = query2sock(query); + + /* + * Send the query! + */ + if (!tcp) { + address = &query->addrinfo->sockaddr; + if (query->exclusivesocket) { + result = isc_socket_connect(sock, address, task, + resquery_udpconnected, + query); + if (result != ISC_R_SUCCESS) { + goto cleanup_message; + } + query->connects++; + } + } + isc_buffer_usedregion(buffer, &r); + + /* + * XXXRTH Make sure we don't send to ourselves! We should probably + * prune out these addresses when we get them from the ADB. + */ + memset(&query->sendevent, 0, sizeof(query->sendevent)); + ISC_EVENT_INIT(&query->sendevent, sizeof(query->sendevent), 0, NULL, + ISC_SOCKEVENT_SENDDONE, resquery_senddone, query, NULL, + NULL, NULL); + + if (query->dscp == -1) { + query->sendevent.attributes &= ~ISC_SOCKEVENTATTR_DSCP; + query->sendevent.dscp = 0; + } else { + query->sendevent.attributes |= ISC_SOCKEVENTATTR_DSCP; + query->sendevent.dscp = query->dscp; + if (tcp) { + isc_socket_dscp(sock, query->dscp); + } + } + + result = isc_socket_sendto2(sock, &r, task, address, NULL, + &query->sendevent, 0); + INSIST(result == ISC_R_SUCCESS); + + query->sends++; + + 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 = isc_socket_getsockname(sock, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + + dns_dt_send(fctx->res->view, dtmsgtype, la, &query->addrinfo->sockaddr, + tcp, &zr, &query->start, NULL, &query->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_removeresponse(&query->dispentry, NULL); + +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_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *)event; + resquery_t *query = event->ev_arg; + bool retry = false; + isc_interval_t interval; + isc_result_t result; + unsigned int attrs; + fetchctx_t *fctx; + + REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT); + REQUIRE(VALID_QUERY(query)); + + QTRACE("connected"); + + UNUSED(task); + + /* + * XXXRTH + * + * Currently we don't wait for the connect event before retrying + * a query. This means that if we get really behind, we may end + * up doing extra work! + */ + + query->connects--; + fctx = query->fctx; + + if (RESQUERY_CANCELED(query)) { + /* + * This query was canceled while the connect() was in + * progress. + */ + isc_socket_detach(&query->tcpsocket); + resquery_destroy(&query); + } else { + switch (sevent->result) { + case ISC_R_SUCCESS: + + /* + * Extend the idle timer for TCP. Half of + * "resolver-query-timeout" will hopefully be long + * enough for a TCP connection to be established, a + * single DNS request to be sent, and the response + * received. + */ + isc_interval_set(&interval, + fctx->res->query_timeout / 1000 / 2, + 0); + result = fctx_startidletimer(query->fctx, &interval); + if (result != ISC_R_SUCCESS) { + FCTXTRACE("query canceled: idle timer failed; " + "responding"); + + fctx_cancelquery(&query, NULL, NULL, false, + false); + fctx_done(fctx, result, __LINE__); + break; + } + /* + * We are connected. Create a dispatcher and + * send the query. + */ + attrs = 0; + attrs |= DNS_DISPATCHATTR_TCP; + attrs |= DNS_DISPATCHATTR_PRIVATE; + attrs |= DNS_DISPATCHATTR_CONNECTED; + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == + AF_INET) + { + attrs |= DNS_DISPATCHATTR_IPV4; + } else { + attrs |= DNS_DISPATCHATTR_IPV6; + } + attrs |= DNS_DISPATCHATTR_MAKEQUERY; + + result = dns_dispatch_createtcp( + query->dispatchmgr, query->tcpsocket, + query->fctx->res->taskmgr, NULL, NULL, 4096, 2, + 1, 1, 3, attrs, &query->dispatch); + + /* + * Regardless of whether dns_dispatch_create() + * succeeded or not, we don't need our reference + * to the socket anymore. + */ + isc_socket_detach(&query->tcpsocket); + + if (result == ISC_R_SUCCESS) { + result = resquery_send(query); + } + + if (result != ISC_R_SUCCESS) { + FCTXTRACE("query canceled: " + "resquery_send() failed; responding"); + + fctx_cancelquery(&query, NULL, NULL, false, + false); + fctx_done(fctx, result, __LINE__); + } + 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: + FCTXTRACE3("query canceled in connected(): " + "no route to host; no response", + sevent->result); + + /* + * No route to remote. + */ + isc_socket_detach(&query->tcpsocket); + /* + * Do not query this server again in this fetch context + * if we already tried reducing the advertised EDNS UDP + * payload size to 512 bytes and the server is + * unavailable over TCP. This prevents query loops + * lasting until the fetch context restart limit is + * reached when attempting to get answers whose size + * exceeds 512 bytes from broken servers. + */ + if ((query->options & DNS_FETCHOPT_EDNS512) != 0) { + add_bad(fctx, query->rmessage, query->addrinfo, + sevent->result, badns_unreachable); + } + fctx_cancelquery(&query, NULL, NULL, true, false); + retry = true; + break; + + default: + FCTXTRACE3("query canceled in connected() due to " + "unexpected event result; responding", + sevent->result); + + isc_socket_detach(&query->tcpsocket); + fctx_cancelquery(&query, NULL, NULL, false, false); + break; + } + } + + isc_event_free(&event); + + if (retry) { + /* + * Behave as if the idle timer has expired. For TCP + * connections this may not actually reflect the latest timer. + */ + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, true, false); + } + } +} + +static void +fctx_finddone(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx; + dns_adbfind_t *find; + dns_resolver_t *res; + bool want_try = false; + bool want_done = false; + bool bucket_empty = false; + unsigned int bucketnum; + bool dodestroy = false; + + find = event->ev_sender; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + + FCTXTRACE("finddone"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + INSIST(fctx->pending > 0); + fctx->pending--; + + 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 (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; + } + } + } else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 && + fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) + { + if (isc_refcount_current(&fctx->references) == 0) { + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + } + UNLOCK(&res->buckets[bucketnum].lock); + + isc_event_free(&event); + dns_adb_destroyfind(&find); + + if (want_try) { + fctx_try(fctx, true, false); + } else if (want_done) { + FCTXTRACE("fetch failed in finddone(); return ISC_R_FAILURE"); + fctx_done(fctx, ISC_R_FAILURE, __LINE__); + } else if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +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, + dns_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; +} + +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; + dns_adbfind_t *find; + dns_resolver_t *res; + bool unshared; + isc_result_t result; + + FCTXTRACE("FINDNAME"); + res = fctx->res; + unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0); + /* + * 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. + */ + find = NULL; + 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/%d - %s", fctx, + fctx->info, fctx->clientstr, fctx->id, + 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); + } + } else 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); + } + } else { + /* + * We don't know any of the addresses for this + * name. + */ + if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { + /* + * We're looking for them and will get an + * event about it later. + */ + fctx->pending++; + /* + * 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)++; + } + } else { + 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); + } + } +} + +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) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copynf(domain, fctx->fwdname); + if (fctx->fwdpolicy == dns_fwdpolicy_only && + isstrictsubdomain(domain, &fctx->domain)) + { + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(domain, fctx->mctx, &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; + ai->dscp = fwd->dscp; + 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 (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; + bool bucket_empty; + + 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(fctx, DNS_R_SERVFAIL, __LINE__); + 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_cleanupall(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(fctx, result, __LINE__); + 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(fctx, DNS_R_SERVFAIL, __LINE__); + 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(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + /* + * In "_ A" mode we're asking for _.domain - + * resolver by default will follow delegations + * then, we don't want that. + */ + if ((options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + options |= DNS_FETCHOPT_NOFOLLOW; + } + fctx_increference(fctx); + task = res->buckets[bucketnum].task; + fctx_stoptimer(fctx); + fctx_stoptimer_trystale(fctx); + 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) { + LOCK(&res->buckets[bucketnum].lock); + RUNTIME_CHECK(!fctx_decreference(fctx)); + UNLOCK(&res->buckets[bucketnum].lock); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + } + 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(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + fctx_increference(fctx); + + result = fctx_query(fctx, addrinfo, fctx->options); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } + } 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; + dns_resolver_t *res; + fetchctx_t *fctx; + isc_result_t result; + bool bucket_empty; + unsigned int bucketnum; + unsigned int findoptions = 0; + dns_name_t *fname, *dcname; + dns_fixedname_t ffixed, dcfixed; + fname = dns_fixedname_initname(&ffixed); + dcname = dns_fixedname_initname(&dcfixed); + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + UNUSED(task); + FCTXTRACE("resume_qmin"); + + 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; + fevent = NULL; + isc_event_free(&event); + + dns_resolver_destroyfetch(&fctx->qminfetch); + + LOCK(&res->buckets[bucketnum].lock); + if (SHUTTINGDOWN(fctx)) { + UNLOCK(&res->buckets[bucketnum].lock); + goto cleanup; + } + UNLOCK(&res->buckets[bucketnum].lock); + + /* + * Note: fevent->rdataset must be disassociated and + * isc_event_free(&event) be called before resuming + * processing of the 'fctx' to prevent use-after-free. + * 'fevent' is set to NULL so as to not have a dangling + * pointer. + */ + if (result == ISC_R_CANCELED) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + /* + * If we're doing "_ A"-style minimization we can get + * NX answer to minimized query - we need to continue then. + * + * Otherwise - either disable minimization if we're + * in relaxed mode or fail if we're in strict mode. + */ + + if ((NXDOMAIN_RESULT(result) && + (fctx->options & DNS_FETCHOPT_QMIN_USE_A) == 0) || + result == DNS_R_FORMERR || result == DNS_R_REMOTEFORMERR || + result == 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 { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + } + + 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) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(fname, fctx->mctx, &fctx->domain); + + result = fcount_incr(fctx, false); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + goto cleanup; + } + + 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_cleanupall(fctx); + } + + fctx_try(fctx, true, false); + +cleanup: + INSIST(event == NULL); + INSIST(fevent == NULL); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +static bool +fctx_unlink(fetchctx_t *fctx) { + dns_resolver_t *res; + unsigned int bucketnum; + + /* + * Caller must be holding the bucket lock. + */ + + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(fctx->state == fetchstate_done || + fctx->state == fetchstate_init); + 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(fctx->pending == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + + FCTXTRACE("unlink"); + + isc_refcount_destroy(&fctx->references); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link); + + INSIST(atomic_fetch_sub_release(&res->nfctx, 1) > 0); + + dec_stats(res, dns_resstatscounter_nfetch); + + if (atomic_load_acquire(&res->buckets[bucketnum].exiting) && + ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs)) + { + return (true); + } + + return (false); +} + +static void +fctx_destroy(fetchctx_t *fctx) { + isc_sockaddr_t *sa, *next_sa; + struct tried *tried; + + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(fctx->state == fetchstate_done || + fctx->state == fetchstate_init); + 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(fctx->pending == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + REQUIRE(!ISC_LINK_LINKED(fctx, link)); + + FCTXTRACE("destroy"); + + 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 (tried = ISC_LIST_HEAD(fctx->edns512); tried != NULL; + tried = ISC_LIST_HEAD(fctx->edns512)) + { + ISC_LIST_UNLINK(fctx->edns512, 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); + isc_timer_destroy(&fctx->timer); + if (fctx->timer_try_stale != NULL) { + isc_timer_destroy(&fctx->timer_try_stale); + } + dns_message_detach(&fctx->qmessage); + if (dns_name_countlabels(&fctx->domain) > 0) { + dns_name_free(&fctx->domain, fctx->mctx); + } + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_name_free(&fctx->name, fctx->mctx); + dns_name_free(&fctx->qminname, fctx->mctx); + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_db_detach(&fctx->cache); + dns_adb_detach(&fctx->adb); + isc_mem_free(fctx->mctx, fctx->info); + isc_mem_putanddetach(&fctx->mctx, fctx, sizeof(*fctx)); +} + +/* + * Fetch event handlers. + */ + +static void +fctx_timeout(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + isc_timerevent_t *tevent = (isc_timerevent_t *)event; + resquery_t *query; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + FCTXTRACE("timeout"); + + inc_stats(fctx->res, dns_resstatscounter_querytimeout); + + if (event->ev_type == ISC_TIMEREVENT_LIFE) { + fctx->reason = NULL; + fctx_done(fctx, ISC_R_TIMEDOUT, __LINE__); + } else { + isc_result_t result; + + fctx->timeouts++; + fctx->timeout = true; + + /* + * We could cancel the running queries here, or we could let + * them keep going. Since we normally use separate sockets for + * different queries, we adopt the former approach to reduce + * the number of open sockets: cancel the oldest query if it + * expired after the query had started (this is usually the + * case but is not always so, depending on the task schedule + * timing). + */ + query = ISC_LIST_HEAD(fctx->queries); + if (query != NULL && + isc_time_compare(&tevent->due, &query->start) >= 0) + { + FCTXTRACE("query timed out; no response"); + fctx_cancelquery(&query, NULL, NULL, true, false); + } + FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT); + + /* + * Our timer has triggered. Reestablish the fctx lifetime + * timer. + */ + result = fctx_starttimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + /* Keep trying */ + fctx_try(fctx, true, false); + } + } + + isc_event_free(&event); +} + +/* + * Fetch event handlers called if stale answers are enabled + * (stale-answer-enabled) and the fetch took more than + * stale-answer-client-timeout to complete. + */ +static void +fctx_timeout_try_stale(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + dns_fetchevent_t *dns_event, *next_event; + isc_task_t *sender_task; + + REQUIRE(VALID_FCTX(fctx)); + + UNUSED(task); + + FCTXTRACE("timeout_try_stale"); + + if (event->ev_type != ISC_TIMEREVENT_LIFE) { + return; + } + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + /* + * Trigger events of type DNS_EVENT_TRYSTALE. + */ + for (dns_event = ISC_LIST_HEAD(fctx->events); dns_event != NULL; + dns_event = next_event) + { + next_event = ISC_LIST_NEXT(dns_event, ev_link); + + if (dns_event->ev_type != DNS_EVENT_TRYSTALE) { + continue; + } + + ISC_LIST_UNLINK(fctx->events, dns_event, ev_link); + sender_task = dns_event->ev_sender; + dns_event->ev_sender = fctx; + dns_event->vresult = ISC_R_TIMEDOUT; + dns_event->result = ISC_R_TIMEDOUT; + + isc_task_sendanddetach(&sender_task, ISC_EVENT_PTR(&dns_event)); + } + + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + isc_event_free(&event); +} + +static void +fctx_shutdown(fetchctx_t *fctx) { + isc_event_t *cevent; + + /* + * Start the shutdown process for fctx, if it isn't already underway. + */ + + FCTXTRACE("shutdown"); + + /* + * The caller must be holding the appropriate bucket lock. + */ + + if (fctx->want_shutdown) { + return; + } + + fctx->want_shutdown = true; + + /* + * 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) { + 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; + bool bucket_empty = false; + dns_resolver_t *res; + unsigned int bucketnum; + dns_validator_t *validator; + bool dodestroy = false; + + 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. + */ + fctx_stopqueries(fctx, false, false); + fctx_cleanupall(fctx); + + LOCK(&res->buckets[bucketnum].lock); + + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + + INSIST(fctx->state == fetchstate_active || + fctx->state == fetchstate_done); + INSIST(fctx->want_shutdown); + + if (fctx->state != fetchstate_done) { + fctx->state = fetchstate_done; + fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); + } + + if (isc_refcount_current(&fctx->references) == 0 && + fctx->pending == 0 && fctx->nqueries == 0 && + ISC_LIST_EMPTY(fctx->validators)) + { + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + + UNLOCK(&res->buckets[bucketnum].lock); + + if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +static void +fctx_start(isc_task_t *task, isc_event_t *event) { + fetchctx_t *fctx = event->ev_arg; + bool done = false, bucket_empty = false; + dns_resolver_t *res; + unsigned int bucketnum; + bool dodestroy = false; + + 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 (fctx->want_shutdown) { + /* + * We haven't started this fctx yet, and we've been requested + * to shut it down. + */ + FCTX_ATTR_SET(fctx, FCTX_ATTR_SHUTTINGDOWN); + fctx->state = fetchstate_done; + fctx_sendevents(fctx, ISC_R_CANCELED, __LINE__); + /* + * Since we haven't started, we INSIST that we have no + * pending ADB finds and no pending validations. + */ + INSIST(fctx->pending == 0); + INSIST(fctx->nqueries == 0); + INSIST(ISC_LIST_EMPTY(fctx->validators)); + if (isc_refcount_current(&fctx->references) == 0) { + /* + * It's now safe to destroy this fctx. + */ + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + done = true; + } else { + /* + * 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); + + if (!done) { + isc_result_t result; + + INSIST(!dodestroy); + + /* + * All is well. Start working on the fetch. + */ + result = fctx_starttimer(fctx); + if (result == ISC_R_SUCCESS && fctx->timer_try_stale != NULL) { + result = fctx_starttimer_trystale(fctx); + } + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } else { + fctx_try(fctx, false, false); + } + } else if (dodestroy) { + fctx_destroy(fctx); + if (bucket_empty) { + empty_bucket(res); + } + } +} + +/* + * Fetch Creation, Joining, and Cancellation. + */ + +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) { + isc_task_t *tclone; + dns_fetchevent_t *event; + + FCTXTRACE("join"); + + /* + * 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. + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_fetchevent_t *)isc_event_allocate( + fctx->res->mctx, tclone, DNS_EVENT_FETCHDONE, 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; + dns_fixedname_init(&event->foundname); + + /* + * Make sure that we can store the sigrdataset in the + * first event if 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); + } + + fctx_increference(fctx); + + fetch->magic = DNS_FETCH_MAGIC; + fetch->private = fctx; + + return (ISC_R_SUCCESS); +} + +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_fetch_t *fetch, isc_eventtype_t event_type) { + isc_task_t *tclone; + dns_fetchevent_t *event; + /* + * 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. + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_fetchevent_t *)isc_event_allocate(fctx->res->mctx, tclone, + event_type, action, arg, + sizeof(*event)); + event->result = DNS_R_SERVFAIL; + event->qtype = fctx->type; + event->db = NULL; + event->node = NULL; + event->rdataset = NULL; + event->sigrdataset = NULL; + event->fetch = fetch; + event->client = client; + event->id = id; + ISC_LIST_APPEND(fctx->events, event, ev_link); +} + +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 isc_result_t +fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type, + const dns_name_t *domain, dns_rdataset_t *nameservers, + const isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, unsigned int bucketnum, unsigned int depth, + isc_counter_t *qc, fetchctx_t **fctxp) { + fetchctx_t *fctx; + 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]; + isc_mem_t *mctx; + size_t p; + bool try_stale; + + /* + * Caller must be holding the lock for bucket number 'bucketnum'. + */ + REQUIRE(fctxp != NULL && *fctxp == NULL); + + mctx = res->buckets[bucketnum].mctx; + fctx = isc_mem_get(mctx, sizeof(*fctx)); + + fctx->qc = NULL; + 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". + */ + 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(mctx, buf); + + FCTXTRACE("create"); + dns_name_init(&fctx->name, NULL); + dns_name_dup(name, mctx, &fctx->name); + dns_name_init(&fctx->qminname, NULL); + dns_name_dup(name, mctx, &fctx->qminname); + dns_name_init(&fctx->domain, NULL); + dns_rdataset_init(&fctx->nameservers); + + fctx->type = type; + fctx->qmintype = type; + fctx->options = options; + /* + * Note! We do not attach to the task. We are relying on the + * resolver to ensure that this task doesn't go away while we are + * using it. + */ + fctx->res = res; + isc_refcount_init(&fctx->references, 0); + fctx->bucketnum = bucketnum; + fctx->dbucketnum = RES_NOBUCKET; + fctx->state = fetchstate_init; + fctx->want_shutdown = false; + fctx->cloned = false; + fctx->depth = depth; + fctx->minimized = false; + fctx->ip6arpaskip = false; + fctx->forwarding = false; + fctx->qmin_labels = 1; + fctx->qmin_warning = ISC_R_SUCCESS; + fctx->qminfetch = NULL; + dns_rdataset_init(&fctx->qminrrset); + dns_name_init(&fctx->qmindcname, NULL); + isc_stdtime_get(&fctx->now); + 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); + fctx->fwdpolicy = dns_fwdpolicy_none; + ISC_LIST_INIT(fctx->bad); + ISC_LIST_INIT(fctx->edns); + ISC_LIST_INIT(fctx->edns512); + ISC_LIST_INIT(fctx->bad_edns); + ISC_LIST_INIT(fctx->validators); + fctx->validator = NULL; + fctx->find = NULL; + fctx->altfind = NULL; + fctx->pending = 0; + fctx->restarts = 0; + fctx->querysent = 0; + fctx->referrals = 0; + + fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname); + + TIME_NOW(&fctx->start); + fctx->timeouts = 0; + fctx->lamecount = 0; + fctx->quotacount = 0; + fctx->adberr = 0; + fctx->neterr = 0; + fctx->badresp = 0; + fctx->findfail = 0; + fctx->valfail = 0; + fctx->result = ISC_R_FAILURE; + fctx->vresult = ISC_R_SUCCESS; + fctx->exitline = -1; /* sentinel */ + fctx->logged = false; + atomic_init(&fctx->attributes, 0); + fctx->spilled = false; + fctx->nqueries = 0; + fctx->reason = NULL; + fctx->rand_buf = 0; + fctx->rand_bits = 0; + fctx->timeout = false; + fctx->addrinfo = NULL; + if (client != NULL) { + isc_sockaddr_format(client, fctx->clientstr, + sizeof(fctx->clientstr)); + } else { + strlcpy(fctx->clientstr, "", sizeof(fctx->clientstr)); + } + fctx->id = id; + fctx->ns_ttl = 0; + fctx->ns_ttl_ok = false; + + dns_name_init(&fctx->nsname, NULL); + fctx->nsfetch = NULL; + dns_rdataset_init(&fctx->nsrrset); + + if (domain == NULL) { + dns_forwarders_t *forwarders = NULL; + dns_fixedname_t fixed; + unsigned int labels; + const dns_name_t *fwdname = name; + dns_name_t suffix; + dns_name_t *fname; + + /* + * 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. */ + fname = dns_fixedname_initname(&fixed); + result = dns_fwdtable_find(fctx->res->view->fwdtable, fwdname, + fname, &forwarders); + if (result == ISC_R_SUCCESS) { + fctx->fwdpolicy = forwarders->fwdpolicy; + dns_name_copynf(fname, fctx->fwdname); + } + + if (fctx->fwdpolicy != dns_fwdpolicy_only) { + dns_fixedname_t dcfixed; + dns_name_t *dcname; + 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_dup(fname, mctx, &fctx->domain); + dns_name_dup(dcname, mctx, &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_dup(fname, mctx, &fctx->domain); + dns_name_dup(fname, mctx, &fctx->qmindcname); + /* + * Disable query minimization + */ + options &= ~DNS_FETCHOPT_QMINIMIZE; + } + } else { + dns_name_dup(domain, mctx, &fctx->domain); + dns_name_dup(domain, mctx, &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_domain; + } + + log_ns_ttl(fctx, "fctx_create"); + + if (!dns_name_issubdomain(&fctx->name, &fctx->domain)) { + dns_name_format(&fctx->domain, buf, sizeof(buf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "'%s' is not subdomain of '%s'", fctx->info, + buf); + result = ISC_R_UNEXPECTED; + goto cleanup_fcount; + } + + fctx->qmessage = NULL; + dns_message_create(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(__FILE__, __LINE__, + "isc_time_nowplusinterval: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + try_stale = ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0); + if (try_stale) { + INSIST(res->view->staleanswerclienttimeout <= + (res->query_timeout - 1000)); + /* + * Compute an expiration time after which stale data will + * attempted to be served, if stale answers are enabled and + * target RRset is available in cache. + */ + 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(__FILE__, __LINE__, + "isc_time_nowplusinterval: %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); + + /* + * Create an inactive timer for resolver-query-timeout. It + * will be made active when the fetch is actually started. + */ + fctx->timer = NULL; + + iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL, + NULL, res->buckets[bucketnum].task, + fctx_timeout, fctx, &fctx->timer); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_timer_create: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + + /* + * If stale answers are enabled, then create an inactive timer + * for stale-answer-client-timeout. It will be made active when + * the fetch is actually started. + */ + fctx->timer_try_stale = NULL; + if (try_stale) { + iresult = isc_timer_create( + res->timermgr, isc_timertype_inactive, NULL, NULL, + res->buckets[bucketnum].task, fctx_timeout_try_stale, + fctx, &fctx->timer_try_stale); + if (iresult != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_create: %s", + isc_result_totext(iresult)); + result = ISC_R_UNEXPECTED; + goto cleanup_qmessage; + } + } + + /* + * Attach to the view's cache and adb. + */ + fctx->cache = NULL; + dns_db_attach(res->view->cachedb, &fctx->cache); + fctx->adb = NULL; + dns_adb_attach(res->view->adb, &fctx->adb); + fctx->mctx = NULL; + isc_mem_attach(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); + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + goto cleanup_mctx; + } + } + + ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link); + + INSIST(atomic_fetch_add_relaxed(&res->nfctx, 1) < UINT32_MAX); + + inc_stats(res, dns_resstatscounter_nfetch); + + *fctxp = fctx; + + return (ISC_R_SUCCESS); + +cleanup_mctx: + fctx->magic = 0; + isc_mem_detach(&fctx->mctx); + dns_adb_detach(&fctx->adb); + dns_db_detach(&fctx->cache); + isc_timer_destroy(&fctx->timer); + isc_timer_destroy(&fctx->timer_try_stale); + +cleanup_qmessage: + dns_message_detach(&fctx->qmessage); + +cleanup_fcount: + fcount_decr(fctx); + +cleanup_domain: + if (dns_name_countlabels(&fctx->domain) > 0) { + dns_name_free(&fctx->domain, mctx); + } + if (dns_name_countlabels(&fctx->qmindcname) > 0) { + dns_name_free(&fctx->qmindcname, mctx); + } + +cleanup_nameservers: + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + dns_name_free(&fctx->name, mctx); + dns_name_free(&fctx->qminname, mctx); + isc_mem_free(mctx, fctx->info); + isc_counter_detach(&fctx->qc); + +cleanup_fetch: + isc_mem_put(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; + dns_rdataset_t *rdataset; + + /* + * Caller must be holding the fctx lock. + */ + + /* + * XXXRTH Currently we support only one question. + */ + if (ISC_UNLIKELY(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 (ISC_UNLIKELY(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); + } + name = NULL; + 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, *hevent; + dns_name_t *name, *hname; + + 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; + hevent = ISC_LIST_HEAD(fctx->events); + if (hevent == NULL) { + return; + } + hname = dns_fixedname_name(&hevent->foundname); + for (event = ISC_LIST_NEXT(hevent, ev_link); event != NULL; + event = ISC_LIST_NEXT(event, ev_link)) + { + 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; + } + name = dns_fixedname_name(&event->foundname); + dns_name_copynf(hname, name); + event->result = hevent->result; + 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) + +/* + * The validator has finished. + */ +static void +validated(isc_task_t *task, isc_event_t *event) { + dns_adbaddrinfo_t *addrinfo; + dns_dbnode_t *node = NULL; + dns_dbnode_t *nsnode = NULL; + dns_fetchevent_t *hevent; + dns_name_t *name; + dns_rdataset_t *ardataset = NULL; + dns_rdataset_t *asigrdataset = NULL; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; + dns_resolver_t *res; + dns_valarg_t *valarg = event->ev_arg; + dns_validatorevent_t *vevent; + fetchctx_t *fctx; + bool chaining; + bool negative; + bool sentresponse; + bool bucket_empty; + 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); + REQUIRE(VALID_FCTX(valarg->fctx)); + REQUIRE(!ISC_LIST_EMPTY(valarg->fctx->validators)); + + fctx = valarg->fctx; + fctx_increference(fctx); + dns_message_attach(valarg->message, &message); + + res = fctx->res; + addrinfo = valarg->addrinfo; + + vevent = (dns_validatorevent_t *)event; + fctx->vresult = vevent->result; + + FCTXTRACE("received validation completion event"); + + 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_copynf(dns_fixedname_name(&vevent->validator->wild), + wild); + } + dns_validator_destroy(&vevent->validator); + dns_message_detach(&valarg->message); + 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. + */ + if (SHUTTINGDOWN(fctx) && !sentresponse) { + UNLOCK(&res->buckets[bucketnum].lock); + 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 (vevent->sigrdataset != NULL) { + (void)dns_db_deleterdataset( + fctx->cache, node, NULL, + dns_rdatatype_rrsig, + vevent->type); + } + dns_db_detachnode(fctx->cache, &node); + } + } else if (!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 (vevent->sigrdataset != NULL) { + (void)dns_db_addrdataset( + fctx->cache, node, NULL, now, + vevent->sigrdataset, 0, NULL); + } + dns_db_detachnode(fctx->cache, &node); + } + } + result = fctx->vresult; + add_bad(fctx, message, addrinfo, result, badns_validation); + 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); + } else if (sentresponse) { + fctx_done(fctx, result, __LINE__); /* 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); + } + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + } else { + fctx_try(fctx, true, true); /* Locks bucket. */ + } + + goto cleanup_event; + } + + 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); + UNLOCK(&res->buckets[bucketnum].lock); + 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)); + 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; + } + 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_copynf(vevent->name, + dns_fixedname_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); + fctx_done(fctx, result, __LINE__); /* Locks bucket. */ + +cleanup_event: + INSIST(node == NULL); + dns_message_detach(&message); + isc_event_free(&event); + + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +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_name_t *aname = 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; + aname = dns_fixedname_name(&event->foundname); + dns_name_copynf(name, aname); + 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; + dns_name_t *name; + dns_resolver_t *res; + dns_db_t **adbp; + dns_dbnode_t *node, **anodep; + dns_rdataset_t *ardataset; + bool need_validation, secure_domain; + dns_name_t *aname; + dns_fetchevent_t *event; + uint32_t ttl; + unsigned int valoptions = 0; + bool checknta = true; + + FCTXTRACE("ncache_message"); + + FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTNCACHE); + + res = fctx->res; + need_validation = false; + POST(need_validation); + secure_domain = false; + eresult = ISC_R_SUCCESS; + name = &fctx->name; + node = NULL; + + /* + * 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. + */ + dns_rdataset_t *trdataset; + dns_name_t *tname; + + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + 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); + + adbp = NULL; + aname = NULL; + anodep = NULL; + ardataset = NULL; + if (!HAVE_ANSWER(fctx)) { + event = ISC_LIST_HEAD(fctx->events); + if (event != NULL) { + adbp = &event->db; + aname = dns_fixedname_name(&event->foundname); + dns_name_copynf(name, aname); + anodep = &event->node; + ardataset = event->rdataset; + } + } else { + event = NULL; + } + + 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 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) { + 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 && + 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_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); + /* + * 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) { + return (check_section(arg, addname, type, 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) { + return (check_section(arg, addname, type, 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_increference(fetchctx_t *fctx) { + REQUIRE(VALID_FCTX(fctx)); + + isc_refcount_increment0(&fctx->references); +} + +/* + * Requires bucket lock to be held. + */ +static bool +fctx_decreference(fetchctx_t *fctx) { + bool bucket_empty = false; + + REQUIRE(VALID_FCTX(fctx)); + + if (isc_refcount_decrement(&fctx->references) == 1) { + /* + * No one cares about the result of this fetch anymore. + */ + if (fctx->pending == 0 && fctx->nqueries == 0 && + ISC_LIST_EMPTY(fctx->validators) && SHUTTINGDOWN(fctx)) + { + /* + * This fctx is already shutdown; we were just + * waiting for the last reference to go away. + */ + bucket_empty = fctx_unlink(fctx); + fctx_destroy(fctx); + } else { + /* + * Initiate shutdown. + */ + fctx_shutdown(fctx); + } + } + return (bucket_empty); +} + +static void +resume_dslookup(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *fevent; + dns_resolver_t *res; + fetchctx_t *fctx; + isc_result_t result; + uint32_t bucketnum; + bool bucket_empty; + dns_rdataset_t nameservers; + dns_fixedname_t fixed; + dns_name_t *domain; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + fevent = (dns_fetchevent_t *)event; + fctx = event->ev_arg; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + bucketnum = fctx->bucketnum; + + 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); + } + + dns_rdataset_init(&nameservers); + + /* + * Note: fevent->rdataset must be disassociated and + * isc_event_free(&event) be called before resuming + * processing of the 'fctx' to prevent use-after-free. + * 'fevent' is set to NULL so as to not have a dangling + * pointer. + */ + if (fevent->result == ISC_R_CANCELED) { + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + dns_resolver_destroyfetch(&fctx->nsfetch); + fctx_done(fctx, ISC_R_CANCELED, __LINE__); + } else if (fevent->result == 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(fevent->rdataset, &fctx->nameservers); + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + log_ns_ttl(fctx, "resume_dslookup"); + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + fcount_decr(fctx); + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + goto cleanup; + } + /* + * Try again. + */ + fctx_try(fctx, true, false); + } else { + unsigned int n; + dns_rdataset_t *nsrdataset = NULL; + + /* + * Retrieve state from fctx->nsfetch before we destroy it. + */ + domain = dns_fixedname_initname(&fixed); + dns_name_copynf(&fctx->nsfetch->private->domain, domain); + if (dns_name_equal(&fctx->nsname, domain)) { + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + dns_resolver_destroyfetch(&fctx->nsfetch); + goto cleanup; + } + 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); + + if (dns_rdataset_isassociated(fevent->rdataset)) { + dns_rdataset_disassociate(fevent->rdataset); + } + fevent = NULL; + isc_event_free(&event); + + FCTXTRACE("continuing to look for parent's NS records"); + + result = dns_resolver_createfetch( + fctx->res, &fctx->nsname, dns_rdatatype_ns, domain, + nsrdataset, NULL, NULL, 0, fctx->options, 0, NULL, task, + resume_dslookup, fctx, &fctx->nsrrset, NULL, + &fctx->nsfetch); + /* + * fevent->rdataset (a.k.a. fctx->nsrrset) must not be + * accessed below this point to prevent races with + * another thread concurrently processing the fetch. + */ + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_done(fctx, result, __LINE__); + } else { + fctx_increference(fctx); + } + } + +cleanup: + INSIST(event == NULL); + INSIST(fevent == NULL); + if (dns_rdataset_isassociated(&nameservers)) { + dns_rdataset_disassociate(&nameservers); + } + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +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_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + resquery_t *query = event->ev_arg; + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + fetchctx_t *fctx; + respctx_t rctx; + + REQUIRE(VALID_QUERY(query)); + fctx = query->fctx; + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + + QTRACE("response"); + + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) { + inc_stats(fctx->res, dns_resstatscounter_responsev4); + } else { + inc_stats(fctx->res, dns_resstatscounter_responsev6); + } + + (void)isc_timer_touch(fctx->timer); + + rctx_respinit(task, devent, query, fctx, &rctx); + + if (atomic_load_acquire(&fctx->res->exiting)) { + result = ISC_R_SHUTTINGDOWN; + FCTXTRACE("resolver shutting down"); + rctx_done(&rctx, result); + return; + } + + fctx->timeouts = 0; + fctx->timeout = false; + fctx->addrinfo = query->addrinfo; + + /* + * 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) { + 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(&devent->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("response did not match question", 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); + /* + * INSIST() that the message comes from the place we sent it to, + * since the dispatch code should ensure this. + * + * INSIST() that the message id is correct (this should also be + * ensured by the dispatch code). + */ + + /* + * 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; + } + /* + * XXXMPA When support for DNS COOKIE becomes ubiquitous, fall + * back to TCP for all non-COOKIE responses. + */ + } + + 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 the result code */ + 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); + + 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(isc_task_t *task, dns_dispatchevent_t *devent, resquery_t *query, + fetchctx_t *fctx, respctx_t *rctx) { + memset(rctx, 0, sizeof(*rctx)); + + rctx->task = task; + rctx->devent = devent; + rctx->query = query; + rctx->fctx = fctx; + rctx->broken_type = badns_response; + rctx->retryopts = query->options; + + /* + * XXXRTH We should really get the current time just once. We + * need a routine to convert from an isc_time_t to an + * isc_stdtime_t. + */ + TIME_NOW(&rctx->tnow); + rctx->finish = &rctx->tnow; + isc_stdtime_get(&rctx->now); +} + +/* + * 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) { + dns_dispatchevent_t *devent = rctx->devent; + fetchctx_t *fctx = rctx->fctx; + resquery_t *query = rctx->query; + + if (devent->result == ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + if (devent->result == ISC_R_EOF && + (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); + } else { + /* + * There's no hope for this response. + */ + rctx->next_server = true; + + /* + * If this is a network error on an exclusive query + * socket, 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 + * adjustment later. + */ + if (query->exclusivesocket && + (devent->result == ISC_R_HOSTUNREACH || + devent->result == ISC_R_NETUNREACH || + devent->result == ISC_R_CONNREFUSED || + devent->result == ISC_R_CANCELED)) + { + rctx->broken_server = devent->result; + rctx->broken_type = badns_unreachable; + rctx->finish = NULL; + rctx->no_response = true; + } + } + FCTXTRACE3("dispatcher failure", devent->result); + rctx_done(rctx, ISC_R_SUCCESS); + return (ISC_R_COMPLETE); +} + +/* + * 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->devent->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) + { + 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, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_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, + DNS_FETCHOPT_NOEDNS0, DNS_FETCHOPT_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) { + 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; + rctx_done(rctx, result); + return (ISC_R_COMPLETE); + } + } + + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_FORMERR) { + rctx->next_server = true; + } + 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"); + + 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, 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, 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, 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, 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, + 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); + + dns_name_free(&fctx->domain, fctx->mctx); + if (dns_rdataset_isassociated(&fctx->nameservers)) { + dns_rdataset_disassociate(&fctx->nameservers); + } + + dns_name_init(&fctx->domain, NULL); + dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->domain); + + if ((fctx->options & DNS_FETCHOPT_QMINIMIZE) != 0) { + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(rctx->ns_name, fctx->mctx, &fctx->qmindcname); + + result = fctx_minimize_qname(fctx); + if (result != ISC_R_SUCCESS) { + rctx->result = result; + return (ISC_R_COMPLETE); + } + } + + 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, 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; + + 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(fctx, DNS_R_SERVFAIL, __LINE__); + 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(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + if (!dns_name_issubdomain(fname, &fctx->domain)) { + /* + * The best nameservers are now above our QDOMAIN. + */ + FCTXTRACE("nameservers now above QDOMAIN"); + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + + fcount_decr(fctx); + + dns_name_free(&fctx->domain, fctx->mctx); + dns_name_init(&fctx->domain, NULL); + dns_name_dup(fname, fctx->mctx, &fctx->domain); + dns_name_free(&fctx->qmindcname, fctx->mctx); + dns_name_init(&fctx->qmindcname, NULL); + dns_name_dup(dcname, fctx->mctx, &fctx->qmindcname); + + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + fctx_cancelqueries(fctx, true, false); + fctx_cleanupall(fctx); + } + + /* + * Try again. + */ + fctx_try(fctx, !rctx->get_nameservers, 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) { + isc_result_t result; + fetchctx_t *fctx = rctx->fctx; + bool bucket_empty; + dns_resolver_t *res = fctx->res; + unsigned int bucketnum; + + FCTXTRACE("resend"); + inc_stats(fctx->res, dns_resstatscounter_retry); + fctx_increference(fctx); + result = fctx_query(fctx, addrinfo, rctx->retryopts); + if (result == ISC_R_SUCCESS) { + return; + } + + bucketnum = fctx->bucketnum; + fctx_done(fctx, result, __LINE__); + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) { + empty_bucket(res); + } +} + +/* + * 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 void +rctx_next(respctx_t *rctx) { +#ifdef WANT_QUERYTRACE + fetchctx_t *fctx = rctx->fctx; +#endif /* ifdef WANT_QUERYTRACE */ + 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, &rctx->devent); + if (result != ISC_R_SUCCESS) { + fctx_done(rctx->fctx, result, __LINE__); + } +} + +/* + * 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; + unsigned int n; + + add_bad(fctx, message, addrinfo, result, rctx->broken_type); + fctx_cancelqueries(fctx, true, false); + fctx_cleanupfinds(fctx); + fctx_cleanupforwaddrs(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"); + + result = dns_resolver_createfetch( + fctx->res, &fctx->nsname, dns_rdatatype_ns, NULL, NULL, NULL, + NULL, 0, fctx->options, 0, NULL, rctx->task, resume_dslookup, + fctx, &fctx->nsrrset, NULL, &fctx->nsfetch); + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_DUPLICATE) { + result = DNS_R_SERVFAIL; + } + fctx_done(fctx, result, __LINE__); + } else { + fctx_increference(fctx); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } + } +} + +/* + * 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; + /* + * Need to attach to the message until the scope + * of this function ends, since there are many places + * where te message is used and/or may be destroyed + * before this function ends. + */ + dns_message_t *message = NULL; + dns_message_attach(query->rmessage, &message); + + FCTXTRACE4("query canceled in response(); ", + rctx->no_response ? "no response" : "responding", result); + + /* + * Cancel the query. + * + * XXXRTH Don't cancel the query if waiting for validation? + */ + if (!rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, false); + } + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver && + (rctx->next_server || rctx->resend || rctx->nextitem)) + { + if (rctx->nextitem) { + fctx_cancelquery(&query, &rctx->devent, rctx->finish, + rctx->no_response, false); + } + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } else +#endif /* ifdef ENABLE_AFL */ + if (rctx->next_server) { + rctx_nextserver(rctx, message, addrinfo, result); + } else if (rctx->resend) { + rctx_resend(rctx, addrinfo); + } else if (rctx->nextitem) { + rctx_next(rctx); + } 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); + /* + * We must not retransmit while the validator is + * working; it has references to the current rmessage. + */ + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, result, __LINE__); + } + } else { + /* + * We're done. + */ + fctx_done(fctx, result, __LINE__); + } + + 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_socket_t *sock = NULL; + 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; + } + + sock = query2sock(rctx->query); + + if (sock != NULL) { + result = isc_socket_getsockname(sock, &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->devent->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->retryopts & DNS_FETCHOPT_NOEDNS0) == 0) + { + /* + * It's very likely they don't like EDNS0. + * If the response code is SERVFAIL, also check if the + * response contains an OPT RR and don't cache the + * failure since it can be returned for various other + * reasons. + * + * XXXRTH We should check if the question + * we're asking requires EDNS0, and + * if so, we should bail out. + */ + 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 (ISFORWARDER(query->addrinfo)) { + /* + * This forwarder doesn't understand us, + * but other forwarders might. Keep trying. + */ + rctx->broken_server = DNS_R_REMOTEFORMERR; + rctx->next_server = true; + } else { + /* + * The server doesn't understand us. Since + * all servers for a zone need similar + * capabilities, we assume that we will get + * FORMERR from all servers, and thus we + * cannot make any more progress with this + * fetch. + */ + log_formerr(fctx, "server sent FORMERR"); + result = DNS_R_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_detach(&res->buckets[i].mctx); + } + isc_mem_put(res->mctx, res->buckets, + res->nbuckets * sizeof(fctxbucket_t)); + for (i = 0; i < RES_DOMAIN_BUCKETS; i++) { + INSIST(ISC_LIST_EMPTY(res->dbuckets[i].list)); + isc_mem_detach(&res->dbuckets[i].mctx); + isc_mutex_destroy(&res->dbuckets[i].lock); + } + isc_mem_put(res->mctx, res->dbuckets, + RES_DOMAIN_BUCKETS * 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); +#if USE_ALGLOCK + isc_rwlock_destroy(&res->alglock); +#endif /* if USE_ALGLOCK */ +#if USE_MBSLOCK + isc_rwlock_destroy(&res->mbslock); +#endif /* if USE_MBSLOCK */ + isc_timer_destroy(&res->spillattimer); + res->magic = 0; + isc_mem_put(res->mctx, res, sizeof(*res)); +} + +static void +send_shutdown_events(dns_resolver_t *res) { + isc_event_t *event, *next_event; + isc_task_t *etask; + + /* + * Caller must be holding the resolver 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); + } +} + +static void +empty_bucket(dns_resolver_t *res) { + RTRACE("empty_bucket"); + + LOCK(&res->lock); + + INSIST(res->activebuckets > 0); + res->activebuckets--; + if (res->activebuckets == 0) { + send_shutdown_events(res); + } + + 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); + INSIST(!atomic_load_acquire(&res->exiting)); + 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_socketmgr_t *socketmgr, isc_timermgr_t *timermgr, + unsigned int options, dns_dispatchmgr_t *dispatchmgr, + dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6, + dns_resolver_t **resp) { + dns_resolver_t *res; + isc_result_t result = ISC_R_SUCCESS; + unsigned int i, buckets_created = 0, dbuckets_created = 0; + isc_task_t *task = NULL; + char name[16]; + unsigned dispattr; + + /* + * 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); + + res = isc_mem_get(view->mctx, sizeof(*res)); + RTRACE("create"); + res->mctx = view->mctx; + res->rdclass = view->rdclass; + res->socketmgr = socketmgr; + res->timermgr = timermgr; + res->taskmgr = taskmgr; + res->dispatchmgr = dispatchmgr; + res->view = view; + res->options = options; + res->lame_ttl = 0; + ISC_LIST_INIT(res->alternates); + res->udpsize = RECV_BUFFER_SIZE; + res->algorithms = NULL; + res->digests = NULL; + res->badcache = NULL; + result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE, + &res->badcache); + if (result != ISC_R_SUCCESS) { + goto cleanup_res; + } + res->mustbesecure = NULL; + res->spillatmin = res->spillat = 10; + res->spillatmax = 100; + res->spillattimer = NULL; + atomic_init(&res->zspill, 0); + res->zero_no_soa_ttl = false; + res->retryinterval = 30000; + res->nonbackofftries = 3; + res->query_timeout = DEFAULT_QUERY_TIMEOUT; + res->maxdepth = DEFAULT_RECURSION_DEPTH; + res->maxqueries = DEFAULT_MAX_QUERIES; + res->quotaresp[dns_quotatype_zone] = DNS_R_DROP; + res->quotaresp[dns_quotatype_server] = DNS_R_SERVFAIL; + res->nbuckets = ntasks; + if (view->resstats != NULL) { + isc_stats_set(view->resstats, ntasks, + dns_resstatscounter_buckets); + } + res->activebuckets = ntasks; + res->buckets = isc_mem_get(view->mctx, ntasks * sizeof(fctxbucket_t)); + for (i = 0; i < ntasks; i++) { + isc_mutex_init(&res->buckets[i].lock); + + res->buckets[i].task = NULL; + /* + * 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) { + isc_mutex_destroy(&res->buckets[i].lock); + goto cleanup_buckets; + } + res->buckets[i].mctx = NULL; + snprintf(name, sizeof(name), "res%u", i); + /* + * Use a separate memory context for each bucket to reduce + * contention among multiple threads. Do this only when + * enabling threads because it will be require more memory. + */ + isc_mem_create(&res->buckets[i].mctx); + isc_mem_setname(res->buckets[i].mctx, name, NULL); + isc_task_setname(res->buckets[i].task, name, res); + ISC_LIST_INIT(res->buckets[i].fctxs); + atomic_init(&res->buckets[i].exiting, false); + buckets_created++; + } + + res->dbuckets = isc_mem_get(view->mctx, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + for (i = 0; i < RES_DOMAIN_BUCKETS; i++) { + ISC_LIST_INIT(res->dbuckets[i].list); + res->dbuckets[i].mctx = NULL; + isc_mem_attach(view->mctx, &res->dbuckets[i].mctx); + isc_mutex_init(&res->dbuckets[i].lock); + dbuckets_created++; + } + + res->dispatches4 = NULL; + if (dispatchv4 != NULL) { + dns_dispatchset_create(view->mctx, socketmgr, taskmgr, + dispatchv4, &res->dispatches4, ndisp); + dispattr = dns_dispatch_getattributes(dispatchv4); + res->exclusivev4 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE); + } + + res->dispatches6 = NULL; + if (dispatchv6 != NULL) { + dns_dispatchset_create(view->mctx, socketmgr, taskmgr, + dispatchv6, &res->dispatches6, ndisp); + dispattr = dns_dispatch_getattributes(dispatchv6); + res->exclusivev6 = (dispattr & DNS_DISPATCHATTR_EXCLUSIVE); + } + + res->querydscp4 = -1; + res->querydscp6 = -1; + isc_refcount_init(&res->references, 1); + atomic_init(&res->exiting, false); + res->frozen = false; + ISC_LIST_INIT(res->whenshutdown); + atomic_init(&res->priming, false); + res->primefetch = NULL; + + atomic_init(&res->nfctx, 0); + + isc_mutex_init(&res->lock); + isc_mutex_init(&res->primelock); + + task = NULL; + 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; + } + +#if USE_ALGLOCK + isc_rwlock_init(&res->alglock, 0, 0); +#endif /* if USE_ALGLOCK */ +#if USE_MBSLOCK + isc_rwlock_init(&res->mbslock, 0, 0); +#endif /* if USE_MBSLOCK */ + + 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 (i = 0; i < dbuckets_created; i++) { + isc_mutex_destroy(&res->dbuckets[i].lock); + isc_mem_detach(&res->dbuckets[i].mctx); + } + isc_mem_put(view->mctx, res->dbuckets, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + +cleanup_buckets: + for (i = 0; i < buckets_created; i++) { + isc_mem_detach(&res->buckets[i].mctx); + 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"); + + UNUSED(task); + + LOCK(&res->primelock); + fetch = res->primefetch; + res->primefetch = NULL; + UNLOCK(&res->primelock); + + INSIST(atomic_compare_exchange_strong_acq_rel(&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)); + INSIST(atomic_compare_exchange_strong_acq_rel( + &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"); + + LOCK(&source->lock); + REQUIRE(!atomic_load_acquire(&source->exiting)); + isc_refcount_increment(&source->references); + UNLOCK(&source->lock); + + *targetp = source; +} + +void +dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task, + isc_event_t **eventp) { + isc_task_t *tclone; + isc_event_t *event; + + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(eventp != NULL); + + event = *eventp; + *eventp = NULL; + + LOCK(&res->lock); + + if (atomic_load_acquire(&res->exiting) && res->activebuckets == 0) { + /* + * We're already shutdown. Send the event. + */ + event->ev_sender = res; + isc_task_send(task, &event); + } else { + tclone = NULL; + isc_task_attach(task, &tclone); + event->ev_sender = tclone; + 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; + + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("shutdown"); + + LOCK(&res->lock); + 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); + } + if (res->dispatches4 != NULL && !res->exclusivev4) { + dns_dispatchset_cancelall(res->dispatches4, + res->buckets[i].task); + } + if (res->dispatches6 != NULL && !res->exclusivev6) { + dns_dispatchset_cancelall(res->dispatches6, + res->buckets[i].task); + } + atomic_store(&res->buckets[i].exiting, true); + if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) { + INSIST(res->activebuckets > 0); + res->activebuckets--; + } + UNLOCK(&res->buckets[i].lock); + } + if (res->activebuckets == 0) { + send_shutdown_events(res); + } + result = isc_timer_reset(res->spillattimer, + isc_timertype_inactive, NULL, NULL, + true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + UNLOCK(&res->lock); +} + +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) { + LOCK(&res->lock); + INSIST(atomic_load_acquire(&res->exiting)); + INSIST(res->activebuckets == 0); + UNLOCK(&res->lock); + 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 isc_result_t +fctx_minimize_qname(fetchctx_t *fctx) { + isc_result_t result = ISC_R_SUCCESS; + unsigned int dlabels, nlabels; + + REQUIRE(VALID_FCTX(fctx)); + + dlabels = dns_name_countlabels(&fctx->qmindcname); + nlabels = dns_name_countlabels(&fctx->name); + dns_name_free(&fctx->qminname, fctx->mctx); + dns_name_init(&fctx->qminname, NULL); + + 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) { + /* + * We want to query for qmin_labels from fctx->name + */ + dns_fixedname_t fname; + dns_name_t *name = dns_fixedname_initname(&fname); + dns_name_split(&fctx->name, fctx->qmin_labels, NULL, + dns_fixedname_name(&fname)); + if ((fctx->options & DNS_FETCHOPT_QMIN_USE_A) != 0) { + isc_buffer_t dbuf; + dns_fixedname_t tmpname; + dns_name_t *tname = dns_fixedname_initname(&tmpname); + char ndata[DNS_NAME_MAXWIRE]; + + isc_buffer_init(&dbuf, ndata, DNS_NAME_MAXWIRE); + dns_fixedname_init(&tmpname); + result = dns_name_concatenate(&underscore_name, name, + tname, &dbuf); + if (result == ISC_R_SUCCESS) { + dns_name_dup(tname, fctx->mctx, + &fctx->qminname); + } + fctx->qmintype = dns_rdatatype_a; + } else { + dns_name_dup(dns_fixedname_name(&fname), fctx->mctx, + &fctx->qminname); + fctx->qmintype = dns_rdatatype_ns; + } + fctx->minimized = true; + } else { + /* Minimization is done, we'll ask for whole qname */ + fctx->qmintype = fctx->type; + dns_name_dup(&fctx->name, fctx->mctx, &fctx->qminname); + 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); + + return (result); +} + +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); + + log_fetch(name, type); + + /* + * XXXRTH use a mempool? + */ + fetch = isc_mem_get(res->mctx, sizeof(*fetch)); + fetch->mctx = NULL; + 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; + } + count++; + } + } + if (count >= spillatmin && spillatmin != 0) { + INSIST(fctx != NULL); + if (count >= spillat) { + fctx->spilled = true; + } + if (fctx->spilled) { + result = DNS_R_DROP; + goto unlock; + } + } + + if (fctx == NULL) { + result = fctx_create(res, name, type, domain, nameservers, + client, id, 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, fetch, + DNS_EVENT_TRYSTALE); + } + + if (new_fctx) { + if (result == ISC_R_SUCCESS) { + /* + * Launch this fctx. + */ + event = &fctx->control_event; + 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 { + /* + * We don't care about the result of fctx_unlink() + * since we know we're not exiting. + */ + (void)fctx_unlink(fctx); + dodestroy = true; + } + } + +unlock: + UNLOCK(&res->buckets[bucketnum].lock); + + if (dodestroy) { + fctx_destroy(fctx); + } + + if (result == ISC_R_SUCCESS) { + FTRACE("created"); + *fetchp = fetch; + } else { + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + } + + return (result); +} + +void +dns_resolver_cancelfetch(dns_fetch_t *fetch) { + fetchctx_t *fctx; + dns_resolver_t *res; + 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; + dns_resolver_t *res; + dns_fetchevent_t *event, *next_event; + fetchctx_t *fctx; + unsigned int bucketnum; + bool bucket_empty; + + REQUIRE(fetchp != NULL); + fetch = *fetchp; + *fetchp = NULL; + REQUIRE(DNS_FETCH_VALID(fetch)); + fctx = fetch->private; + REQUIRE(VALID_FCTX(fctx)); + res = fctx->res; + + FTRACE("destroyfetch"); + + bucketnum = fctx->bucketnum; + LOCK(&res->buckets[bucketnum].lock); + + /* + * Sanity check: the caller should have gotten its event before + * trying to destroy the fetch. + */ + event = NULL; + if (fctx->state != fetchstate_done) { + 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); + } + } + + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + + isc_mem_putanddetach(&fetch->mctx, fetch, sizeof(*fetch)); + + if (bucket_empty) { + empty_bucket(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_socketmgr_t * +dns_resolver_socketmgr(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->socketmgr); +} + +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; +} + +isc_result_t +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); + + return (ISC_R_SUCCESS); +} + +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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->algorithms != NULL) { + dns_rbt_destroy(&resolver->algorithms); + } +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ +} + +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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + 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: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + 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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + 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 USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + 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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + if (resolver->digests != NULL) { + dns_rbt_destroy(&resolver->digests); + } +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ +} + +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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + 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: +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif /* if USE_ALGLOCK */ + 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 USE_ALGLOCK + RWLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + 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 USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_read); +#endif /* if USE_ALGLOCK */ + if (found) { + return (false); + } + return (dst_ds_digest_supported(digest_type)); +} + +void +dns_resolver_resetmustbesecure(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + +#if USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + if (resolver->mustbesecure != NULL) { + dns_rbt_destroy(&resolver->mustbesecure); + } +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ +} + +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 USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + 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: +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif /* if USE_MBSLOCK */ + 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 USE_MBSLOCK + RWLOCK(&resolver->mbslock, isc_rwlocktype_read); +#endif /* if USE_MBSLOCK */ + 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: +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_read); +#endif /* if USE_MBSLOCK */ + 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_setquerydscp4(dns_resolver_t *resolver, isc_dscp_t dscp) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->querydscp4 = dscp; +} + +isc_dscp_t +dns_resolver_getquerydscp4(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->querydscp4); +} + +void +dns_resolver_setquerydscp6(dns_resolver_t *resolver, isc_dscp_t dscp) { + REQUIRE(VALID_RESOLVER(resolver)); + + resolver->querydscp6 = dscp; +} + +isc_dscp_t +dns_resolver_getquerydscp6(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + return (resolver->querydscp6); +} + +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) { + int i; + + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(fp != NULL); + REQUIRE(format == isc_statsformat_file); + + for (i = 0; i < RES_DOMAIN_BUCKETS; 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..9921291 --- /dev/null +++ b/lib/dns/result.c @@ -0,0 +1,454 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 const char *text[DNS_R_NRESULTS] = { + "label too long", /*%< 0 DNS_R_LABELTOOLONG */ + "bad escape", /*%< 1 DNS_R_BADESCAPE */ + /*! + * Note that DNS_R_BADBITSTRING and DNS_R_BITSTRINGTOOLONG are + * deprecated. + */ + "bad bitstring", /*%< 2 DNS_R_BADBITSTRING */ + "bitstring too long", /*%< 3 DNS_R_BITSTRINGTOOLONG */ + "empty label", /*%< 4 DNS_R_EMPTYLABEL */ + + "bad dotted quad", /*%< 5 DNS_R_BADDOTTEDQUAD */ + "invalid NS owner name (wildcard)", /*%< 6 DNS_R_INVALIDNS */ + "unknown class/type", /*%< 7 DNS_R_UNKNOWN */ + "bad label type", /*%< 8 DNS_R_BADLABELTYPE */ + "bad compression pointer", /*%< 9 DNS_R_BADPOINTER */ + + "too many hops", /*%< 10 DNS_R_TOOMANYHOPS */ + "disallowed (by application policy)", /*%< 11 DNS_R_DISALLOWED */ + "extra input text", /*%< 12 DNS_R_EXTRATOKEN */ + "extra input data", /*%< 13 DNS_R_EXTRADATA */ + "text too long", /*%< 14 DNS_R_TEXTTOOLONG */ + + "not at top of zone", /*%< 15 DNS_R_NOTZONETOP */ + "syntax error", /*%< 16 DNS_R_SYNTAX */ + "bad checksum", /*%< 17 DNS_R_BADCKSUM */ + "bad IPv6 address", /*%< 18 DNS_R_BADAAAA */ + "no owner", /*%< 19 DNS_R_NOOWNER */ + + "no ttl", /*%< 20 DNS_R_NOTTL */ + "bad class", /*%< 21 DNS_R_BADCLASS */ + "name too long", /*%< 22 DNS_R_NAMETOOLONG */ + "partial match", /*%< 23 DNS_R_PARTIALMATCH */ + "new origin", /*%< 24 DNS_R_NEWORIGIN */ + + "unchanged", /*%< 25 DNS_R_UNCHANGED */ + "bad ttl", /*%< 26 DNS_R_BADTTL */ + "more data needed/to be rendered", /*%< 27 DNS_R_NOREDATA */ + "continue", /*%< 28 DNS_R_CONTINUE */ + "delegation", /*%< 29 DNS_R_DELEGATION */ + + "glue", /*%< 30 DNS_R_GLUE */ + "dname", /*%< 31 DNS_R_DNAME */ + "cname", /*%< 32 DNS_R_CNAME */ + "bad database", /*%< 33 DNS_R_BADDB */ + "zonecut", /*%< 34 DNS_R_ZONECUT */ + + "bad zone", /*%< 35 DNS_R_BADZONE */ + "more data", /*%< 36 DNS_R_MOREDATA */ + "up to date", /*%< 37 DNS_R_UPTODATE */ + "tsig verify failure", /*%< 38 DNS_R_TSIGVERIFYFAILURE */ + "tsig indicates error", /*%< 39 DNS_R_TSIGERRORSET */ + + "RRSIG failed to verify", /*%< 40 DNS_R_SIGINVALID */ + "RRSIG has expired", /*%< 41 DNS_R_SIGEXPIRED */ + "RRSIG validity period has not begun", /*%< 42 DNS_R_SIGFUTURE */ + "key is unauthorized to sign data", /*%< 43 DNS_R_KEYUNAUTHORIZED */ + "invalid time", /*%< 44 DNS_R_INVALIDTIME */ + + "expected a TSIG or SIG(0)", /*%< 45 DNS_R_EXPECTEDTSIG */ + "did not expect a TSIG or SIG(0)", /*%< 46 DNS_R_UNEXPECTEDTSIG */ + "TKEY is unacceptable", /*%< 47 DNS_R_INVALIDTKEY */ + "hint", /*%< 48 DNS_R_HINT */ + "drop", /*%< 49 DNS_R_DROP */ + + "zone not loaded", /*%< 50 DNS_R_NOTLOADED */ + "ncache nxdomain", /*%< 51 DNS_R_NCACHENXDOMAIN */ + "ncache nxrrset", /*%< 52 DNS_R_NCACHENXRRSET */ + "wait", /*%< 53 DNS_R_WAIT */ + "not verified yet", /*%< 54 DNS_R_NOTVERIFIEDYET */ + + "no identity", /*%< 55 DNS_R_NOIDENTITY */ + "no journal", /*%< 56 DNS_R_NOJOURNAL */ + "alias", /*%< 57 DNS_R_ALIAS */ + "use TCP", /*%< 58 DNS_R_USETCP */ + "no valid RRSIG", /*%< 59 DNS_R_NOVALIDSIG */ + + "no valid NSEC", /*%< 60 DNS_R_NOVALIDNSEC */ + "insecurity proof failed", /*%< 61 DNS_R_NOTINSECURE */ + "unknown service", /*%< 62 DNS_R_UNKNOWNSERVICE */ + "recoverable error occurred", /*%< 63 DNS_R_RECOVERABLE */ + "unknown opt attribute record", /*%< 64 DNS_R_UNKNOWNOPT */ + + "unexpected message id", /*%< 65 DNS_R_UNEXPECTEDID */ + "seen include file", /*%< 66 DNS_R_SEENINCLUDE */ + "not exact", /*%< 67 DNS_R_NOTEXACT */ + "address blackholed", /*%< 68 DNS_R_BLACKHOLED */ + "bad algorithm", /*%< 69 DNS_R_BADALG */ + + "invalid use of a meta type", /*%< 70 DNS_R_METATYPE */ + "CNAME and other data", /*%< 71 DNS_R_CNAMEANDOTHER */ + "multiple RRs of singleton type", /*%< 72 DNS_R_SINGLETON */ + "hint nxrrset", /*%< 73 DNS_R_HINTNXRRSET */ + "no master file configured", /*%< 74 DNS_R_NOMASTERFILE */ + + "unknown protocol", /*%< 75 DNS_R_UNKNOWNPROTO */ + "clocks are unsynchronized", /*%< 76 DNS_R_CLOCKSKEW */ + "IXFR failed", /*%< 77 DNS_R_BADIXFR */ + "not authoritative", /*%< 78 DNS_R_NOTAUTHORITATIVE */ + "no valid KEY", /*%< 79 DNS_R_NOVALIDKEY */ + + "obsolete", /*%< 80 DNS_R_OBSOLETE */ + "already frozen", /*%< 81 DNS_R_FROZEN */ + "unknown flag", /*%< 82 DNS_R_UNKNOWNFLAG */ + "expected a response", /*%< 83 DNS_R_EXPECTEDRESPONSE */ + "no valid DS", /*%< 84 DNS_R_NOVALIDDS */ + + "NS is an address", /*%< 85 DNS_R_NSISADDRESS */ + "received FORMERR", /*%< 86 DNS_R_REMOTEFORMERR */ + "truncated TCP response", /*%< 87 DNS_R_TRUNCATEDTCP */ + "lame server detected", /*%< 88 DNS_R_LAME */ + "unexpected RCODE", /*%< 89 DNS_R_UNEXPECTEDRCODE */ + + "unexpected OPCODE", /*%< 90 DNS_R_UNEXPECTEDOPCODE */ + "chase DS servers", /*%< 91 DNS_R_CHASEDSSERVERS */ + "empty name", /*%< 92 DNS_R_EMPTYNAME */ + "empty wild", /*%< 93 DNS_R_EMPTYWILD */ + "bad bitmap", /*%< 94 DNS_R_BADBITMAP */ + + "from wildcard", /*%< 95 DNS_R_FROMWILDCARD */ + "bad owner name (check-names)", /*%< 96 DNS_R_BADOWNERNAME */ + "bad name (check-names)", /*%< 97 DNS_R_BADNAME */ + "dynamic zone", /*%< 98 DNS_R_DYNAMIC */ + "unknown command", /*%< 99 DNS_R_UNKNOWNCOMMAND */ + + "must-be-secure", /*%< 100 DNS_R_MUSTBESECURE */ + "covering NSEC record returned", /*%< 101 DNS_R_COVERINGNSEC */ + "MX is an address", /*%< 102 DNS_R_MXISADDRESS */ + "duplicate query", /*%< 103 DNS_R_DUPLICATE */ + "invalid NSEC3 owner name (wildcard)", /*%< 104 DNS_R_INVALIDNSEC3 */ + + "not master", /*%< 105 DNS_R_NOTMASTER */ + "broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */ + "expired", /*%< 107 DNS_R_EXPIRED */ + "not dynamic", /*%< 108 DNS_R_NOTDYNAMIC */ + "bad EUI", /*%< 109 DNS_R_BADEUI */ + + "covered by negative trust anchor", /*%< 110 DNS_R_NTACOVERED */ + "bad CDS", /*%< 111 DNS_R_BADCDS */ + "bad CDNSKEY", /*%< 112 DNS_R_BADCDNSKEY */ + "malformed OPT option", /*%< 113 DNS_R_OPTERR */ + "malformed DNSTAP data", /*%< 114 DNS_R_BADDNSTAP */ + + "TSIG in wrong location", /*%< 115 DNS_R_BADTSIG */ + "SIG(0) in wrong location", /*%< 116 DNS_R_BADSIG0 */ + "too many records", /*%< 117 DNS_R_TOOMANYRECORDS */ + "verify failure", /*%< 118 DNS_R_VERIFYFAILURE */ + "at top of zone", /*%< 119 DNS_R_ATZONETOP */ + + "no matching key found", /*%< 120 DNS_R_NOKEYMATCH */ + "too many keys matching", /*%< 121 DNS_R_TOOMANYKEYS */ + "key is not actively signing", /*%< 122 DNS_R_KEYNOTACTIVE */ + "NSEC3 iterations out of range", /*%< 123 DNS_R_NSEC3ITERRANGE */ + "NSEC3 salt length too high", /*%< 124 DNS_R_NSEC3SALTRANGE */ + + "cannot use NSEC3 with key algorithm", /*%< 125 DNS_R_NSEC3BADALG */ + "NSEC3 resalt", /*%< 126 DNS_R_NSEC3RESALT */ + "inconsistent resource record", /*%< 127 DNS_R_INCONSISTENTRR */ +}; + +static const char *ids[DNS_R_NRESULTS] = { + "DNS_R_LABELTOOLONG", + "DNS_R_BADESCAPE", + /*! + * Note that DNS_R_BADBITSTRING and DNS_R_BITSTRINGTOOLONG are + * deprecated. + */ + "DNS_R_BADBITSTRING", + "DNS_R_BITSTRINGTOOLONG", + "DNS_R_EMPTYLABEL", + "DNS_R_BADDOTTEDQUAD", + "DNS_R_INVALIDNS", + "DNS_R_UNKNOWN", + "DNS_R_BADLABELTYPE", + "DNS_R_BADPOINTER", + "DNS_R_TOOMANYHOPS", + "DNS_R_DISALLOWED", + "DNS_R_EXTRATOKEN", + "DNS_R_EXTRADATA", + "DNS_R_TEXTTOOLONG", + "DNS_R_NOTZONETOP", + "DNS_R_SYNTAX", + "DNS_R_BADCKSUM", + "DNS_R_BADAAAA", + "DNS_R_NOOWNER", + "DNS_R_NOTTL", + "DNS_R_BADCLASS", + "DNS_R_NAMETOOLONG", + "DNS_R_PARTIALMATCH", + "DNS_R_NEWORIGIN", + "DNS_R_UNCHANGED", + "DNS_R_BADTTL", + "DNS_R_NOREDATA", + "DNS_R_CONTINUE", + "DNS_R_DELEGATION", + "DNS_R_GLUE", + "DNS_R_DNAME", + "DNS_R_CNAME", + "DNS_R_BADDB", + "DNS_R_ZONECUT", + "DNS_R_BADZONE", + "DNS_R_MOREDATA", + "DNS_R_UPTODATE", + "DNS_R_TSIGVERIFYFAILURE", + "DNS_R_TSIGERRORSET", + "DNS_R_SIGINVALID", + "DNS_R_SIGEXPIRED", + "DNS_R_SIGFUTURE", + "DNS_R_KEYUNAUTHORIZED", + "DNS_R_INVALIDTIME", + "DNS_R_EXPECTEDTSIG", + "DNS_R_UNEXPECTEDTSIG", + "DNS_R_INVALIDTKEY", + "DNS_R_HINT", + "DNS_R_DROP", + "DNS_R_NOTLOADED", + "DNS_R_NCACHENXDOMAIN", + "DNS_R_NCACHENXRRSET", + "DNS_R_WAIT", + "DNS_R_NOTVERIFIEDYET", + "DNS_R_NOIDENTITY", + "DNS_R_NOJOURNAL", + "DNS_R_ALIAS", + "DNS_R_USETCP", + "DNS_R_NOVALIDSIG", + "DNS_R_NOVALIDNSEC", + "DNS_R_NOTINSECURE", + "DNS_R_UNKNOWNSERVICE", + "DNS_R_RECOVERABLE", + "DNS_R_UNKNOWNOPT", + "DNS_R_UNEXPECTEDID", + "DNS_R_SEENINCLUDE", + "DNS_R_NOTEXACT", + "DNS_R_BLACKHOLED", + "DNS_R_BADALG", + "DNS_R_METATYPE", + "DNS_R_CNAMEANDOTHER", + "DNS_R_SINGLETON", + "DNS_R_HINTNXRRSET", + "DNS_R_NOMASTERFILE", + "DNS_R_UNKNOWNPROTO", + "DNS_R_CLOCKSKEW", + "DNS_R_BADIXFR", + "DNS_R_NOTAUTHORITATIVE", + "DNS_R_NOVALIDKEY", + "DNS_R_OBSOLETE", + "DNS_R_FROZEN", + "DNS_R_UNKNOWNFLAG", + "DNS_R_EXPECTEDRESPONSE", + "DNS_R_NOVALIDDS", + "DNS_R_NSISADDRESS", + "DNS_R_REMOTEFORMERR", + "DNS_R_TRUNCATEDTCP", + "DNS_R_LAME", + "DNS_R_UNEXPECTEDRCODE", + "DNS_R_UNEXPECTEDOPCODE", + "DNS_R_CHASEDSSERVERS", + "DNS_R_EMPTYNAME", + "DNS_R_EMPTYWILD", + "DNS_R_BADBITMAP", + "DNS_R_FROMWILDCARD", + "DNS_R_BADOWNERNAME", + "DNS_R_BADNAME", + "DNS_R_DYNAMIC", + "DNS_R_UNKNOWNCOMMAND", + "DNS_R_MUSTBESECURE", + "DNS_R_COVERINGNSEC", + "DNS_R_MXISADDRESS", + "DNS_R_DUPLICATE", + "DNS_R_INVALIDNSEC3", + "DNS_R_NOTMASTER", + "DNS_R_BROKENCHAIN", + "DNS_R_EXPIRED", + "DNS_R_NOTDYNAMIC", + "DNS_R_BADEUI", + "DNS_R_NTACOVERED", + "DNS_R_BADCDS", + "DNS_R_BADCDNSKEY", + "DNS_R_OPTERR", + "DNS_R_BADDNSTAP", + "DNS_R_BADTSIG", + "DNS_R_BADSIG0", + "DNS_R_TOOMANYRECORDS", + "DNS_R_VERIFYFAILURE", + "DNS_R_ATZONETOP", + "DNS_R_NOKEYMATCH", + "DNS_R_TOOMANYKEYS", + "DNS_R_KEYNOTACTIVE", + "DNS_R_NSEC3ITERRANGE", + "DNS_R_NSEC3SALTRANGE", + "DNS_R_NSEC3BADALG", + "DNS_R_NSEC3RESALT", + "DNS_R_INCONSISTENTRR", +}; + +static const char *rcode_text[DNS_R_NRCODERESULTS] = { + "NOERROR", /*%< 0 DNS_R_NOERROR */ + "FORMERR", /*%< 1 DNS_R_FORMERR */ + "SERVFAIL", /*%< 2 DNS_R_SERVFAIL */ + "NXDOMAIN", /*%< 3 DNS_R_NXDOMAIN */ + "NOTIMP", /*%< 4 DNS_R_NOTIMP */ + + "REFUSED", /*%< 5 DNS_R_REFUSED */ + "YXDOMAIN", /*%< 6 DNS_R_YXDOMAIN */ + "YXRRSET", /*%< 7 DNS_R_YXRRSET */ + "NXRRSET", /*%< 8 DNS_R_NXRRSET */ + "NOTAUTH", /*%< 9 DNS_R_NOTAUTH */ + + "NOTZONE", /*%< 10 DNS_R_NOTZONE */ + "", /*%< 11 DNS_R_RCODE11 */ + "", /*%< 12 DNS_R_RCODE12 */ + "", /*%< 13 DNS_R_RCODE13 */ + "", /*%< 14 DNS_R_RCODE14 */ + + "", /*%< 15 DNS_R_RCODE15 */ + "BADVERS", /*%< 16 DNS_R_BADVERS */ +}; + +static const char *rcode_ids[DNS_R_NRCODERESULTS] = { + "DNS_R_NOERROR", "DNS_R_FORMERR", "DNS_R_SERVFAIL", "DNS_R_NXDOMAIN", + "DNS_R_NOTIMP", "DNS_R_REFUSED", "DNS_R_YXDOMAIN", "DNS_R_YXRRSET", + "DNS_R_NXRRSET", "DNS_R_NOTAUTH", "DNS_R_NOTZONE", "DNS_R_RCODE11", + "RNS_R_RCODE12", "DNS_R_RCODE13", "DNS_R_RCODE14", "DNS_R_RCODE15", + "DNS_R_BADVERS", +}; + +#define DNS_RESULT_RESULTSET 2 +#define DNS_RESULT_RCODERESULTSET 3 + +static isc_once_t once = ISC_ONCE_INIT; + +static void +initialize_action(void) { + isc_result_t result; + + result = isc_result_register(ISC_RESULTCLASS_DNS, DNS_R_NRESULTS, text, + DNS_RESULT_RESULTSET); + if (result == ISC_R_SUCCESS) { + result = isc_result_register(ISC_RESULTCLASS_DNSRCODE, + DNS_R_NRCODERESULTS, rcode_text, + DNS_RESULT_RCODERESULTSET); + } + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_register() failed: %u", result); + } + + result = isc_result_registerids(ISC_RESULTCLASS_DNS, DNS_R_NRESULTS, + ids, DNS_RESULT_RESULTSET); + if (result == ISC_R_SUCCESS) { + result = isc_result_registerids(ISC_RESULTCLASS_DNSRCODE, + DNS_R_NRCODERESULTS, rcode_ids, + DNS_RESULT_RCODERESULTSET); + } + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_registerids() failed: %u", result); + } +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +const char * +dns_result_totext(isc_result_t result) { + initialize(); + + return (isc_result_totext(result)); +} + +void +dns_result_register(void) { + initialize(); +} + +dns_rcode_t +dns_result_torcode(isc_result_t result) { + dns_rcode_t rcode = dns_rcode_servfail; + + if (DNS_RESULT_ISRCODE(result)) { + /* + * Rcodes can't be bigger than 12 bits, which is why we + * AND with 0xFFF instead of 0xFFFF. + */ + return ((dns_rcode_t)((result)&0xFFF)); + } + + /* + * Try to supply an appropriate rcode. + */ + switch (result) { + case ISC_R_SUCCESS: + rcode = dns_rcode_noerror; + break; + 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: + rcode = dns_rcode_formerr; + break; + case DNS_R_DISALLOWED: + rcode = dns_rcode_refused; + break; + case DNS_R_TSIGVERIFYFAILURE: + case DNS_R_CLOCKSKEW: + rcode = dns_rcode_notauth; + break; + default: + rcode = dns_rcode_servfail; + } + + return (rcode); +} diff --git a/lib/dns/rootns.c b/lib/dns/rootns.c new file mode 100644 index 0000000..69e2667 --- /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 /* Required for HP/UX (and others?) */ +#include + +#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, dns_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, dns_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..20db72f --- /dev/null +++ b/lib/dns/rpz.c @@ -0,0 +1,2891 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +/* + * 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 + +/* + * Maximum number of nodes to process per quantum + */ +#define DNS_RPZ_QUANTUM 1024 + +static void +dns_rpz_update_from_db(dns_rpz_zone_t *rpz); + +static void +dns_rpz_update_taskaction(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 void +rpz_detach(dns_rpz_zone_t **rpzp); + +static void +rpz_detach_rpzs(dns_rpz_zones_t **rpzsp); + +#if 0 +/* + * Catch a name while debugging. + */ +static void +catch_name(const dns_name_t *src_name, const char *tgt, const char *str) { + dns_fixedname_t tgt_namef; + dns_name_t *tgt_name; + + tgt_name = dns_fixedname_initname(&tgt_namef); + dns_name_fromstring(tgt_name, tgt, DNS_NAME_DOWNCASE, NULL); + if (dns_name_equal(src_name, tgt_name)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "rpz hit failed: %s %s", str, tgt); + } +} +#endif /* if 0 */ + +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(__FILE__, __LINE__, "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; + + 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_cidr_node_t *child; + dns_rpz_addr_zbits_t sum; + + do { + sum = cnode->set; + + child = cnode->child[0]; + 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_zones_t *rpzs, dns_rpz_num_t rpz_num, + 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 = &rpzs->triggers[rpz_num].client_ipv4; + have = &rpzs->have.client_ipv4; + } else { + cnt = &rpzs->triggers[rpz_num].client_ipv6; + have = &rpzs->have.client_ipv6; + } + break; + case DNS_RPZ_TYPE_QNAME: + cnt = &rpzs->triggers[rpz_num].qname; + have = &rpzs->have.qname; + break; + case DNS_RPZ_TYPE_IP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpzs->triggers[rpz_num].ipv4; + have = &rpzs->have.ipv4; + } else { + cnt = &rpzs->triggers[rpz_num].ipv6; + have = &rpzs->have.ipv6; + } + break; + case DNS_RPZ_TYPE_NSDNAME: + cnt = &rpzs->triggers[rpz_num].nsdname; + have = &rpzs->have.nsdname; + break; + case DNS_RPZ_TYPE_NSIP: + REQUIRE(tgt_ip != NULL); + if (KEY_IS_IPV4(tgt_prefix, tgt_ip)) { + cnt = &rpzs->triggers[rpz_num].nsipv4; + have = &rpzs->have.nsipv4; + } else { + cnt = &rpzs->triggers[rpz_num].nsipv6; + have = &rpzs->have.nsipv6; + } + break; + default: + UNREACHABLE(); + } + + if (inc) { + if (++*cnt == 1U) { + *have |= DNS_RPZ_ZBIT(rpz_num); + fix_qname_skip_recurse(rpzs); + } + } else { + REQUIRE(*cnt != 0U); + if (--*cnt == 0U) { + *have &= ~DNS_RPZ_ZBIT(rpz_num); + fix_qname_skip_recurse(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; + int i, words, wlen; + + node = isc_mem_get(rpzs->mctx, sizeof(*node)); + memset(node, 0, sizeof(*node)); + + if (child != NULL) { + node->sum = child->sum; + } + + node->prefix = prefix; + 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) { + char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * bin/tests/system/rpz/tests.sh looks for "invalid rpz". + */ + if (level < DNS_RPZ_DEBUG_QUIET && isc_log_wouldlog(dns_lctx, level)) { + 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 */ + int w[DNS_RPZ_CIDR_WORDS * 2]; + char str[1 + 8 + 1 + INET6_ADDRSTRLEN + 1]; + isc_buffer_t buffer; + isc_result_t result; + int best_first, best_len, cur_first, cur_len; + int i, n, 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 { + len = snprintf(str, sizeof(str), "%d", tgt_prefix); + if (len < 0 || (size_t)len >= sizeof(str)) { + return (ISC_R_FAILURE); + } + + for (i = 0; i < DNS_RPZ_CIDR_WORDS; i++) { + w[i * 2 + 1] = + ((tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] >> 16) & + 0xffff); + w[i * 2] = tgt_ip->w[DNS_RPZ_CIDR_WORDS - 1 - i] & + 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 (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 (n = 0; n <= 7; ++n) { + 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, const dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + 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) { + dns_rpz_zone_t *rpz; + char ip_str[DNS_NAME_FORMATSIZE], ip2_str[DNS_NAME_FORMATSIZE]; + dns_offsets_t ip_name_offsets; + dns_fixedname_t ip_name2f; + dns_name_t ip_name, *ip_name2; + const char *prefix_str, *cp, *end; + char *cp2; + int ip_labels; + dns_rpz_prefix_t prefix; + unsigned long prefix_num, l; + isc_result_t result; + int i; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + 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. + */ + 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)) + { + 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_zones_t *rpzs, dns_rpz_num_t rpz_num, 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_rpz_zone_t *rpz; + dns_offsets_t tmp_name_offsets; + dns_name_t tmp_name; + unsigned int prefix_len, n; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + /* + * 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 (ISC_UNLIKELY(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, *parent, *child, *new_parent, *sibling; + dns_rpz_addr_zbits_t set; + int cur_num, child_num; + dns_rpz_prefix_t dbit; + isc_result_t find_result; + + set = *tgt_set; + find_result = ISC_R_NOTFOUND; + *found = NULL; + cur = rpzs->cidr; + parent = NULL; + cur_num = 0; + for (;;) { + 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_zones_t *rpzs, dns_rpz_num_t rpz_num, 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; + isc_result_t result; + + result = name2ipkey(DNS_RPZ_ERROR_LEVEL, rpzs, rpz_num, 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(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(rpzs, rpz_num, 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; + dns_rpz_nm_data_t *nm_data; + 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_zones_t *rpzs, dns_rpz_num_t rpz_num, 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; + 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(rpzs, rpz_num, rpz_type, src_name, trig_name, &new_data); + + result = add_nm(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(rpzs, rpz_num, 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(dns_rpz_zones_t **rpzsp, char *rps_cstr, size_t rps_cstr_size, + isc_mem_t *mctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr) { + dns_rpz_zones_t *zones; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + + zones = isc_mem_get(mctx, sizeof(*zones)); + memset(zones, 0, sizeof(*zones)); + + isc_rwlock_init(&zones->search_lock, 0, 0); + isc_mutex_init(&zones->maint_lock); + isc_refcount_init(&zones->refs, 1); + isc_refcount_init(&zones->irefs, 1); + + zones->rps_cstr = rps_cstr; + zones->rps_cstr_size = rps_cstr_size; +#ifdef USE_DNSRPS + if (rps_cstr != NULL) { + result = dns_dnsrps_view_init(zones, rps_cstr); + } +#else /* ifdef USE_DNSRPS */ + INSIST(!zones->p.dnsrps_enabled); +#endif /* ifdef USE_DNSRPS */ + if (result == ISC_R_SUCCESS && !zones->p.dnsrps_enabled) { + result = dns_rbt_create(mctx, rpz_node_deleter, mctx, + &zones->rbt); + } + + if (result != ISC_R_SUCCESS) { + goto cleanup_rbt; + } + + result = isc_task_create(taskmgr, 0, &zones->updater); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + + isc_mem_attach(mctx, &zones->mctx); + zones->timermgr = timermgr; + zones->taskmgr = taskmgr; + + *rpzsp = zones; + return (ISC_R_SUCCESS); + +cleanup_task: + dns_rbt_destroy(&zones->rbt); + +cleanup_rbt: + isc_refcount_decrementz(&zones->irefs); + isc_refcount_destroy(&zones->irefs); + isc_refcount_decrementz(&zones->refs); + isc_refcount_destroy(&zones->refs); + isc_mutex_destroy(&zones->maint_lock); + isc_rwlock_destroy(&zones->search_lock); + isc_mem_put(mctx, zones, sizeof(*zones)); + + return (result); +} + +isc_result_t +dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) { + dns_rpz_zone_t *zone; + isc_result_t result; + + REQUIRE(rpzp != NULL && *rpzp == NULL); + REQUIRE(rpzs != NULL); + if (rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) { + return (ISC_R_NOSPACE); + } + + zone = isc_mem_get(rpzs->mctx, sizeof(*zone)); + + memset(zone, 0, sizeof(*zone)); + isc_refcount_init(&zone->refs, 1); + + result = isc_timer_create(rpzs->timermgr, isc_timertype_inactive, NULL, + NULL, rpzs->updater, + dns_rpz_update_taskaction, zone, + &zone->updatetimer); + if (result != ISC_R_SUCCESS) { + goto cleanup_timer; + } + + /* + * This will never be used, but costs us nothing and + * simplifies update_from_db + */ + + isc_ht_init(&zone->nodes, rpzs->mctx, 1); + + dns_name_init(&zone->origin, NULL); + dns_name_init(&zone->client_ip, NULL); + dns_name_init(&zone->ip, NULL); + dns_name_init(&zone->nsdname, NULL); + dns_name_init(&zone->nsip, NULL); + dns_name_init(&zone->passthru, NULL); + dns_name_init(&zone->drop, NULL); + dns_name_init(&zone->tcp_only, NULL); + dns_name_init(&zone->cname, NULL); + + isc_time_settoepoch(&zone->lastupdated); + zone->updatepending = false; + zone->updaterunning = false; + zone->db = NULL; + zone->dbversion = NULL; + zone->updb = NULL; + zone->updbversion = NULL; + zone->updbit = NULL; + isc_refcount_increment(&rpzs->irefs); + zone->rpzs = rpzs; + zone->db_registered = false; + zone->addsoa = true; + ISC_EVENT_INIT(&zone->updateevent, sizeof(zone->updateevent), 0, NULL, + 0, NULL, NULL, NULL, NULL, NULL); + + zone->num = rpzs->p.num_zones++; + rpzs->zones[zone->num] = zone; + + *rpzp = zone; + + return (ISC_R_SUCCESS); + +cleanup_timer: + isc_refcount_decrementz(&zone->refs); + isc_refcount_destroy(&zone->refs); + + isc_mem_put(rpzs->mctx, zone, sizeof(*zone)); + + return (result); +} + +isc_result_t +dns_rpz_dbupdate_callback(dns_db_t *db, void *fn_arg) { + dns_rpz_zone_t *zone = (dns_rpz_zone_t *)fn_arg; + isc_time_t now; + uint64_t tdiff; + isc_result_t result = ISC_R_SUCCESS; + char dname[DNS_NAME_FORMATSIZE]; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(zone != NULL); + + LOCK(&zone->rpzs->maint_lock); + + /* New zone came as AXFR */ + if (zone->db != NULL && zone->db != db) { + /* We need to clean up the old DB */ + if (zone->dbversion != NULL) { + dns_db_closeversion(zone->db, &zone->dbversion, false); + } + dns_db_updatenotify_unregister(zone->db, + dns_rpz_dbupdate_callback, zone); + dns_db_detach(&zone->db); + } + + if (zone->db == NULL) { + RUNTIME_CHECK(zone->dbversion == NULL); + dns_db_attach(db, &zone->db); + } + + if (!zone->updatepending && !zone->updaterunning) { + zone->updatepending = true; + isc_time_now(&now); + tdiff = isc_time_microdiff(&now, &zone->lastupdated) / 1000000; + if (tdiff < zone->min_update_interval) { + uint64_t defer = zone->min_update_interval - tdiff; + isc_interval_t interval; + dns_name_format(&zone->origin, dname, + DNS_NAME_FORMATSIZE); + 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(zone->db, &zone->dbversion); + result = isc_timer_reset(zone->updatetimer, + isc_timertype_once, NULL, + &interval, true); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } else { + isc_event_t *event; + + dns_db_currentversion(zone->db, &zone->dbversion); + INSIST(!ISC_LINK_LINKED(&zone->updateevent, ev_link)); + ISC_EVENT_INIT(&zone->updateevent, + sizeof(zone->updateevent), 0, NULL, + DNS_EVENT_RPZUPDATED, + dns_rpz_update_taskaction, zone, zone, + NULL, NULL); + event = &zone->updateevent; + isc_task_send(zone->rpzs->updater, &event); + } + } else { + zone->updatepending = true; + dns_name_format(&zone->origin, dname, DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), + "rpz: %s: update already queued or running", + dname); + if (zone->dbversion != NULL) { + dns_db_closeversion(zone->db, &zone->dbversion, false); + } + dns_db_currentversion(zone->db, &zone->dbversion); + } + +cleanup: + UNLOCK(&zone->rpzs->maint_lock); + + return (result); +} + +static void +dns_rpz_update_taskaction(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_rpz_zone_t *zone; + + REQUIRE(event != NULL); + REQUIRE(event->ev_arg != NULL); + + UNUSED(task); + zone = (dns_rpz_zone_t *)event->ev_arg; + isc_event_free(&event); + LOCK(&zone->rpzs->maint_lock); + zone->updatepending = false; + zone->updaterunning = true; + dns_rpz_update_from_db(zone); + result = isc_timer_reset(zone->updatetimer, isc_timertype_inactive, + NULL, NULL, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = isc_time_now(&zone->lastupdated); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + UNLOCK(&zone->rpzs->maint_lock); +} + +static isc_result_t +setup_update(dns_rpz_zone_t *rpz) { + isc_result_t result; + char domain[DNS_NAME_FORMATSIZE]; + unsigned int nodecount; + uint32_t hashsize; + + 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); + + nodecount = dns_db_nodecount(rpz->updb); + hashsize = 1; + while (nodecount != 0 && + hashsize <= (DNS_RPZ_HTSIZE_MAX + DNS_RPZ_HTSIZE_DIV)) + { + hashsize++; + nodecount >>= 1; + } + + if (hashsize > DNS_RPZ_HTSIZE_DIV) { + hashsize -= DNS_RPZ_HTSIZE_DIV; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(1), "rpz: %s: using hashtable size %d", + domain, hashsize); + + isc_ht_init(&rpz->newnodes, rpz->rpzs->mctx, hashsize); + + result = dns_db_createiterator(rpz->updb, DNS_DB_NONSEC3, &rpz->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)); + goto cleanup; + } + + result = dns_dbiterator_first(rpz->updbit); + if (result != ISC_R_SUCCESS) { + 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; + } + + result = dns_dbiterator_pause(rpz->updbit); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, + "rpz: %s: failed to pause db iterator - %s", + domain, isc_result_totext(result)); + goto cleanup; + } + +cleanup: + if (result != ISC_R_SUCCESS) { + if (rpz->updbit != NULL) { + dns_dbiterator_destroy(&rpz->updbit); + } + if (rpz->newnodes != NULL) { + isc_ht_destroy(&rpz->newnodes); + } + dns_db_closeversion(rpz->updb, &rpz->updbversion, false); + } + + return (result); +} + +static void +finish_update(dns_rpz_zone_t *rpz) { + LOCK(&rpz->rpzs->maint_lock); + rpz->updaterunning = false; + + /* + * If there's an update pending, schedule it. + */ + if (rpz->updatepending) { + if (rpz->min_update_interval > 0) { + uint64_t defer = rpz->min_update_interval; + char dname[DNS_NAME_FORMATSIZE]; + isc_interval_t interval; + + dns_name_format(&rpz->origin, dname, + DNS_NAME_FORMATSIZE); + 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); + 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_update_taskaction, rpz, rpz, + NULL, NULL); + event = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &event); + } + } + UNLOCK(&rpz->rpzs->maint_lock); +} + +static void +cleanup_quantum(isc_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + char domain[DNS_NAME_FORMATSIZE]; + dns_rpz_zone_t *rpz = NULL; + isc_ht_iter_t *iter = NULL; + dns_fixedname_t fname; + dns_name_t *name = NULL; + int count = 0; + + UNUSED(task); + + REQUIRE(event != NULL); + REQUIRE(event->ev_sender != NULL); + + rpz = (dns_rpz_zone_t *)event->ev_sender; + iter = (isc_ht_iter_t *)event->ev_arg; + isc_event_free(&event); + + if (iter == NULL) { + /* + * Iterate over old ht with existing nodes deleted to + * delete deleted nodes from RPZ + */ + isc_ht_iter_create(rpz->nodes, &iter); + } + + name = dns_fixedname_initname(&fname); + + LOCK(&rpz->rpzs->maint_lock); + + /* Check that we aren't shutting down. */ + if (rpz->rpzs->zones[rpz->num] == NULL) { + UNLOCK(&rpz->rpzs->maint_lock); + goto cleanup; + } + + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM; + result = isc_ht_iter_delcurrent_next(iter)) + { + isc_region_t region; + unsigned char *key = NULL; + size_t keysize; + + isc_ht_iter_currentkey(iter, &key, &keysize); + region.base = key; + region.length = (unsigned int)keysize; + dns_name_fromregion(name, ®ion); + dns_rpz_delete(rpz->rpzs, rpz->num, name); + } + + if (result == ISC_R_SUCCESS) { + isc_event_t *nevent = NULL; + + /* + * We finished a quantum; trigger the next one and return. + */ + + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, + NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum, + iter, rpz, NULL, NULL); + nevent = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &nevent); + UNLOCK(&rpz->rpzs->maint_lock); + return; + } else if (result == ISC_R_NOMORE) { + isc_ht_t *tmpht = NULL; + + /* + * Done with cleanup of deleted nodes; finalize + * the update. + */ + tmpht = rpz->nodes; + rpz->nodes = rpz->newnodes; + rpz->newnodes = tmpht; + + UNLOCK(&rpz->rpzs->maint_lock); + finish_update(rpz); + 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 done", domain); + } else { + UNLOCK(&rpz->rpzs->maint_lock); + } + + /* + * If we're here, we're finished or something went wrong. + */ +cleanup: + if (iter != NULL) { + isc_ht_iter_destroy(&iter); + } + if (rpz->newnodes != NULL) { + isc_ht_destroy(&rpz->newnodes); + } + dns_db_closeversion(rpz->updb, &rpz->updbversion, false); + dns_db_detach(&rpz->updb); + rpz_detach(&rpz); +} + +static void +update_quantum(isc_task_t *task, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + dns_dbnode_t *node = NULL; + dns_rpz_zone_t *rpz = NULL; + char domain[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixname; + dns_name_t *name = NULL; + isc_event_t *nevent = NULL; + int count = 0; + + UNUSED(task); + + REQUIRE(event != NULL); + REQUIRE(event->ev_arg != NULL); + + rpz = (dns_rpz_zone_t *)event->ev_arg; + isc_event_free(&event); + + REQUIRE(rpz->updbit != NULL); + REQUIRE(rpz->newnodes != NULL); + + name = dns_fixedname_initname(&fixname); + + dns_name_format(&rpz->origin, domain, DNS_NAME_FORMATSIZE); + + LOCK(&rpz->rpzs->maint_lock); + + /* Check that we aren't shutting down. */ + if (rpz->rpzs->zones[rpz->num] == NULL) { + UNLOCK(&rpz->rpzs->maint_lock); + goto cleanup; + } + + while (result == ISC_R_SUCCESS && count++ < DNS_RPZ_QUANTUM) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_rdatasetiter_t *rdsiter = NULL; + + result = dns_dbiterator_current(rpz->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)); + dns_db_detachnode(rpz->updb, &node); + break; + } + + 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); + break; + } + + result = dns_rdatasetiter_first(rdsiter); + dns_rdatasetiter_destroy(&rdsiter); + if (result != ISC_R_SUCCESS) { /* 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)); + } + dns_db_detachnode(rpz->updb, &node); + result = dns_dbiterator_next(rpz->updbit); + continue; + } + + dns_name_downcase(name, name, NULL); + result = isc_ht_add(rpz->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)); + dns_db_detachnode(rpz->updb, &node); + result = dns_dbiterator_next(rpz->updbit); + continue; + } + + result = isc_ht_find(rpz->nodes, name->ndata, name->length, + NULL); + if (result == ISC_R_SUCCESS) { + isc_ht_delete(rpz->nodes, name->ndata, name->length); + } else { /* not found */ + result = dns_rpz_add(rpz->rpzs, rpz->num, name); + 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 { + 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); + } + } + + dns_db_detachnode(rpz->updb, &node); + result = dns_dbiterator_next(rpz->updbit); + } + + if (result == ISC_R_SUCCESS) { + /* + * Pause the iterator so that the DB is not locked. + */ + dns_dbiterator_pause(rpz->updbit); + + /* + * We finished a quantum; trigger the next one and return. + */ + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, + NULL, DNS_EVENT_RPZUPDATED, update_quantum, rpz, + rpz, NULL, NULL); + nevent = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &nevent); + UNLOCK(&rpz->rpzs->maint_lock); + return; + } else if (result == ISC_R_NOMORE) { + /* + * Done with the new database; now we just need to + * clean up the old. + */ + dns_dbiterator_destroy(&rpz->updbit); + + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, + NULL, DNS_EVENT_RPZUPDATED, cleanup_quantum, + NULL, rpz, NULL, NULL); + nevent = &rpz->updateevent; + isc_task_send(rpz->rpzs->updater, &nevent); + UNLOCK(&rpz->rpzs->maint_lock); + return; + } + + /* + * If we're here, something went wrong, so clean up. + */ + UNLOCK(&rpz->rpzs->maint_lock); + +cleanup: + if (rpz->updbit != NULL) { + dns_dbiterator_destroy(&rpz->updbit); + } + if (rpz->newnodes != NULL) { + isc_ht_destroy(&rpz->newnodes); + } + dns_db_closeversion(rpz->updb, &rpz->updbversion, false); + dns_db_detach(&rpz->updb); + rpz_detach(&rpz); +} + +static void +dns_rpz_update_from_db(dns_rpz_zone_t *rpz) { + isc_result_t result; + isc_event_t *event; + + REQUIRE(rpz != NULL); + REQUIRE(DNS_DB_VALID(rpz->db)); + REQUIRE(rpz->updb == NULL); + REQUIRE(rpz->updbversion == NULL); + REQUIRE(rpz->updbit == NULL); + REQUIRE(rpz->newnodes == NULL); + + isc_refcount_increment(&rpz->refs); + dns_db_attach(rpz->db, &rpz->updb); + rpz->updbversion = rpz->dbversion; + rpz->dbversion = NULL; + + result = setup_update(rpz); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + event = &rpz->updateevent; + INSIST(!ISC_LINK_LINKED(&rpz->updateevent, ev_link)); + ISC_EVENT_INIT(&rpz->updateevent, sizeof(rpz->updateevent), 0, NULL, + DNS_EVENT_RPZUPDATED, update_quantum, rpz, rpz, NULL, + NULL); + isc_task_send(rpz->rpzs->updater, &event); + return; + +cleanup: + if (rpz->updbit != NULL) { + dns_dbiterator_destroy(&rpz->updbit); + } + if (rpz->newnodes != NULL) { + isc_ht_destroy(&rpz->newnodes); + } + dns_db_closeversion(rpz->updb, &rpz->updbversion, false); + dns_db_detach(&rpz->updb); + rpz_detach(&rpz); +} + +/* + * Free the radix tree of a response policy database. + */ +static void +cidr_free(dns_rpz_zones_t *rpzs) { + dns_rpz_cidr_node_t *cur, *child, *parent; + + 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; + } +} + +/* + * Discard a response policy zone blob + * before discarding the overall rpz structure. + */ +static void +rpz_detach(dns_rpz_zone_t **rpzp) { + dns_rpz_zone_t *rpz; + dns_rpz_zones_t *rpzs; + + REQUIRE(rpzp != NULL && *rpzp != NULL); + + rpz = *rpzp; + *rpzp = NULL; + + if (isc_refcount_decrement(&rpz->refs) == 1) { + isc_refcount_destroy(&rpz->refs); + + 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); + } + if (rpz->updaterunning) { + isc_task_purgeevent(rpzs->updater, &rpz->updateevent); + if (rpz->updbit != NULL) { + dns_dbiterator_destroy(&rpz->updbit); + } + if (rpz->newnodes != NULL) { + isc_ht_destroy(&rpz->newnodes); + } + if (rpz->updb != NULL) { + if (rpz->updbversion != NULL) { + dns_db_closeversion(rpz->updb, + &rpz->updbversion, + false); + } + dns_db_detach(&rpz->updb); + } + } + + 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)); + rpz_detach_rpzs(&rpzs); + } +} + +void +dns_rpz_attach_rpzs(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **rpzsp) { + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + isc_refcount_increment(&rpzs->refs); + *rpzsp = rpzs; +} + +/* + * Forget a view's policy zones. + */ +void +dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) { + REQUIRE(rpzsp != NULL && *rpzsp != NULL); + dns_rpz_zones_t *rpzs = *rpzsp; + *rpzsp = NULL; + + if (isc_refcount_decrement(&rpzs->refs) == 1) { + LOCK(&rpzs->maint_lock); + /* + * Forget the last of view's rpz machinery after + * the last reference. + */ + for (dns_rpz_num_t rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; + ++rpz_num) + { + dns_rpz_zone_t *rpz = rpzs->zones[rpz_num]; + rpzs->zones[rpz_num] = NULL; + if (rpz != NULL) { + rpz_detach(&rpz); + } + } + UNLOCK(&rpzs->maint_lock); + rpz_detach_rpzs(&rpzs); + } +} + +static void +rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) { + REQUIRE(rpzsp != NULL && *rpzsp != NULL); + dns_rpz_zones_t *rpzs = *rpzsp; + *rpzsp = NULL; + + if (isc_refcount_decrement(&rpzs->irefs) == 1) { + 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_destroy(&rpzs->updater); + isc_mutex_destroy(&rpzs->maint_lock); + isc_rwlock_destroy(&rpzs->search_lock); + isc_refcount_destroy(&rpzs->refs); + isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs)); + } +} + +/* + * Deprecated and removed. + */ +isc_result_t +dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num) { + UNUSED(load_rpzsp); + UNUSED(rpzs); + UNUSED(rpz_num); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * Deprecated and removed. + */ +isc_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, dns_rpz_zones_t **load_rpzsp, + dns_rpz_num_t rpz_num) { + UNUSED(rpzs); + UNUSED(load_rpzsp); + UNUSED(rpz_num); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * Add an IP address to the radix tree or a name to the summary database. + */ +isc_result_t +dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + const dns_name_t *src_name) { + dns_rpz_zone_t *rpz; + dns_rpz_type_t rpz_type; + isc_result_t result = ISC_R_FAILURE; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + 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(rpzs, rpz_num, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + result = add_cidr(rpzs, rpz_num, 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_zones_t *rpzs, dns_rpz_num_t rpz_num, 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, *parent, *child; + + /* + * 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, rpzs, rpz_num, rpz_type, + src_name, &tgt_ip, &tgt_prefix, &tgt_set); + if (result != ISC_R_SUCCESS) { + return; + } + + result = search(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(rpzs, rpz_num, 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) { + 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(rpzs->mctx, tgt, sizeof(*tgt)); + + tgt = parent; + } while (tgt != NULL); +} + +static void +del_name(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, 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; + dns_rbtnode_t *nmnode; + dns_rpz_nm_data_t *nm_data, 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(rpzs, rpz_num, rpz_type, src_name, trig_name, &del_data); + + nmnode = NULL; + result = dns_rbt_findnode(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(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(rpzs, rpz_num, rpz_type, NULL, 0, false); + } +} + +/* + * Remove an IP address from the radix tree or a name from the summary database. + */ +void +dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, + const dns_name_t *src_name) { + dns_rpz_zone_t *rpz; + dns_rpz_type_t rpz_type; + + REQUIRE(rpzs != NULL && rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + 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(rpzs, rpz_num, rpz_type, src_name); + break; + case DNS_RPZ_TYPE_CLIENT_IP: + case DNS_RPZ_TYPE_IP: + case DNS_RPZ_TYPE_NSIP: + del_cidr(rpzs, rpz_num, 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; + 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(); + } + } 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(); + } + } 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; + const dns_rpz_nm_data_t *nm_data; + 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..9b40597 --- /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..a8c2525 --- /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_copynf(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 (ISC_R_SUCCESS); + } + + /* + * 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..2c6802f --- /dev/null +++ b/lib/dns/sdb.c @@ -0,0 +1,1613 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_copynf(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) { + UNUSED(db); + + 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, + NULL, /* serialize */ + 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 */ + NULL /* adjusthashsize */ +}; + +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_copynf(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_copynf(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..8ba7aae --- /dev/null +++ b/lib/dns/sdlz.c @@ -0,0 +1,2108 @@ +/* + * 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_copynf(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) { + UNUSED(db); + + 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, + NULL, /* serialize */ + 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 */ + NULL /* adjusthashsize */ +}; + +/* + * 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_copynf(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_copynf(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..1529a61 --- /dev/null +++ b/lib/dns/ssu.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_rdatatype_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; +}; + +isc_result_t +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; + return (ISC_R_SUCCESS); +} + +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(dns_name_t)); + } + if (rule->name != NULL) { + dns_name_free(rule->name, mctx); + isc_mem_put(mctx, rule->name, sizeof(dns_name_t)); + } + if (rule->types != NULL) { + isc_mem_put(mctx, rule->types, + rule->ntypes * sizeof(dns_rdatatype_t)); + } + 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); + } +} + +isc_result_t +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_rdatatype_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(dns_ssurule_t)); + + rule->identity = NULL; + rule->name = NULL; + rule->types = NULL; + + rule->grant = grant; + + rule->identity = isc_mem_get(mctx, sizeof(dns_name_t)); + dns_name_init(rule->identity, NULL); + dns_name_dup(identity, mctx, rule->identity); + + rule->name = isc_mem_get(mctx, sizeof(dns_name_t)); + 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(dns_rdatatype_t)); + memmove(rule->types, types, ntypes * sizeof(dns_rdatatype_t)); + } else { + rule->types = NULL; + } + + rule->magic = SSURULEMAGIC; + ISC_LIST_INITANDAPPEND(table->rules, rule, link); + + return (ISC_R_SUCCESS); +} + +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, const dns_aclenv_t *env, dns_rdatatype_t type, + const dst_key_t *key) { + dns_ssurule_t *rule; + unsigned int i; + dns_fixedname_t fixed; + dns_name_t *wildcard; + dns_name_t *tcpself; + dns_name_t *stfself; + isc_result_t result; + int match; + + 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_name: + case dns_ssumatchtype_local: + case dns_ssumatchtype_subdomain: + case dns_ssumatchtype_wildcard: + case dns_ssumatchtype_self: + case dns_ssumatchtype_selfsub: + case dns_ssumatchtype_selfwild: + 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: + 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; + } + dns_acl_match(addr, NULL, env->localhost, NULL, &match, + NULL); + 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: + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + if (dst_gssapi_identitymatchesrealmkrb5( + signer, NULL, rule->identity, false)) + { + break; + } + continue; + case dns_ssumatchtype_subdomainms: + if (!dns_name_issubdomain(name, rule->name)) { + continue; + } + if (dst_gssapi_identitymatchesrealmms( + signer, NULL, 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] == dns_rdatatype_any || + rule->types[i] == type) + { + break; + } + } + if (i == rule->ntypes) { + continue; + } + } + 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_rdatatype_t **types) { + REQUIRE(VALID_SSURULE(rule)); + REQUIRE(types != NULL && *types != NULL); + *types = rule->types; + return (rule->ntypes); +} + +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 + */ +isc_result_t +dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, + dns_dlzdb_t *dlzdatabase) { + isc_result_t result; + dns_ssurule_t *rule; + dns_ssutable_t *table = NULL; + + REQUIRE(tablep != NULL && *tablep == NULL); + + result = dns_ssutable_create(mctx, &table); + if (result != ISC_R_SUCCESS) { + return (result); + } + + table->dlzdatabase = dlzdatabase; + + rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t)); + + rule->identity = NULL; + rule->name = NULL; + rule->types = 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; + return (ISC_R_SUCCESS); +} + +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, "krb5-subdomain") == 0) { + *mtype = dns_ssumatchtype_subdomainkrb5; + } 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..d5e4715 --- /dev/null +++ b/lib/dns/ssu_external.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. + */ + +/* + * 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 + +#ifdef ISC_PLATFORM_HAVESYSUNH +#include +#include +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + +#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; +#ifdef ISC_PLATFORM_HAVESYSUNH + 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); + } +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + 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..bdd98c5 --- /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: + ***/ +LIBDNS_EXTERNAL_DATA 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/tcpmsg.c b/lib/dns/tcpmsg.c new file mode 100644 index 0000000..44694d8 --- /dev/null +++ b/lib/dns/tcpmsg.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. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef TCPMSG_DEBUG +#include /* Required for printf. */ +#define XDEBUG(x) printf x +#else /* ifdef TCPMSG_DEBUG */ +#define XDEBUG(x) +#endif /* ifdef TCPMSG_DEBUG */ + +#define TCPMSG_MAGIC ISC_MAGIC('T', 'C', 'P', 'm') +#define VALID_TCPMSG(foo) ISC_MAGIC_VALID(foo, TCPMSG_MAGIC) + +static void +recv_length(isc_task_t *, isc_event_t *); +static void +recv_message(isc_task_t *, isc_event_t *); + +static void +recv_length(isc_task_t *task, isc_event_t *ev_in) { + isc_socketevent_t *ev = (isc_socketevent_t *)ev_in; + isc_event_t *dev; + dns_tcpmsg_t *tcpmsg = ev_in->ev_arg; + isc_region_t region; + isc_result_t result; + + INSIST(VALID_TCPMSG(tcpmsg)); + + dev = &tcpmsg->event; + tcpmsg->address = ev->address; + + if (ev->result != ISC_R_SUCCESS) { + tcpmsg->result = ev->result; + goto send_and_free; + } + + /* + * Success. + */ + tcpmsg->size = ntohs(tcpmsg->size); + if (tcpmsg->size == 0) { + tcpmsg->result = ISC_R_UNEXPECTEDEND; + goto send_and_free; + } + if (tcpmsg->size > tcpmsg->maxsize) { + tcpmsg->result = ISC_R_RANGE; + goto send_and_free; + } + + region.base = isc_mem_get(tcpmsg->mctx, tcpmsg->size); + region.length = tcpmsg->size; + if (region.base == NULL) { + tcpmsg->result = ISC_R_NOMEMORY; + goto send_and_free; + } + XDEBUG(("Allocated %d bytes\n", tcpmsg->size)); + + isc_buffer_init(&tcpmsg->buffer, region.base, region.length); + result = isc_socket_recv(tcpmsg->sock, ®ion, 0, task, recv_message, + tcpmsg); + if (result != ISC_R_SUCCESS) { + tcpmsg->result = result; + goto send_and_free; + } + + isc_event_free(&ev_in); + return; + +send_and_free: + isc_task_send(tcpmsg->task, &dev); + tcpmsg->task = NULL; + isc_event_free(&ev_in); + return; +} + +static void +recv_message(isc_task_t *task, isc_event_t *ev_in) { + isc_socketevent_t *ev = (isc_socketevent_t *)ev_in; + isc_event_t *dev; + dns_tcpmsg_t *tcpmsg = ev_in->ev_arg; + + (void)task; + + INSIST(VALID_TCPMSG(tcpmsg)); + + dev = &tcpmsg->event; + tcpmsg->address = ev->address; + + if (ev->result != ISC_R_SUCCESS) { + tcpmsg->result = ev->result; + goto send_and_free; + } + + tcpmsg->result = ISC_R_SUCCESS; + isc_buffer_add(&tcpmsg->buffer, ev->n); + + XDEBUG(("Received %u bytes (of %d)\n", ev->n, tcpmsg->size)); + +send_and_free: + isc_task_send(tcpmsg->task, &dev); + tcpmsg->task = NULL; + isc_event_free(&ev_in); +} + +void +dns_tcpmsg_init(isc_mem_t *mctx, isc_socket_t *sock, dns_tcpmsg_t *tcpmsg) { + REQUIRE(mctx != NULL); + REQUIRE(sock != NULL); + REQUIRE(tcpmsg != NULL); + + tcpmsg->magic = TCPMSG_MAGIC; + tcpmsg->size = 0; + tcpmsg->buffer.base = NULL; + tcpmsg->buffer.length = 0; + tcpmsg->maxsize = 65535; /* Largest message possible. */ + tcpmsg->mctx = mctx; + tcpmsg->sock = sock; + tcpmsg->task = NULL; /* None yet. */ + tcpmsg->result = ISC_R_UNEXPECTED; /* None yet. */ + + /* Should probably initialize the event here, but it can wait. */ +} + +void +dns_tcpmsg_setmaxsize(dns_tcpmsg_t *tcpmsg, unsigned int maxsize) { + REQUIRE(VALID_TCPMSG(tcpmsg)); + REQUIRE(maxsize < 65536); + + tcpmsg->maxsize = maxsize; +} + +isc_result_t +dns_tcpmsg_readmessage(dns_tcpmsg_t *tcpmsg, isc_task_t *task, + isc_taskaction_t action, void *arg) { + isc_result_t result; + isc_region_t region; + + REQUIRE(VALID_TCPMSG(tcpmsg)); + REQUIRE(task != NULL); + REQUIRE(tcpmsg->task == NULL); /* not currently in use */ + + if (tcpmsg->buffer.base != NULL) { + isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base, + tcpmsg->buffer.length); + tcpmsg->buffer.base = NULL; + tcpmsg->buffer.length = 0; + } + + tcpmsg->task = task; + tcpmsg->action = action; + tcpmsg->arg = arg; + tcpmsg->result = ISC_R_UNEXPECTED; /* unknown right now */ + + ISC_EVENT_INIT(&tcpmsg->event, sizeof(isc_event_t), 0, 0, + DNS_EVENT_TCPMSG, action, arg, tcpmsg, NULL, NULL); + + region.base = (unsigned char *)&tcpmsg->size; + region.length = 2; /* uint16_t */ + result = isc_socket_recv(tcpmsg->sock, ®ion, 0, tcpmsg->task, + recv_length, tcpmsg); + + if (result != ISC_R_SUCCESS) { + tcpmsg->task = NULL; + } + + return (result); +} + +void +dns_tcpmsg_cancelread(dns_tcpmsg_t *tcpmsg) { + REQUIRE(VALID_TCPMSG(tcpmsg)); + + isc_socket_cancel(tcpmsg->sock, NULL, ISC_SOCKCANCEL_RECV); +} + +void +dns_tcpmsg_keepbuffer(dns_tcpmsg_t *tcpmsg, isc_buffer_t *buffer) { + REQUIRE(VALID_TCPMSG(tcpmsg)); + REQUIRE(buffer != NULL); + + *buffer = tcpmsg->buffer; + tcpmsg->buffer.base = NULL; + tcpmsg->buffer.length = 0; +} + +#if 0 +void +dns_tcpmsg_freebuffer(dns_tcpmsg_t *tcpmsg) { + REQUIRE(VALID_TCPMSG(tcpmsg)); + + if (tcpmsg->buffer.base == NULL) { + return; + } + + isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base, tcpmsg->buffer.length); + tcpmsg->buffer.base = NULL; + tcpmsg->buffer.length = 0; +} +#endif /* if 0 */ + +void +dns_tcpmsg_invalidate(dns_tcpmsg_t *tcpmsg) { + REQUIRE(VALID_TCPMSG(tcpmsg)); + + tcpmsg->magic = 0; + + if (tcpmsg->buffer.base != NULL) { + isc_mem_put(tcpmsg->mctx, tcpmsg->buffer.base, + tcpmsg->buffer.length); + tcpmsg->buffer.base = NULL; + tcpmsg->buffer.length = 0; + } +} diff --git a/lib/dns/tests/Kdh.+002+18602.key b/lib/dns/tests/Kdh.+002+18602.key new file mode 100644 index 0000000..09b4cf5 --- /dev/null +++ b/lib/dns/tests/Kdh.+002+18602.key @@ -0,0 +1 @@ +dh. IN KEY 0 2 2 AAEBAAAAYIHI/wjtOagNga9GILSoS02IVelgLilPE/TfhtvShsiDAXqb IfxQcj2JkuOnNLs5ttb2WZXWl5/jsSjIxHMwMF2XY4gwt/lwHBf/vgYH r7aIxnKXov1jk9rymTLHGKIOtg== diff --git a/lib/dns/tests/Krsa.+008+29238.key b/lib/dns/tests/Krsa.+008+29238.key new file mode 100644 index 0000000..8a09067 --- /dev/null +++ b/lib/dns/tests/Krsa.+008+29238.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 29235, for rsa. +; Created: 20160819191802 (Fri Aug 19 21:18:02 2016) +; Publish: 20160819191802 (Fri Aug 19 21:18:02 2016) +; Activate: 20160819191802 (Fri Aug 19 21:18:02 2016) +rsa. IN DNSKEY 256 3 8 AwEAAdLT1R3qiqCqll3Xzh2qFMvehQ9FODsPftw5U4UjB3QwnJ/3+dph 9kZBBeaJagUBVYzoArk6XNydpp3HhSCFDcIiepL6r8XAifW3SqI1KCne OD38kSCl/Qm9P0+3CFWokGVubsSQ+3dpQZxqx5bzOXthbuzAr6X+gDUE LAyHtCQNmJ+4ktdCoj3DNYW0z/xLvrcB2Lns7H+/qWnGPL4f3hr7Vbak Oeay+4J4KGdY2LFxJUVts6QrgAA8gz4mV9YIJFP+C4B3b/Z7qgqZRxmT 0pic+fJC5+sq0l8KwavPn0n+HqVuJNvppVKMdTbsmmuk69RFGMjbFkP7 tnCiqC9Zi6s= diff --git a/lib/dns/tests/Kyuafile b/lib/dns/tests/Kyuafile new file mode 100644 index 0000000..53b4686 --- /dev/null +++ b/lib/dns/tests/Kyuafile @@ -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. + +syntax(2) +test_suite('bind9') + +tap_test_program{name='acl_test'} +tap_test_program{name='db_test'} +tap_test_program{name='dbdiff_test'} +tap_test_program{name='dbiterator_test'} +tap_test_program{name='dbversion_test'} +tap_test_program{name='dh_test'} +tap_test_program{name='dispatch_test'} +tap_test_program{name='dnstap_test'} +tap_test_program{name='dst_test'} +tap_test_program{name='geoip_test'} +tap_test_program{name='keytable_test'} +tap_test_program{name='master_test'} +tap_test_program{name='name_test'} +tap_test_program{name='nsec3_test'} +tap_test_program{name='peer_test'} +tap_test_program{name='private_test'} +tap_test_program{name='rbt_serialize_test', is_exclusive=true} +tap_test_program{name='rbt_test'} +tap_test_program{name='rbtdb_test'} +tap_test_program{name='rdata_test'} +tap_test_program{name='rdataset_test'} +tap_test_program{name='rdatasetstats_test'} +tap_test_program{name='resolver_test'} +tap_test_program{name='result_test'} +tap_test_program{name='rsa_test'} +tap_test_program{name='sigs_test'} +tap_test_program{name='time_test'} +tap_test_program{name='tsig_test'} +tap_test_program{name='update_test'} +tap_test_program{name='zonemgr_test'} +tap_test_program{name='zt_test'} diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in new file mode 100644 index 0000000..90c1371 --- /dev/null +++ b/lib/dns/tests/Makefile.in @@ -0,0 +1,287 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I. -Iinclude ${DNS_INCLUDES} ${ISC_INCLUDES} \ + ${FSTRM_CFLAGS} ${OPENSSL_CFLAGS} \ + ${PROTOBUF_C_CFLAGS} ${MAXMINDDB_CFLAGS} @CMOCKA_CFLAGS@ +CDEFINES = -DTESTS="\"${top_builddir}/lib/dns/tests/\"" + +ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@ +ISCDEPLIBS = ../../isc/libisc.@A@ +DNSLIBS = ../libdns.@A@ @NO_LIBTOOL_DNSLIBS@ +DNSDEPLIBS = ../libdns.@A@ + +LIBS = @LIBS@ @CMOCKA_LIBS@ + +OBJS = dnstest.@O@ +SRCS = acl_test.c \ + db_test.c \ + dbdiff_test.c \ + dbiterator_test.c \ + dh_test.c \ + dispatch_test.c \ + dnstap_test.c \ + dst_test.c \ + dnstest.c \ + geoip_test.c \ + keytable_test.c \ + master_test.c \ + name_test.c \ + nsec3_test.c \ + nsec3param_test.c \ + peer_test.c \ + private_test.c \ + rbt_test.c \ + rbt_serialize_test.c \ + rbtdb_test.c \ + rdata_test.c \ + rdataset_test.c \ + rdatasetstats_test.c \ + resolver_test.c \ + result_test.c \ + rsa_test.c \ + sigs_test.c \ + time_test.c \ + tsig_test.c \ + update_test.c \ + zonemgr_test.c \ + zt_test.c + +SUBDIRS = +TARGETS = acl_test@EXEEXT@ \ + db_test@EXEEXT@ \ + dbdiff_test@EXEEXT@ \ + dbiterator_test@EXEEXT@ \ + dbversion_test@EXEEXT@ \ + dh_test@EXEEXT@ \ + dispatch_test@EXEEXT@ \ + dnstap_test@EXEEXT@ \ + dst_test@EXEEXT@ \ + geoip_test@EXEEXT@ \ + keytable_test@EXEEXT@ \ + master_test@EXEEXT@ \ + name_test@EXEEXT@ \ + nsec3_test@EXEEXT@ \ + nsec3param_test@EXEEXT@ \ + peer_test@EXEEXT@ \ + private_test@EXEEXT@ \ + rbt_test@EXEEXT@ \ + rbt_serialize_test@EXEEXT@ \ + rbtdb_test@EXEEXT@ \ + rdata_test@EXEEXT@ \ + rdataset_test@EXEEXT@ \ + rdatasetstats_test@EXEEXT@ \ + resolver_test@EXEEXT@ \ + result_test@EXEEXT@ \ + rsa_test@EXEEXT@ \ + sigs_test@EXEEXT@ \ + time_test@EXEEXT@ \ + tsig_test@EXEEXT@ \ + update_test@EXEEXT@ \ + zonemgr_test@EXEEXT@ \ + zt_test@EXEEXT@ + +@BIND9_MAKE_RULES@ + +LD_WRAP_TESTS=@LD_WRAP_TESTS@ + +acl_test@EXEEXT@: acl_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ acl_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + +db_test@EXEEXT@: db_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ db_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + +dbdiff_test@EXEEXT@: dbdiff_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dbdiff_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dbiterator_test@EXEEXT@: dbiterator_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dbiterator_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dbversion_test@EXEEXT@: dbversion_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dbversion_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dh_test@EXEEXT@: dh_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dh_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dispatch_test@EXEEXT@: dispatch_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dispatch_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dnstap_test@EXEEXT@: dnstap_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dnstap_test.@O@ dnstest.@O@ \ + ${FSTRM_LIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + +dst_test@EXEEXT@: dst_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ dst_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +geoip_test@EXEEXT@: geoip_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ geoip_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${MAXMINDDB_LIBS} ${ISCLIBS} ${LIBS} + +keytable_test@EXEEXT@: keytable_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ keytable_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +master_test@EXEEXT@: master_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + test -d testdata || mkdir testdata + test -d testdata/master || mkdir testdata/master + ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master12.data.in \ + > testdata/master/master12.data + ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master13.data.in \ + > testdata/master/master13.data + ${PERL} ${srcdir}/mkraw.pl < ${srcdir}/testdata/master/master14.data.in \ + > testdata/master/master14.data + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ master_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +name_test@EXEEXT@: name_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ name_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +nsec3_test@EXEEXT@: nsec3_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ nsec3_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +nsec3param_test@EXEEXT@: nsec3param_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ nsec3param_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +peer_test@EXEEXT@: peer_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ peer_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +private_test@EXEEXT@: private_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ private_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rbt_serialize_test@EXEEXT@: rbt_serialize_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rbt_serialize_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rbt_test@EXEEXT@: rbt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rbt_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rbtdb_test@EXEEXT@: rbtdb_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rbtdb_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rdata_test@EXEEXT@: rdata_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rdata_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rdataset_test@EXEEXT@: rdataset_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rdataset_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rdatasetstats_test@EXEEXT@: rdatasetstats_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rdatasetstats_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +resolver_test@EXEEXT@: resolver_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ resolver_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ result_test.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +rsa_test@EXEEXT@: rsa_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ rsa_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +sigs_test@EXEEXT@: sigs_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ sigs_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +time_test@EXEEXT@: time_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ time_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +WRAP_OPTIONS = \ + -Wl,--wrap=isc__mem_put \ + -Wl,--wrap=isc__mem_get \ + -Wl,--wrap=isc_mem_attach \ + -Wl,--wrap=isc_mem_detach \ + -Wl,--wrap=isc__mem_putanddetach + +tsig_test@EXEEXT@: tsig_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ tsig_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +update_test@EXEEXT@: update_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ update_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +zonemgr_test@EXEEXT@: zonemgr_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ zonemgr_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +zt_test@EXEEXT@: zt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ zt_test.@O@ dnstest.@O@ \ + ${DNSLIBS} ${ISCLIBS} ${LIBS} + +unit:: + sh ${top_builddir}/unit/unittest.sh + +clean distclean:: + rm -f ${TARGETS} + rm -f atf.out + rm -f testdata/master/master12.data testdata/master/master13.data \ + testdata/master/master14.data + rm -f zone.bin diff --git a/lib/dns/tests/acl_test.c b/lib/dns/tests/acl_test.c new file mode 100644 index 0000000..21941a2 --- /dev/null +++ b/lib/dns/tests/acl_test.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +#define BUFLEN 255 +#define BIGBUFLEN (70 * 1024) +#define TEST_ORIGIN "test" + +/* test that dns_acl_isinsecure works */ +static void +dns_acl_isinsecure_test(void **state) { + isc_result_t result; + dns_acl_t *any = NULL; + dns_acl_t *none = NULL; + dns_acl_t *notnone = NULL; + dns_acl_t *notany = NULL; +#if defined(HAVE_GEOIP2) + dns_acl_t *geoip = NULL; + dns_acl_t *notgeoip = NULL; + dns_aclelement_t *de; +#endif /* HAVE_GEOIP2 */ + + UNUSED(state); + + result = dns_acl_any(dt_mctx, &any); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_none(dt_mctx, &none); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_create(dt_mctx, 1, ¬none); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_create(dt_mctx, 1, ¬any); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notnone, none, false); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notany, any, false); + assert_int_equal(result, ISC_R_SUCCESS); + +#if defined(HAVE_GEOIP2) + result = dns_acl_create(dt_mctx, 1, &geoip); + assert_int_equal(result, ISC_R_SUCCESS); + + de = geoip->elements; + assert_non_null(de); + strlcpy(de->geoip_elem.as_string, "AU", + sizeof(de->geoip_elem.as_string)); + de->geoip_elem.subtype = dns_geoip_country_code; + de->type = dns_aclelementtype_geoip; + de->negative = false; + assert_true(geoip->length < geoip->alloc); + dns_acl_node_count(geoip)++; + de->node_num = dns_acl_node_count(geoip); + geoip->length++; + + result = dns_acl_create(dt_mctx, 1, ¬geoip); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notgeoip, geoip, false); + assert_int_equal(result, ISC_R_SUCCESS); +#endif /* HAVE_GEOIP2 */ + + assert_true(dns_acl_isinsecure(any)); /* any; */ + assert_false(dns_acl_isinsecure(none)); /* none; */ + assert_false(dns_acl_isinsecure(notany)); /* !any; */ + assert_false(dns_acl_isinsecure(notnone)); /* !none; */ + +#if defined(HAVE_GEOIP2) + assert_true(dns_acl_isinsecure(geoip)); /* geoip; */ + assert_false(dns_acl_isinsecure(notgeoip)); /* !geoip; */ +#endif /* HAVE_GEOIP2 */ + + dns_acl_detach(&any); + dns_acl_detach(&none); + dns_acl_detach(¬any); + dns_acl_detach(¬none); +#if defined(HAVE_GEOIP2) + dns_acl_detach(&geoip); + dns_acl_detach(¬geoip); +#endif /* HAVE_GEOIP2 */ +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(dns_acl_isinsecure_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key new file mode 100644 index 0000000..5c43165 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 53461, for example-d. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example-d. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE= diff --git a/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private new file mode 100644 index 0000000..a693428 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-d.+008+53461.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ== +PublicExponent: AQAB +PrivateExponent: AhR3VvVoV6OGOjiiNUt728hidEMoX4PJWtHNWqinyRek5tSnqgaXeKC3NuU0mUIjDvBps9oH4lK3yNa5fBr/nodwP4wNyTd3obR/z6JcLersxJjHi4nYX2ju8vjdsBSIulNudqlrsPhLJe0+Tff3FRfClSQmQ/JtakHo4lIx8zxiOJY8aWFeHGdWJDkAf6NStt3eVYyOyAwISfv3muaGPZKShiIOfLyTvqFqzwYFgdTWmvFqTdwgjIMc5XAwqw73WP2BPCN+fdCiMtrw0fCrhWzw/gfMJBHdOPH0diUZysAJhM0vdVKQzEi/g3YOo00fahZiPzaxNtZnLNj2mA54YQ== +Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE= +Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key new file mode 100644 index 0000000..a4b0d03 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 53973, for example-e. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example-e. IN DNSKEY 256 3 8 BQEAAAABophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2Z V7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ 4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+ m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7k VDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+ oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN 1ik92pzkr2UJAQ== diff --git a/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private new file mode 100644 index 0000000..765ca1a --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-e.+008+53973.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ== +PublicExponent: AQAAAAE= +PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ== +Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE= +Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key new file mode 100644 index 0000000..da2e16a --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 37464, for example-n. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example-n. IN DNSKEY 256 3 8 AwEAAbxHOF8G0xw9ekCodhL8KivuZ3o0jmGlycLiXBjBN8c5R5fjLjUh D0gy3IDbDC+kLaPhHGF/MwrSEjrgSowxZ8nrxDzsq5ZdpeUsYaNrbQEY /mqf35T/9/Ulm4v06x58v/NTugWd05Xq04aAyfm7EViyGFzmVOVfPnll h9xQtvWEWoRWPseFw+dY5/nc/+xB/IsQMihoH2rO+cek/lsP3R9DsHCG RbQ/ks/+rrp6/O+QJZyZrzsONl7mlMDXNy3Pz9J4qMW2W6Mz702LN324 7/9UsetDGGbuZfrCLMpKWXzdsJm36DOk4aMooS9111plfXaXQgQNcL5G 021utpTau+8= diff --git a/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private new file mode 100644 index 0000000..65689a2 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-n.+008+37464.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: vEc4XwbTHD16QKh2EvwqK+5nejSOYaXJwuJcGME3xzlHl+MuNSEPSDLcgNsML6Qto+EcYX8zCtISOuBKjDFnyevEPOyrll2l5Sxho2ttARj+ap/flP/39SWbi/TrHny/81O6BZ3TlerThoDJ+bsRWLIYXOZU5V8+eWWH3FC29YRahFY+x4XD51jn+dz/7EH8ixAyKGgfas75x6T+Ww/dH0OwcIZFtD+Sz/6uunr875AlnJmvOw42XuaUwNc3Lc/P0nioxbZbozPvTYs3fbjv/1Sx60MYZu5l+sIsykpZfN2wmbfoM6ThoyihL3XXWmV9dpdCBA1wvkbTbW62lNq77w== +PublicExponent: AQAB +PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ== +Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE= +Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key new file mode 100644 index 0000000..20ffcfd --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 53461, for example-p. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example-p. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE= diff --git a/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private new file mode 100644 index 0000000..063c925 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-p.+008+53461.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ== +PublicExponent: AQAB +PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ== +Prime1: 5YpfVjEtL1owW9gSFbIMx65POr+fiktxirgy1bc5fSsVqUgG6zhbaN/VpWcNZG0Zg5xd6S7C8V3djGlnJN8wZIyjIh7+Z3WWjqbOD9oY7rC1fR+W0OvbCmZiEzOpRJ5qoMOh1MzkkanhMy0/ICpaa8eQ9zEb80oTIQpFgoLn7K0= +Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key new file mode 100644 index 0000000..7cc002d --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.key @@ -0,0 +1 @@ +example-private. IN KEY 512 3 2 AAECAAAAgKVXnUOFKMvLvwO/VdY9bq+eOPBxrRWsDpcL9FJ9+hklVvii pcLOIhiKLeHI/u9vM2nhd8+opIW92+j2pB185MRgSrINQcC+XpI/xiDG HwE78bQ+2Ykb/memG+ctkVyrFGHtaJLCUGWrUHy1jbtvYeaKeS92jR/2 4oryt3N851u5 diff --git a/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private new file mode 100644 index 0000000..1f00fa9 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-private.+002+65316.private @@ -0,0 +1,9 @@ +Private-key-format: v1.3 +Algorithm: 2 (DH) +Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8= +Generator(g): Ag== +Private_value(x): dLr0sfk/P1V0DfQ7Ke3IIaSM8nHjtrBRlMcQXRMVrLhbbKeCodvpSRtI0Nwtt38Df8dbGGtP676my2Ht2UHyL7rO0+ASv98NCysL0Xp6q2a7fn67iGFUBTg3jzXC89FYv4sYNeVLDGrKC3EjtGkalzgDVuzEC8CqRkWKeys3ufc= +Public_value(y): pVedQ4Uoy8u/A79V1j1ur5448HGtFawOlwv0Un36GSVW+KKlws4iGIot4cj+728zaeF3z6ikhb3b6PakHXzkxGBKsg1BwL5ekj/GIMYfATvxtD7ZiRv+Z6Yb5y2RXKsUYe1oksJQZatQfLWNu29h5op5L3aNH/biivK3c3znW7k= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key new file mode 100644 index 0000000..5d4a0e7 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 53461, for example-q. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example-q. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE= diff --git a/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private new file mode 100644 index 0000000..6b2e563 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample-q.+008+53461.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ== +PublicExponent: AQAB +PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ== +Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE= +Prime2: 0fs3ncL5/2qzq2dmPXLYcOfc1EGSuESO0VpREP8EpTkyPKeVw5LaF9TgZRqPWlRf2T0LPoZ766xLAn090u0pLQ5fWM96NMas7kS+rxtRssat6MiQo3YfoU3ysk3xuPzrMBHyn/N42CjSG+bJEToHR7V16KsCT6dBIPkI3tj/Yos= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample.+002+65316.key b/lib/dns/tests/comparekeys/Kexample.+002+65316.key new file mode 100644 index 0000000..c2f4703 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+002+65316.key @@ -0,0 +1 @@ +example. IN KEY 512 3 2 AAECAAAAgKVXnUOFKMvLvwO/VdY9bq+eOPBxrRWsDpcL9FJ9+hklVvii pcLOIhiKLeHI/u9vM2nhd8+opIW92+j2pB185MRgSrINQcC+XpI/xiDG HwE78bQ+2Ykb/memG+ctkVyrFGHtaJLCUGWrUHy1jbtvYeaKeS92jR/2 4oryt3N851u5 diff --git a/lib/dns/tests/comparekeys/Kexample.+002+65316.private b/lib/dns/tests/comparekeys/Kexample.+002+65316.private new file mode 100644 index 0000000..e872834 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+002+65316.private @@ -0,0 +1,9 @@ +Private-key-format: v1.3 +Algorithm: 2 (DH) +Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8= +Generator(g): Ag== +Private_value(x): bUMVaaSCAPT0NK7AkIa0JA1SSw83x8WxS+iePECQwr4xDDMnevNHWK1nIofUM2qNbpDe2KvFIt9tu+1UgZgOTLoQFipePtHKOjoRX6XsGNzKmL8WZOlw/QJw0D5RIn7l7tvmBCeNHINl9IWVgMLTi+wgzrJxSeGe406q23Jn4Uc= +Public_value(y): pVedQ4Uoy8u/A79V1j1ur5448HGtFawOlwv0Un36GSVW+KKlws4iGIot4cj+728zaeF3z6ikhb3b6PakHXzkxGBKsg1BwL5ekj/GIMYfATvxtD7ZiRv+Z6Yb5y2RXKsUYe1oksJQZatQfLWNu29h5op5L3aNH/biivK3c3znW7k= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample.+008+53461.key b/lib/dns/tests/comparekeys/Kexample.+008+53461.key new file mode 100644 index 0000000..33c8188 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+008+53461.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 53461, for example. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example. IN DNSKEY 256 3 8 AwEAAaKYSOPDzZvfue5sU71xPCJKJpB5kZGl4vTp3OI8W+nN1YFtmVe2 2gM666AEutDAEB7cLkyoKCOH0+4Lh1ucPr6OmdWkHfk7uZv58eH0kOAV tNz2xhEF/YHSD7cnBEU9g0knGwpWuzSJKRhGhNoaVus9g1MaAn8efptz HIduIwgAeXV3BDCUpY6HbpwjDxOGCzCUYDRgcex37kYuCyW0PvlO5FQ0 DT0LpjcgBmIBpXol7sYpmKdOKJrm4x2lwGntr4K+bCdNYI2PRPJjPqAf jlvIvJylGUaqFJasw7PSMQIkgcQ4OQXVrhE8uGLdYvP1cusLuROIjdYp Pdqc5K9lCQE= diff --git a/lib/dns/tests/comparekeys/Kexample.+008+53461.private b/lib/dns/tests/comparekeys/Kexample.+008+53461.private new file mode 100644 index 0000000..dd4d9a4 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+008+53461.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: ophI48PNm9+57mxTvXE8IkomkHmRkaXi9Onc4jxb6c3VgW2ZV7baAzrroAS60MAQHtwuTKgoI4fT7guHW5w+vo6Z1aQd+Tu5m/nx4fSQ4BW03PbGEQX9gdIPtycERT2DSScbCla7NIkpGEaE2hpW6z2DUxoCfx5+m3Mch24jCAB5dXcEMJSljodunCMPE4YLMJRgNGBx7HfuRi4LJbQ++U7kVDQNPQumNyAGYgGleiXuximYp04omubjHaXAae2vgr5sJ01gjY9E8mM+oB+OW8i8nKUZRqoUlqzDs9IxAiSBxDg5BdWuETy4Yt1i8/Vy6wu5E4iN1ik92pzkr2UJAQ== +PublicExponent: AQAB +PrivateExponent: lFgeQHf3klxXlfkNmczDEYHXl37i2iCgZdUsqtho/3LFdfWZrxZr6ACM040dKLHiw1UdhODy5h/Zstif4Ww3LsKKBgpbMnZUTMOI9R+eQmRrhCI96XAur5AIuJCHa+jIbCiamh8xY6g0byp/sUHQxYV02I/lcTdQSeGHSOSqX3QjB835OVa18hyW6txAxM4DVGo/NvIJw2ItSl2qwHTMDHK45t4YbnKEd6suriUiveyax5dU1JtpviwHJiAFPy+L68jMo8cfr+JCLWW2OJYkrBXb8kwqaPsV0RCGZ59sePyRdSYRgNi1brBStesctVc5UfSxH6p2A6C28LdrubcXAQ== +Prime1: z08i0sCcEpr4MZi4TReohPWp3F5vMQYVux8B3ltmJ3kKraXEmVEVmujhWa+ZDxhJmwKoba65vNEsUbSJN6WwJd7PVyskHb2GnWGK8NtlainFEuiS5CDxwULR4o2SI+Pij9thMQoA13ZTKc9s3E57VgcvJ7vaoD/1ZtpP7tdaerE= +Prime2: yMid465M6bCXXUfWg7oq6A4MZUULbEPKvs+qGIersdiHfrFRGJ0Lviujs8KHaPS5rt4YmbpQU9tGbJBauY17T03qr/mQOBDx5gDkAJcJ0EUHudFslwqyn50THlJsKrFOxBYl7laY0v6CGCMyuZok8qyhiPHv5dhzSc9zwKaXZ1E= +Exponent1: iresWJOzm6uAukczw7o59EYiFChIhOhKcDyOVoiYMX+ICqvqgqDEMTT1XbrnUzdwQT4lD8ej11msKzv/uXGwDZcq7GwcrZ3dTsAvZX2ZPdGXYlCnwejde/FHWi5bBJL/Tj2AqnzEFWjCuy5l7IDDfMwv3ImSADrr7ZfVdr85dvE= +Exponent2: aULzs4ePfvw7foXI6mpRUDL9QKI/6NRpmDtam12VH7m63yqqr1K1808BlZ4oS1fxeMGq9/z7W9sbQpMzXQ/VU7Avl24os5v+lWxmHAES/gMSl9I5Mb5EAvXgLgdb+c3W02ohHKB9ojAXl/vr/e3X7Pmf/iGIeWFOn6WIs7kiquE= +Coefficient: HS4bN41s6Ak9+6m3vhmLzgHtWMavnLpDkmd6wTBttbtKXHfjbvxMUt4RYeF8BXRtfIqIOZqJJngais1wQfOsgVhHrKVwX+MOThyOk4SD+pvnG6g1B+qUS1czPGP7Rf+7668wK8ZxV9w0+YDbTJgPgivD0lBnLXwT+KCLprMXTe4= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample.+013+19786.key b/lib/dns/tests/comparekeys/Kexample.+013+19786.key new file mode 100644 index 0000000..ccfcc97 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+013+19786.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 19786, for example. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example. IN DNSKEY 256 3 13 S35Z1XtGlnnU7BBahMJwAZXXff+JupyIDssNfJyrugLKq5R10TJ5tU3W r3VuP6aJNs6+uL2cMPVTVT1vr1Aqwg== diff --git a/lib/dns/tests/comparekeys/Kexample.+013+19786.private b/lib/dns/tests/comparekeys/Kexample.+013+19786.private new file mode 100644 index 0000000..0d72cf1 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+013+19786.private @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: ZYcYhR5f98vI1+BFGKLIrarZrqxJM4mRy9tvwntdYoo= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample.+015+63663.key b/lib/dns/tests/comparekeys/Kexample.+015+63663.key new file mode 100644 index 0000000..92db9fb --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+015+63663.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 63663, for example. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example. IN DNSKEY 256 3 15 ZLlkI5q8XDkP3D7Zxdbmuqh4yp90mbvdcNT0xSGLDtI= diff --git a/lib/dns/tests/comparekeys/Kexample.+015+63663.private b/lib/dns/tests/comparekeys/Kexample.+015+63663.private new file mode 100644 index 0000000..c2c48f3 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample.+015+63663.private @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 15 (ED25519) +PrivateKey: rGYsnf8nPlg7kg7qRcIXYShPsTiMHTeWJInNrW9GwSQ= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample2.+002+19823.key b/lib/dns/tests/comparekeys/Kexample2.+002+19823.key new file mode 100644 index 0000000..9d521f7 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+002+19823.key @@ -0,0 +1 @@ +example2. IN KEY 512 3 2 AAECAAAAgCxVfxiyTe8C83ou8KXSu9WmzGwCYWB2NkdS87Kz0PgTuBay JkDDAEeR6CIYClA6PXBp2GXUPHoYWag9zVOVU85PYu0KRZF69EN0IVsA OCtgikOcr5yD4esSMwTTPk/OQ8qW/yGf1DvdpXuiu3P/wSpzVGL8tHFQ 2XURydYytol0 diff --git a/lib/dns/tests/comparekeys/Kexample2.+002+19823.private b/lib/dns/tests/comparekeys/Kexample2.+002+19823.private new file mode 100644 index 0000000..f6722f6 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+002+19823.private @@ -0,0 +1,9 @@ +Private-key-format: v1.3 +Algorithm: 2 (DH) +Prime(p): ///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5lOB//////////8= +Generator(g): Ag== +Private_value(x): W0EpuIMltmMuZAKcCmRe/Ix9WsHPU/GLfqbjHKCjgYdRFzwqHyVp6z+uf8EgmHBD1bbBjwfcnRse8xfqqmt/wZIRdDzjRq/oZdKtJHqFZSO+MQZ5DKrdojKU7UEl/j44heJzVO0qFkrPvWglRt+780LP0awkfetecXDxvJT+HIw= +Public_value(y): LFV/GLJN7wLzei7wpdK71abMbAJhYHY2R1LzsrPQ+BO4FrImQMMAR5HoIhgKUDo9cGnYZdQ8ehhZqD3NU5VTzk9i7QpFkXr0Q3QhWwA4K2CKQ5yvnIPh6xIzBNM+T85Dypb/IZ/UO92le6K7c//BKnNUYvy0cVDZdRHJ1jK2iXQ= +Created: 20211027221355 +Publish: 20211027221355 +Activate: 20211027221355 diff --git a/lib/dns/tests/comparekeys/Kexample2.+008+37993.key b/lib/dns/tests/comparekeys/Kexample2.+008+37993.key new file mode 100644 index 0000000..c0e09a1 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+008+37993.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 37993, for example2. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example2. IN DNSKEY 256 3 8 AwEAAaSRhPf0XhYR52Kpi0RZJgEnpidvuz2Ywdyh8k5CKwal9nM15PNc 4ZoPEVGO+ize53hq0iUkVlBhAfhQE31Fhf4zU544fezBEaz33hiajEzL ZITux9N83WfoYSNnyufvSGzNcpNM6LHKdDwMr1kr9tTgNeuiTAlPv5z9 BNtfv2B25moVm1DoxMCd8WH0jYC452a2lGM+Fbd45o02OO7V8balPwJh MM2bbeWg5G+tbvCAot93KxtavyOMKV4siv3ZH639J0dIb10L8nNrN0Ge UjkX8yU3fgeWB4Oldtzx0SHxG75NWjRLnpVzBq5GeacLc4RsN+S+nhYW 4Wv2A066w70= diff --git a/lib/dns/tests/comparekeys/Kexample2.+008+37993.private b/lib/dns/tests/comparekeys/Kexample2.+008+37993.private new file mode 100644 index 0000000..887ad2b --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+008+37993.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: pJGE9/ReFhHnYqmLRFkmASemJ2+7PZjB3KHyTkIrBqX2czXk81zhmg8RUY76LN7neGrSJSRWUGEB+FATfUWF/jNTnjh97MERrPfeGJqMTMtkhO7H03zdZ+hhI2fK5+9IbM1yk0zoscp0PAyvWSv21OA166JMCU+/nP0E21+/YHbmahWbUOjEwJ3xYfSNgLjnZraUYz4Vt3jmjTY47tXxtqU/AmEwzZtt5aDkb61u8ICi33crG1q/I4wpXiyK/dkfrf0nR0hvXQvyc2s3QZ5SORfzJTd+B5YHg6V23PHRIfEbvk1aNEuelXMGrkZ5pwtzhGw35L6eFhbha/YDTrrDvQ== +PublicExponent: AQAB +PrivateExponent: O/HFvYwFuYRMBGQ9lmfisAkBPNw2F/nMo9FZsafohENvwgefngX3J2bVqB+sgSuwpOxEH8NcrWqojQqeDsOES1Pm4XsyY0rwZVDkVZH2CQMNWl6f6ylQfMjomTz1bAZ9GyS612zsVdapADaeqJybDG+fNHWpvLqP0V9YpY/65efTvrA3Qu+XpDvLaJ34yjkeEGUgysNP3KkDTeJTY/ksKi6ODtdzbKpufjZS8b6BL97XcFcNGiwu/gNPCvtmm/H+tXaNYyijG7bNGPOpHFhlMCT13o8XLrR/OGty6VY6PpjaEnvlZUZnWHUwn/JmNoZRJoXAAEerk3nS+tOhRmcrAQ== +Prime1: 2W8JHYaTn7XefxxwaDZWFrVtHnnd0vUZvBBNA1PJeRfDr+yPxyWcgYx1OBxKkJsYGiob0i992W2HXuz2KS81yBCtH/uLK1Y+mkjgme4MWZupZ0RsKA1TkgIrJs174Dv3P/yqc+/R4eiwUGt10493MS1PJFF0CmisDzgjai/JLIM= +Prime2: wcIKikgzOsq2A2Hl7qPCeA3oKTc66eFVNvB/KH91/hNFKNm0kAvhHrNe9rSoU+JywCNbX/Fs7X6SuHHJaRs+KpSBadnqfwEIngCq2Y00nT3sbETx4VNbXFD6MPPo3MWDi61/TCyrtBujutavo5ghj2oVzNGqMT3UyhaVNrJp2r8= +Exponent1: GsEO3hMxFvXJ6toU+r202hZ41scoBE0kXX+j+kTVBZFnAr6Y8mguWcJuqfjRM/nhfVaxFavCUH6pqYR+xZKJi5SBuO26shpqmZFeEZK48k21Cn/gzwzUu6KIrL2cAHtgcP8l+h4INUPsbfjLBr0gbWyl0FI1dRJsGXNO6EH4/wE= +Exponent2: WrgcsUQ+4E8bS5ghzUtVeVqhkfKvHeSIPpH6J58OQukI36iXNz6op/Q6CW7qxWPocHfdh52Fb+lsjvmP4SuFPvCLa2FBvzdfroMHe5b2xIzCzqq1Sdf6lc3AZv080WmVPuf8C1F7D3hFf+yXDhTj2b9E98JPWoDlyb0rHhIJKAc= +Coefficient: aVDpGheH0UJ8aWPRIRHyjMTCIPB8zmhfwugpV11Z/OXNb2uaNRcnVKujs1mlSydoIfFQSuFf3iPs7ytaJUfcQJ+k1QAtssJC1HXF14t0p5o99QxuQLgmNtPHD7m2aeAyFJoycF24UmDnFmOPSNm1fnJs9LrBPFZFdTBGhl8plEo= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample2.+013+16384.key b/lib/dns/tests/comparekeys/Kexample2.+013+16384.key new file mode 100644 index 0000000..b6351ad --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+013+16384.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 16384, for example2. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example2. IN DNSKEY 256 3 13 p+ohRFVh6wmdAhbU/cF2FYoE/i49FxnvKwif0Co0D7RhBui4AMFOsFYu 9AIqEBaCGurjGYl7WDYRrjRMRjWW1g== diff --git a/lib/dns/tests/comparekeys/Kexample2.+013+16384.private b/lib/dns/tests/comparekeys/Kexample2.+013+16384.private new file mode 100644 index 0000000..74a371c --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+013+16384.private @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: ZNsU73iCEeC837TA59nT/QDtd3oYsrYDWy8jfazZQkA= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample2.+015+37529.key b/lib/dns/tests/comparekeys/Kexample2.+015+37529.key new file mode 100644 index 0000000..9a8cf77 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+015+37529.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 37529, for example2. +; Created: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Publish: 20000101000000 (Sat Jan 1 00:00:00 2000) +; Activate: 20000101000000 (Sat Jan 1 00:00:00 2000) +example2. IN DNSKEY 256 3 15 zyiLjDymEPN90rwi/y0mWXLnUm0Nq7T9Kc8VoubF2Io= diff --git a/lib/dns/tests/comparekeys/Kexample2.+015+37529.private b/lib/dns/tests/comparekeys/Kexample2.+015+37529.private new file mode 100644 index 0000000..aa70804 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample2.+015+37529.private @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 15 (ED25519) +PrivateKey: pUR2VLqbi4XtBNImbVDRHrjugMMmaXmy6noV0/jy/rA= +Created: 20000101000000 +Publish: 20000101000000 +Activate: 20000101000000 diff --git a/lib/dns/tests/comparekeys/Kexample3.+002+17187.key b/lib/dns/tests/comparekeys/Kexample3.+002+17187.key new file mode 100644 index 0000000..0260293 --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample3.+002+17187.key @@ -0,0 +1 @@ +example3. IN KEY 512 3 2 AIDzVP5SeKpxZmlbok6AbdFT5aCVAg3AnQln24CKoc0IXRyTdnhmIEWw VatzRrUY0V6SSwwe3740yk46/TdSvVmZkakw52yn3M661ra5L8kouAK4 rpyM6uulsS0dyjyRzomHJg2zwOukzHBVINFheKsJ25MvdPpp9E64IVji jp/x3wABBQCAFb1Je3cKpt/4gS+KRx9hxFOF5c64ytC9tf0hp2hP4OiP YCu8o5C3qh+PexZAx58m6cSaFlzf3DZ3GGsvamDj5H8YpvP4FeRVva9V jH4VeU4/VtbdNweDUHwAguGJ77MXw+bruHhpbsFVSxHrnNg99WHghaRy YzfFzphJHKBxACo= diff --git a/lib/dns/tests/comparekeys/Kexample3.+002+17187.private b/lib/dns/tests/comparekeys/Kexample3.+002+17187.private new file mode 100644 index 0000000..47ef4bc --- /dev/null +++ b/lib/dns/tests/comparekeys/Kexample3.+002+17187.private @@ -0,0 +1,9 @@ +Private-key-format: v1.3 +Algorithm: 2 (DH) +Prime(p): 81T+UniqcWZpW6JOgG3RU+WglQINwJ0JZ9uAiqHNCF0ck3Z4ZiBFsFWrc0a1GNFekksMHt++NMpOOv03Ur1ZmZGpMOdsp9zOuta2uS/JKLgCuK6cjOrrpbEtHco8kc6JhyYNs8DrpMxwVSDRYXirCduTL3T6afROuCFY4o6f8d8= +Generator(g): BQ== +Private_value(x): ccA7JRCvjAE1ASWTtObkvO5k58oKdJ+bzcd/H3cOQsPAhItUc8Pfca2ILWYzDfs+nl+WKLfODQ9cRUabp4SUh0GKPnqJVM1UgDXwme/98NEtVFhs2VawT40wHLkcdPN9jACH11l28u1qsDVb7MRj2UXGC/oszRwQ7s3rN1UlHO8= +Public_value(y): Fb1Je3cKpt/4gS+KRx9hxFOF5c64ytC9tf0hp2hP4OiPYCu8o5C3qh+PexZAx58m6cSaFlzf3DZ3GGsvamDj5H8YpvP4FeRVva9VjH4VeU4/VtbdNweDUHwAguGJ77MXw+bruHhpbsFVSxHrnNg99WHghaRyYzfFzphJHKBxACo= +Created: 20211027221447 +Publish: 20211027221447 +Activate: 20211027221447 diff --git a/lib/dns/tests/db_test.c b/lib/dns/tests/db_test.c new file mode 100644 index 0000000..5b7ba64 --- /dev/null +++ b/lib/dns/tests/db_test.c @@ -0,0 +1,428 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +/* + * Individual unit tests + */ + +/* test multiple calls to dns_db_getoriginnode */ +static void +getoriginnode_test(void **state) { + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + isc_mem_t *mctx = NULL; + isc_result_t result; + + UNUSED(state); + + isc_mem_create(&mctx); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_getoriginnode(db, &node); + assert_int_equal(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + + result = dns_db_getoriginnode(db, &node); + assert_int_equal(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + + dns_db_detach(&db); + isc_mem_detach(&mctx); +} + +/* test getservestalettl and setservestalettl */ +static void +getsetservestalettl_test(void **state) { + dns_db_t *db = NULL; + isc_mem_t *mctx = NULL; + isc_result_t result; + dns_ttl_t ttl; + + UNUSED(state); + + isc_mem_create(&mctx); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + ttl = 5000; + result = dns_db_getservestalettl(db, &ttl); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(ttl, 0); + + ttl = 6 * 3600; + result = dns_db_setservestalettl(db, ttl); + assert_int_equal(result, ISC_R_SUCCESS); + + ttl = 5000; + result = dns_db_getservestalettl(db, &ttl); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(ttl, 6 * 3600); + + dns_db_detach(&db); + isc_mem_detach(&mctx); +} + +/* check DNS_DBFIND_STALEOK works */ +static void +dns_dbfind_staleok_test(void **state) { + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_fixedname_t example_fixed; + dns_fixedname_t found_fixed; + dns_name_t *example; + dns_name_t *found; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + int count; + int pass; + isc_mem_t *mctx = NULL; + isc_result_t result; + unsigned char data[] = { 0x0a, 0x00, 0x00, 0x01 }; + + UNUSED(state); + + isc_mem_create(&mctx); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + example = dns_fixedname_initname(&example_fixed); + found = dns_fixedname_initname(&found_fixed); + + result = dns_name_fromstring(example, "example", 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Pass 0: default; no stale processing permitted. + * Pass 1: stale processing for 1 second. + * Pass 2: stale turned off after being on. + */ + for (pass = 0; pass < 3; pass++) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* 10.0.0.1 */ + rdata.data = data; + rdata.length = 4; + rdata.rdclass = dns_rdataclass_in; + rdata.type = dns_rdatatype_a; + + dns_rdatalist_init(&rdatalist); + rdatalist.ttl = 2; + rdatalist.type = dns_rdatatype_a; + rdatalist.rdclass = dns_rdataclass_in; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + switch (pass) { + case 0: + /* default: stale processing off */ + break; + case 1: + /* turn on stale processing */ + result = dns_db_setservestalettl(db, 1); + assert_int_equal(result, ISC_R_SUCCESS); + break; + case 2: + /* turn off stale processing */ + result = dns_db_setservestalettl(db, 0); + assert_int_equal(result, ISC_R_SUCCESS); + break; + } + + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_findnode(db, example, true, &node); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_addrdataset(db, node, NULL, 0, &rdataset, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + result = dns_db_find(db, example, NULL, dns_rdatatype_a, 0, 0, + &node, found, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * May loop for up to 2 seconds performing non stale lookups. + */ + count = 0; + do { + count++; + assert_in_range(count, 1, 21); /* loop sanity */ + assert_int_equal(rdataset.attributes & + DNS_RDATASETATTR_STALE, + 0); + assert_true(rdataset.ttl > 0); + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + usleep(100000); /* 100 ms */ + + result = dns_db_find(db, example, NULL, dns_rdatatype_a, + 0, 0, &node, found, &rdataset, + NULL); + } while (result == ISC_R_SUCCESS); + + assert_int_equal(result, ISC_R_NOTFOUND); + + /* + * Check whether we can get stale data. + */ + result = dns_db_find(db, example, NULL, dns_rdatatype_a, + DNS_DBFIND_STALEOK, 0, &node, found, + &rdataset, NULL); + switch (pass) { + case 0: + assert_int_equal(result, ISC_R_NOTFOUND); + break; + case 1: + /* + * Should loop for 1 second with stale lookups then + * stop. + */ + count = 0; + do { + count++; + assert_in_range(count, 0, 49); /* loop sanity */ + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdataset.attributes & + DNS_RDATASETATTR_STALE, + DNS_RDATASETATTR_STALE); + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + usleep(100000); /* 100 ms */ + + result = dns_db_find( + db, example, NULL, dns_rdatatype_a, + DNS_DBFIND_STALEOK, 0, &node, found, + &rdataset, NULL); + } while (result == ISC_R_SUCCESS); + /* + * usleep(100000) can be slightly less than 10ms so + * allow the count to reach 11. + */ + assert_in_range(count, 1, 11); + assert_int_equal(result, ISC_R_NOTFOUND); + break; + case 2: + assert_int_equal(result, ISC_R_NOTFOUND); + break; + } + } + + dns_db_detach(&db); + isc_mem_detach(&mctx); +} + +/* database class */ +static void +class_test(void **state) { + isc_result_t result; + dns_db_t *db = NULL; + + UNUSED(state); + + result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text, + 0); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(dns_db_class(db), dns_rdataclass_in); + + dns_db_detach(&db); +} + +/* database type */ +static void +dbtype_test(void **state) { + isc_result_t result; + dns_db_t *db = NULL; + + UNUSED(state); + + /* DB has zone semantics */ + result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text, + 0); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(dns_db_iszone(db)); + assert_false(dns_db_iscache(db)); + dns_db_detach(&db); + + /* DB has cache semantics */ + result = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_db_load(db, "testdata/db/data.db", dns_masterformat_text, + 0); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(dns_db_iscache(db)); + assert_false(dns_db_iszone(db)); + dns_db_detach(&db); +} + +/* database versions */ +static void +version_test(void **state) { + isc_result_t result; + dns_fixedname_t fname, ffound; + dns_name_t *name, *foundname; + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL, *new = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + + UNUSED(state); + + result = dns_test_loaddb(&db, dns_dbtype_zone, "test.test", + "testdata/db/data.db"); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Open current version for reading */ + dns_db_currentversion(db, &ver); + dns_test_namefromstring("b.test.test", &fname); + name = dns_fixedname_name(&fname); + foundname = dns_fixedname_initname(&ffound); + dns_rdataset_init(&rdataset); + result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node, + foundname, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, &ver, false); + + /* Open new version for writing */ + dns_db_currentversion(db, &ver); + dns_test_namefromstring("b.test.test", &fname); + name = dns_fixedname_name(&fname); + foundname = dns_fixedname_initname(&ffound); + dns_rdataset_init(&rdataset); + result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node, + foundname, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_newversion(db, &new); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Delete the rdataset from the new version */ + result = dns_db_deleterdataset(db, node, new, dns_rdatatype_a, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + + /* This should fail now */ + result = dns_db_find(db, name, new, dns_rdatatype_a, 0, 0, &node, + foundname, &rdataset, NULL); + assert_int_equal(result, DNS_R_NXDOMAIN); + + dns_db_closeversion(db, &new, true); + + /* But this should still succeed */ + result = dns_db_find(db, name, ver, dns_rdatatype_a, 0, 0, &node, + foundname, &rdataset, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, &ver, false); + + dns_db_detach(&db); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(getoriginnode_test), + cmocka_unit_test(getsetservestalettl_test), + cmocka_unit_test(dns_dbfind_staleok_test), + cmocka_unit_test_setup_teardown(class_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(dbtype_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(version_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dbdiff_test.c b/lib/dns/tests/dbdiff_test.c new file mode 100644 index 0000000..05bb761 --- /dev/null +++ b/lib/dns/tests/dbdiff_test.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include + +#include +#include +#include +#include + +#include "dnstest.h" + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static void +test_create(const char *oldfile, dns_db_t **old, const char *newfile, + dns_db_t **newdb) { + isc_result_t result; + + result = dns_test_loaddb(old, dns_dbtype_zone, TEST_ORIGIN, oldfile); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(newdb, dns_dbtype_zone, TEST_ORIGIN, newfile); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* dns_db_diffx of identical content */ +static void +diffx_same(void **state) { + dns_db_t *newdb = NULL, *olddb = NULL; + isc_result_t result; + dns_diff_t diff; + + UNUSED(state); + + test_create("testdata/diff/zone1.data", &olddb, + "testdata/diff/zone1.data", &newdb); + + dns_diff_init(dt_mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_true(ISC_LIST_EMPTY(diff.tuples)); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); +} + +/* dns_db_diffx of zone with record added */ +static void +diffx_add(void **state) { + dns_db_t *newdb = NULL, *olddb = NULL; + dns_difftuple_t *tuple; + isc_result_t result; + dns_diff_t diff; + int count = 0; + + UNUSED(state); + + test_create("testdata/diff/zone1.data", &olddb, + "testdata/diff/zone2.data", &newdb); + + dns_diff_init(dt_mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_false(ISC_LIST_EMPTY(diff.tuples)); + for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + assert_int_equal(tuple->op, DNS_DIFFOP_ADD); + count++; + } + assert_int_equal(count, 1); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); +} + +/* dns_db_diffx of zone with record removed */ +static void +diffx_remove(void **state) { + dns_db_t *newdb = NULL, *olddb = NULL; + dns_difftuple_t *tuple; + isc_result_t result; + dns_diff_t diff; + int count = 0; + + UNUSED(state); + + test_create("testdata/diff/zone1.data", &olddb, + "testdata/diff/zone3.data", &newdb); + + dns_diff_init(dt_mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_false(ISC_LIST_EMPTY(diff.tuples)); + for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) + { + assert_int_equal(tuple->op, DNS_DIFFOP_DEL); + count++; + } + assert_int_equal(count, 1); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(diffx_same, _setup, _teardown), + cmocka_unit_test_setup_teardown(diffx_add, _setup, _teardown), + cmocka_unit_test_setup_teardown(diffx_remove, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dbiterator_test.c b/lib/dns/tests/dbiterator_test.c new file mode 100644 index 0000000..2182672 --- /dev/null +++ b/lib/dns/tests/dbiterator_test.c @@ -0,0 +1,394 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include + +#include +#include +#include + +#include "dnstest.h" + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static isc_result_t +make_name(const char *src, dns_name_t *name) { + isc_buffer_t b; + isc_buffer_constinit(&b, src, strlen(src)); + isc_buffer_add(&b, strlen(src)); + return (dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); +} + +/* create: make sure we can create a dbiterator */ +static void +test_create(const char *filename) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +create(void **state) { + UNUSED(state); + + test_create("testdata/dbiterator/zone1.data"); +} + +static void +create_nsec3(void **state) { + UNUSED(state); + + test_create("testdata/dbiterator/zone2.data"); +} + +/* walk: walk a database */ +static void +test_walk(const char *filename, int nodes) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_dbnode_t *node = NULL; + dns_name_t *name; + dns_fixedname_t f; + int i = 0; + + name = dns_fixedname_initname(&f); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + for (result = dns_dbiterator_first(iter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(iter)) + { + result = dns_dbiterator_current(iter, &node, name); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + assert_int_equal(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + i++; + } + + assert_int_equal(i, nodes); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +walk(void **state) { + UNUSED(state); + + test_walk("testdata/dbiterator/zone1.data", 12); +} + +static void +walk_nsec3(void **state) { + UNUSED(state); + + test_walk("testdata/dbiterator/zone2.data", 33); +} + +/* reverse: walk database backwards */ +static void +test_reverse(const char *filename) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_dbnode_t *node = NULL; + dns_name_t *name; + dns_fixedname_t f; + int i = 0; + + name = dns_fixedname_initname(&f); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + for (result = dns_dbiterator_last(iter); result == ISC_R_SUCCESS; + result = dns_dbiterator_prev(iter)) + { + result = dns_dbiterator_current(iter, &node, name); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + assert_int_equal(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + i++; + } + + assert_int_equal(i, 12); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +reverse(void **state) { + UNUSED(state); + + test_reverse("testdata/dbiterator/zone1.data"); +} + +static void +reverse_nsec3(void **state) { + UNUSED(state); + + test_reverse("testdata/dbiterator/zone2.data"); +} + +/* seek: walk database starting at a particular node */ +static void +test_seek_node(const char *filename, int nodes) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_dbnode_t *node = NULL; + dns_name_t *name, *seekname; + dns_fixedname_t f1, f2; + int i = 0; + + name = dns_fixedname_initname(&f1); + seekname = dns_fixedname_initname(&f2); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + result = make_name("c." TEST_ORIGIN, seekname); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + assert_int_equal(result, ISC_R_SUCCESS); + + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(iter, &node, name); + if (result == DNS_R_NEWORIGIN) { + result = ISC_R_SUCCESS; + } + assert_int_equal(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(iter); + i++; + } + + assert_int_equal(i, nodes); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +seek_node(void **state) { + UNUSED(state); + + test_seek_node("testdata/dbiterator/zone1.data", 9); +} + +static void +seek_node_nsec3(void **state) { + UNUSED(state); + + test_seek_node("testdata/dbiterator/zone2.data", 30); +} + +/* + * seek_emty: walk database starting at an empty nonterminal node + * (should fail) + */ +static void +test_seek_empty(const char *filename) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_name_t *seekname; + dns_fixedname_t f1; + + seekname = dns_fixedname_initname(&f1); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + result = make_name("d." TEST_ORIGIN, seekname); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + assert_int_equal(result, DNS_R_PARTIALMATCH); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +seek_empty(void **state) { + UNUSED(state); + + test_seek_empty("testdata/dbiterator/zone1.data"); +} + +static void +seek_empty_nsec3(void **state) { + UNUSED(state); + + test_seek_empty("testdata/dbiterator/zone2.data"); +} + +/* + * seek_nx: walk database starting at a nonexistent node + */ +static void +test_seek_nx(const char *filename) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_name_t *seekname; + dns_fixedname_t f1; + + seekname = dns_fixedname_initname(&f1); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, filename); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + assert_int_equal(result, ISC_R_SUCCESS); + + result = make_name("nonexistent." TEST_ORIGIN, seekname); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + assert_int_equal(result, DNS_R_PARTIALMATCH); + + result = make_name("nonexistent.", seekname); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + assert_int_equal(result, ISC_R_NOTFOUND); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); +} + +static void +seek_nx(void **state) { + UNUSED(state); + + test_seek_nx("testdata/dbiterator/zone1.data"); +} + +static void +seek_nx_nsec3(void **state) { + UNUSED(state); + + test_seek_nx("testdata/dbiterator/zone2.data"); +} + +/* + * XXX: + * dns_dbiterator API calls that are not yet part of this unit test: + * + * dns_dbiterator_pause + * dns_dbiterator_origin + * dns_dbiterator_setcleanmode + */ +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(create, _setup, _teardown), + cmocka_unit_test_setup_teardown(create_nsec3, _setup, + _teardown), + cmocka_unit_test_setup_teardown(walk, _setup, _teardown), + cmocka_unit_test_setup_teardown(walk_nsec3, _setup, _teardown), + cmocka_unit_test_setup_teardown(reverse, _setup, _teardown), + cmocka_unit_test_setup_teardown(reverse_nsec3, _setup, + _teardown), + cmocka_unit_test_setup_teardown(seek_node, _setup, _teardown), + cmocka_unit_test_setup_teardown(seek_node_nsec3, _setup, + _teardown), + cmocka_unit_test_setup_teardown(seek_empty, _setup, _teardown), + cmocka_unit_test_setup_teardown(seek_empty_nsec3, _setup, + _teardown), + cmocka_unit_test_setup_teardown(seek_nx, _setup, _teardown), + cmocka_unit_test_setup_teardown(seek_nx_nsec3, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dbversion_test.c b/lib/dns/tests/dbversion_test.c new file mode 100644 index 0000000..70a5124 --- /dev/null +++ b/lib/dns/tests/dbversion_test.c @@ -0,0 +1,499 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dnstest.h" + +static char tempname[11] = "dtXXXXXXXX"; +static dns_db_t *db1 = NULL, *db2 = NULL; +static dns_dbversion_t *v1 = NULL, *v2 = NULL; + +/* + * The code below enables us to trap assertion failures for testing + * purposes. local_callback() is set as the callback function for + * isc_assertion_failed(). It calls mock_assert() so that CMOCKA + * will be able to see it, then returns to the calling function via + * longjmp() so that the abort() call in isc_assertion_failed() will + * never be reached. Use check_assertion() to check for assertions + * instead of expect_assert_failure(). + */ +jmp_buf assertion; + +#define check_assertion(function_call) \ + do { \ + const int r = setjmp(assertion); \ + if (r == 0) { \ + expect_assert_failure(function_call); \ + } \ + } while (false); + +static void +local_callback(const char *file, int line, isc_assertiontype_t type, + const char *cond) { + UNUSED(type); + + mock_assert(1, cond, file, line); + longjmp(assertion, 1); +} + +static int +_setup(void **state) { + isc_result_t res; + + UNUSED(state); + + isc_assertion_setcallback(local_callback); + + res = dns_test_begin(NULL, false); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db1); + assert_int_equal(res, ISC_R_SUCCESS); + dns_db_newversion(db1, &v1); + assert_non_null(v1); + + res = dns_db_create(dt_mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db2); + assert_int_equal(res, ISC_R_SUCCESS); + dns_db_newversion(db2, &v2); + assert_non_null(v1); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + if (strcmp(tempname, "dtXXXXXXXX") != 0) { + unlink(tempname); + } + + if (v1 != NULL) { + dns_db_closeversion(db1, &v1, false); + assert_null(v1); + } + if (db1 != NULL) { + dns_db_detach(&db1); + assert_null(db1); + } + + if (v2 != NULL) { + dns_db_closeversion(db2, &v2, false); + assert_null(v2); + } + if (db2 != NULL) { + dns_db_detach(&db2); + assert_null(db2); + } + + dns_test_end(); + + return (0); +} + +/* + * Check dns_db_attachversion() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +attachversion(void **state) { + dns_dbversion_t *v = NULL; + + UNUSED(state); + + dns_db_attachversion(db1, v1, &v); + assert_ptr_equal(v, v1); + dns_db_closeversion(db1, &v, false); + assert_null(v); + + check_assertion(dns_db_attachversion(db1, v2, &v)); +} + +/* + * Check dns_db_closeversion() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +closeversion(void **state) { + UNUSED(state); + + assert_non_null(v1); + dns_db_closeversion(db1, &v1, false); + assert_null(v1); + + check_assertion(dns_db_closeversion(db1, &v2, false)); +} + +/* + * Check dns_db_find() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +find(void **state) { + isc_result_t res; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_name_t *name = NULL; + + UNUSED(state); + + name = dns_fixedname_initname(&fixed); + + dns_rdataset_init(&rdataset); + res = dns_db_find(db1, dns_rootname, v1, dns_rdatatype_soa, 0, 0, NULL, + name, &rdataset, NULL); + assert_int_equal(res, DNS_R_NXDOMAIN); + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + dns_rdataset_init(&rdataset); + check_assertion((void)dns_db_find(db1, dns_rootname, v2, + dns_rdatatype_soa, 0, 0, NULL, name, + &rdataset, NULL)); +} + +/* + * Check dns_db_allrdatasets() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +allrdatasets(void **state) { + isc_result_t res; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *iterator = NULL; + + UNUSED(state); + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_allrdatasets(db1, node, v1, 0, 0, &iterator); + assert_int_equal(res, ISC_R_SUCCESS); + + check_assertion(dns_db_allrdatasets(db1, node, v2, 0, 0, &iterator)); + + dns_rdatasetiter_destroy(&iterator); + assert_null(iterator); + + dns_db_detachnode(db1, &node); + assert_null(node); +} + +/* + * Check dns_db_findrdataset() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +findrdataset(void **state) { + isc_result_t res; + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + + UNUSED(state); + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + dns_rdataset_init(&rdataset); + res = dns_db_findrdataset(db1, node, v1, dns_rdatatype_soa, 0, 0, + &rdataset, NULL); + assert_int_equal(res, ISC_R_NOTFOUND); + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + dns_rdataset_init(&rdataset); + check_assertion(dns_db_findrdataset(db1, node, v2, dns_rdatatype_soa, 0, + 0, &rdataset, NULL)); + + dns_db_detachnode(db1, &node); + assert_null(node); +} + +/* + * Check dns_db_deleterdataset() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +deleterdataset(void **state) { + isc_result_t res; + dns_dbnode_t *node = NULL; + + UNUSED(state); + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_deleterdataset(db1, node, v1, dns_rdatatype_soa, 0); + assert_int_equal(res, DNS_R_UNCHANGED); + + check_assertion( + dns_db_deleterdataset(db1, node, v2, dns_rdatatype_soa, 0)); + dns_db_detachnode(db1, &node); + assert_null(node); +} + +/* + * Check dns_db_subtractrdataset() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +subtract(void **state) { + isc_result_t res; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + dns_dbnode_t *node = NULL; + + UNUSED(state); + + dns_rdataset_init(&rdataset); + dns_rdatalist_init(&rdatalist); + + rdatalist.rdclass = dns_rdataclass_in; + + res = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_subtractrdataset(db1, node, v1, &rdataset, 0, NULL); + assert_int_equal(res, DNS_R_UNCHANGED); + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + + dns_rdataset_init(&rdataset); + res = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(res, ISC_R_SUCCESS); + + check_assertion( + dns_db_subtractrdataset(db1, node, v2, &rdataset, 0, NULL)); + + dns_db_detachnode(db1, &node); + assert_null(node); +} + +/* + * Check dns_db_dump() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +dump(void **state) { + isc_result_t res; + FILE *f = NULL; + + UNUSED(state); + + res = isc_file_openunique(tempname, &f); + fclose(f); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_dump(db1, v1, tempname); + assert_int_equal(res, ISC_R_SUCCESS); + + check_assertion(dns_db_dump(db1, v2, tempname)); +} + +/* + * Check dns_db_addrdataset() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +addrdataset(void **state) { + isc_result_t res; + dns_rdataset_t rdataset; + dns_dbnode_t *node = NULL; + dns_rdatalist_t rdatalist; + + UNUSED(state); + + dns_rdataset_init(&rdataset); + dns_rdatalist_init(&rdatalist); + + rdatalist.rdclass = dns_rdataclass_in; + + res = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, NULL); + assert_int_equal(res, ISC_R_SUCCESS); + + check_assertion( + dns_db_addrdataset(db1, node, v2, 0, &rdataset, 0, NULL)); + + dns_db_detachnode(db1, &node); + assert_null(node); +} + +/* + * Check dns_db_getnsec3parameters() passes with matching db and version, + * and asserts with mis-matching db and version. + */ +static void +getnsec3parameters(void **state) { + isc_result_t res; + dns_hash_t hash; + uint8_t flags; + uint16_t iterations; + unsigned char salt[DNS_NSEC3_SALTSIZE]; + size_t salt_length = sizeof(salt); + + UNUSED(state); + + res = dns_db_getnsec3parameters(db1, v1, &hash, &flags, &iterations, + salt, &salt_length); + assert_int_equal(res, ISC_R_NOTFOUND); + + check_assertion(dns_db_getnsec3parameters( + db1, v2, &hash, &flags, &iterations, salt, &salt_length)); +} + +/* + * Check dns_db_resigned() passes with matching db and version, and + * asserts with mis-matching db and version. + */ +static void +resigned(void **state) { + isc_result_t res; + dns_rdataset_t rdataset, added; + dns_dbnode_t *node = NULL; + dns_rdatalist_t rdatalist; + dns_rdata_rrsig_t rrsig; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t b; + unsigned char buf[1024]; + + UNUSED(state); + + /* + * Create a dummy RRSIG record and set a resigning time. + */ + dns_rdataset_init(&added); + dns_rdataset_init(&rdataset); + dns_rdatalist_init(&rdatalist); + isc_buffer_init(&b, buf, sizeof(buf)); + + DNS_RDATACOMMON_INIT(&rrsig, dns_rdatatype_rrsig, dns_rdataclass_in); + rrsig.covered = dns_rdatatype_a; + rrsig.algorithm = 100; + rrsig.labels = 0; + rrsig.originalttl = 0; + rrsig.timeexpire = 3600; + rrsig.timesigned = 0; + rrsig.keyid = 0; + dns_name_init(&rrsig.signer, NULL); + dns_name_clone(dns_rootname, &rrsig.signer); + rrsig.siglen = 0; + rrsig.signature = NULL; + + res = dns_rdata_fromstruct(&rdata, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig, &b); + assert_int_equal(res, ISC_R_SUCCESS); + + rdatalist.rdclass = dns_rdataclass_in; + rdatalist.type = dns_rdatatype_rrsig; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + res = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(res, ISC_R_SUCCESS); + + rdataset.attributes |= DNS_RDATASETATTR_RESIGN; + rdataset.resign = 7200; + + res = dns_db_findnode(db1, dns_rootname, false, &node); + assert_int_equal(res, ISC_R_SUCCESS); + + res = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, &added); + assert_int_equal(res, ISC_R_SUCCESS); + + dns_db_detachnode(db1, &node); + assert_null(node); + + check_assertion(dns_db_resigned(db1, &added, v2)); + + dns_db_resigned(db1, &added, v1); + + dns_rdataset_disassociate(&added); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(dump, _setup, _teardown), + cmocka_unit_test_setup_teardown(find, _setup, _teardown), + cmocka_unit_test_setup_teardown(allrdatasets, _setup, + _teardown), + cmocka_unit_test_setup_teardown(findrdataset, _setup, + _teardown), + cmocka_unit_test_setup_teardown(deleterdataset, _setup, + _teardown), + cmocka_unit_test_setup_teardown(subtract, _setup, _teardown), + cmocka_unit_test_setup_teardown(addrdataset, _setup, _teardown), + cmocka_unit_test_setup_teardown(getnsec3parameters, _setup, + _teardown), + cmocka_unit_test_setup_teardown(resigned, _setup, _teardown), + cmocka_unit_test_setup_teardown(attachversion, _setup, + _teardown), + cmocka_unit_test_setup_teardown(closeversion, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dh_test.c b/lib/dns/tests/dh_test.c new file mode 100644 index 0000000..bd60d6d --- /dev/null +++ b/lib/dns/tests/dh_test.c @@ -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. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include + +#include + +#include + +#include "../dst_internal.h" +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* OpenSSL DH_compute_key() failure */ +static void +dh_computesecret(void **state) { + dst_key_t *key = NULL; + isc_buffer_t buf; + unsigned char array[1024]; + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + + UNUSED(state); + + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&buf, "dh.", 3); + isc_buffer_add(&buf, 3); + result = dns_name_fromtext(name, &buf, NULL, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dst_key_fromfile(name, 18602, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_KEY, "./", dt_mctx, + &key); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&buf, array, sizeof(array)); + result = dst_key_computesecret(key, key, &buf); + assert_int_equal(result, DST_R_NOTPRIVATEKEY); + result = key->func->computesecret(key, key, &buf); + assert_int_equal(result, DST_R_COMPUTESECRETFAILURE); + + dst_key_free(&key); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(dh_computesecret, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dispatch_test.c b/lib/dns/tests/dispatch_test.c new file mode 100644 index 0000000..9f9737b --- /dev/null +++ b/lib/dns/tests/dispatch_test.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. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dnstest.h" + +dns_dispatchmgr_t *dispatchmgr = NULL; +dns_dispatchset_t *dset = NULL; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static isc_result_t +make_dispatchset(unsigned int ndisps) { + isc_result_t result; + isc_sockaddr_t any; + unsigned int attrs; + dns_dispatch_t *disp = NULL; + + result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_sockaddr_any(&any); + attrs = DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_UDP; + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &any, 512, + 6, 1024, 17, 19, attrs, attrs, &disp); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_dispatchset_create(dt_mctx, socketmgr, taskmgr, disp, + &dset, ndisps); + dns_dispatch_detach(&disp); + + return (result); +} + +static void +reset(void) { + if (dset != NULL) { + dns_dispatchset_destroy(&dset); + } + if (dispatchmgr != NULL) { + dns_dispatchmgr_destroy(&dispatchmgr); + } +} + +/* create dispatch set */ +static void +dispatchset_create(void **state) { + isc_result_t result; + + UNUSED(state); + + result = make_dispatchset(1); + assert_int_equal(result, ISC_R_SUCCESS); + reset(); + + result = make_dispatchset(10); + assert_int_equal(result, ISC_R_SUCCESS); + reset(); +} + +/* test dispatch set round-robin */ +static void +dispatchset_get(void **state) { + isc_result_t result; + dns_dispatch_t *d1, *d2, *d3, *d4, *d5; + + UNUSED(state); + + result = make_dispatchset(1); + assert_int_equal(result, ISC_R_SUCCESS); + + d1 = dns_dispatchset_get(dset); + d2 = dns_dispatchset_get(dset); + d3 = dns_dispatchset_get(dset); + d4 = dns_dispatchset_get(dset); + d5 = dns_dispatchset_get(dset); + + assert_ptr_equal(d1, d2); + assert_ptr_equal(d2, d3); + assert_ptr_equal(d3, d4); + assert_ptr_equal(d4, d5); + + reset(); + + result = make_dispatchset(4); + assert_int_equal(result, ISC_R_SUCCESS); + + d1 = dns_dispatchset_get(dset); + d2 = dns_dispatchset_get(dset); + d3 = dns_dispatchset_get(dset); + d4 = dns_dispatchset_get(dset); + d5 = dns_dispatchset_get(dset); + + assert_ptr_equal(d1, d5); + assert_ptr_not_equal(d1, d2); + assert_ptr_not_equal(d2, d3); + assert_ptr_not_equal(d3, d4); + assert_ptr_not_equal(d4, d5); + + reset(); +} + +static void +senddone(isc_task_t *task, isc_event_t *event) { + isc_socket_t *sock = event->ev_arg; + + UNUSED(task); + + isc_socket_detach(&sock); + isc_event_free(&event); +} + +static void +nameserver(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_region_t region; + isc_socket_t *dummy; + isc_socket_t *sock = event->ev_arg; + isc_socketevent_t *ev = (isc_socketevent_t *)event; + static unsigned char buf1[16]; + static unsigned char buf2[16]; + + memmove(buf1, ev->region.base, 12); + memset(buf1 + 12, 0, 4); + buf1[2] |= 0x80; /* qr=1 */ + + memmove(buf2, ev->region.base, 12); + memset(buf2 + 12, 1, 4); + buf2[2] |= 0x80; /* qr=1 */ + + /* + * send message to be discarded. + */ + region.base = buf1; + region.length = sizeof(buf1); + dummy = NULL; + isc_socket_attach(sock, &dummy); + result = isc_socket_sendto(sock, ®ion, task, senddone, sock, + &ev->address, NULL); + if (result != ISC_R_SUCCESS) { + isc_socket_detach(&dummy); + } + + /* + * send nextitem message. + */ + region.base = buf2; + region.length = sizeof(buf2); + dummy = NULL; + isc_socket_attach(sock, &dummy); + result = isc_socket_sendto(sock, ®ion, task, senddone, sock, + &ev->address, NULL); + if (result != ISC_R_SUCCESS) { + isc_socket_detach(&dummy); + } + isc_event_free(&event); +} + +static dns_dispatch_t *dispatch = NULL; +static dns_dispentry_t *dispentry = NULL; +static atomic_bool first = true; +static isc_sockaddr_t local; +static atomic_uint_fast32_t responses; + +static void +response(isc_task_t *task, isc_event_t *event) { + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + bool exp_true = true; + + UNUSED(task); + + atomic_fetch_add_relaxed(&responses, 1); + if (atomic_compare_exchange_strong(&first, &exp_true, false)) { + isc_result_t result = dns_dispatch_getnext(dispentry, &devent); + assert_int_equal(result, ISC_R_SUCCESS); + } else { + dns_dispatch_removeresponse(&dispentry, &devent); + isc_app_shutdown(); + } +} + +static void +startit(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_socket_t *sock = NULL; + + isc_socket_attach(dns_dispatch_getsocket(dispatch), &sock); + result = isc_socket_sendto(sock, event->ev_arg, task, senddone, sock, + &local, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + isc_event_free(&event); +} + +/* test dispatch getnext */ +static void +dispatch_getnext(void **state) { + isc_region_t region; + isc_result_t result; + isc_socket_t *sock = NULL; + isc_task_t *task = NULL; + uint16_t id; + struct in_addr ina; + unsigned char message[12]; + unsigned int attrs; + unsigned char rbuf[12]; + + UNUSED(state); + + atomic_init(&responses, 0); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr); + assert_int_equal(result, ISC_R_SUCCESS); + + ina.s_addr = htonl(INADDR_LOOPBACK); + isc_sockaddr_fromin(&local, &ina, 0); + attrs = DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_UDP; + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &local, + 512, 6, 1024, 17, 19, attrs, attrs, + &dispatch); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Create a local udp nameserver on the loopback. + */ + result = isc_socket_create(socketmgr, AF_INET, isc_sockettype_udp, + &sock); + assert_int_equal(result, ISC_R_SUCCESS); + + ina.s_addr = htonl(INADDR_LOOPBACK); + isc_sockaddr_fromin(&local, &ina, 0); + result = isc_socket_bind(sock, &local, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_socket_getsockname(sock, &local); + assert_int_equal(result, ISC_R_SUCCESS); + + region.base = rbuf; + region.length = sizeof(rbuf); + result = isc_socket_recv(sock, ®ion, 1, task, nameserver, sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dispatch_addresponse(dispatch, 0, &local, task, response, + NULL, &id, &dispentry, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(message, 0, sizeof(message)); + message[0] = (id >> 8) & 0xff; + message[1] = id & 0xff; + + region.base = message; + region.length = sizeof(message); + result = isc_app_onrun(dt_mctx, task, startit, ®ion); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_app_run(); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(atomic_load_acquire(&responses), 2); + + /* + * Shutdown nameserver. + */ + isc_socket_cancel(sock, task, ISC_SOCKCANCEL_RECV); + isc_socket_detach(&sock); + isc_task_detach(&task); + + /* + * Shutdown the dispatch. + */ + dns_dispatch_detach(&dispatch); + dns_dispatchmgr_destroy(&dispatchmgr); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(dispatchset_create, _setup, + _teardown), + cmocka_unit_test_setup_teardown(dispatchset_get, _setup, + _teardown), + cmocka_unit_test_setup_teardown(dispatch_getnext, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/dnstap_test.c b/lib/dns/tests/dnstap_test.c new file mode 100644 index 0000000..17addc6 --- /dev/null +++ b/lib/dns/tests/dnstap_test.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dnstest.h" + +#ifdef HAVE_DNSTAP + +#include + +#include + +#define TAPFILE "testdata/dnstap/dnstap.file" +#define TAPSOCK "testdata/dnstap/dnstap.sock" + +#define TAPSAVED "testdata/dnstap/dnstap.saved" +#define TAPTEXT "testdata/dnstap/dnstap.text" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static void +cleanup() { + (void)isc_file_remove(TAPFILE); + (void)isc_file_remove(TAPSOCK); +} + +/* set up dnstap environment */ +static void +create_test(void **state) { + isc_result_t result; + dns_dtenv_t *dtenv = NULL; + struct fstrm_iothr_options *fopt; + + UNUSED(state); + + cleanup(); + + fopt = fstrm_iothr_options_init(); + assert_non_null(fopt); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(dt_mctx, dns_dtmode_file, TAPFILE, &fopt, NULL, + &dtenv); + assert_int_equal(result, ISC_R_SUCCESS); + if (dtenv != NULL) { + dns_dt_detach(&dtenv); + } + if (fopt != NULL) { + fstrm_iothr_options_destroy(&fopt); + } + + assert_true(isc_file_exists(TAPFILE)); + + fopt = fstrm_iothr_options_init(); + assert_non_null(fopt); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(dt_mctx, dns_dtmode_unix, TAPSOCK, &fopt, NULL, + &dtenv); + assert_int_equal(result, ISC_R_SUCCESS); + if (dtenv != NULL) { + dns_dt_detach(&dtenv); + } + if (fopt != NULL) { + fstrm_iothr_options_destroy(&fopt); + } + + /* 'create' should succeed, but the file shouldn't exist yet */ + assert_false(isc_file_exists(TAPSOCK)); + + fopt = fstrm_iothr_options_init(); + assert_non_null(fopt); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(dt_mctx, 33, TAPSOCK, &fopt, NULL, &dtenv); + assert_int_equal(result, ISC_R_FAILURE); + assert_null(dtenv); + if (dtenv != NULL) { + dns_dt_detach(&dtenv); + } + if (fopt != NULL) { + fstrm_iothr_options_destroy(&fopt); + } + + cleanup(); +} + +/* send dnstap messages */ +static void +send_test(void **state) { + isc_result_t result; + dns_dtenv_t *dtenv = NULL; + dns_dthandle_t *handle = NULL; + uint8_t *data; + size_t dsize; + unsigned char zone[DNS_NAME_MAXWIRE]; + unsigned char qambuffer[4096], rambuffer[4096]; + unsigned char qrmbuffer[4096], rrmbuffer[4096]; + isc_buffer_t zb, qamsg, ramsg, qrmsg, rrmsg; + size_t qasize, qrsize, rasize, rrsize; + dns_fixedname_t zfname; + dns_name_t *zname; + dns_dtmsgtype_t dt; + dns_view_t *view = NULL; + dns_compress_t cctx; + isc_region_t zr; + isc_sockaddr_t qaddr; + isc_sockaddr_t raddr; + struct in_addr in; + isc_stdtime_t now; + isc_time_t p, f; + struct fstrm_iothr_options *fopt; + + UNUSED(state); + + cleanup(); + + result = dns_test_makeview("test", &view); + assert_int_equal(result, ISC_R_SUCCESS); + + fopt = fstrm_iothr_options_init(); + assert_non_null(fopt); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(dt_mctx, dns_dtmode_file, TAPFILE, &fopt, NULL, + &dtenv); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_dt_attach(dtenv, &view->dtenv); + view->dttypes = DNS_DTTYPE_ALL; + + /* + * Set up some test data + */ + zname = dns_fixedname_initname(&zfname); + isc_buffer_constinit(&zb, "example.com.", 12); + isc_buffer_add(&zb, 12); + result = dns_name_fromtext(zname, &zb, NULL, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(&zr, 0, sizeof(zr)); + isc_buffer_init(&zb, zone, sizeof(zone)); + result = dns_compress_init(&cctx, -1, dt_mctx); + assert_int_equal(result, ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(zname, &cctx, &zb); + assert_int_equal(result, ISC_R_SUCCESS); + dns_compress_invalidate(&cctx); + isc_buffer_usedregion(&zb, &zr); + + in.s_addr = inet_addr("10.53.0.1"); + isc_sockaddr_fromin(&qaddr, &in, 2112); + in.s_addr = inet_addr("10.53.0.2"); + isc_sockaddr_fromin(&raddr, &in, 2112); + + isc_stdtime_get(&now); + isc_time_set(&p, now - 3600, 0); /* past */ + isc_time_set(&f, now + 3600, 0); /* future */ + + result = dns_test_getdata("testdata/dnstap/query.auth", qambuffer, + sizeof(qambuffer), &qasize); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&qamsg, qambuffer, qasize); + isc_buffer_add(&qamsg, qasize); + + result = dns_test_getdata("testdata/dnstap/response.auth", rambuffer, + sizeof(rambuffer), &rasize); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&ramsg, rambuffer, rasize); + isc_buffer_add(&ramsg, rasize); + + result = dns_test_getdata("testdata/dnstap/query.recursive", qrmbuffer, + sizeof(qrmbuffer), &qrsize); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&qrmsg, qrmbuffer, qrsize); + isc_buffer_add(&qrmsg, qrsize); + + result = dns_test_getdata("testdata/dnstap/response.recursive", + rrmbuffer, sizeof(rrmbuffer), &rrsize); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&rrmsg, rrmbuffer, rrsize); + isc_buffer_add(&rrmsg, rrsize); + + for (dt = DNS_DTTYPE_SQ; dt <= DNS_DTTYPE_TR; dt <<= 1) { + isc_buffer_t *m; + isc_sockaddr_t *q = &qaddr, *r = &raddr; + + switch (dt) { + case DNS_DTTYPE_AQ: + m = &qamsg; + break; + case DNS_DTTYPE_AR: + m = &ramsg; + break; + default: + m = &qrmsg; + if ((dt & DNS_DTTYPE_RESPONSE) != 0) { + m = &ramsg; + } + break; + } + + dns_dt_send(view, dt, q, r, false, &zr, &p, &f, m); + dns_dt_send(view, dt, q, r, false, &zr, NULL, &f, m); + dns_dt_send(view, dt, q, r, false, &zr, &p, NULL, m); + dns_dt_send(view, dt, q, r, false, &zr, NULL, NULL, m); + dns_dt_send(view, dt, q, r, true, &zr, &p, &f, m); + dns_dt_send(view, dt, q, r, true, &zr, NULL, &f, m); + dns_dt_send(view, dt, q, r, true, &zr, &p, NULL, m); + dns_dt_send(view, dt, q, r, true, &zr, NULL, NULL, m); + } + + dns_dt_detach(&view->dtenv); + dns_dt_detach(&dtenv); + dns_view_detach(&view); + + result = dns_dt_open(TAPFILE, dns_dtmode_file, dt_mctx, &handle); + assert_int_equal(result, ISC_R_SUCCESS); + + while (dns_dt_getframe(handle, &data, &dsize) == ISC_R_SUCCESS) { + dns_dtdata_t *dtdata = NULL; + isc_region_t r; + static dns_dtmsgtype_t expected = DNS_DTTYPE_SQ; + static int n = 0; + + r.base = data; + r.length = dsize; + + result = dns_dt_parse(dt_mctx, &r, &dtdata); + assert_int_equal(result, ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) { + n++; + continue; + } + + assert_int_equal(dtdata->type, expected); + if (++n % 8 == 0) { + expected <<= 1; + } + + dns_dtdata_free(&dtdata); + } + + if (fopt != NULL) { + fstrm_iothr_options_destroy(&fopt); + } + if (handle != NULL) { + dns_dt_close(&handle); + } + cleanup(); +} + +/* dnstap message to text */ +static void +totext_test(void **state) { + isc_result_t result; + dns_dthandle_t *handle = NULL; + uint8_t *data; + size_t dsize; + FILE *fp = NULL; + + UNUSED(state); + + result = dns_dt_open(TAPSAVED, dns_dtmode_file, dt_mctx, &handle); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_stdio_open(TAPTEXT, "r", &fp); + assert_int_equal(result, ISC_R_SUCCESS); + + while (dns_dt_getframe(handle, &data, &dsize) == ISC_R_SUCCESS) { + dns_dtdata_t *dtdata = NULL; + isc_buffer_t *b = NULL; + isc_region_t r; + char s[BUFSIZ], *p; + + r.base = data; + r.length = dsize; + + /* read the corresponding line of text */ + p = fgets(s, sizeof(s), fp); + assert_ptr_equal(p, s); + if (p == NULL) { + break; + } + + p = strchr(p, '\n'); + if (p != NULL) { + *p = '\0'; + } + + /* parse dnstap frame */ + result = dns_dt_parse(dt_mctx, &r, &dtdata); + assert_int_equal(result, ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) { + continue; + } + + isc_buffer_allocate(dt_mctx, &b, 2048); + assert_non_null(b); + if (b == NULL) { + break; + } + + /* convert to text and compare */ + result = dns_dt_datatotext(dtdata, &b); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_string_equal((char *)isc_buffer_base(b), s); + + dns_dtdata_free(&dtdata); + isc_buffer_free(&b); + } + + if (handle != NULL) { + dns_dt_close(&handle); + } + cleanup(); +} +#endif /* HAVE_DNSTAP */ + +int +main(void) { +#if HAVE_DNSTAP + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(create_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(send_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(totext_test, _setup, _teardown), + }; + + /* make sure text conversion gets the right local time */ + setenv("TZ", "PST8", 1); + + return (cmocka_run_group_tests(tests, NULL, NULL)); +#else /* if HAVE_DNSTAP */ + print_message("1..0 # Skipped: dnstap not enabled\n"); + return (SKIPPED_TEST_EXIT_CODE); +#endif /* HAVE_DNSTAP */ +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c new file mode 100644 index 0000000..a8cd6b5 --- /dev/null +++ b/lib/dns/tests/dnstest.c @@ -0,0 +1,643 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_CMOCKA +#define UNIT_TESTING +#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 "dnstest.h" + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +isc_mem_t *dt_mctx = NULL; +isc_log_t *lctx = NULL; +isc_nm_t *netmgr = NULL; +isc_taskmgr_t *taskmgr = NULL; +isc_task_t *maintask = NULL; +isc_timermgr_t *timermgr = NULL; +isc_socketmgr_t *socketmgr = NULL; +dns_zonemgr_t *zonemgr = NULL; +bool app_running = false; +int ncpus; +bool debug_mem_record = true; + +static bool dst_active = false; +static bool test_running = false; + +/* + * Logging categories: this needs to match the list in bin/named/log.c. + */ +static isc_logcategory_t categories[] = { { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } }; + +static void +cleanup_managers(void) { + if (maintask != NULL) { + isc_task_shutdown(maintask); + isc_task_destroy(&maintask); + } + + isc_managers_destroy(netmgr == NULL ? NULL : &netmgr, + taskmgr == NULL ? NULL : &taskmgr); + + if (socketmgr != NULL) { + isc_socketmgr_destroy(&socketmgr); + } + if (timermgr != NULL) { + isc_timermgr_destroy(&timermgr); + } + if (app_running) { + isc_app_finish(); + } +} + +static isc_result_t +create_managers(void) { + isc_result_t result; + ncpus = isc_os_ncpus(); + + CHECK(isc_managers_create(dt_mctx, ncpus, 0, &netmgr, &taskmgr)); + CHECK(isc_timermgr_create(dt_mctx, &timermgr)); + CHECK(isc_socketmgr_create(dt_mctx, &socketmgr)); + CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0)); + return (ISC_R_SUCCESS); + +cleanup: + cleanup_managers(); + return (result); +} + +isc_result_t +dns_test_begin(FILE *logfile, bool start_managers) { + isc_result_t result; + + INSIST(!test_running); + test_running = true; + + if (start_managers) { + CHECK(isc_app_start()); + } + if (debug_mem_record) { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } + + INSIST(dt_mctx == NULL); + isc_mem_create(&dt_mctx); + + /* Don't check the memory leaks as they hide the assertions */ + isc_mem_setdestroycheck(dt_mctx, false); + + INSIST(!dst_active); + CHECK(dst_lib_init(dt_mctx, NULL)); + dst_active = true; + + if (logfile != NULL) { + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + INSIST(lctx == NULL); + isc_log_create(dt_mctx, &lctx, &logconfig); + isc_log_registercategories(lctx, categories); + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + + destination.file.stream = logfile; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, &destination, 0); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + } + + dns_result_register(); + + if (start_managers) { + CHECK(create_managers()); + } + + /* + * The caller might run from another directory, so tests + * that access test data files must first chdir to the proper + * location. + */ + if (chdir(TESTS) == -1) { + CHECK(ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); + +cleanup: + dns_test_end(); + return (result); +} + +void +dns_test_end(void) { + cleanup_managers(); + + dst_lib_destroy(); + dst_active = false; + + if (lctx != NULL) { + isc_log_destroy(&lctx); + } + + if (dt_mctx != NULL) { + isc_mem_destroy(&dt_mctx); + } + + test_running = false; +} + +/* + * Create a view. + */ +isc_result_t +dns_test_makeview(const char *name, dns_view_t **viewp) { + isc_result_t result; + dns_view_t *view = NULL; + + CHECK(dns_view_create(dt_mctx, dns_rdataclass_in, name, &view)); + *viewp = view; + + return (ISC_R_SUCCESS); + +cleanup: + if (view != NULL) { + dns_view_detach(&view); + } + return (result); +} + +isc_result_t +dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + bool createview) { + dns_fixedname_t fixed_origin; + dns_zone_t *zone = NULL; + isc_result_t result; + dns_name_t *origin; + + REQUIRE(view == NULL || !createview); + + /* + * Create the zone structure. + */ + result = dns_zone_create(&zone, dt_mctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Set zone type and origin. + */ + dns_zone_settype(zone, dns_zone_primary); + origin = dns_fixedname_initname(&fixed_origin); + result = dns_name_fromstring(origin, name, 0, NULL); + if (result != ISC_R_SUCCESS) { + goto detach_zone; + } + result = dns_zone_setorigin(zone, origin); + if (result != ISC_R_SUCCESS) { + goto detach_zone; + } + + /* + * If requested, create a view. + */ + if (createview) { + result = dns_test_makeview("view", &view); + if (result != ISC_R_SUCCESS) { + goto detach_zone; + } + } + + /* + * If a view was passed as an argument or created above, attach the + * created zone to it. Otherwise, set the zone's class to IN. + */ + if (view != NULL) { + dns_zone_setview(zone, view); + dns_zone_setclass(zone, view->rdclass); + dns_view_addzone(view, zone); + } else { + dns_zone_setclass(zone, dns_rdataclass_in); + } + + *zonep = zone; + + return (ISC_R_SUCCESS); + +detach_zone: + dns_zone_detach(&zone); + + return (result); +} + +isc_result_t +dns_test_setupzonemgr(void) { + isc_result_t result; + REQUIRE(zonemgr == NULL); + + result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, + &zonemgr); + return (result); +} + +isc_result_t +dns_test_managezone(dns_zone_t *zone) { + isc_result_t result; + REQUIRE(zonemgr != NULL); + + result = dns_zonemgr_setsize(zonemgr, 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_zonemgr_managezone(zonemgr, zone); + return (result); +} + +void +dns_test_releasezone(dns_zone_t *zone) { + REQUIRE(zonemgr != NULL); + dns_zonemgr_releasezone(zonemgr, zone); +} + +void +dns_test_closezonemgr(void) { + REQUIRE(zonemgr != NULL); + + dns_zonemgr_shutdown(zonemgr); + dns_zonemgr_detach(&zonemgr); +} + +/* + * Sleep for 'usec' microseconds. + */ +void +dns_test_nap(uint32_t usec) { + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +} + +isc_result_t +dns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin, + const char *testfile) { + isc_result_t result; + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + + result = dns_name_fromstring(name, origin, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_create(dt_mctx, "rbt", name, dbtype, dns_rdataclass_in, + 0, NULL, db); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_load(*db, testfile, dns_masterformat_text, 0); + return (result); +} + +static int +fromhex(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } else if (c >= 'a' && c <= 'f') { + return (c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + return (c - 'A' + 10); + } + + printf("bad input format: %02x\n", c); + exit(3); +} + +/* + * Format contents of given memory region as a hex string, using the buffer + * of length 'buflen' pointed to by 'buf'. 'buflen' must be at least three + * times 'len'. Always returns 'buf'. + */ +char * +dns_test_tohex(const unsigned char *data, size_t len, char *buf, + size_t buflen) { + isc_constregion_t source = { .base = data, .length = len }; + isc_buffer_t target; + isc_result_t result; + + memset(buf, 0, buflen); + isc_buffer_init(&target, buf, buflen); + result = isc_hex_totext((isc_region_t *)&source, 1, " ", &target); + assert_int_equal(result, ISC_R_SUCCESS); + + return (buf); +} + +isc_result_t +dns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz, + size_t *sizep) { + isc_result_t result; + unsigned char *bp; + char *rp, *wp; + char s[BUFSIZ]; + size_t len, i; + FILE *f = NULL; + int n; + + result = isc_stdio_open(file, "r", &f); + if (result != ISC_R_SUCCESS) { + return (result); + } + + bp = buf; + while (fgets(s, sizeof(s), f) != NULL) { + rp = s; + wp = s; + len = 0; + while (*rp != '\0') { + if (*rp == '#') { + break; + } + if (*rp != ' ' && *rp != '\t' && *rp != '\r' && + *rp != '\n') + { + *wp++ = *rp; + len++; + } + rp++; + } + if (len == 0U) { + continue; + } + if (len % 2 != 0U) { + CHECK(ISC_R_UNEXPECTEDEND); + } + if (len > bufsiz * 2) { + CHECK(ISC_R_NOSPACE); + } + rp = s; + for (i = 0; i < len; i += 2) { + n = fromhex(*rp++); + n *= 16; + n += fromhex(*rp++); + *bp++ = n; + } + } + + *sizep = bp - buf; + + result = ISC_R_SUCCESS; + +cleanup: + isc_stdio_close(f); + return (result); +} + +static void +nullmsg(dns_rdatacallbacks_t *cb, const char *fmt, ...) { + UNUSED(cb); + UNUSED(fmt); +} + +isc_result_t +dns_test_rdatafromstring(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, unsigned char *dst, + size_t dstlen, const char *src, bool warnings) { + dns_rdatacallbacks_t callbacks; + isc_buffer_t source, target; + isc_lex_t *lex = NULL; + isc_lexspecials_t specials = { 0 }; + isc_result_t result; + size_t length; + + REQUIRE(rdata != NULL); + REQUIRE(DNS_RDATA_INITIALIZED(rdata)); + REQUIRE(dst != NULL); + REQUIRE(src != NULL); + + /* + * Set up source to hold the input string. + */ + length = strlen(src); + isc_buffer_constinit(&source, src, length); + isc_buffer_add(&source, length); + + /* + * Create a lexer as one is required by dns_rdata_fromtext(). + */ + result = isc_lex_create(dt_mctx, 64, &lex); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Set characters which will be treated as valid multi-line RDATA + * delimiters while reading the source string. These should match + * specials from lib/dns/master.c. + */ + specials[0] = 1; + specials['('] = 1; + specials[')'] = 1; + specials['"'] = 1; + isc_lex_setspecials(lex, specials); + + /* + * Expect DNS masterfile comments. + */ + isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); + + /* + * Point lexer at source. + */ + result = isc_lex_openbuffer(lex, &source); + if (result != ISC_R_SUCCESS) { + goto destroy_lexer; + } + + /* + * Set up target for storing uncompressed wire form of provided RDATA. + */ + isc_buffer_init(&target, dst, dstlen); + + /* + * Set up callbacks so warnings and errors are not printed. + */ + if (!warnings) { + dns_rdatacallbacks_init(&callbacks); + callbacks.warn = callbacks.error = nullmsg; + } + + /* + * Parse input string, determining result. + */ + result = dns_rdata_fromtext(rdata, rdclass, rdtype, lex, dns_rootname, + 0, dt_mctx, &target, &callbacks); + +destroy_lexer: + isc_lex_destroy(&lex); + + return (result); +} + +void +dns_test_namefromstring(const char *namestr, dns_fixedname_t *fname) { + size_t length; + isc_buffer_t *b = NULL; + isc_result_t result; + dns_name_t *name; + + length = strlen(namestr); + + name = dns_fixedname_initname(fname); + + isc_buffer_allocate(dt_mctx, &b, length); + + isc_buffer_putmem(b, (const unsigned char *)namestr, length); + result = dns_name_fromtext(name, b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_free(&b); +} + +isc_result_t +dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes, + bool warnings) { + isc_result_t result = ISC_R_SUCCESS; + unsigned char rdata_buf[1024]; + dns_difftuple_t *tuple = NULL; + isc_consttextregion_t region; + dns_rdatatype_t rdatatype; + dns_fixedname_t fixedname; + dns_rdata_t rdata; + dns_name_t *name; + size_t i; + + REQUIRE(diff != NULL); + REQUIRE(changes != NULL); + + dns_diff_init(dt_mctx, diff); + + for (i = 0; changes[i].owner != NULL; i++) { + /* + * Parse owner name. + */ + name = dns_fixedname_initname(&fixedname); + result = dns_name_fromstring(name, changes[i].owner, 0, + dt_mctx); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Parse RDATA type. + */ + region.base = changes[i].type; + region.length = strlen(changes[i].type); + result = dns_rdatatype_fromtext(&rdatatype, + (isc_textregion_t *)®ion); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Parse RDATA. + */ + dns_rdata_init(&rdata); + result = dns_test_rdatafromstring( + &rdata, dns_rdataclass_in, rdatatype, rdata_buf, + sizeof(rdata_buf), changes[i].rdata, warnings); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Create a diff tuple for the parsed change and append it to + * the diff. + */ + result = dns_difftuple_create(dt_mctx, changes[i].op, name, + changes[i].ttl, &rdata, &tuple); + if (result != ISC_R_SUCCESS) { + break; + } + dns_diff_append(diff, &tuple); + } + + if (result != ISC_R_SUCCESS) { + dns_diff_clear(diff); + } + + return (result); +} +#endif /* HAVE_CMOCKA */ diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h new file mode 100644 index 0000000..f19b518 --- /dev/null +++ b/lib/dns/tests/dnstest.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + +typedef struct { + dns_diffop_t op; + const char *owner; + dns_ttl_t ttl; + const char *type; + const char *rdata; +} zonechange_t; + +#define ZONECHANGE_SENTINEL \ + { \ + 0, NULL, 0, NULL, NULL \ + } + +extern isc_mem_t *dt_mctx; +extern isc_log_t *lctx; +extern isc_taskmgr_t *taskmgr; +extern isc_task_t *maintask; +extern isc_timermgr_t *timermgr; +extern isc_socketmgr_t *socketmgr; +extern dns_zonemgr_t *zonemgr; +extern bool app_running; +extern int ncpus; +extern bool debug_mem_record; + +isc_result_t +dns_test_begin(FILE *logfile, bool create_managers); + +void +dns_test_end(void); + +isc_result_t +dns_test_makeview(const char *name, dns_view_t **viewp); + +/*% + * Create a zone with origin 'name', return a pointer to the zone object in + * 'zonep'. + * + * If 'view' is set, the returned zone will be assigned to the passed view. + * 'createview' must be set to false when 'view' is non-NULL. + * + * If 'view' is not set and 'createview' is true, a new view is also created + * and the returned zone is assigned to it. This imposes two requirements on + * the caller: 1) the returned zone has to be subsequently assigned to a zone + * manager, otherwise its cleanup will fail, 2) the created view has to be + * cleaned up by the caller. + * + * If 'view' is not set and 'createview' is false, the returned zone will not + * be assigned to any view. + */ +isc_result_t +dns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + bool createview); + +isc_result_t +dns_test_setupzonemgr(void); + +isc_result_t +dns_test_managezone(dns_zone_t *zone); + +void +dns_test_releasezone(dns_zone_t *zone); + +void +dns_test_closezonemgr(void); + +void +dns_test_nap(uint32_t usec); + +isc_result_t +dns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin, + const char *testfile); + +isc_result_t +dns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz, + size_t *sizep); + +char * +dns_test_tohex(const unsigned char *data, size_t len, char *buf, size_t buflen); + +/*% + * Try parsing text form RDATA in "src" (of class "rdclass" and type "rdtype") + * into a structure representing that RDATA at "rdata", storing the + * uncompressed wire form of that RDATA at "dst", which is "dstlen" bytes long. + * Set 'warnings' to true to print logged warnings from dns_rdata_fromtext(). + */ +isc_result_t +dns_test_rdatafromstring(dns_rdata_t *rdata, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, unsigned char *dst, + size_t dstlen, const char *src, bool warnings); + +void +dns_test_namefromstring(const char *namestr, dns_fixedname_t *fname); + +/*% + * Given a pointer to an uninitialized dns_diff_t structure in 'diff', make it + * contain diff tuples representing zone database changes listed in 'changes'. + * Set 'warnings' to true to print logged warnings from dns_rdata_fromtext(). + */ +isc_result_t +dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes, + bool warnings); diff --git a/lib/dns/tests/dst_test.c b/lib/dns/tests/dst_test.c new file mode 100644 index 0000000..f5370cc --- /dev/null +++ b/lib/dns/tests/dst_test.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "../dst_internal.h" +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* Read sig in file at path to buf. Check signature ineffability */ +static isc_result_t +sig_fromfile(const char *path, isc_buffer_t *buf) { + isc_result_t result; + size_t rval, len; + FILE *fp = NULL; + unsigned char val; + char *p, *data; + off_t size; + + result = isc_stdio_open(path, "rb", &fp); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_file_getsizefd(fileno(fp), &size); + assert_int_equal(result, ISC_R_SUCCESS); + + data = isc_mem_get(dt_mctx, (size + 1)); + assert_non_null(data); + + len = (size_t)size; + p = data; + while (len != 0U) { + result = isc_stdio_read(p, 1, len, fp, &rval); + assert_int_equal(result, ISC_R_SUCCESS); + len -= rval; + p += rval; + } + isc_stdio_close(fp); + + p = data; + len = size; + while (len > 0U) { + if ((*p == '\r') || (*p == '\n')) { + ++p; + --len; + continue; + } else if (len < 2U) { + goto err; + } + if (('0' <= *p) && (*p <= '9')) { + val = *p - '0'; + } else if (('A' <= *p) && (*p <= 'F')) { + val = *p - 'A' + 10; + } else { + result = ISC_R_BADHEX; + goto err; + } + ++p; + val <<= 4; + --len; + if (('0' <= *p) && (*p <= '9')) { + val |= (*p - '0'); + } else if (('A' <= *p) && (*p <= 'F')) { + val |= (*p - 'A' + 10); + } else { + result = ISC_R_BADHEX; + goto err; + } + ++p; + --len; + isc_buffer_putuint8(buf, val); + } + + result = ISC_R_SUCCESS; + +err: + isc_mem_put(dt_mctx, data, size + 1); + return (result); +} + +static void +check_sig(const char *datapath, const char *sigpath, const char *keyname, + dns_keytag_t id, dns_secalg_t alg, int type, bool expect) { + isc_result_t result; + size_t rval, len; + FILE *fp; + dst_key_t *key = NULL; + unsigned char sig[512]; + unsigned char *p; + unsigned char *data; + off_t size; + isc_buffer_t b; + isc_buffer_t databuf, sigbuf; + isc_region_t datareg, sigreg; + dns_fixedname_t fname; + dns_name_t *name; + dst_context_t *ctx = NULL; + + /* + * Read data from file in a form usable by dst_verify. + */ + result = isc_stdio_open(datapath, "rb", &fp); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_file_getsizefd(fileno(fp), &size); + assert_int_equal(result, ISC_R_SUCCESS); + + data = isc_mem_get(dt_mctx, (size + 1)); + assert_non_null(data); + + p = data; + len = (size_t)size; + do { + result = isc_stdio_read(p, 1, len, fp, &rval); + assert_int_equal(result, ISC_R_SUCCESS); + len -= rval; + p += rval; + } while (len); + isc_stdio_close(fp); + + /* + * Read key from file in a form usable by dst_verify. + */ + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&b, keyname, strlen(keyname)); + isc_buffer_add(&b, strlen(keyname)); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dst_key_fromfile(name, id, alg, type, "testdata/dst", dt_mctx, + &key); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&databuf, data, (unsigned int)size); + isc_buffer_add(&databuf, (unsigned int)size); + isc_buffer_usedregion(&databuf, &datareg); + + memset(sig, 0, sizeof(sig)); + isc_buffer_init(&sigbuf, sig, sizeof(sig)); + + /* + * Read precomputed signature from file in a form usable by dst_verify. + */ + result = sig_fromfile(sigpath, &sigbuf); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Verify that the key signed the data. + */ + isc_buffer_remainingregion(&sigbuf, &sigreg); + + result = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_GENERAL, + false, 0, &ctx); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dst_context_adddata(ctx, &datareg); + assert_int_equal(result, ISC_R_SUCCESS); + result = dst_context_verify(ctx, &sigreg); + + /* + * Compute the expected signature and emit it + * so the precomputed signature can be updated. + * This should only be done if the covered data + * is updated. + */ + if (expect && result != ISC_R_SUCCESS) { + isc_result_t result2; + + dst_context_destroy(&ctx); + result2 = dst_context_create( + key, dt_mctx, DNS_LOGCATEGORY_GENERAL, false, 0, &ctx); + assert_int_equal(result2, ISC_R_SUCCESS); + + result2 = dst_context_adddata(ctx, &datareg); + assert_int_equal(result2, ISC_R_SUCCESS); + + char sigbuf2[4096]; + isc_buffer_t sigb; + isc_buffer_init(&sigb, sigbuf2, sizeof(sigbuf2)); + + result2 = dst_context_sign(ctx, &sigb); + assert_int_equal(result2, ISC_R_SUCCESS); + + isc_region_t r; + isc_buffer_usedregion(&sigb, &r); + + char hexbuf[4096] = { 0 }; + isc_buffer_t hb; + isc_buffer_init(&hb, hexbuf, sizeof(hexbuf)); + + isc_hex_totext(&r, 0, "", &hb); + + fprintf(stderr, "# %s:\n# %s\n", sigpath, hexbuf); + } + + isc_mem_put(dt_mctx, data, size + 1); + dst_context_destroy(&ctx); + dst_key_free(&key); + + assert_true((expect && (result == ISC_R_SUCCESS)) || + (!expect && (result != ISC_R_SUCCESS))); + + return; +} + +static void +sig_test(void **state) { + UNUSED(state); + + struct { + const char *datapath; + const char *sigpath; + const char *keyname; + dns_keytag_t keyid; + dns_secalg_t alg; + bool expect; + } testcases[] = { + { "testdata/dst/test1.data", "testdata/dst/test1.ecdsa256sig", + "test.", 49130, DST_ALG_ECDSA256, true }, + { "testdata/dst/test1.data", "testdata/dst/test1.rsasha256sig", + "test.", 11349, DST_ALG_RSASHA256, true }, + { /* wrong sig */ + "testdata/dst/test1.data", "testdata/dst/test1.ecdsa256sig", + "test.", 11349, DST_ALG_RSASHA256, false }, + { /* wrong data */ + "testdata/dst/test2.data", "testdata/dst/test1.ecdsa256sig", + "test.", 49130, DST_ALG_ECDSA256, false }, + }; + unsigned int i; + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + if (!dst_algorithm_supported(testcases[i].alg)) { + continue; + } + + check_sig(testcases[i].datapath, testcases[i].sigpath, + testcases[i].keyname, testcases[i].keyid, + testcases[i].alg, DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, + testcases[i].expect); + } +} + +#if !defined(USE_PKCS11) +static void +check_cmp(const char *key1_name, dns_keytag_t key1_id, const char *key2_name, + dns_keytag_t key2_id, dns_secalg_t alg, int type, bool expect) { + isc_result_t result; + dst_key_t *key1 = NULL; + dst_key_t *key2 = NULL; + isc_buffer_t b1; + isc_buffer_t b2; + dns_fixedname_t fname1; + dns_fixedname_t fname2; + dns_name_t *name1; + dns_name_t *name2; + + /* + * Read key1 from the file. + */ + name1 = dns_fixedname_initname(&fname1); + isc_buffer_constinit(&b1, key1_name, strlen(key1_name)); + isc_buffer_add(&b1, strlen(key1_name)); + result = dns_name_fromtext(name1, &b1, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dst_key_fromfile(name1, key1_id, alg, type, "comparekeys", + dt_mctx, &key1); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Read key2 from the file. + */ + name2 = dns_fixedname_initname(&fname2); + isc_buffer_constinit(&b2, key2_name, strlen(key2_name)); + isc_buffer_add(&b2, strlen(key2_name)); + result = dns_name_fromtext(name2, &b2, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dst_key_fromfile(name2, key2_id, alg, type, "comparekeys", + dt_mctx, &key2); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Compare the keys (for public-only keys). + */ + if ((type & DST_TYPE_PRIVATE) == 0) { + assert_true(dst_key_pubcompare(key1, key2, false) == expect); + } + + /* + * Compare the keys (for both public-only keys and keypairs). + */ + assert_true(dst_key_compare(key1, key2) == expect); + + /* + * Free the keys + */ + dst_key_free(&key2); + dst_key_free(&key1); + + return; +} + +static void +cmp_test(void **state) { + UNUSED(state); + + struct { + const char *key1_name; + dns_keytag_t key1_id; + const char *key2_name; + dns_keytag_t key2_id; + dns_secalg_t alg; + int type; + bool expect; + } testcases[] = { + /* RSA Keypair: self */ + { "example.", 53461, "example.", 53461, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true }, + + /* RSA Keypair: different key */ + { "example.", 53461, "example2.", 37993, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Keypair: different PublicExponent (e) */ + { "example.", 53461, "example-e.", 53973, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Keypair: different Modulus (n) */ + { "example.", 53461, "example-n.", 37464, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Keypair: different PrivateExponent (d) */ + { "example.", 53461, "example-d.", 53461, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Keypair: different Prime1 (p) */ + { "example.", 53461, "example-p.", 53461, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Keypair: different Prime2 (q) */ + { "example.", 53461, "example-q.", 53461, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* RSA Public Key: self */ + { "example.", 53461, "example.", 53461, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC, true }, + + /* RSA Public Key: different key */ + { "example.", 53461, "example2.", 37993, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC, false }, + + /* RSA Public Key: different PublicExponent (e) */ + { "example.", 53461, "example-e.", 53973, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC, false }, + + /* RSA Public Key: different Modulus (n) */ + { "example.", 53461, "example-n.", 37464, DST_ALG_RSASHA256, + DST_TYPE_PUBLIC, false }, + + /* ECDSA Keypair: self */ + { "example.", 19786, "example.", 19786, DST_ALG_ECDSA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true }, + + /* ECDSA Keypair: different key */ + { "example.", 19786, "example2.", 16384, DST_ALG_ECDSA256, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* ECDSA Public Key: self */ + { "example.", 19786, "example.", 19786, DST_ALG_ECDSA256, + DST_TYPE_PUBLIC, true }, + + /* ECDSA Public Key: different key */ + { "example.", 19786, "example2.", 16384, DST_ALG_ECDSA256, + DST_TYPE_PUBLIC, false }, + + /* EdDSA Keypair: self */ + { "example.", 63663, "example.", 63663, DST_ALG_ED25519, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, true }, + + /* EdDSA Keypair: different key */ + { "example.", 63663, "example2.", 37529, DST_ALG_ED25519, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, false }, + + /* EdDSA Public Key: self */ + { "example.", 63663, "example.", 63663, DST_ALG_ED25519, + DST_TYPE_PUBLIC, true }, + + /* EdDSA Public Key: different key */ + { "example.", 63663, "example2.", 37529, DST_ALG_ED25519, + DST_TYPE_PUBLIC, false }, + + /* DH Keypair: self */ + { "example.", 65316, "example.", 65316, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, true }, + + /* DH Keypair: different key */ + { "example.", 65316, "example2.", 19823, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false }, + + /* DH Keypair: different key (with generator=5) */ + { "example.", 65316, "example3.", 17187, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false }, + + /* DH Keypair: different private key */ + { "example.", 65316, "example-private.", 65316, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_KEY, false }, + + /* DH Public Key: self */ + { "example.", 65316, "example.", 65316, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_KEY, true }, + + /* DH Public Key: different key */ + { "example.", 65316, "example2.", 19823, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_KEY, false }, + + /* DH Public Key: different key (with generator=5) */ + { "example.", 65316, "example3.", 17187, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_KEY, false }, + }; + unsigned int i; + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + if (!dst_algorithm_supported(testcases[i].alg)) { + continue; + } + + check_cmp(testcases[i].key1_name, testcases[i].key1_id, + testcases[i].key2_name, testcases[i].key2_id, + testcases[i].alg, testcases[i].type, + testcases[i].expect); + } +} +#endif /* #if !defined(USE_PKCS11) */ + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(sig_test, _setup, _teardown), +#if !defined(USE_PKCS11) + cmocka_unit_test_setup_teardown(cmp_test, _setup, _teardown), +#endif /* #if !defined(USE_PKCS11) */ + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/geoip_test.c b/lib/dns/tests/geoip_test.c new file mode 100644 index 0000000..054213e --- /dev/null +++ b/lib/dns/tests/geoip_test.c @@ -0,0 +1,433 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +#if defined(HAVE_GEOIP2) +#include + +#include "../geoip2.c" + +/* Use GeoIP2 databases from the 'geoip2' system test */ +#define TEST_GEOIP_DATA "../../../bin/tests/system/geoip2/data" + +static dns_geoip_databases_t geoip; + +static MMDB_s geoip_country, geoip_city, geoip_as, geoip_isp, geoip_domain; + +static void +load_geoip(const char *dir); +static void +close_geoip(void); + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + close_geoip(); + + dns_test_end(); + + return (0); +} + +static MMDB_s * +open_geoip2(const char *dir, const char *dbfile, MMDB_s *mmdb) { + char pathbuf[PATH_MAX]; + int ret; + + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, dbfile); + ret = MMDB_open(pathbuf, MMDB_MODE_MMAP, mmdb); + if (ret == MMDB_SUCCESS) { + return (mmdb); + } + + return (NULL); +} + +static void +load_geoip(const char *dir) { + geoip.country = open_geoip2(dir, "GeoIP2-Country.mmdb", &geoip_country); + geoip.city = open_geoip2(dir, "GeoIP2-City.mmdb", &geoip_city); + geoip.as = open_geoip2(dir, "GeoLite2-ASN.mmdb", &geoip_as); + geoip.isp = open_geoip2(dir, "GeoIP2-ISP.mmdb", &geoip_isp); + geoip.domain = open_geoip2(dir, "GeoIP2-Domain.mmdb", &geoip_domain); +} + +static void +close_geoip(void) { + MMDB_close(&geoip_country); + MMDB_close(&geoip_city); + MMDB_close(&geoip_as); + MMDB_close(&geoip_isp); + MMDB_close(&geoip_domain); +} + +static bool +/* Check if an MMDB entry of a given subtype exists for the given IP */ +entry_exists(dns_geoip_subtype_t subtype, const char *addr) { + struct in6_addr in6; + struct in_addr in4; + isc_netaddr_t na; + MMDB_s *db; + + if (inet_pton(AF_INET6, addr, &in6) == 1) { + isc_netaddr_fromin6(&na, &in6); + } else if (inet_pton(AF_INET, addr, &in4) == 1) { + isc_netaddr_fromin(&na, &in4); + } else { + UNREACHABLE(); + } + + db = geoip2_database(&geoip, fix_subtype(&geoip, subtype)); + + return (db != NULL && get_entry_for(db, &na) != NULL); +} + +/* + * Baseline test - check if get_entry_for() works as expected, i.e. that its + * return values are consistent with the contents of the test MMDBs found in + * bin/tests/system/geoip2/data/ (10.53.0.1 and fd92:7065:b8e:ffff::1 should be + * present in all databases, 192.0.2.128 should only be present in the country + * database, ::1 should be absent from all databases). + */ +static void +baseline(void **state) { + dns_geoip_subtype_t subtype; + + UNUSED(state); + + subtype = dns_geoip_city_name; + + assert_true(entry_exists(subtype, "10.53.0.1")); + assert_false(entry_exists(subtype, "192.0.2.128")); + assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1")); + assert_false(entry_exists(subtype, "::1")); + + subtype = dns_geoip_country_name; + + assert_true(entry_exists(subtype, "10.53.0.1")); + assert_true(entry_exists(subtype, "192.0.2.128")); + assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1")); + assert_false(entry_exists(subtype, "::1")); + + subtype = dns_geoip_domain_name; + + assert_true(entry_exists(subtype, "10.53.0.1")); + assert_false(entry_exists(subtype, "192.0.2.128")); + assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1")); + assert_false(entry_exists(subtype, "::1")); + + subtype = dns_geoip_isp_name; + + assert_true(entry_exists(subtype, "10.53.0.1")); + assert_false(entry_exists(subtype, "192.0.2.128")); + assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1")); + assert_false(entry_exists(subtype, "::1")); + + subtype = dns_geoip_as_asnum; + + assert_true(entry_exists(subtype, "10.53.0.1")); + assert_false(entry_exists(subtype, "192.0.2.128")); + assert_true(entry_exists(subtype, "fd92:7065:b8e:ffff::1")); + assert_false(entry_exists(subtype, "::1")); +} + +static bool +do_lookup_string(const char *addr, dns_geoip_subtype_t subtype, + const char *string) { + dns_geoip_elem_t elt; + struct in_addr in4; + isc_netaddr_t na; + int n; + + n = inet_pton(AF_INET, addr, &in4); + assert_int_equal(n, 1); + isc_netaddr_fromin(&na, &in4); + + elt.subtype = subtype; + strlcpy(elt.as_string, string, sizeof(elt.as_string)); + + return (dns_geoip_match(&na, &geoip, &elt)); +} + +static bool +do_lookup_string_v6(const char *addr, dns_geoip_subtype_t subtype, + const char *string) { + dns_geoip_elem_t elt; + struct in6_addr in6; + isc_netaddr_t na; + int n; + + n = inet_pton(AF_INET6, addr, &in6); + assert_int_equal(n, 1); + isc_netaddr_fromin6(&na, &in6); + + elt.subtype = subtype; + strlcpy(elt.as_string, string, sizeof(elt.as_string)); + + return (dns_geoip_match(&na, &geoip, &elt)); +} + +/* GeoIP country matching */ +static void +country(void **state) { + bool match; + + UNUSED(state); + + if (geoip.country == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.1", dns_geoip_country_code, "AU"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_country_name, + "Australia"); + assert_true(match); + + match = do_lookup_string("192.0.2.128", dns_geoip_country_code, "O1"); + assert_true(match); + + match = do_lookup_string("192.0.2.128", dns_geoip_country_name, + "Other"); + assert_true(match); +} + +/* GeoIP country (ipv6) matching */ +static void +country_v6(void **state) { + bool match; + + UNUSED(state); + + if (geoip.country == NULL) { + skip(); + } + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_country_code, "AU"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_country_name, "Australia"); + assert_true(match); +} + +/* GeoIP city (ipv4) matching */ +static void +city(void **state) { + bool match; + + UNUSED(state); + + if (geoip.city == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.1", dns_geoip_city_continentcode, + "NA"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_countrycode, "US"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_countryname, + "United States"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_region, "CA"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_regionname, + "California"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_name, + "Redwood City"); + assert_true(match); + + match = do_lookup_string("10.53.0.1", dns_geoip_city_postalcode, + "94063"); + assert_true(match); +} + +/* GeoIP city (ipv6) matching */ +static void +city_v6(void **state) { + bool match; + + UNUSED(state); + + if (geoip.city == NULL) { + skip(); + } + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_continentcode, "NA"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_countrycode, "US"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_countryname, + "United States"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_region, "CA"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_regionname, "California"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_name, "Redwood City"); + assert_true(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", + dns_geoip_city_postalcode, "94063"); + assert_true(match); +} + +/* GeoIP asnum matching */ +static void +asnum(void **state) { + bool match; + + UNUSED(state); + + if (geoip.as == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.3", dns_geoip_as_asnum, "AS100003"); + assert_true(match); +} + +/* GeoIP isp matching */ +static void +isp(void **state) { + bool match; + + UNUSED(state); + + if (geoip.isp == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.1", dns_geoip_isp_name, + "One Systems, Inc."); + assert_true(match); +} + +/* GeoIP org matching */ +static void +org(void **state) { + bool match; + + UNUSED(state); + + if (geoip.as == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.2", dns_geoip_org_name, + "Two Technology Ltd."); + assert_true(match); +} + +/* GeoIP domain matching */ +static void +domain(void **state) { + bool match; + + UNUSED(state); + + if (geoip.domain == NULL) { + skip(); + } + + match = do_lookup_string("10.53.0.5", dns_geoip_domain_name, "five.es"); + assert_true(match); +} +#endif /* HAVE_GEOIP2 */ + +int +main(void) { +#if defined(HAVE_GEOIP2) + const struct CMUnitTest tests[] = { + cmocka_unit_test(baseline), cmocka_unit_test(country), + cmocka_unit_test(country_v6), cmocka_unit_test(city), + cmocka_unit_test(city_v6), cmocka_unit_test(asnum), + cmocka_unit_test(isp), cmocka_unit_test(org), + cmocka_unit_test(domain), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +#else /* if defined(HAVE_GEOIP2) */ + print_message("1..0 # Skip GeoIP not enabled\n"); +#endif /* if defined(HAVE_GEOIP2) */ +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c new file mode 100644 index 0000000..670b1b2 --- /dev/null +++ b/lib/dns/tests/keytable_test.c @@ -0,0 +1,720 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +dns_keytable_t *keytable = NULL; +dns_ntatable_t *ntatable = NULL; + +static const char *keystr1 = "BQEAAAABok+vaUC9neRv8yeT/" + "FEGgN7svR8s7VBUVSBd8NsAiV8AlaAg " + "O5FHar3JQd95i/puZos6Vi6at9/" + "JBbN8qVmO2AuiXxVqfxMKxIcy+LEB " + "0Vw4NaSJ3N3uaVREso6aTSs98H/" + "25MjcwLOr7SFfXA7bGhZatLtYY/xu kp6Km5hMfkE="; + +static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/" + "inaJpYv7vH " + "XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/" + "ItBfh/hL2lm " + "Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi7953" + "2Qeklxh x8pXWdeAaRU="; + +static dns_view_t *view = NULL; + +/* + * Test utilities. In general, these assume input parameters are valid + * (checking with assert_int_equal, thus aborting if not) and unlikely run time + * errors (such as memory allocation failure) won't happen. This helps keep + * the test code concise. + */ + +/* + * Utility to convert C-string to dns_name_t. Return a pointer to + * static data, and so is not thread safe. + */ +static dns_name_t * +str2name(const char *namestr) { + static dns_fixedname_t fname; + static dns_name_t *name; + static isc_buffer_t namebuf; + void *deconst_namestr; + + name = dns_fixedname_initname(&fname); + DE_CONST(namestr, deconst_namestr); /* OK, since we don't modify it */ + isc_buffer_init(&namebuf, deconst_namestr, strlen(deconst_namestr)); + isc_buffer_add(&namebuf, strlen(namestr)); + assert_int_equal( + dns_name_fromtext(name, &namebuf, dns_rootname, 0, NULL), + ISC_R_SUCCESS); + + return (name); +} + +static void +create_keystruct(uint16_t flags, uint8_t proto, uint8_t alg, const char *keystr, + dns_rdata_dnskey_t *keystruct) { + unsigned char keydata[4096]; + isc_buffer_t keydatabuf; + isc_region_t r; + const dns_rdataclass_t rdclass = dns_rdataclass_in; + + keystruct->common.rdclass = rdclass; + keystruct->common.rdtype = dns_rdatatype_dnskey; + keystruct->mctx = dt_mctx; + ISC_LINK_INIT(&keystruct->common, link); + keystruct->flags = flags; + keystruct->protocol = proto; + keystruct->algorithm = alg; + + isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); + assert_int_equal(isc_base64_decodestring(keystr, &keydatabuf), + ISC_R_SUCCESS); + isc_buffer_usedregion(&keydatabuf, &r); + keystruct->datalen = r.length; + keystruct->data = isc_mem_allocate(dt_mctx, r.length); + memmove(keystruct->data, r.base, r.length); +} + +static void +create_dsstruct(dns_name_t *name, uint16_t flags, uint8_t proto, uint8_t alg, + const char *keystr, unsigned char *digest, + dns_rdata_ds_t *dsstruct) { + isc_result_t result; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t dnskey; + + /* + * Populate DNSKEY rdata structure. + */ + create_keystruct(flags, proto, alg, keystr, &dnskey); + + /* + * Convert to wire format. + */ + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + result = dns_rdata_fromstruct(&rdata, dnskey.common.rdclass, + dnskey.common.rdtype, &dnskey, + &rrdatabuf); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Build DS rdata struct. + */ + result = dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, digest, + dsstruct); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_rdata_freestruct(&dnskey); +} + +/* Common setup: create a keytable and ntatable to test with a few keys */ +static void +create_tables() { + isc_result_t result; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_fixedname_t fn; + dns_name_t *keyname = dns_fixedname_name(&fn); + isc_stdtime_t now; + + result = dns_test_makeview("view", &view); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(dns_keytable_create(dt_mctx, &keytable), + ISC_R_SUCCESS); + assert_int_equal( + dns_ntatable_create(view, taskmgr, timermgr, &ntatable), + ISC_R_SUCCESS); + + /* Add a normal key */ + dns_test_namefromstring("example.com", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds), + ISC_R_SUCCESS); + + /* Add an initializing managed key */ + dns_test_namefromstring("managed.com", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds), + ISC_R_SUCCESS); + + /* Add a null key */ + assert_int_equal(dns_keytable_marksecure(keytable, str2name("null." + "example")), + ISC_R_SUCCESS); + + /* Add a negative trust anchor, duration 1 hour */ + isc_stdtime_get(&now); + assert_int_equal(dns_ntatable_add(ntatable, + str2name("insecure.example"), false, + now, 3600), + ISC_R_SUCCESS); +} + +static void +destroy_tables() { + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + if (keytable != NULL) { + dns_keytable_detach(&keytable); + } + + dns_view_detach(&view); +} + +/* add keys to the keytable */ +static void +add_test(void **state) { + dns_keynode_t *keynode = NULL; + dns_keynode_t *null_keynode = NULL; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_fixedname_t fn; + dns_name_t *keyname = dns_fixedname_name(&fn); + + UNUSED(state); + + create_tables(); + + /* + * Getting the keynode for the example.com key should succeed. + */ + assert_int_equal( + dns_keytable_find(keytable, str2name("example.com"), &keynode), + ISC_R_SUCCESS); + + /* + * Try to add the same key. This should have no effect but + * report success. + */ + dns_test_namefromstring("example.com", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds), + ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + assert_int_equal( + dns_keytable_find(keytable, str2name("example.com"), &keynode), + ISC_R_SUCCESS); + + /* Add another key (different keydata) */ + dns_keytable_detachkeynode(keytable, &keynode); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("example.com"), &keynode), + ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Get the keynode for the managed.com key. Ensure the + * retrieved key is an initializing key, then mark it as trusted using + * dns_keynode_trust() and ensure the latter works as expected. + */ + assert_int_equal( + dns_keytable_find(keytable, str2name("managed.com"), &keynode), + ISC_R_SUCCESS); + assert_int_equal(dns_keynode_initial(keynode), true); + dns_keynode_trust(keynode); + assert_int_equal(dns_keynode_initial(keynode), false); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Add a different managed key for managed.com, marking it as an + * initializing key. Since there is already a trusted key at the + * node, the node should *not* be marked as initializing. + */ + dns_test_namefromstring("managed.com", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("managed.com"), &keynode), + ISC_R_SUCCESS); + assert_int_equal(dns_keynode_initial(keynode), false); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Add the same managed key again, but this time mark it as a + * non-initializing key. Ensure the previously added key is upgraded + * to a non-initializing key and make sure there are still two key + * nodes for managed.com, both containing non-initializing keys. + */ + assert_int_equal(dns_keytable_add(keytable, true, false, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("managed.com"), &keynode), + ISC_R_SUCCESS); + assert_int_equal(dns_keynode_initial(keynode), false); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Add a managed key at a new node, two.com, marking it as an + * initializing key. + */ + dns_test_namefromstring("two.com", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("two.com"), &keynode), + ISC_R_SUCCESS); + assert_int_equal(dns_keynode_initial(keynode), true); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Add a different managed key for two.com, marking it as a + * non-initializing key. Since there is already an iniitalizing + * trust anchor for two.com and we haven't run dns_keynode_trust(), + * the initialization status should not change. + */ + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, true, false, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("two.com"), &keynode), + ISC_R_SUCCESS); + assert_int_equal(dns_keynode_initial(keynode), true); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * Add a normal key to a name that has a null key. The null key node + * will be updated with the normal key. + */ + assert_int_equal(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), + ISC_R_SUCCESS); + dns_test_namefromstring("null.example", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds), + ISC_R_SUCCESS); + assert_int_equal( + dns_keytable_find(keytable, str2name("null.example"), &keynode), + ISC_R_SUCCESS); + assert_ptr_equal(keynode, null_keynode); /* should be the same node */ + dns_keytable_detachkeynode(keytable, &null_keynode); + + /* + * Try to add a null key to a name that already has a key. It's + * effectively no-op, so the same key node is still there. + * (Note: this and above checks confirm that if a name has a null key + * that's the only key for the name). + */ + assert_int_equal(dns_keytable_marksecure(keytable, str2name("null." + "example")), + ISC_R_SUCCESS); + assert_int_equal(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), + ISC_R_SUCCESS); + assert_ptr_equal(keynode, null_keynode); + dns_keytable_detachkeynode(keytable, &null_keynode); + + dns_keytable_detachkeynode(keytable, &keynode); + destroy_tables(); +} + +/* delete keys from the keytable */ +static void +delete_test(void **state) { + UNUSED(state); + + create_tables(); + + /* dns_keytable_delete requires exact match */ + assert_int_equal(dns_keytable_delete(keytable, str2name("example.org")), + ISC_R_NOTFOUND); + assert_int_equal(dns_keytable_delete(keytable, str2name("s.example." + "com")), + ISC_R_NOTFOUND); + assert_int_equal(dns_keytable_delete(keytable, str2name("example.com")), + ISC_R_SUCCESS); + + /* works also for nodes with a null key */ + assert_int_equal(dns_keytable_delete(keytable, str2name("null." + "example")), + ISC_R_SUCCESS); + + /* or a negative trust anchor */ + assert_int_equal(dns_ntatable_delete(ntatable, str2name("insecure." + "example")), + ISC_R_SUCCESS); + + destroy_tables(); +} + +/* delete key nodes from the keytable */ +static void +deletekey_test(void **state) { + dns_rdata_dnskey_t dnskey; + dns_fixedname_t fn; + dns_name_t *keyname = dns_fixedname_name(&fn); + + UNUSED(state); + + create_tables(); + + /* key name doesn't match */ + dns_test_namefromstring("example.org", &fn); + create_keystruct(257, 3, 5, keystr1, &dnskey); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + ISC_R_NOTFOUND); + dns_rdata_freestruct(&dnskey); + + /* subdomain match is the same as no match */ + dns_test_namefromstring("sub.example.org", &fn); + create_keystruct(257, 3, 5, keystr1, &dnskey); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + ISC_R_NOTFOUND); + dns_rdata_freestruct(&dnskey); + + /* name matches but key doesn't match (resulting in PARTIALMATCH) */ + dns_test_namefromstring("example.com", &fn); + create_keystruct(257, 3, 5, keystr2, &dnskey); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + DNS_R_PARTIALMATCH); + dns_rdata_freestruct(&dnskey); + + /* + * exact match: should return SUCCESS on the first try, then + * PARTIALMATCH on the second (because the name existed but + * not a matching key). + */ + create_keystruct(257, 3, 5, keystr1, &dnskey); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + ISC_R_SUCCESS); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + DNS_R_PARTIALMATCH); + + /* + * after deleting the node, any deletekey or delete attempt should + * result in NOTFOUND. + */ + assert_int_equal(dns_keytable_delete(keytable, keyname), ISC_R_SUCCESS); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + ISC_R_NOTFOUND); + dns_rdata_freestruct(&dnskey); + + /* + * A null key node for a name is not deleted when searched by key; + * it must be deleted by dns_keytable_delete() + */ + dns_test_namefromstring("null.example", &fn); + create_keystruct(257, 3, 5, keystr1, &dnskey); + assert_int_equal(dns_keytable_deletekey(keytable, keyname, &dnskey), + DNS_R_PARTIALMATCH); + assert_int_equal(dns_keytable_delete(keytable, keyname), ISC_R_SUCCESS); + dns_rdata_freestruct(&dnskey); + + destroy_tables(); +} + +/* check find-variant operations */ +static void +find_test(void **state) { + dns_keynode_t *keynode = NULL; + dns_fixedname_t fname; + dns_name_t *name; + + UNUSED(state); + + create_tables(); + + /* + * dns_keytable_find() requires exact name match. It matches node + * that has a null key, too. + */ + assert_int_equal( + dns_keytable_find(keytable, str2name("example.org"), &keynode), + ISC_R_NOTFOUND); + assert_int_equal(dns_keytable_find(keytable, + str2name("sub.example.com"), + &keynode), + ISC_R_NOTFOUND); + assert_int_equal( + dns_keytable_find(keytable, str2name("example.com"), &keynode), + ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + assert_int_equal( + dns_keytable_find(keytable, str2name("null.example"), &keynode), + ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * dns_keytable_finddeepestmatch() allows partial match. Also match + * nodes with a null key. + */ + name = dns_fixedname_initname(&fname); + assert_int_equal(dns_keytable_finddeepestmatch( + keytable, str2name("example.com"), name), + ISC_R_SUCCESS); + assert_true(dns_name_equal(name, str2name("example.com"))); + assert_int_equal(dns_keytable_finddeepestmatch( + keytable, str2name("s.example.com"), name), + ISC_R_SUCCESS); + assert_true(dns_name_equal(name, str2name("example.com"))); + assert_int_equal(dns_keytable_finddeepestmatch( + keytable, str2name("example.org"), name), + ISC_R_NOTFOUND); + assert_int_equal(dns_keytable_finddeepestmatch( + keytable, str2name("null.example"), name), + ISC_R_SUCCESS); + assert_true(dns_name_equal(name, str2name("null.example"))); + + destroy_tables(); +} + +/* check issecuredomain() */ +static void +issecuredomain_test(void **state) { + bool issecure; + const char **n; + const char *names[] = { "example.com", "sub.example.com", + "null.example", "sub.null.example", NULL }; + + UNUSED(state); + create_tables(); + + /* + * Domains that are an exact or partial match of a key name are + * considered secure. It's the case even if the key is null + * (validation will then fail, but that's actually the intended effect + * of installing a null key). + */ + for (n = names; *n != NULL; n++) { + assert_int_equal(dns_keytable_issecuredomain(keytable, + str2name(*n), NULL, + &issecure), + ISC_R_SUCCESS); + assert_true(issecure); + } + + /* + * If the key table has no entry (not even a null one) for a domain or + * any of its ancestors, that domain is considered insecure. + */ + assert_int_equal(dns_keytable_issecuredomain(keytable, + str2name("example.org"), + NULL, &issecure), + ISC_R_SUCCESS); + assert_false(issecure); + + destroy_tables(); +} + +/* check dns_keytable_dump() */ +static void +dump_test(void **state) { + FILE *f = fopen("/dev/null", "w"); + + UNUSED(state); + + create_tables(); + + /* + * Right now, we only confirm the dump attempt doesn't cause disruption + * (so we don't check the dump content). + */ + assert_int_equal(dns_keytable_dump(keytable, f), ISC_R_SUCCESS); + fclose(f); + + destroy_tables(); +} + +/* check negative trust anchors */ +static void +nta_test(void **state) { + isc_result_t result; + bool issecure, covered; + dns_fixedname_t fn; + dns_name_t *keyname = dns_fixedname_name(&fn); + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_view_t *myview = NULL; + isc_stdtime_t now; + + UNUSED(state); + + result = dns_test_makeview("view", &myview); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &myview->task); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_view_initsecroots(myview, dt_mctx); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_view_getsecroots(myview, &keytable); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_view_initntatable(myview, taskmgr, timermgr); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_view_getntatable(myview, &ntatable); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_test_namefromstring("example", &fn); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + result = dns_keytable_add(keytable, false, false, keyname, &ds), + assert_int_equal(result, ISC_R_SUCCESS); + + isc_stdtime_get(&now); + result = dns_ntatable_add(ntatable, str2name("insecure.example"), false, + now, 1); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Should be secure */ + result = dns_view_issecuredomain(myview, + str2name("test.secure.example"), now, + true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_false(covered); + assert_true(issecure); + + /* Should not be secure */ + result = dns_view_issecuredomain(myview, + str2name("test.insecure.example"), now, + true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(covered); + assert_false(issecure); + + /* NTA covered */ + covered = dns_view_ntacovers(myview, now, str2name("insecure.example"), + dns_rootname); + assert_true(covered); + + /* Not NTA covered */ + covered = dns_view_ntacovers(myview, now, str2name("secure.example"), + dns_rootname); + assert_false(covered); + + /* As of now + 2, the NTA should be clear */ + result = dns_view_issecuredomain(myview, + str2name("test.insecure.example"), + now + 2, true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_false(covered); + assert_true(issecure); + + /* Now check deletion */ + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_false(covered); + assert_true(issecure); + + result = dns_ntatable_add(ntatable, str2name("new.example"), false, now, + 3600); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(covered); + assert_false(issecure); + + result = dns_ntatable_delete(ntatable, str2name("new.example")); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &covered, &issecure); + assert_int_equal(result, ISC_R_SUCCESS); + assert_false(covered); + assert_true(issecure); + + /* Clean up */ + dns_ntatable_detach(&ntatable); + dns_keytable_detach(&keytable); + dns_view_detach(&myview); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_test), + cmocka_unit_test(delete_test), + cmocka_unit_test(deletekey_test), + cmocka_unit_test(find_test), + cmocka_unit_test(issecuredomain_test), + cmocka_unit_test(dump_test), + cmocka_unit_test(nta_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/master_test.c b/lib/dns/tests/master_test.c new file mode 100644 index 0000000..c060c0d --- /dev/null +++ b/lib/dns/tests/master_test.c @@ -0,0 +1,633 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static void +nullmsg(dns_rdatacallbacks_t *cb, const char *fmt, ...) { + UNUSED(cb); + UNUSED(fmt); +} + +#define BUFLEN 255 +#define BIGBUFLEN (70 * 1024) +#define TEST_ORIGIN "test" + +static dns_masterrawheader_t header; +static bool headerset; + +dns_name_t dns_origin; +char origin[sizeof(TEST_ORIGIN)]; +unsigned char name_buf[BUFLEN]; +dns_rdatacallbacks_t callbacks; +char *include_file = NULL; + +static void +rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header); + +static isc_result_t +add_callback(void *arg, const dns_name_t *owner, dns_rdataset_t *dataset) { + char buf[BIGBUFLEN]; + isc_buffer_t target; + isc_result_t result; + + UNUSED(arg); + + isc_buffer_init(&target, buf, BIGBUFLEN); + result = dns_rdataset_totext(dataset, owner, false, false, &target); + return (result); +} + +static void +rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *h) { + UNUSED(zone); + header = *h; + headerset = true; +} + +static isc_result_t +setup_master(void (*warn)(struct dns_rdatacallbacks *, const char *, ...), + void (*error)(struct dns_rdatacallbacks *, const char *, ...)) { + isc_result_t result; + int len; + isc_buffer_t source; + isc_buffer_t target; + + strlcpy(origin, TEST_ORIGIN, sizeof(origin)); + len = strlen(origin); + isc_buffer_init(&source, origin, len); + isc_buffer_add(&source, len); + isc_buffer_setactive(&source, len); + isc_buffer_init(&target, name_buf, BUFLEN); + dns_name_init(&dns_origin, NULL); + dns_master_initrawheader(&header); + + result = dns_name_fromtext(&dns_origin, &source, dns_rootname, 0, + &target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdatacallbacks_init_stdio(&callbacks); + callbacks.add = add_callback; + callbacks.rawdata = rawdata_callback; + callbacks.zone = NULL; + if (warn != NULL) { + callbacks.warn = warn; + } + if (error != NULL) { + callbacks.error = error; + } + headerset = false; + return (result); +} + +static isc_result_t +test_master(const char *testfile, dns_masterformat_t format, + void (*warn)(struct dns_rdatacallbacks *, const char *, ...), + void (*error)(struct dns_rdatacallbacks *, const char *, ...)) { + isc_result_t result; + + result = setup_master(warn, error); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdatacallbacks_init_stdio(&callbacks); + callbacks.add = add_callback; + callbacks.rawdata = rawdata_callback; + callbacks.zone = NULL; + if (warn != NULL) { + callbacks.warn = warn; + } + if (error != NULL) { + callbacks.error = error; + } + + result = dns_master_loadfile(testfile, &dns_origin, &dns_origin, + dns_rdataclass_in, true, 0, &callbacks, + NULL, NULL, dt_mctx, format, 0); + return (result); +} + +static void +include_callback(const char *filename, void *arg) { + char **argp = (char **)arg; + *argp = isc_mem_strdup(dt_mctx, filename); +} + +/* + * Successful load test: + * dns_master_loadfile() loads a valid master file and returns success + */ +static void +load_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master1.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * Unexpected end of file test: + * dns_master_loadfile() returns DNS_R_UNEXPECTED when file ends too soon + */ +static void +unexpected_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master2.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_UNEXPECTEDEND); +} + +/* + * No owner test: + * dns_master_loadfile() accepts broken zones with no TTL for first record + * if it is an SOA + */ +static void +noowner_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master3.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, DNS_R_NOOWNER); +} + +/* + * No TTL test: + * dns_master_loadfile() returns DNS_R_NOOWNER when no owner name is + * specified + */ +static void +nottl_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master4.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * Bad class test: + * dns_master_loadfile() returns DNS_R_BADCLASS when record class doesn't + * match zone class + */ +static void +badclass_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master5.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, DNS_R_BADCLASS); +} + +/* + * Too big rdata test: + * dns_master_loadfile() returns ISC_R_NOSPACE when record is too big + */ +static void +toobig_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master15.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_NOSPACE); +} + +/* + * Maximum rdata test: + * dns_master_loadfile() returns ISC_R_SUCCESS when record is maximum size + */ +static void +maxrdata_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master16.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * DNSKEY test: + * dns_master_loadfile() understands DNSKEY with key material + */ +static void +dnskey_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master6.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * DNSKEY with no key material test: + * dns_master_loadfile() understands DNSKEY with no key material + * + * RFC 4034 removed the ability to signal NOKEY, so empty key material should + * be rejected. + */ +static void +dnsnokey_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master7.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_UNEXPECTEDEND); +} + +/* + * Include test: + * dns_master_loadfile() understands $INCLUDE + */ +static void +include_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master8.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, DNS_R_SEENINCLUDE); +} + +/* + * Include file list test: + * dns_master_loadfile4() returns names of included file + */ +static void +master_includelist_test(void **state) { + isc_result_t result; + char *filename = NULL; + + UNUSED(state); + + result = setup_master(nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_master_loadfile( + "testdata/master/master8.data", &dns_origin, &dns_origin, + dns_rdataclass_in, 0, true, &callbacks, include_callback, + &filename, dt_mctx, dns_masterformat_text, 0); + assert_int_equal(result, DNS_R_SEENINCLUDE); + assert_non_null(filename); + if (filename != NULL) { + assert_string_equal(filename, "testdata/master/master6.data"); + isc_mem_free(dt_mctx, filename); + } +} + +/* + * Include failure test: + * dns_master_loadfile() understands $INCLUDE failures + */ +static void +includefail_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master9.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, DNS_R_BADCLASS); +} + +/* + * Non-empty blank lines test: + * dns_master_loadfile() handles non-empty blank lines + */ +static void +blanklines_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master10.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * SOA leading zeroes test: + * dns_master_loadfile() allows leading zeroes in SOA + */ + +static void +leadingzero_test(void **state) { + isc_result_t result; + + UNUSED(state); + + result = test_master("testdata/master/master11.data", + dns_masterformat_text, nullmsg, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* masterfile totext tests */ +static void +totext_test(void **state) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + isc_buffer_t target; + unsigned char buf[BIGBUFLEN]; + + UNUSED(state); + + /* First, test with an empty rdataset */ + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = dns_rdataclass_in; + rdatalist.type = dns_rdatatype_none; + rdatalist.covers = dns_rdatatype_none; + + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&target, buf, BIGBUFLEN); + result = dns_master_rdatasettotext(dns_rootname, &rdataset, + &dns_master_style_debug, NULL, + &target); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_buffer_usedlength(&target), 0); + + /* + * XXX: We will also need to add tests for dumping various + * rdata types, classes, etc, and comparing the results against + * known-good output. + */ +} + +/* + * Raw load test: + * dns_master_loadfile() loads a valid raw file and returns success + */ +static void +loadraw_test(void **state) { + isc_result_t result; + + UNUSED(state); + + /* Raw format version 0 */ + result = test_master("testdata/master/master12.data", + dns_masterformat_raw, nullmsg, nullmsg); + assert_string_equal(isc_result_totext(result), "success"); + assert_true(headerset); + assert_int_equal(header.flags, 0); + + /* Raw format version 1, no source serial */ + result = test_master("testdata/master/master13.data", + dns_masterformat_raw, nullmsg, nullmsg); + assert_string_equal(isc_result_totext(result), "success"); + assert_true(headerset); + assert_int_equal(header.flags, 0); + + /* Raw format version 1, source serial == 2011120101 */ + result = test_master("testdata/master/master14.data", + dns_masterformat_raw, nullmsg, nullmsg); + assert_string_equal(isc_result_totext(result), "success"); + assert_true(headerset); + assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0); + assert_int_equal(header.sourceserial, 2011120101); +} + +/* + * Raw dump test: + * dns_master_dump*() functions dump valid raw files + */ +static void +dumpraw_test(void **state) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + char myorigin[sizeof(TEST_ORIGIN)]; + dns_name_t dnsorigin; + isc_buffer_t source, target; + unsigned char namebuf[BUFLEN]; + int len; + + UNUSED(state); + + strlcpy(myorigin, TEST_ORIGIN, sizeof(myorigin)); + len = strlen(myorigin); + isc_buffer_init(&source, myorigin, len); + isc_buffer_add(&source, len); + isc_buffer_setactive(&source, len); + isc_buffer_init(&target, namebuf, BUFLEN); + dns_name_init(&dnsorigin, NULL); + result = dns_name_fromtext(&dnsorigin, &source, dns_rootname, 0, + &target); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_create(dt_mctx, "rbt", &dnsorigin, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_load(db, "testdata/master/master1.data", + dns_masterformat_text, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_db_currentversion(db, &version); + + result = dns_master_dump(dt_mctx, db, version, + &dns_master_style_default, "test.dump", + dns_masterformat_raw, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = test_master("test.dump", dns_masterformat_raw, nullmsg, + nullmsg); + assert_string_equal(isc_result_totext(result), "success"); + assert_true(headerset); + assert_int_equal(header.flags, 0); + + dns_master_initrawheader(&header); + header.sourceserial = 12345; + header.flags |= DNS_MASTERRAW_SOURCESERIALSET; + + unlink("test.dump"); + result = dns_master_dump(dt_mctx, db, version, + &dns_master_style_default, "test.dump", + dns_masterformat_raw, &header); + assert_int_equal(result, ISC_R_SUCCESS); + + result = test_master("test.dump", dns_masterformat_raw, nullmsg, + nullmsg); + assert_string_equal(isc_result_totext(result), "success"); + assert_true(headerset); + assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0); + assert_int_equal(header.sourceserial, 12345); + + unlink("test.dump"); + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); +} + +static const char *warn_expect_value; +static bool warn_expect_result; + +static void +warn_expect(struct dns_rdatacallbacks *mycallbacks, const char *fmt, ...) { + char buf[4096]; + va_list ap; + + UNUSED(mycallbacks); + + warn_expect_result = false; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (warn_expect_value != NULL && strstr(buf, warn_expect_value) != NULL) + { + warn_expect_result = true; + } +} + +/* + * Origin change test: + * dns_master_loadfile() rejects zones with inherited name following $ORIGIN + */ +static void +neworigin_test(void **state) { + isc_result_t result; + + UNUSED(state); + + warn_expect_value = "record with inherited owner"; + result = test_master("testdata/master/master17.data", + dns_masterformat_text, warn_expect, nullmsg); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(warn_expect_result); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(load_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(unexpected_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(noowner_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(nottl_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(badclass_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(dnskey_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(dnsnokey_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(include_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(master_includelist_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(includefail_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(blanklines_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(leadingzero_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(totext_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(loadraw_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(dumpraw_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(toobig_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(maxrdata_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(neworigin_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/mkraw.pl b/lib/dns/tests/mkraw.pl new file mode 100644 index 0000000..5e0db75 --- /dev/null +++ b/lib/dns/tests/mkraw.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# Convert a hexdump to binary format. +# +# To convert binary data to the input format for this command, +# use the following: +# +# perl -e 'while (read(STDIN, my $byte, 1)) { +# print unpack("H2", $byte); +# } +# print "\n";' < file > file.in + +use strict; +chomp(my $line = ); +print pack("H*", $line); diff --git a/lib/dns/tests/name_test.c b/lib/dns/tests/name_test.c new file mode 100644 index 0000000..e48c64e --- /dev/null +++ b/lib/dns/tests/name_test.c @@ -0,0 +1,796 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dnstest.h" + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* dns_name_fullcompare test */ +static void +fullcompare_test(void **state) { + dns_fixedname_t fixed1; + dns_fixedname_t fixed2; + dns_name_t *name1; + dns_name_t *name2; + dns_namereln_t relation; + int i; + isc_result_t result; + struct { + const char *name1; + const char *name2; + dns_namereln_t relation; + int order; + unsigned int nlabels; + } data[] = { + /* relative */ + { "", "", dns_namereln_equal, 0, 0 }, + { "foo", "", dns_namereln_subdomain, 1, 0 }, + { "", "foo", dns_namereln_contains, -1, 0 }, + { "foo", "bar", dns_namereln_none, 4, 0 }, + { "bar", "foo", dns_namereln_none, -4, 0 }, + { "bar.foo", "foo", dns_namereln_subdomain, 1, 1 }, + { "foo", "bar.foo", dns_namereln_contains, -1, 1 }, + { "baz.bar.foo", "bar.foo", dns_namereln_subdomain, 1, 2 }, + { "bar.foo", "baz.bar.foo", dns_namereln_contains, -1, 2 }, + { "foo.example", "bar.example", dns_namereln_commonancestor, 4, + 1 }, + + /* absolute */ + { ".", ".", dns_namereln_equal, 0, 1 }, + { "foo.", "bar.", dns_namereln_commonancestor, 4, 1 }, + { "bar.", "foo.", dns_namereln_commonancestor, -4, 1 }, + { "foo.example.", "bar.example.", dns_namereln_commonancestor, + 4, 2 }, + { "bar.foo.", "foo.", dns_namereln_subdomain, 1, 2 }, + { "foo.", "bar.foo.", dns_namereln_contains, -1, 2 }, + { "baz.bar.foo.", "bar.foo.", dns_namereln_subdomain, 1, 3 }, + { "bar.foo.", "baz.bar.foo.", dns_namereln_contains, -1, 3 }, + { NULL, NULL, dns_namereln_none, 0, 0 } + }; + + UNUSED(state); + + name1 = dns_fixedname_initname(&fixed1); + name2 = dns_fixedname_initname(&fixed2); + for (i = 0; data[i].name1 != NULL; i++) { + int order = 3000; + unsigned int nlabels = 3000; + + if (data[i].name1[0] == 0) { + dns_fixedname_init(&fixed1); + } else { + result = dns_name_fromstring2(name1, data[i].name1, + NULL, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + } + if (data[i].name2[0] == 0) { + dns_fixedname_init(&fixed2); + } else { + result = dns_name_fromstring2(name2, data[i].name2, + NULL, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + } + relation = dns_name_fullcompare(name1, name1, &order, &nlabels); + assert_int_equal(relation, dns_namereln_equal); + assert_int_equal(order, 0); + assert_int_equal(nlabels, name1->labels); + + /* Some random initializer */ + order = 3001; + nlabels = 3001; + + relation = dns_name_fullcompare(name1, name2, &order, &nlabels); + assert_int_equal(relation, data[i].relation); + assert_int_equal(order, data[i].order); + assert_int_equal(nlabels, data[i].nlabels); + } +} + +static void +compress_test(dns_name_t *name1, dns_name_t *name2, dns_name_t *name3, + unsigned char *expected, unsigned int length, + dns_compress_t *cctx, dns_decompress_t *dctx) { + isc_buffer_t source; + isc_buffer_t target; + dns_name_t name; + unsigned char buf1[1024]; + unsigned char buf2[1024]; + + isc_buffer_init(&source, buf1, sizeof(buf1)); + isc_buffer_init(&target, buf2, sizeof(buf2)); + + assert_int_equal(dns_name_towire(name1, cctx, &source), ISC_R_SUCCESS); + + assert_int_equal(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS); + assert_int_equal(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS); + assert_int_equal(dns_name_towire(name3, cctx, &source), ISC_R_SUCCESS); + + isc_buffer_setactive(&source, source.used); + + dns_name_init(&name, NULL); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) == + ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) == + ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) == + ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, 0, &target) == + ISC_R_SUCCESS); + dns_decompress_invalidate(dctx); + + assert_int_equal(target.used, length); + assert_true(memcmp(target.base, expected, target.used) == 0); +} + +/* name compression test */ +static void +compression_test(void **state) { + unsigned int allowed; + dns_compress_t cctx; + dns_decompress_t dctx; + dns_name_t name1; + dns_name_t name2; + dns_name_t name3; + isc_region_t r; + unsigned char plain1[] = "\003yyy\003foo"; + unsigned char plain2[] = "\003bar\003yyy\003foo"; + unsigned char plain3[] = "\003xxx\003bar\003foo"; + unsigned char plain[] = "\003yyy\003foo\0\003bar\003yyy\003foo\0\003" + "bar\003yyy\003foo\0\003xxx\003bar\003foo"; + + UNUSED(state); + + dns_name_init(&name1, NULL); + r.base = plain1; + r.length = sizeof(plain1); + dns_name_fromregion(&name1, &r); + + dns_name_init(&name2, NULL); + r.base = plain2; + r.length = sizeof(plain2); + dns_name_fromregion(&name2, &r); + + dns_name_init(&name3, NULL); + r.base = plain3; + r.length = sizeof(plain3); + dns_name_fromregion(&name3, &r); + + /* Test 1: NONE */ + allowed = DNS_COMPRESS_NONE; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); + + /* Test2: GLOBAL14 */ + allowed = DNS_COMPRESS_GLOBAL14; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); + + /* Test3: ALL */ + allowed = DNS_COMPRESS_ALL; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); + + /* Test4: NONE disabled */ + allowed = DNS_COMPRESS_NONE; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_compress_disable(&cctx); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); + + /* Test5: GLOBAL14 disabled */ + allowed = DNS_COMPRESS_GLOBAL14; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_compress_disable(&cctx); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); + + /* Test6: ALL disabled */ + allowed = DNS_COMPRESS_ALL; + assert_int_equal(dns_compress_init(&cctx, -1, dt_mctx), ISC_R_SUCCESS); + dns_compress_setmethods(&cctx, allowed); + dns_compress_disable(&cctx); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, allowed); + + compress_test(&name1, &name2, &name3, plain, sizeof(plain), &cctx, + &dctx); + + dns_compress_rollback(&cctx, 0); + dns_compress_invalidate(&cctx); +} + +/* is trust-anchor-telemetry test */ +static void +istat_test(void **state) { + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + size_t i; + struct { + const char *name; + bool istat; + } data[] = { { ".", false }, + { "_ta-", false }, + { "_ta-1234", true }, + { "_TA-1234", true }, + { "+TA-1234", false }, + { "_fa-1234", false }, + { "_td-1234", false }, + { "_ta_1234", false }, + { "_ta-g234", false }, + { "_ta-1h34", false }, + { "_ta-12i4", false }, + { "_ta-123j", false }, + { "_ta-1234-abcf", true }, + { "_ta-1234-abcf-ED89", true }, + { "_ta-12345-abcf-ED89", false }, + { "_ta-.example", false }, + { "_ta-1234.example", true }, + { "_ta-1234-abcf.example", true }, + { "_ta-1234-abcf-ED89.example", true }, + { "_ta-12345-abcf-ED89.example", false }, + { "_ta-1234-abcfe-ED89.example", false }, + { "_ta-1234-abcf-EcD89.example", false } }; + + UNUSED(state); + + name = dns_fixedname_initname(&fixed); + + for (i = 0; i < (sizeof(data) / sizeof(data[0])); i++) { + result = dns_name_fromstring(name, data[i].name, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dns_name_istat(name), data[i].istat); + } +} + +/* dns_nane_init */ +static void +init_test(void **state) { + dns_name_t name; + unsigned char offsets[1]; + + UNUSED(state); + + dns_name_init(&name, offsets); + + assert_null(name.ndata); + assert_int_equal(name.length, 0); + assert_int_equal(name.labels, 0); + assert_int_equal(name.attributes, 0); + assert_ptr_equal(name.offsets, offsets); + assert_null(name.buffer); +} + +/* dns_nane_invalidate */ +static void +invalidate_test(void **state) { + dns_name_t name; + unsigned char offsets[1]; + + UNUSED(state); + + dns_name_init(&name, offsets); + dns_name_invalidate(&name); + + assert_null(name.ndata); + assert_int_equal(name.length, 0); + assert_int_equal(name.labels, 0); + assert_int_equal(name.attributes, 0); + assert_null(name.offsets); + assert_null(name.buffer); +} + +/* dns_nane_setbuffer/hasbuffer */ +static void +buffer_test(void **state) { + dns_name_t name; + unsigned char buf[BUFSIZ]; + isc_buffer_t b; + + UNUSED(state); + + isc_buffer_init(&b, buf, BUFSIZ); + dns_name_init(&name, NULL); + dns_name_setbuffer(&name, &b); + assert_ptr_equal(name.buffer, &b); + assert_true(dns_name_hasbuffer(&name)); +} + +/* dns_nane_isabsolute */ +static void +isabsolute_test(void **state) { + struct { + const char *namestr; + bool expect; + } testcases[] = { { "x", false }, + { "a.b.c.d.", true }, + { "x.z", false } }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_name_t name; + unsigned char data[BUFSIZ]; + isc_buffer_t b, nb; + size_t len; + + len = strlen(testcases[i].namestr); + isc_buffer_constinit(&b, testcases[i].namestr, len); + isc_buffer_add(&b, len); + + dns_name_init(&name, NULL); + isc_buffer_init(&nb, data, BUFSIZ); + dns_name_setbuffer(&name, &nb); + result = dns_name_fromtext(&name, &b, NULL, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(dns_name_isabsolute(&name), + testcases[i].expect); + } +} + +/* dns_nane_hash */ +static void +hash_test(void **state) { + struct { + const char *name1; + const char *name2; + bool expect; + bool expecti; + } testcases[] = { + { "a.b.c.d", "A.B.C.D", true, false }, + { "a.b.c.d.", "A.B.C.D.", true, false }, + { "a.b.c.d", "a.b.c.d", true, true }, + { "A.B.C.D.", "A.B.C.D.", true, false }, + { "x.y.z.w", "a.b.c.d", false, false }, + { "x.y.z.w.", "a.b.c.d.", false, false }, + }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_fixedname_t f1, f2; + dns_name_t *n1, *n2; + unsigned int h1, h2; + + n1 = dns_fixedname_initname(&f1); + n2 = dns_fixedname_initname(&f2); + + result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Check case-insensitive hashing first */ + h1 = dns_name_hash(n1, false); + h2 = dns_name_hash(n2, false); + + if (verbose) { + print_message("# %s hashes to %u, " + "%s to %u, case insensitive\n", + testcases[i].name1, h1, + testcases[i].name2, h2); + } + + assert_int_equal((h1 == h2), testcases[i].expect); + + /* Now case-sensitive */ + h1 = dns_name_hash(n1, false); + h2 = dns_name_hash(n2, false); + + if (verbose) { + print_message("# %s hashes to %u, " + "%s to %u, case sensitive\n", + testcases[i].name1, h1, + testcases[i].name2, h2); + } + + assert_int_equal((h1 == h2), testcases[i].expect); + } +} + +/* dns_nane_issubdomain */ +static void +issubdomain_test(void **state) { + struct { + const char *name1; + const char *name2; + bool expect; + } testcases[] = { + { "c.d", "a.b.c.d", false }, { "c.d.", "a.b.c.d.", false }, + { "b.c.d", "c.d", true }, { "a.b.c.d.", "c.d.", true }, + { "a.b.c", "a.b.c", true }, { "a.b.c.", "a.b.c.", true }, + { "x.y.z", "a.b.c", false } + }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_fixedname_t f1, f2; + dns_name_t *n1, *n2; + + n1 = dns_fixedname_initname(&f1); + n2 = dns_fixedname_initname(&f2); + + result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + if (verbose) { + print_message("# check: %s %s a subdomain of %s\n", + testcases[i].name1, + testcases[i].expect ? "is" : "is not", + testcases[i].name2); + } + + assert_int_equal(dns_name_issubdomain(n1, n2), + testcases[i].expect); + } +} + +/* dns_nane_countlabels */ +static void +countlabels_test(void **state) { + struct { + const char *namestr; + unsigned int expect; + } testcases[] = { + { "c.d", 2 }, { "c.d.", 3 }, { "a.b.c.d.", 5 }, + { "a.b.c.d", 4 }, { "a.b.c", 3 }, { ".", 1 }, + }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + + name = dns_fixedname_initname(&fname); + + result = dns_name_fromstring2(name, testcases[i].namestr, NULL, + 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + if (verbose) { + print_message("# %s: expect %u labels\n", + testcases[i].namestr, + testcases[i].expect); + } + + assert_int_equal(dns_name_countlabels(name), + testcases[i].expect); + } +} + +/* dns_nane_getlabel */ +static void +getlabel_test(void **state) { + struct { + const char *name1; + unsigned int pos1; + const char *name2; + unsigned int pos2; + } testcases[] = { + { "c.d", 1, "a.b.c.d", 3 }, + { "a.b.c.d", 3, "c.d", 1 }, + { "a.b.c.", 3, "A.B.C.", 3 }, + }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_fixedname_t f1, f2; + dns_name_t *n1, *n2; + dns_label_t l1, l2; + unsigned int j; + + n1 = dns_fixedname_initname(&f1); + n2 = dns_fixedname_initname(&f2); + + result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_name_getlabel(n1, testcases[i].pos1, &l1); + dns_name_getlabel(n2, testcases[i].pos2, &l2); + assert_int_equal(l1.length, l2.length); + + for (j = 0; j < l1.length; j++) { + assert_int_equal(l1.base[j], l2.base[j]); + } + } +} + +/* dns_nane_getlabelsequence */ +static void +getlabelsequence_test(void **state) { + struct { + const char *name1; + unsigned int pos1; + const char *name2; + unsigned int pos2; + unsigned int range; + } testcases[] = { + { "c.d", 1, "a.b.c.d", 3, 1 }, + { "a.b.c.d.e", 2, "c.d", 0, 2 }, + { "a.b.c", 0, "a.b.c", 0, 3 }, + }; + unsigned int i; + + UNUSED(state); + + for (i = 0; i < (sizeof(testcases) / sizeof(testcases[0])); i++) { + isc_result_t result; + dns_name_t t1, t2; + dns_fixedname_t f1, f2; + dns_name_t *n1, *n2; + + /* target names */ + dns_name_init(&t1, NULL); + dns_name_init(&t2, NULL); + + /* source names */ + n1 = dns_fixedname_initname(&f1); + n2 = dns_fixedname_initname(&f2); + + result = dns_name_fromstring2(n1, testcases[i].name1, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, NULL, 0, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_name_getlabelsequence(n1, testcases[i].pos1, + testcases[i].range, &t1); + dns_name_getlabelsequence(n2, testcases[i].pos2, + testcases[i].range, &t2); + + assert_true(dns_name_equal(&t1, &t2)); + } +} + +#ifdef DNS_BENCHMARK_TESTS + +/* + * XXXMUKS: Don't delete this code. It is useful in benchmarking the + * name parser, but we don't require it as part of the unit test runs. + */ + +/* Benchmark dns_name_fromwire() implementation */ + +static void * +fromwire_thread(void *arg) { + unsigned int maxval = 32000000; + uint8_t data[] = { 3, 'w', 'w', 'w', 7, 'e', 'x', + 'a', 'm', 'p', 'l', 'e', 7, 'i', + 'n', 'v', 'a', 'l', 'i', 'd', 0 }; + unsigned char output_data[DNS_NAME_MAXWIRE]; + isc_buffer_t source, target; + unsigned int i; + dns_decompress_t dctx; + + UNUSED(arg); + + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT); + dns_decompress_setmethods(&dctx, DNS_COMPRESS_NONE); + + isc_buffer_init(&source, data, sizeof(data)); + isc_buffer_add(&source, sizeof(data)); + isc_buffer_init(&target, output_data, sizeof(output_data)); + + /* Parse 32 million names in each thread */ + for (i = 0; i < maxval; i++) { + dns_name_t name; + + isc_buffer_clear(&source); + isc_buffer_clear(&target); + isc_buffer_add(&source, sizeof(data)); + isc_buffer_setactive(&source, sizeof(data)); + + dns_name_init(&name, NULL); + (void)dns_name_fromwire(&name, &source, &dctx, 0, &target); + } + + return (NULL); +} + +static void +benchmark_test(void **state) { + isc_result_t result; + unsigned int i; + isc_time_t ts1, ts2; + double t; + unsigned int nthreads; + isc_thread_t threads[32]; + + UNUSED(state); + + debug_mem_record = false; + + result = isc_time_now(&ts1); + assert_int_equal(result, ISC_R_SUCCESS); + + nthreads = ISC_MIN(isc_os_ncpus(), 32); + nthreads = ISC_MAX(nthreads, 1); + for (i = 0; i < nthreads; i++) { + isc_thread_create(fromwire_thread, NULL, &threads[i]); + } + + for (i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + result = isc_time_now(&ts2); + assert_int_equal(result, ISC_R_SUCCESS); + + t = isc_time_microdiff(&ts2, &ts1); + + printf("%u dns_name_fromwire() calls, %f seconds, %f calls/second\n", + nthreads * 32000000, t / 1000000.0, + (nthreads * 32000000) / (t / 1000000.0)); +} + +#endif /* DNS_BENCHMARK_TESTS */ + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(fullcompare_test), + cmocka_unit_test_setup_teardown(compression_test, _setup, + _teardown), + cmocka_unit_test(istat_test), + cmocka_unit_test(init_test), + cmocka_unit_test(invalidate_test), + cmocka_unit_test(buffer_test), + cmocka_unit_test(isabsolute_test), + cmocka_unit_test(hash_test), + cmocka_unit_test(issubdomain_test), + cmocka_unit_test(countlabels_test), + cmocka_unit_test(getlabel_test), + cmocka_unit_test(getlabelsequence_test), +#ifdef DNS_BENCHMARK_TESTS + cmocka_unit_test_setup_teardown(benchmark_test, _setup, + _teardown), +#endif /* DNS_BENCHMARK_TESTS */ + }; + int c; + + while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { + switch (c) { + case 'v': + verbose = true; + break; + default: + break; + } + } + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/nsec3_test.c b/lib/dns/tests/nsec3_test.c new file mode 100644 index 0000000..69f4be5 --- /dev/null +++ b/lib/dns/tests/nsec3_test.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static void +iteration_test(const char *file, unsigned int expected) { + isc_result_t result; + dns_db_t *db = NULL; + unsigned int iterations; + + result = dns_test_loaddb(&db, dns_dbtype_zone, "test", file); + assert_int_equal(result, ISC_R_SUCCESS); + + iterations = dns_nsec3_maxiterations(); + + assert_int_equal(iterations, expected); + + dns_db_detach(&db); +} + +/*% + * Structure containing parameters for nsec3param_salttotext_test(). + */ +typedef struct { + const char *nsec3param_text; /* NSEC3PARAM RDATA in text form */ + const char *expected_salt; /* string expected in target buffer */ +} nsec3param_salttotext_test_params_t; + +/*% + * Check whether dns_nsec3param_salttotext() handles supplied text form + * NSEC3PARAM RDATA correctly: test whether the result of calling the former is + * as expected and whether it properly checks available buffer space. + * + * Assumes supplied text form NSEC3PARAM RDATA is valid as testing handling of + * invalid NSEC3PARAM RDATA is out of scope of this unit test. + */ +static void +nsec3param_salttotext_test(const nsec3param_salttotext_test_params_t *params) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3param_t nsec3param; + unsigned char buf[1024]; + isc_result_t result; + char salt[64]; + size_t length; + + /* + * Prepare a dns_rdata_nsec3param_t structure for testing. + */ + result = dns_test_rdatafromstring( + &rdata, dns_rdataclass_in, dns_rdatatype_nsec3param, buf, + sizeof(buf), params->nsec3param_text, false); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Check typical use. + */ + result = dns_nsec3param_salttotext(&nsec3param, salt, sizeof(salt)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(salt, params->expected_salt); + + /* + * Ensure available space in the buffer is checked before the salt is + * printed to it and that the amount of space checked for includes the + * terminating NULL byte. + */ + length = strlen(params->expected_salt); + assert_true(length < sizeof(salt) - 1); /* prevent buffer overwrite */ + assert_true(length > 0U); /* prevent length underflow */ + + result = dns_nsec3param_salttotext(&nsec3param, salt, length - 1); + assert_int_equal(result, ISC_R_NOSPACE); + + result = dns_nsec3param_salttotext(&nsec3param, salt, length); + assert_int_equal(result, ISC_R_NOSPACE); + + result = dns_nsec3param_salttotext(&nsec3param, salt, length + 1); + assert_int_equal(result, ISC_R_SUCCESS); +} + +/* + * check that appropriate max iterations is returned for different + * key size mixes + */ +static void +max_iterations(void **state) { + UNUSED(state); + + iteration_test("testdata/nsec3/1024.db", 150); + iteration_test("testdata/nsec3/2048.db", 150); + iteration_test("testdata/nsec3/4096.db", 150); + iteration_test("testdata/nsec3/min-1024.db", 150); + iteration_test("testdata/nsec3/min-2048.db", 150); +} + +/* check dns_nsec3param_salttotext() */ +static void +nsec3param_salttotext(void **state) { + size_t i; + + const nsec3param_salttotext_test_params_t tests[] = { + /* + * Tests with non-empty salts. + */ + { "0 0 10 0123456789abcdef", "0123456789ABCDEF" }, + { "0 1 11 0123456789abcdef", "0123456789ABCDEF" }, + { "1 0 12 42", "42" }, + { "1 1 13 42", "42" }, + /* + * Test with empty salt. + */ + { "0 0 0 -", "-" }, + }; + + UNUSED(state); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + nsec3param_salttotext_test(&tests[i]); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(max_iterations, _setup, + _teardown), + cmocka_unit_test_setup_teardown(nsec3param_salttotext, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/nsec3param_test.c b/lib/dns/tests/nsec3param_test.c new file mode 100644 index 0000000..6fd1fc4 --- /dev/null +++ b/lib/dns/tests/nsec3param_test.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include +#include +#include + +#include "../zone_p.h" +#include "dnstest.h" + +#define HASH 1 +#define FLAGS 0 +#define ITER 5 +#define SALTLEN 4 +#define SALT "FEDCBA98" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/*% + * Structures containing parameters for nsec3param_salttotext_test(). + */ +typedef struct { + dns_hash_t hash; + unsigned char flags; + dns_iterations_t iterations; + unsigned char salt_length; + const char *salt; +} nsec3param_rdata_test_params_t; + +typedef struct { + nsec3param_rdata_test_params_t lookup; + nsec3param_rdata_test_params_t expect; + bool resalt; + isc_result_t expected_result; +} nsec3param_change_test_params_t; + +static void +decode_salt(const char *string, unsigned char *salt, size_t saltlen) { + isc_buffer_t buf; + isc_result_t result; + + isc_buffer_init(&buf, salt, saltlen); + result = isc_hex_decodestring(string, &buf); + assert_int_equal(result, ISC_R_SUCCESS); +} + +static void +copy_params(nsec3param_rdata_test_params_t from, dns_rdata_nsec3param_t *to, + unsigned char *saltbuf, size_t saltlen) { + to->hash = from.hash; + to->flags = from.flags; + to->iterations = from.iterations; + to->salt_length = from.salt_length; + if (from.salt == NULL) { + to->salt = NULL; + } else if (strcmp(from.salt, "-") == 0) { + DE_CONST("-", to->salt); + } else { + decode_salt(from.salt, saltbuf, saltlen); + to->salt = saltbuf; + } +} + +static nsec3param_rdata_test_params_t +rdata_fromparams(uint8_t hash, uint8_t flags, uint16_t iter, uint8_t saltlen, + const char *salt) { + nsec3param_rdata_test_params_t nsec3param; + nsec3param.hash = hash; + nsec3param.flags = flags; + nsec3param.iterations = iter; + nsec3param.salt_length = saltlen; + nsec3param.salt = salt; + return (nsec3param); +} + +/*% + * Check whether zone_lookup_nsec3param() finds the correct NSEC3PARAM + * and sets the correct parameters to use in dns_zone_setnsec3param(). + */ +static void +nsec3param_change_test(const nsec3param_change_test_params_t *test) { + dns_zone_t *zone = NULL; + dns_rdata_nsec3param_t param, lookup, expect; + isc_result_t result; + unsigned char lookupsalt[255]; + unsigned char expectsalt[255]; + unsigned char saltbuf[255]; + + /* + * Prepare a zone along with its signing keys. + */ + result = dns_test_makezone("nsec3", &zone, NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_zone_setfile(zone, "testdata/nsec3param/nsec3.db.signed", + dns_masterformat_text, + &dns_master_style_default); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_zone_load(zone, false); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Copy parameters. + */ + copy_params(test->lookup, &lookup, lookupsalt, sizeof(lookupsalt)); + copy_params(test->expect, &expect, expectsalt, sizeof(expectsalt)); + + /* + * Test dns__zone_lookup_nsec3param(). + */ + result = dns__zone_lookup_nsec3param(zone, &lookup, ¶m, saltbuf, + test->resalt); + assert_int_equal(result, test->expected_result); + assert_int_equal(param.hash, expect.hash); + assert_int_equal(param.flags, expect.flags); + assert_int_equal(param.iterations, expect.iterations); + assert_int_equal(param.salt_length, expect.salt_length); + assert_non_null(param.salt); + if (expect.salt != NULL) { + int ret = memcmp(param.salt, expect.salt, expect.salt_length); + assert_true(ret == 0); + } else { + /* + * We don't know what the new salt is, but we can compare it + * to the previous salt and test that it has changed. + */ + unsigned char salt[SALTLEN]; + int ret; + decode_salt(SALT, salt, SALTLEN); + ret = memcmp(param.salt, salt, SALTLEN); + assert_false(ret == 0); + } + + /* + * Detach. + */ + dns_zone_detach(&zone); +} + +static void +nsec3param_change(void **state) { + size_t i; + + /* + * Define tests. + */ + const nsec3param_change_test_params_t tests[] = { + /* + * 1. Change nothing (don't care about salt). + * This should return ISC_R_SUCCESS because we are already + * using these NSEC3 parameters. + */ + { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), + rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), false, + ISC_R_SUCCESS }, + /* + * 2. Change nothing, but force a resalt. + * This should change the salt. Set 'expect.salt' to NULL to + * test a new salt has been generated. + */ + { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), + rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), true, + DNS_R_NSEC3RESALT }, + /* + * 3. Change iterations. + * The NSEC3 paarameters are not found, and there is no + * need to resalt because an explicit salt has been set, + * and resalt is not enforced. + */ + { rdata_fromparams(HASH, FLAGS, 10, SALTLEN, SALT), + rdata_fromparams(HASH, FLAGS, 10, SALTLEN, SALT), false, + ISC_R_NOTFOUND }, + /* + * 4. Change iterations, don't care about the salt. + * We don't care about the salt. Since we need to change the + * NSEC3 parameters, we will also resalt. + */ + { rdata_fromparams(HASH, FLAGS, 10, SALTLEN, NULL), + rdata_fromparams(HASH, FLAGS, 10, SALTLEN, NULL), false, + DNS_R_NSEC3RESALT }, + /* + * 5. Change salt length. + * Changing salt length means we need to resalt. + */ + { rdata_fromparams(HASH, FLAGS, ITER, 16, NULL), + rdata_fromparams(HASH, FLAGS, ITER, 16, NULL), false, + DNS_R_NSEC3RESALT }, + /* + * 6. Set explicit salt. + * A different salt, so the NSEC3 parameters are not found. + * No need to resalt because an explicit salt is available. + */ + { rdata_fromparams(HASH, FLAGS, ITER, 4, "12345678"), + rdata_fromparams(HASH, FLAGS, ITER, 4, "12345678"), false, + ISC_R_NOTFOUND }, + /* + * 7. Same salt. + * Nothing changed, so expect ISC_R_SUCCESS as a result. + */ + { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), + rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), false, + ISC_R_SUCCESS }, + /* + * 8. Same salt, and force resalt. + * Nothing changed, but a resalt is enforced. + */ + { rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, SALT), + rdata_fromparams(HASH, FLAGS, ITER, SALTLEN, NULL), true, + DNS_R_NSEC3RESALT }, + /* + * 9. No salt. + * Change parameters to use no salt. These parameters are + * not found, and no new salt needs to be generated. + */ + { rdata_fromparams(HASH, FLAGS, ITER, 0, NULL), + rdata_fromparams(HASH, FLAGS, ITER, 0, "-"), true, + ISC_R_NOTFOUND }, + /* + * 10. No salt, explicit. + * Same as above, but set no salt explicitly. + */ + { rdata_fromparams(HASH, FLAGS, ITER, 0, "-"), + rdata_fromparams(HASH, FLAGS, ITER, 0, "-"), true, + ISC_R_NOTFOUND }, + }; + + UNUSED(state); + + /* + * Run tests. + */ + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + nsec3param_change_test(&tests[i]); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(nsec3param_change, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/peer_test.c b/lib/dns/tests/peer_test.c new file mode 100644 index 0000000..69ca8bb --- /dev/null +++ b/lib/dns/tests/peer_test.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include + +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* Test DSCP set/get functions */ +static void +dscp(void **state) { + isc_result_t result; + isc_netaddr_t netaddr; + struct in_addr ina; + dns_peer_t *peer = NULL; + isc_dscp_t dscp; + + UNUSED(state); + + /* + * Create peer structure for the loopback address. + */ + ina.s_addr = INADDR_LOOPBACK; + isc_netaddr_fromin(&netaddr, &ina); + result = dns_peer_new(dt_mctx, &netaddr, &peer); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * All should be not set on creation. + * 'dscp' should remain unchanged. + */ + dscp = 100; + result = dns_peer_getquerydscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + result = dns_peer_getnotifydscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + result = dns_peer_gettransferdscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + /* + * Test that setting query dscp does not affect the other + * dscp values. 'dscp' should remain unchanged until + * dns_peer_getquerydscp is called. + */ + dscp = 100; + result = dns_peer_setquerydscp(peer, 1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_peer_getnotifydscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + result = dns_peer_gettransferdscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + result = dns_peer_getquerydscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 1); + + /* + * Test that setting notify dscp does not affect the other + * dscp values. 'dscp' should remain unchanged until + * dns_peer_getquerydscp is called then should change again + * on dns_peer_getnotifydscp. + */ + dscp = 100; + result = dns_peer_setnotifydscp(peer, 2); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_peer_gettransferdscp(peer, &dscp); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_int_equal(dscp, 100); + + result = dns_peer_getquerydscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 1); + + result = dns_peer_getnotifydscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 2); + + /* + * Test that setting notify dscp does not affect the other + * dscp values. Check that appropriate values are returned. + */ + dscp = 100; + result = dns_peer_settransferdscp(peer, 3); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_peer_getquerydscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 1); + + result = dns_peer_getnotifydscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 2); + + result = dns_peer_gettransferdscp(peer, &dscp); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dscp, 3); + + dns_peer_detach(&peer); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(dscp, _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/private_test.c b/lib/dns/tests/private_test.c new file mode 100644 index 0000000..92ee391 --- /dev/null +++ b/lib/dns/tests/private_test.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. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +static dns_rdatatype_t privatetype = 65534; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +typedef struct { + unsigned char alg; + dns_keytag_t keyid; + bool remove; + bool complete; +} signing_testcase_t; + +typedef struct { + unsigned char hash; + unsigned char flags; + unsigned int iterations; + unsigned long salt; + bool remove; + bool pending; + bool nonsec; +} nsec3_testcase_t; + +static void +make_signing(signing_testcase_t *testcase, dns_rdata_t *private, + unsigned char *buf, size_t len) { + dns_rdata_init(private); + + buf[0] = testcase->alg; + buf[1] = (testcase->keyid & 0xff00) >> 8; + buf[2] = (testcase->keyid & 0xff); + buf[3] = testcase->remove; + buf[4] = testcase->complete; + private->data = buf; + private->length = len; + private->type = privatetype; + private->rdclass = dns_rdataclass_in; +} + +static void +make_nsec3(nsec3_testcase_t *testcase, dns_rdata_t *private, + unsigned char *pbuf) { + dns_rdata_nsec3param_t params; + dns_rdata_t nsec3param = DNS_RDATA_INIT; + unsigned char bufdata[BUFSIZ]; + isc_buffer_t buf; + uint32_t salt; + unsigned char *sp; + int slen = 4; + + /* for simplicity, we're using a maximum salt length of 4 */ + salt = htonl(testcase->salt); + sp = (unsigned char *)&salt; + while (slen > 0 && *sp == '\0') { + slen--; + sp++; + } + + params.common.rdclass = dns_rdataclass_in; + params.common.rdtype = dns_rdatatype_nsec3param; + params.hash = testcase->hash; + params.iterations = testcase->iterations; + params.salt = sp; + params.salt_length = slen; + + params.flags = testcase->flags; + if (testcase->remove) { + params.flags |= DNS_NSEC3FLAG_REMOVE; + if (testcase->nonsec) { + params.flags |= DNS_NSEC3FLAG_NONSEC; + } + } else { + params.flags |= DNS_NSEC3FLAG_CREATE; + if (testcase->pending) { + params.flags |= DNS_NSEC3FLAG_INITIAL; + } + } + + isc_buffer_init(&buf, bufdata, sizeof(bufdata)); + dns_rdata_fromstruct(&nsec3param, dns_rdataclass_in, + dns_rdatatype_nsec3param, ¶ms, &buf); + + dns_rdata_init(private); + + dns_nsec3param_toprivate(&nsec3param, private, privatetype, pbuf, + DNS_NSEC3PARAM_BUFFERSIZE + 1); +} + +/* convert private signing records to text */ +static void +private_signing_totext_test(void **state) { + dns_rdata_t private; + int i; + + signing_testcase_t testcases[] = { { DST_ALG_RSASHA512, 12345, 0, 0 }, + { DST_ALG_RSASHA256, 54321, 1, 0 }, + { DST_ALG_NSEC3RSASHA1, 22222, 0, + 1 }, + { DST_ALG_RSASHA1, 33333, 1, 1 } }; + const char *results[] = { "Signing with key 12345/RSASHA512", + "Removing signatures for key 54321/RSASHA256", + "Done signing with key 22222/NSEC3RSASHA1", + ("Done removing signatures for key " + "33333/RSASHA1") }; + int ncases = 4; + + UNUSED(state); + + for (i = 0; i < ncases; i++) { + unsigned char data[5]; + char output[BUFSIZ]; + isc_buffer_t buf; + + isc_buffer_init(&buf, output, sizeof(output)); + + make_signing(&testcases[i], &private, data, sizeof(data)); + dns_private_totext(&private, &buf); + assert_string_equal(output, results[i]); + } +} + +/* convert private chain records to text */ +static void +private_nsec3_totext_test(void **state) { + dns_rdata_t private; + int i; + + nsec3_testcase_t testcases[] = { + { 1, 0, 1, 0xbeef, 0, 0, 0 }, + { 1, 1, 10, 0xdadd, 0, 0, 0 }, + { 1, 0, 20, 0xbead, 0, 1, 0 }, + { 1, 0, 30, 0xdeaf, 1, 0, 0 }, + { 1, 0, 100, 0xfeedabee, 1, 0, 1 }, + }; + const char *results[] = { "Creating NSEC3 chain 1 0 1 BEEF", + "Creating NSEC3 chain 1 1 10 DADD", + "Pending NSEC3 chain 1 0 20 BEAD", + ("Removing NSEC3 chain 1 0 30 DEAF / " + "creating NSEC chain"), + "Removing NSEC3 chain 1 0 100 FEEDABEE" }; + int ncases = 5; + + UNUSED(state); + + for (i = 0; i < ncases; i++) { + unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1]; + char output[BUFSIZ]; + isc_buffer_t buf; + + isc_buffer_init(&buf, output, sizeof(output)); + + make_nsec3(&testcases[i], &private, data); + dns_private_totext(&private, &buf); + assert_string_equal(output, results[i]); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(private_signing_totext_test, + _setup, _teardown), + cmocka_unit_test_setup_teardown(private_nsec3_totext_test, + _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rbt_serialize_test.c b/lib/dns/tests/rbt_serialize_test.c new file mode 100644 index 0000000..df56981 --- /dev/null +++ b/lib/dns/tests/rbt_serialize_test.c @@ -0,0 +1,489 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif /* ifndef MAP_FILE */ + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +typedef struct data_holder { + int len; + const char *data; +} data_holder_t; + +typedef struct rbt_testdata { + const char *name; + size_t name_len; + data_holder_t data; +} rbt_testdata_t; + +#define DATA_ITEM(name) \ + { \ + (name), sizeof(name) - 1, { sizeof(name), (name) } \ + } + +rbt_testdata_t testdata[] = { DATA_ITEM("first.com."), + DATA_ITEM("one.net."), + DATA_ITEM("two.com."), + DATA_ITEM("three.org."), + DATA_ITEM("asdf.com."), + DATA_ITEM("ghjkl.com."), + DATA_ITEM("1.edu."), + DATA_ITEM("2.edu."), + DATA_ITEM("3.edu."), + DATA_ITEM("123.edu."), + DATA_ITEM("1236.com."), + DATA_ITEM("and_so_forth.com."), + DATA_ITEM("thisisalongname.com."), + DATA_ITEM("a.b."), + DATA_ITEM("test.net."), + DATA_ITEM("whoknows.org."), + DATA_ITEM("blargh.com."), + DATA_ITEM("www.joe.com."), + DATA_ITEM("test.com."), + DATA_ITEM("isc.org."), + DATA_ITEM("uiop.mil."), + DATA_ITEM("last.fm."), + { NULL, 0, { 0, NULL } } }; + +static void +delete_data(void *data, void *arg) { + UNUSED(arg); + UNUSED(data); +} + +static isc_result_t +write_data(FILE *file, unsigned char *datap, void *arg, uint64_t *crc) { + isc_result_t result; + size_t ret = 0; + data_holder_t *data; + data_holder_t temp; + off_t where; + + UNUSED(arg); + + REQUIRE(file != NULL); + REQUIRE(crc != NULL); + REQUIRE(datap != NULL); + data = (data_holder_t *)datap; + REQUIRE((data->len == 0 && data->data == NULL) || + (data->len != 0 && data->data != NULL)); + + result = isc_stdio_tell(file, &where); + if (result != ISC_R_SUCCESS) { + return (result); + } + + temp = *data; + temp.data = (data->len == 0 ? NULL + : (char *)((uintptr_t)where + + sizeof(data_holder_t))); + + isc_crc64_update(crc, (void *)&temp, sizeof(temp)); + ret = fwrite(&temp, sizeof(data_holder_t), 1, file); + if (ret != 1) { + return (ISC_R_FAILURE); + } + if (data->len > 0) { + isc_crc64_update(crc, (const void *)data->data, data->len); + ret = fwrite(data->data, data->len, 1, file); + if (ret != 1) { + return (ISC_R_FAILURE); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fix_data(dns_rbtnode_t *p, void *base, size_t max, void *arg, uint64_t *crc) { + data_holder_t *data; + size_t size; + + UNUSED(base); + UNUSED(max); + UNUSED(arg); + + REQUIRE(crc != NULL); + REQUIRE(p != NULL); + + data = p->data; + + if (data == NULL || (data->len == 0 && data->data != NULL) || + (data->len != 0 && data->data == NULL)) + { + return (ISC_R_INVALIDFILE); + } + + size = max - ((char *)p - (char *)base); + + if (data->len > (int)size || data->data > (const char *)max) { + return (ISC_R_INVALIDFILE); + } + + isc_crc64_update(crc, (void *)data, sizeof(*data)); + + data->data = NULL; + if (data->len != 0) { + data->data = (char *)data + sizeof(data_holder_t); + } + + if (data->len > 0) { + isc_crc64_update(crc, (const void *)data->data, data->len); + } + + return (ISC_R_SUCCESS); +} + +/* + * Load test data into the RBT. + */ +static void +add_test_data(isc_mem_t *mctx, dns_rbt_t *rbt) { + char buffer[1024]; + isc_buffer_t b; + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + dns_compress_t cctx; + rbt_testdata_t *testdatap = testdata; + + dns_compress_init(&cctx, -1, mctx); + + while (testdatap->name != NULL && testdatap->data.data != NULL) { + memmove(buffer, testdatap->name, testdatap->name_len); + + isc_buffer_init(&b, buffer, testdatap->name_len); + isc_buffer_add(&b, testdatap->name_len); + name = dns_fixedname_initname(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + testdatap++; + continue; + } + + if (name != NULL) { + result = dns_rbt_addname(rbt, name, &testdatap->data); + assert_int_equal(result, ISC_R_SUCCESS); + } + testdatap++; + } + + dns_compress_invalidate(&cctx); +} + +/* + * Walk the tree and ensure that all the test nodes are present. + */ +static void +check_test_data(dns_rbt_t *rbt) { + char buffer[1024]; + char *arg; + dns_fixedname_t fname; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + data_holder_t *data; + isc_result_t result; + dns_name_t *foundname; + rbt_testdata_t *testdatap = testdata; + + foundname = dns_fixedname_initname(&fixed); + + while (testdatap->name != NULL && testdatap->data.data != NULL) { + memmove(buffer, testdatap->name, testdatap->name_len + 1); + arg = buffer; + + isc_buffer_init(&b, arg, testdatap->name_len); + isc_buffer_add(&b, testdatap->name_len); + name = dns_fixedname_initname(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + testdatap++; + continue; + } + + data = NULL; + result = dns_rbt_findname(rbt, name, 0, foundname, + (void *)&data); + assert_int_equal(result, ISC_R_SUCCESS); + + testdatap++; + } +} + +static void +data_printer(FILE *out, void *datap) { + data_holder_t *data = (data_holder_t *)datap; + + fprintf(out, "%d bytes, %s", data->len, data->data); +} + +/* Test writing an rbt to file */ +static void +serialize_test(void **state) { + dns_rbt_t *rbt = NULL; + isc_result_t result; + FILE *rbtfile = NULL; + dns_rbt_t *rbt_deserialized = NULL; + off_t offset; + int fd; + off_t filesize = 0; + char *base; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_rbt_create(dt_mctx, delete_data, NULL, &rbt); + assert_int_equal(result, ISC_R_SUCCESS); + + add_test_data(dt_mctx, rbt); + + if (verbose) { + dns_rbt_printtext(rbt, data_printer, stdout); + } + + /* + * Serialize the tree. + */ + rbtfile = fopen("./zone.bin", "w+b"); + assert_non_null(rbtfile); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + assert_true(result == ISC_R_SUCCESS); + dns_rbt_destroy(&rbt); + + /* + * Deserialize the tree. + * Map in the whole file in one go + */ + fd = open("zone.bin", O_RDWR); + assert_int_not_equal(fd, -1); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_PRIVATE, fd, 0); + assert_true(base != NULL && base != MAP_FAILED); + close(fd); + + result = dns_rbt_deserialize_tree(base, filesize, 0, dt_mctx, + delete_data, NULL, fix_data, NULL, + NULL, &rbt_deserialized); + + /* Test to make sure we have a valid tree */ + assert_true(result == ISC_R_SUCCESS); + if (rbt_deserialized == NULL) { + fail_msg("deserialized rbt is null!"); /* Abort execution. */ + } + + check_test_data(rbt_deserialized); + + if (verbose) { + dns_rbt_printtext(rbt_deserialized, data_printer, stdout); + } + + dns_rbt_destroy(&rbt_deserialized); + munmap(base, filesize); + unlink("zone.bin"); +} + +/* Test reading a corrupt map file */ +static void +deserialize_corrupt_test(void **state) { + dns_rbt_t *rbt = NULL; + isc_result_t result; + FILE *rbtfile = NULL; + off_t offset; + int fd; + off_t filesize = 0; + char *base, *p, *q; + int i; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + /* Set up map file */ + result = dns_rbt_create(dt_mctx, delete_data, NULL, &rbt); + assert_int_equal(result, ISC_R_SUCCESS); + + add_test_data(dt_mctx, rbt); + rbtfile = fopen("./zone.bin", "w+b"); + assert_non_null(rbtfile); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + assert_true(result == ISC_R_SUCCESS); + dns_rbt_destroy(&rbt); + + /* Read back with random fuzzing */ + for (i = 0; i < 256; i++) { + dns_rbt_t *rbt_deserialized = NULL; + + fd = open("zone.bin", O_RDWR); + assert_int_not_equal(fd, -1); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_PRIVATE, fd, 0); + assert_true(base != NULL && base != MAP_FAILED); + close(fd); + + /* Randomly fuzz a portion of the memory */ + /* cppcheck-suppress nullPointerArithmeticRedundantCheck */ + p = base + (isc_random_uniform(filesize)); + /* cppcheck-suppress nullPointerArithmeticRedundantCheck */ + q = base + filesize; + q -= (isc_random_uniform(q - p)); + while (p++ < q) { + *p = isc_random8(); + } + + result = dns_rbt_deserialize_tree( + base, filesize, 0, dt_mctx, delete_data, NULL, fix_data, + NULL, NULL, &rbt_deserialized); + + /* Test to make sure we have a valid tree */ + assert_true(result == ISC_R_SUCCESS || + result == ISC_R_INVALIDFILE); + if (result != ISC_R_SUCCESS) { + assert_null(rbt_deserialized); + } + + if (rbt_deserialized != NULL) { + dns_rbt_destroy(&rbt_deserialized); + } + + munmap(base, filesize); + } + + unlink("zone.bin"); +} + +/* Test the dns_rbt_serialize_align() function */ +static void +serialize_align_test(void **state) { + UNUSED(state); + + assert_true(dns_rbt_serialize_align(0) == 0); + assert_true(dns_rbt_serialize_align(1) == 8); + assert_true(dns_rbt_serialize_align(2) == 8); + assert_true(dns_rbt_serialize_align(3) == 8); + assert_true(dns_rbt_serialize_align(4) == 8); + assert_true(dns_rbt_serialize_align(5) == 8); + assert_true(dns_rbt_serialize_align(6) == 8); + assert_true(dns_rbt_serialize_align(7) == 8); + assert_true(dns_rbt_serialize_align(8) == 8); + assert_true(dns_rbt_serialize_align(9) == 16); + assert_true(dns_rbt_serialize_align(0xff) == 0x100); + assert_true(dns_rbt_serialize_align(0x301) == 0x308); +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(serialize_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(deserialize_corrupt_test, + _setup, _teardown), + cmocka_unit_test(serialize_align_test), + }; + int c; + + while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { + switch (c) { + case 'v': + verbose = true; + break; + default: + break; + } + } + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rbt_test.c b/lib/dns/tests/rbt_test.c new file mode 100644 index 0000000..e73ac09 --- /dev/null +++ b/lib/dns/tests/rbt_test.c @@ -0,0 +1,1390 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#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 "dnstest.h" + +typedef struct { + dns_rbt_t *rbt; + dns_rbt_t *rbt_distances; +} test_context_t; + +/* The initial structure of domain tree will be as follows: + * + * . + * | + * b + * / \ + * a d.e.f + * / | \ + * c | g.h + * | | + * w.y i + * / | \ \ + * x | z k + * | | + * p j + * / \ + * o q + */ + +/* The full absolute names of the nodes in the tree (the tree also + * contains "." which is not included in this list). + */ +static const char *const domain_names[] = { + "c", "b", "a", "x.d.e.f", + "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f", + "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h" +}; + +static const size_t domain_names_count = + (sizeof(domain_names) / sizeof(domain_names[0])); + +/* These are set as the node data for the tree used in distances check + * (for the names in domain_names[] above). + */ +static const int node_distances[] = { 3, 1, 2, 2, 2, 3, 1, 2, 1, 1, 2, 2 }; + +/* + * The domain order should be: + * ., a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f, + * q.w.y.d.e.f, z.d.e.f, j.z.d.e.f, g.h, i.g.h, k.g.h + * . (no data, can't be found) + * | + * b + * / \ + * a d.e.f + * / | \ + * c | g.h + * | | + * w.y i + * / | \ \ + * x | z k + * | | + * p j + * / \ + * o q + */ + +static const char *const ordered_names[] = { + "a", "b", "c", "d.e.f", "x.d.e.f", + "w.y.d.e.f", "o.w.y.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "z.d.e.f", + "j.z.d.e.f", "g.h", "i.g.h", "k.g.h" +}; + +static const size_t ordered_names_count = + (sizeof(ordered_names) / sizeof(*ordered_names)); + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} +static void +delete_data(void *data, void *arg) { + UNUSED(arg); + + isc_mem_put(dt_mctx, data, sizeof(size_t)); +} + +static test_context_t * +test_context_setup(void) { + test_context_t *ctx; + isc_result_t result; + size_t i; + + ctx = isc_mem_get(dt_mctx, sizeof(*ctx)); + assert_non_null(ctx); + + ctx->rbt = NULL; + result = dns_rbt_create(dt_mctx, delete_data, NULL, &ctx->rbt); + assert_int_equal(result, ISC_R_SUCCESS); + + ctx->rbt_distances = NULL; + result = dns_rbt_create(dt_mctx, delete_data, NULL, + &ctx->rbt_distances); + assert_int_equal(result, ISC_R_SUCCESS); + + for (i = 0; i < domain_names_count; i++) { + size_t *n; + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(domain_names[i], &fname); + + name = dns_fixedname_name(&fname); + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = i + 1; + result = dns_rbt_addname(ctx->rbt, name, n); + assert_int_equal(result, ISC_R_SUCCESS); + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = node_distances[i]; + result = dns_rbt_addname(ctx->rbt_distances, name, n); + assert_int_equal(result, ISC_R_SUCCESS); + } + + return (ctx); +} + +static void +test_context_teardown(test_context_t *ctx) { + dns_rbt_destroy(&ctx->rbt); + dns_rbt_destroy(&ctx->rbt_distances); + + isc_mem_put(dt_mctx, ctx, sizeof(*ctx)); +} + +/* + * Walk the tree and ensure that all the test nodes are present. + */ +static void +check_test_data(dns_rbt_t *rbt) { + dns_fixedname_t fixed; + isc_result_t result; + dns_name_t *foundname; + size_t i; + + foundname = dns_fixedname_initname(&fixed); + + for (i = 0; i < domain_names_count; i++) { + dns_fixedname_t fname; + dns_name_t *name; + size_t *n; + + dns_test_namefromstring(domain_names[i], &fname); + + name = dns_fixedname_name(&fname); + n = NULL; + result = dns_rbt_findname(rbt, name, 0, foundname, (void *)&n); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(*n, i + 1); + } +} + +/* Test the creation of an rbt */ +static void +rbt_create(void **state) { + test_context_t *ctx; + bool tree_ok; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + check_test_data(ctx->rbt); + + tree_ok = dns__rbt_checkproperties(ctx->rbt); + assert_true(tree_ok); + + test_context_teardown(ctx); +} + +/* Test dns_rbt_nodecount() on a tree */ +static void +rbt_nodecount(void **state) { + test_context_t *ctx; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + assert_int_equal(15, dns_rbt_nodecount(ctx->rbt)); + + test_context_teardown(ctx); +} + +/* Test dns_rbtnode_get_distance() on a tree */ +static void +rbtnode_get_distance(void **state) { + isc_result_t result; + test_context_t *ctx; + const char *name_str = "a"; + dns_fixedname_t fname; + dns_name_t *name; + dns_rbtnode_t *node = NULL; + dns_rbtnodechain_t chain; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + dns_test_namefromstring(name_str, &fname); + name = dns_fixedname_name(&fname); + + dns_rbtnodechain_init(&chain); + + result = dns_rbt_findnode(ctx->rbt_distances, name, NULL, &node, &chain, + 0, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + while (node != NULL) { + const size_t *distance = (const size_t *)node->data; + if (distance != NULL) { + assert_int_equal(*distance, + dns__rbtnode_getdistance(node)); + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result == ISC_R_NOMORE) { + break; + } + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + } + + assert_int_equal(result, ISC_R_NOMORE); + + dns_rbtnodechain_invalidate(&chain); + + test_context_teardown(ctx); +} + +/* + * Test tree balance, inserting names in random order. + * + * This test checks an important performance-related property of + * the red-black tree, which is important for us: the longest + * path from a sub-tree's root to a node is no more than + * 2log(n). This check verifies that the tree is balanced. + */ +static void +rbt_check_distance_random(void **state) { + dns_rbt_t *mytree = NULL; + const unsigned int log_num_nodes = 16; + isc_result_t result; + bool tree_ok; + int i; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Names are inserted in random order. */ + + /* Make a large 65536 node top-level domain tree, i.e., the + * following code inserts names such as: + * + * savoucnsrkrqzpkqypbygwoiliawpbmz. + * wkadamcbbpjtundbxcmuayuycposvngx. + * wzbpznemtooxdpjecdxynsfztvnuyfao. + * yueojmhyffslpvfmgyfwioxegfhepnqq. + */ + for (i = 0; i < (1 << log_num_nodes); i++) { + size_t *n; + char namebuf[34]; + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = i + 1; + + while (1) { + int j; + dns_fixedname_t fname; + dns_name_t *name; + + for (j = 0; j < 32; j++) { + uint32_t v = isc_random_uniform(26); + namebuf[j] = 'a' + v; + } + namebuf[32] = '.'; + namebuf[33] = 0; + + dns_test_namefromstring(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_addname(mytree, name, n); + if (result == ISC_R_SUCCESS) { + break; + } + } + } + + /* 1 (root . node) + (1 << log_num_nodes) */ + assert_int_equal(1U + (1U << log_num_nodes), dns_rbt_nodecount(mytree)); + + /* The distance from each node to its sub-tree root must be less + * than 2 * log(n). + */ + assert_true((2U * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + assert_true(tree_ok); + + dns_rbt_destroy(&mytree); +} + +/* + * Test tree balance, inserting names in sorted order. + * + * This test checks an important performance-related property of + * the red-black tree, which is important for us: the longest + * path from a sub-tree's root to a node is no more than + * 2log(n). This check verifies that the tree is balanced. + */ +static void +rbt_check_distance_ordered(void **state) { + dns_rbt_t *mytree = NULL; + const unsigned int log_num_nodes = 16; + isc_result_t result; + bool tree_ok; + int i; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Names are inserted in sorted order. */ + + /* Make a large 65536 node top-level domain tree, i.e., the + * following code inserts names such as: + * + * name00000000. + * name00000001. + * name00000002. + * name00000003. + */ + for (i = 0; i < (1 << log_num_nodes); i++) { + size_t *n; + char namebuf[14]; + dns_fixedname_t fname; + dns_name_t *name; + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = i + 1; + + snprintf(namebuf, sizeof(namebuf), "name%08x.", i); + dns_test_namefromstring(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_addname(mytree, name, n); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* 1 (root . node) + (1 << log_num_nodes) */ + assert_int_equal(1U + (1U << log_num_nodes), dns_rbt_nodecount(mytree)); + + /* The distance from each node to its sub-tree root must be less + * than 2 * log(n). + */ + assert_true((2U * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + assert_true(tree_ok); + + dns_rbt_destroy(&mytree); +} + +static isc_result_t +insert_helper(dns_rbt_t *rbt, const char *namestr, dns_rbtnode_t **node) { + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(namestr, &fname); + name = dns_fixedname_name(&fname); + + return (dns_rbt_addnode(rbt, name, node)); +} + +static bool +compare_labelsequences(dns_rbtnode_t *node, const char *labelstr) { + dns_name_t name; + isc_result_t result; + char *nodestr = NULL; + bool is_equal; + + dns_name_init(&name, NULL); + dns_rbt_namefromnode(node, &name); + + result = dns_name_tostring(&name, &nodestr, dt_mctx); + assert_int_equal(result, ISC_R_SUCCESS); + + is_equal = strcmp(labelstr, nodestr) == 0 ? true : false; + + isc_mem_free(dt_mctx, nodestr); + + return (is_equal); +} + +/* Test insertion into a tree */ +static void +rbt_insert(void **state) { + isc_result_t result; + test_context_t *ctx; + dns_rbtnode_t *node; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + /* Check node count before beginning. */ + assert_int_equal(15, dns_rbt_nodecount(ctx->rbt)); + + /* Try to insert a node that already exists. */ + node = NULL; + result = insert_helper(ctx->rbt, "d.e.f", &node); + assert_int_equal(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + assert_int_equal(15, dns_rbt_nodecount(ctx->rbt)); + + /* Try to insert a node that doesn't exist. */ + node = NULL; + result = insert_helper(ctx->rbt, "0", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "0")); + + /* Node count must have increased. */ + assert_int_equal(16, dns_rbt_nodecount(ctx->rbt)); + + /* Another. */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(node); + assert_null(node->data); + + /* Node count must have increased. */ + assert_int_equal(17, dns_rbt_nodecount(ctx->rbt)); + + /* Re-adding it should return EXISTS */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + assert_int_equal(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + assert_int_equal(17, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node d.e.f */ + node = NULL; + result = insert_helper(ctx->rbt, "k.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "k")); + + /* Node count must have incremented twice ("d.e.f" fissioned to + * "d" and "e.f", and the newly added "k"). + */ + assert_int_equal(19, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node "g.h" */ + node = NULL; + result = insert_helper(ctx->rbt, "h", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "h")); + + /* Node count must have incremented ("g.h" fissioned to "g" and + * "h"). + */ + assert_int_equal(20, dns_rbt_nodecount(ctx->rbt)); + + /* Add child domains */ + + node = NULL; + result = insert_helper(ctx->rbt, "m.p.w.y.d.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "m")); + assert_int_equal(21, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "n.p.w.y.d.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "n")); + assert_int_equal(22, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "l.a", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(compare_labelsequences(node, "l")); + assert_int_equal(23, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "r.d.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + node = NULL; + result = insert_helper(ctx->rbt, "s.d.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(25, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "h.w.y.d.e.f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Add more nodes one by one to cover left and right rotation + * functions. + */ + node = NULL; + result = insert_helper(ctx->rbt, "f", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "m", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "nm", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "om", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "k", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "l", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "fe", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "ge", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "i", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "ae", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "n", &node); + assert_int_equal(result, ISC_R_SUCCESS); + + test_context_teardown(ctx); +} + +/* + * Test removal from a tree + * + * This testcase checks that after node removal, the binary-search tree is + * valid and all nodes that are supposed to exist are present in the + * correct order. It mainly tests DomainTree as a BST, and not particularly + * as a red-black tree. This test checks node deletion when upper nodes + * have data. + */ +static void +rbt_remove(void **state) { + isc_result_t result; + size_t j; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + /* + * Delete single nodes and check if the rest of the nodes exist. + */ + for (j = 0; j < ordered_names_count; j++) { + dns_rbt_t *mytree = NULL; + dns_rbtnode_t *node; + size_t i; + size_t *n; + bool tree_ok; + dns_rbtnodechain_t chain; + size_t start_node; + + /* Create a tree. */ + result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Insert test data into the tree. */ + for (i = 0; i < domain_names_count; i++) { + node = NULL; + result = insert_helper(mytree, domain_names[i], &node); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* Check that all names exist in order. */ + for (i = 0; i < ordered_names_count; i++) { + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(ordered_names[i], &fname); + + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, &node, + NULL, DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Add node data */ + assert_non_null(node); + assert_null(node->data); + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = i; + + node->data = n; + } + + /* Now, delete the j'th node from the tree. */ + { + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(ordered_names[j], &fname); + + name = dns_fixedname_name(&fname); + + result = dns_rbt_deletename(mytree, name, false); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* Check RB tree properties. */ + tree_ok = dns__rbt_checkproperties(mytree); + assert_true(tree_ok); + + dns_rbtnodechain_init(&chain); + + /* Now, walk through nodes in order. */ + if (j == 0) { + /* + * Node for ordered_names[0] was already deleted + * above. We start from node 1. + */ + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, &node, + NULL, 0, NULL, NULL); + assert_int_equal(result, ISC_R_NOTFOUND); + + dns_test_namefromstring(ordered_names[1], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, &node, + &chain, 0, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + start_node = 1; + } else { + /* Start from node 0. */ + dns_fixedname_t fname; + dns_name_t *name; + + dns_test_namefromstring(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, &node, + &chain, 0, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + start_node = 0; + } + + /* + * node and chain have been set by the code above at + * this point. + */ + for (i = start_node; i < ordered_names_count; i++) { + dns_fixedname_t fname_j, fname_i; + dns_name_t *name_j, *name_i; + + dns_test_namefromstring(ordered_names[j], &fname_j); + name_j = dns_fixedname_name(&fname_j); + dns_test_namefromstring(ordered_names[i], &fname_i); + name_i = dns_fixedname_name(&fname_i); + + if (dns_name_equal(name_i, name_j)) { + /* + * This may be true for the last node if + * we seek ahead in the loop using + * dns_rbtnodechain_next() below. + */ + if (node == NULL) { + break; + } + + /* All ordered nodes have data + * initially. If any node is empty, it + * means it was removed, but an empty + * node exists because it is a + * super-domain. Just skip it. + */ + if (node->data == NULL) { + result = dns_rbtnodechain_next( + &chain, NULL, NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current( + &chain, NULL, NULL, + &node); + } + } + continue; + } + + assert_non_null(node); + + n = (size_t *)node->data; + if (n != NULL) { + /* printf("n=%zu, i=%zu\n", *n, i); */ + assert_int_equal(*n, i); + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current(&chain, NULL, NULL, + &node); + } + } + + /* We should have reached the end of the tree. */ + assert_null(node); + + dns_rbt_destroy(&mytree); + } +} + +static void +insert_nodes(dns_rbt_t *mytree, char **names, size_t *names_count, + uint32_t num_names) { + uint32_t i; + dns_rbtnode_t *node; + + for (i = 0; i < num_names; i++) { + size_t *n; + char namebuf[34]; + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + + *n = i; /* Unused value */ + + while (1) { + int j; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + for (j = 0; j < 32; j++) { + uint32_t v = isc_random_uniform(26); + namebuf[j] = 'a' + v; + } + namebuf[32] = '.'; + namebuf[33] = 0; + + dns_test_namefromstring(namebuf, &fname); + name = dns_fixedname_name(&fname); + + node = NULL; + result = dns_rbt_addnode(mytree, name, &node); + if (result == ISC_R_SUCCESS) { + node->data = n; + names[*names_count] = isc_mem_strdup(dt_mctx, + namebuf); + assert_non_null(names[*names_count]); + *names_count += 1; + break; + } + } + } +} + +static void +remove_nodes(dns_rbt_t *mytree, char **names, size_t *names_count, + uint32_t num_names) { + uint32_t i; + + UNUSED(mytree); + + for (i = 0; i < num_names; i++) { + uint32_t node; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + node = isc_random_uniform(*names_count); + + dns_test_namefromstring(names[node], &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_deletename(mytree, name, false); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_mem_free(dt_mctx, names[node]); + if (*names_count > 0) { + names[node] = names[*names_count - 1]; + names[*names_count - 1] = NULL; + *names_count -= 1; + } + } +} + +static void +check_tree(dns_rbt_t *mytree, char **names, size_t names_count) { + bool tree_ok; + + UNUSED(names); + + assert_int_equal(names_count + 1, dns_rbt_nodecount(mytree)); + + /* + * The distance from each node to its sub-tree root must be less + * than 2 * log_2(1024). + */ + assert_true((2 * 10) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + assert_true(tree_ok); +} + +/* + * Test insert and remove in a loop. + * + * What is the best way to test our red-black tree code? It is + * not a good method to test every case handled in the actual + * code itself. This is because our approach itself may be + * incorrect. + * + * We test our code at the interface level here by exercising the + * tree randomly multiple times, checking that red-black tree + * properties are valid, and all the nodes that are supposed to be + * in the tree exist and are in order. + * + * NOTE: These tests are run within a single tree level in the + * forest. The number of nodes in the tree level doesn't grow + * over 1024. + */ +static void +rbt_insert_and_remove(void **state) { + isc_result_t result; + dns_rbt_t *mytree = NULL; + size_t *n; + char *names[1024]; + size_t names_count; + int i; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_rbt_create(dt_mctx, delete_data, NULL, &mytree); + assert_int_equal(result, ISC_R_SUCCESS); + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + result = dns_rbt_addname(mytree, dns_rootname, n); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(names, 0, sizeof(names)); + names_count = 0; + + /* Repeat the insert/remove test some 4096 times */ + for (i = 0; i < 4096; i++) { + uint32_t num_names; + + if (names_count < 1024) { + num_names = isc_random_uniform(1024 - names_count); + num_names++; + } else { + num_names = 0; + } + + insert_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count); + + if (names_count > 0) { + num_names = isc_random_uniform(names_count); + num_names++; + } else { + num_names = 0; + } + + remove_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count); + } + + /* Remove the rest of the nodes */ + remove_nodes(mytree, names, &names_count, names_count); + check_tree(mytree, names, names_count); + + for (i = 0; i < 1024; i++) { + if (names[i] != NULL) { + isc_mem_free(dt_mctx, names[i]); + } + } + + result = dns_rbt_deletename(mytree, dns_rootname, false); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(dns_rbt_nodecount(mytree), 0); + + dns_rbt_destroy(&mytree); +} + +/* Test findname return values */ +static void +rbt_findname(void **state) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_fixedname_t fname, found; + dns_name_t *name = NULL, *foundname = NULL; + size_t *n = NULL; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + /* Try to find a name that exists. */ + dns_test_namefromstring("d.e.f", &fname); + name = dns_fixedname_name(&fname); + + foundname = dns_fixedname_initname(&found); + + result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA, + foundname, (void *)&n); + assert_true(dns_name_equal(foundname, name)); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Now without EMPTYDATA */ + result = dns_rbt_findname(ctx->rbt, name, 0, foundname, (void *)&n); + assert_int_equal(result, ISC_R_NOTFOUND); + + /* Now one that partially matches */ + dns_test_namefromstring("d.e.f.g.h.i.j", &fname); + name = dns_fixedname_name(&fname); + result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA, + foundname, (void *)&n); + assert_int_equal(result, DNS_R_PARTIALMATCH); + + /* Now one that doesn't match */ + dns_test_namefromstring("1.2", &fname); + name = dns_fixedname_name(&fname); + result = dns_rbt_findname(ctx->rbt, name, DNS_RBTFIND_EMPTYDATA, + foundname, (void *)&n); + assert_int_equal(result, DNS_R_PARTIALMATCH); + assert_true(dns_name_equal(foundname, dns_rootname)); + + test_context_teardown(ctx); +} + +/* Test addname return values */ +static void +rbt_addname(void **state) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_fixedname_t fname; + dns_name_t *name = NULL; + size_t *n; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = 1; + + dns_test_namefromstring("d.e.f.g.h.i.j.k", &fname); + name = dns_fixedname_name(&fname); + + /* Add a name that doesn't exist */ + result = dns_rbt_addname(ctx->rbt, name, n); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Now add again, should get ISC_R_EXISTS */ + n = isc_mem_get(dt_mctx, sizeof(size_t)); + assert_non_null(n); + *n = 2; + result = dns_rbt_addname(ctx->rbt, name, n); + assert_int_equal(result, ISC_R_EXISTS); + isc_mem_put(dt_mctx, n, sizeof(size_t)); + + test_context_teardown(ctx); +} + +/* Test deletename return values */ +static void +rbt_deletename(void **state) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_fixedname_t fname; + dns_name_t *name = NULL; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + /* Delete a name that doesn't exist */ + dns_test_namefromstring("z.x.y.w", &fname); + name = dns_fixedname_name(&fname); + result = dns_rbt_deletename(ctx->rbt, name, false); + assert_int_equal(result, ISC_R_NOTFOUND); + + /* Now one that does */ + dns_test_namefromstring("d.e.f", &fname); + name = dns_fixedname_name(&fname); + result = dns_rbt_deletename(ctx->rbt, name, false); + assert_int_equal(result, ISC_R_NOTFOUND); + + test_context_teardown(ctx); +} + +/* Test nodechain */ +static void +rbt_nodechain(void **state) { + isc_result_t result; + test_context_t *ctx; + dns_fixedname_t fname, found, expect; + dns_name_t *name, *foundname, *expected; + dns_rbtnode_t *node = NULL; + dns_rbtnodechain_t chain; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + dns_rbtnodechain_init(&chain); + + dns_test_namefromstring("a", &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_findnode(ctx->rbt, name, NULL, &node, &chain, 0, NULL, + NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + foundname = dns_fixedname_initname(&found); + + dns_test_namefromstring("a", &expect); + expected = dns_fixedname_name(&expect); + UNUSED(expected); + + result = dns_rbtnodechain_first(&chain, ctx->rbt, foundname, NULL); + assert_int_equal(result, DNS_R_NEWORIGIN); + assert_int_equal(dns_name_countlabels(foundname), 0); + + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + assert_int_equal(result, ISC_R_NOMORE); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL); + assert_int_equal(result, DNS_R_NEWORIGIN); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + assert_int_equal(result, ISC_R_NOMORE); + + result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL); + assert_int_equal(result, DNS_R_NEWORIGIN); + + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_rbtnodechain_invalidate(&chain); + + test_context_teardown(ctx); +} + +/* Test addname return values */ +static void +rbtnode_namelen(void **state) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_rbtnode_t *node; + unsigned int len; + + UNUSED(state); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + ctx = test_context_setup(); + + node = NULL; + result = insert_helper(ctx->rbt, ".", &node); + len = dns__rbtnode_namelen(node); + assert_int_equal(result, ISC_R_EXISTS); + assert_int_equal(len, 1); + node = NULL; + + result = insert_helper(ctx->rbt, "a.b.c.d.e.f.g.h.i.j.k.l.m", &node); + len = dns__rbtnode_namelen(node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(len, 27); + + node = NULL; + result = insert_helper(ctx->rbt, "isc.org", &node); + len = dns__rbtnode_namelen(node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(len, 9); + + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + len = dns__rbtnode_namelen(node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(len, 13); + + test_context_teardown(ctx); +} + +#if defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) + +/* + * XXXMUKS: Don't delete this code. It is useful in benchmarking the + * RBT, but we don't require it as part of the unit test runs. + */ + +static dns_fixedname_t *fnames; +static dns_name_t **names; +static int *values; + +static void * +find_thread(void *arg) { + dns_rbt_t *mytree; + isc_result_t result; + dns_rbtnode_t *node; + unsigned int j, i; + unsigned int start = 0; + + mytree = (dns_rbt_t *)arg; + while (start == 0) { + start = random() % 4000000; + } + + /* Query 32 million random names from it in each thread */ + for (j = 0; j < 8; j++) { + for (i = start; i != start - 1; i = (i + 1) % 4000000) { + node = NULL; + result = dns_rbt_findnode(mytree, names[i], NULL, &node, + NULL, DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(node); + assert_int_equal(values[i], (intptr_t)node->data); + } + } + + return (NULL); +} + +/* Benchmark RBT implementation */ +static void +benchmark(void **state) { + isc_result_t result; + char namestr[sizeof("name18446744073709551616.example.org.")]; + unsigned int r; + dns_rbt_t *mytree; + dns_rbtnode_t *node; + unsigned int i; + unsigned int maxvalue = 1000000; + isc_time_t ts1, ts2; + double t; + unsigned int nthreads; + isc_thread_t threads[32]; + + UNUSED(state); + + srandom(time(NULL)); + + debug_mem_record = false; + + fnames = (dns_fixedname_t *)malloc(4000000 * sizeof(dns_fixedname_t)); + names = (dns_name_t **)malloc(4000000 * sizeof(dns_name_t *)); + values = (int *)malloc(4000000 * sizeof(int)); + + for (i = 0; i < 4000000; i++) { + r = ((unsigned long)random()) % maxvalue; + snprintf(namestr, sizeof(namestr), "name%u.example.org.", r); + dns_test_namefromstring(namestr, &fnames[i]); + names[i] = dns_fixedname_name(&fnames[i]); + values[i] = r; + } + + /* Create a tree. */ + mytree = NULL; + result = dns_rbt_create(dt_mctx, NULL, NULL, &mytree); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Insert test data into the tree. */ + for (i = 0; i < maxvalue; i++) { + snprintf(namestr, sizeof(namestr), "name%u.example.org.", i); + node = NULL; + result = insert_helper(mytree, namestr, &node); + assert_int_equal(result, ISC_R_SUCCESS); + node->data = (void *)(intptr_t)i; + } + + result = isc_time_now(&ts1); + assert_int_equal(result, ISC_R_SUCCESS); + + nthreads = ISC_MIN(isc_os_ncpus(), 32); + nthreads = ISC_MAX(nthreads, 1); + for (i = 0; i < nthreads; i++) { + isc_thread_create(find_thread, mytree, &threads[i]); + } + + for (i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + result = isc_time_now(&ts2); + assert_int_equal(result, ISC_R_SUCCESS); + + t = isc_time_microdiff(&ts2, &ts1); + + printf("%u findnode calls, %f seconds, %f calls/second\n", + nthreads * 8 * 4000000, t / 1000000.0, + (nthreads * 8 * 4000000) / (t / 1000000.0)); + + free(values); + free(names); + free(fnames); + + dns_rbt_destroy(&mytree); +} +#endif /* defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) */ + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(rbt_create, _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_nodecount, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbtnode_get_distance, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbt_check_distance_random, + _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_check_distance_ordered, + _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_insert, _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_remove, _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_insert_and_remove, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbt_findname, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbt_addname, _setup, _teardown), + cmocka_unit_test_setup_teardown(rbt_deletename, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbt_nodechain, _setup, + _teardown), + cmocka_unit_test_setup_teardown(rbtnode_namelen, _setup, + _teardown), +#if defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) + cmocka_unit_test_setup_teardown(benchmark, _setup, _teardown), +#endif /* defined(DNS_BENCHMARK_TESTS) && !defined(__SANITIZE_THREAD__) */ + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rbtdb_test.c b/lib/dns/tests/rbtdb_test.c new file mode 100644 index 0000000..ac7b776 --- /dev/null +++ b/lib/dns/tests/rbtdb_test.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include +#include +#include + +#include "dnstest.h" + +/* Include the main file */ + +#include "../rbtdb.c" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +const char *ownercase_vectors[12][2] = { + { + "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ", + }, + { + "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz", + }, + { + "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVxXyYzZ", + "aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvxxyyzz", + }, + { + "WwW.ExAmPlE.OrG", + "wWw.eXaMpLe.oRg", + }, + { + "_SIP.tcp.example.org", + "_sip.TCP.example.org", + }, + { + "bind-USERS.lists.example.org", + "bind-users.lists.example.org", + }, + { + "a0123456789.example.org", + "A0123456789.example.org", + }, + { + "\\000.example.org", + "\\000.example.org", + }, + { + "wWw.\\000.isc.org", + "www.\\000.isc.org", + }, + { + "\255.example.org", + "\255.example.ORG", + } +}; + +static bool +ownercase_test_one(const char *str1, const char *str2) { + isc_result_t result; + rbtdb_nodelock_t node_locks[1]; + dns_rbtdb_t rbtdb = { .node_locks = node_locks }; + dns_rbtnode_t rbtnode = { .locknum = 0 }; + rdatasetheader_t header = { 0 }; + unsigned char *raw = (unsigned char *)(&header) + sizeof(header); + dns_rdataset_t rdataset = { + .magic = DNS_RDATASET_MAGIC, + .private1 = &rbtdb, + .private2 = &rbtnode, + .private3 = raw, + .methods = &rdataset_methods, + }; + + isc_buffer_t b; + dns_fixedname_t fname1, fname2; + dns_name_t *name1, *name2; + + memset(node_locks, 0, sizeof(node_locks)); + /* Minimal initialization of the mock objects */ + NODE_INITLOCK(&rbtdb.node_locks[0].lock); + + name1 = dns_fixedname_initname(&fname1); + isc_buffer_constinit(&b, str1, strlen(str1)); + isc_buffer_add(&b, strlen(str1)); + result = dns_name_fromtext(name1, &b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + name2 = dns_fixedname_initname(&fname2); + isc_buffer_constinit(&b, str2, strlen(str2)); + isc_buffer_add(&b, strlen(str2)); + result = dns_name_fromtext(name2, &b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Store the case from name1 */ + dns_rdataset_setownercase(&rdataset, name1); + + assert_true(CASESET(&header)); + + /* Retrieve the case to name2 */ + dns_rdataset_getownercase(&rdataset, name2); + + NODE_DESTROYLOCK(&rbtdb.node_locks[0].lock); + + return (dns_name_caseequal(name1, name2)); +} + +static void +ownercase_test(void **state) { + UNUSED(state); + + for (size_t n = 0; n < ARRAY_SIZE(ownercase_vectors); n++) { + assert_true(ownercase_test_one(ownercase_vectors[n][0], + ownercase_vectors[n][1])); + } + + assert_false(ownercase_test_one("W.example.org", "\\000.example.org")); + + /* Ö and ö in ISO Latin 1 */ + assert_false(ownercase_test_one("\\216", "\\246")); +} + +static void +setownercase_test(void **state) { + isc_result_t result; + rbtdb_nodelock_t node_locks[1]; + dns_rbtdb_t rbtdb = { .node_locks = node_locks }; + dns_rbtnode_t rbtnode = { .locknum = 0 }; + rdatasetheader_t header = { 0 }; + unsigned char *raw = (unsigned char *)(&header) + sizeof(header); + dns_rdataset_t rdataset = { + .magic = DNS_RDATASET_MAGIC, + .private1 = &rbtdb, + .private2 = &rbtnode, + .private3 = raw, + .methods = &rdataset_methods, + }; + const char *str1 = + "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"; + + isc_buffer_t b; + dns_fixedname_t fname1, fname2; + dns_name_t *name1, *name2; + + UNUSED(state); + + /* Minimal initialization of the mock objects */ + memset(node_locks, 0, sizeof(node_locks)); + NODE_INITLOCK(&rbtdb.node_locks[0].lock); + + name1 = dns_fixedname_initname(&fname1); + isc_buffer_constinit(&b, str1, strlen(str1)); + isc_buffer_add(&b, strlen(str1)); + result = dns_name_fromtext(name1, &b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + name2 = dns_fixedname_initname(&fname2); + isc_buffer_constinit(&b, str1, strlen(str1)); + isc_buffer_add(&b, strlen(str1)); + result = dns_name_fromtext(name2, &b, dns_rootname, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_false(CASESET(&header)); + + /* Retrieve the case to name2 */ + dns_rdataset_getownercase(&rdataset, name2); + + NODE_DESTROYLOCK(&rbtdb.node_locks[0].lock); + + assert_true(dns_name_caseequal(name1, name2)); +} + +/* + * No operation water() callback. We need it to cause overmem condition, but + * nothing has to be done in the callback. + */ +static void +overmempurge_water(void *arg, int mark) { + UNUSED(arg); + UNUSED(mark); +} + +/* + * Add to a cache DB 'db' an rdataset of type 'rtype' at a name + * .example.com. The rdataset would contain one data, and rdata_len is + * its length. 'rtype' is supposed to be some private type whose data can be + * arbitrary (and it doesn't matter in this test). + */ +static void +overmempurge_addrdataset(dns_db_t *db, isc_stdtime_t now, int idx, + dns_rdatatype_t rtype, size_t rdata_len, + bool longname) { + isc_result_t result; + dns_rdata_t rdata; + dns_dbnode_t *node = NULL; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + dns_fixedname_t fname; + dns_name_t *name; + char namebuf[DNS_NAME_FORMATSIZE]; + unsigned char rdatabuf[65535]; /* large enough for any valid RDATA */ + + REQUIRE(rdata_len <= sizeof(rdatabuf)); + + if (longname) { + /* + * Build a longest possible name (in wire format) that would + * result in a new rbt node with the long name data. + */ + snprintf(namebuf, sizeof(namebuf), + "%010d.%010dabcdef%010dabcdef%010dabcdef%010dabcde." + "%010dabcdef%010dabcdef%010dabcdef%010dabcde." + "%010dabcdef%010dabcdef%010dabcdef%010dabcde." + "%010dabcdef%010dabcdef%010dabcdef01.", + idx, idx, idx, idx, idx, idx, idx, idx, idx, idx, idx, + idx, idx, idx, idx, idx); + } else { + snprintf(namebuf, sizeof(namebuf), "%d.example.com.", idx); + } + + dns_test_namefromstring(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_db_findnode(db, name, true, &node); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(node); + + dns_rdata_init(&rdata); + rdata.length = rdata_len; + rdata.data = rdatabuf; + rdata.rdclass = dns_rdataclass_in; + rdata.type = rtype; + + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = dns_rdataclass_in; + rdatalist.type = rtype; + rdatalist.ttl = 3600; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_db_addrdataset(db, node, NULL, now, &rdataset, 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_db_detachnode(db, &node); +} + +static void +overmempurge_bigrdata_test(void **state) { + size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */ + size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */ + size_t lowater = maxcache - (maxcache >> 2); /* ditto */ + isc_result_t result; + dns_db_t *db = NULL; + isc_mem_t *mctx2 = NULL; + isc_stdtime_t now; + size_t i; + + UNUSED(state); + + isc_stdtime_get(&now); + + isc_mem_create(&mctx2); + + result = dns_db_create(mctx2, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_mem_setwater(mctx2, overmempurge_water, NULL, hiwater, lowater); + + /* + * Add cache entries with minimum size of data until 'overmem' + * condition is triggered. + * This should eventually happen, but we also limit the number of + * iteration to avoid an infinite loop in case something gets wrong. + */ + for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) { + overmempurge_addrdataset(db, now, i, 50053, 0, false); + } + assert_true(isc_mem_isovermem(mctx2)); + + /* + * Then try to add the same number of entries, each has very large data. + * 'overmem purge' should keep the total cache size from not exceeding + * the 'hiwater' mark too much. So we should be able to assume the + * cache size doesn't reach the "max". + */ + while (i-- > 0) { + overmempurge_addrdataset(db, now, i, 50054, 65535, false); + assert_true(isc_mem_inuse(mctx2) < maxcache); + } + + dns_db_detach(&db); + isc_mem_destroy(&mctx2); +} + +static void +overmempurge_longname_test(void **state) { + size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */ + size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */ + size_t lowater = maxcache - (maxcache >> 2); /* ditto */ + isc_result_t result; + dns_db_t *db = NULL; + isc_mem_t *mctx2 = NULL; + isc_stdtime_t now; + size_t i; + + UNUSED(state); + + isc_stdtime_get(&now); + isc_mem_create(&mctx2); + + result = dns_db_create(mctx2, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_mem_setwater(mctx2, overmempurge_water, NULL, hiwater, lowater); + + /* + * Add cache entries with minimum size of data until 'overmem' + * condition is triggered. + * This should eventually happen, but we also limit the number of + * iteration to avoid an infinite loop in case something gets wrong. + */ + for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) { + overmempurge_addrdataset(db, now, i, 50053, 0, false); + } + assert_true(isc_mem_isovermem(mctx2)); + + /* + * Then try to add the same number of entries, each has very large data. + * 'overmem purge' should keep the total cache size from not exceeding + * the 'hiwater' mark too much. So we should be able to assume the + * cache size doesn't reach the "max". + */ + while (i-- > 0) { + overmempurge_addrdataset(db, now, i, 50054, 0, true); + assert_true(isc_mem_inuse(mctx2) < maxcache); + } + + dns_db_detach(&db); + isc_mem_destroy(&mctx2); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(ownercase_test), + cmocka_unit_test(setownercase_test), + cmocka_unit_test(overmempurge_bigrdata_test), + cmocka_unit_test(overmempurge_longname_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c new file mode 100644 index 0000000..9bcac99 --- /dev/null +++ b/lib/dns/tests/rdata_test.c @@ -0,0 +1,3229 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +static bool debug = false; + +/* + * An array of these structures is passed to compare_ok(). + */ +struct compare_ok { + const char *text1; /* text passed to fromtext_*() */ + const char *text2; /* text passed to fromtext_*() */ + int answer; /* -1, 0, 1 */ + int lineno; /* source line defining this RDATA */ +}; +typedef struct compare_ok compare_ok_t; + +struct textvsunknown { + const char *text1; + const char *text2; +}; +typedef struct textvsunknown textvsunknown_t; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* + * An array of these structures is passed to check_text_ok(). + */ +typedef struct text_ok { + const char *text_in; /* text passed to fromtext_*() */ + const char *text_out; /* text expected from totext_*(); + * NULL indicates text_in is invalid */ + unsigned int loop; +} text_ok_t; + +/* + * An array of these structures is passed to check_wire_ok(). + */ +typedef struct wire_ok { + unsigned char data[512]; /* RDATA in wire format */ + size_t len; /* octets of data to parse */ + bool ok; /* is this RDATA valid? */ + unsigned int loop; +} wire_ok_t; + +#define COMPARE(r1, r2, answer) \ + { \ + r1, r2, answer, __LINE__ \ + } +#define COMPARE_SENTINEL() \ + { \ + NULL, NULL, 0, __LINE__ \ + } + +#define TEXT_VALID_CHANGED(data_in, data_out) \ + { \ + data_in, data_out, 0 \ + } +#define TEXT_VALID(data) \ + { \ + data, data, 0 \ + } +#define TEXT_VALID_LOOP(loop, data) \ + { \ + data, data, loop \ + } +#define TEXT_VALID_LOOPCHG(loop, data_in, data_out) \ + { \ + data_in, data_out, loop \ + } +#define TEXT_INVALID(data) \ + { \ + data, NULL, 0 \ + } +#define TEXT_SENTINEL() TEXT_INVALID(NULL) + +#define VARGC(...) (sizeof((unsigned char[]){ __VA_ARGS__ })) +#define WIRE_TEST(ok, loop, ...) \ + { \ + { __VA_ARGS__ }, VARGC(__VA_ARGS__), ok, loop \ + } +#define WIRE_VALID(...) WIRE_TEST(true, 0, __VA_ARGS__) +#define WIRE_VALID_LOOP(loop, ...) WIRE_TEST(true, loop, __VA_ARGS__) +/* + * WIRE_INVALID() test cases must always have at least one octet specified to + * distinguish them from WIRE_SENTINEL(). Use the 'empty_ok' parameter passed + * to check_wire_ok() for indicating whether empty RDATA is allowed for a given + * RR type or not. + */ +#define WIRE_INVALID(FIRST, ...) WIRE_TEST(false, 0, FIRST, __VA_ARGS__) +#define WIRE_SENTINEL() WIRE_TEST(false, 0) + +/* + * Call dns_rdata_fromwire() for data in 'src', which is 'srclen' octets in + * size and represents RDATA of given 'type' and 'class'. Store the resulting + * uncompressed wire form in 'dst', which is 'dstlen' octets in size, and make + * 'rdata' refer to that uncompressed wire form. + */ +static isc_result_t +wire_to_rdata(const unsigned char *src, size_t srclen, dns_rdataclass_t rdclass, + dns_rdatatype_t type, unsigned char *dst, size_t dstlen, + dns_rdata_t *rdata) { + isc_buffer_t source, target; + dns_decompress_t dctx; + isc_result_t result; + + /* + * Set up len-octet buffer pointing at data. + */ + isc_buffer_constinit(&source, src, srclen); + isc_buffer_add(&source, srclen); + isc_buffer_setactive(&source, srclen); + + /* + * Initialize target buffer. + */ + isc_buffer_init(&target, dst, dstlen); + + /* + * Try converting input data into uncompressed wire form. + */ + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY); + result = dns_rdata_fromwire(rdata, rdclass, type, &source, &dctx, 0, + &target); + dns_decompress_invalidate(&dctx); + + return (result); +} + +/* + * Call dns_rdata_towire() for rdata and write to result to dst. + */ +static isc_result_t +rdata_towire(dns_rdata_t *rdata, unsigned char *dst, size_t dstlen, + size_t *length) { + isc_buffer_t target; + dns_compress_t cctx; + isc_result_t result; + + /* + * Initialize target buffer. + */ + isc_buffer_init(&target, dst, dstlen); + + /* + * Try converting input data into uncompressed wire form. + */ + dns_compress_init(&cctx, -1, dt_mctx); + result = dns_rdata_towire(rdata, &cctx, &target); + dns_compress_invalidate(&cctx); + + *length = isc_buffer_usedlength(&target); + + return (result); +} + +static isc_result_t +additionaldata_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype) { + UNUSED(arg); + UNUSED(name); + UNUSED(qtype); + return (ISC_R_SUCCESS); +} + +/* + * call dns_rdata_additionaldata() for rdata. + */ +static isc_result_t +rdata_additionadata(dns_rdata_t *rdata) { + return (dns_rdata_additionaldata(rdata, additionaldata_cb, NULL)); +} + +/* + * Call dns_rdata_checknames() with various owner names chosen to + * match well known forms. + * + * We are currently only checking that the calls do not trigger + * assertion failures. + * + * XXXMPA A future extension could be to record the expected + * result and the expected value of 'bad'. + */ +static void +rdata_checknames(dns_rdata_t *rdata) { + dns_fixedname_t fixed, bfixed; + dns_name_t *name, *bad; + isc_result_t result; + + name = dns_fixedname_initname(&fixed); + bad = dns_fixedname_initname(&bfixed); + + (void)dns_rdata_checknames(rdata, dns_rootname, NULL); + (void)dns_rdata_checknames(rdata, dns_rootname, bad); + + result = dns_name_fromstring(name, "example.net", 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + (void)dns_rdata_checknames(rdata, name, NULL); + (void)dns_rdata_checknames(rdata, name, bad); + + result = dns_name_fromstring(name, "in-addr.arpa", 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + (void)dns_rdata_checknames(rdata, name, NULL); + (void)dns_rdata_checknames(rdata, name, bad); + + result = dns_name_fromstring(name, "ip6.arpa", 0, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + (void)dns_rdata_checknames(rdata, name, NULL); + (void)dns_rdata_checknames(rdata, name, bad); +} + +/* + * Test whether converting rdata to a type-specific struct and then back to + * rdata results in the same uncompressed wire form. This checks whether + * tostruct_*() and fromstruct_*() routines for given RR class and type behave + * consistently. + * + * This function is called for every correctly processed input RDATA, from both + * check_text_ok_single() and check_wire_ok_single(). + */ +static void +check_struct_conversions(dns_rdata_t *rdata, size_t structsize, + unsigned int loop) { + dns_rdataclass_t rdclass = rdata->rdclass; + dns_rdatatype_t type = rdata->type; + isc_result_t result; + isc_buffer_t target; + void *rdata_struct; + char buf[1024]; + unsigned int count = 0; + + rdata_struct = isc_mem_allocate(dt_mctx, structsize); + assert_non_null(rdata_struct); + + /* + * Convert from uncompressed wire form into type-specific struct. + */ + result = dns_rdata_tostruct(rdata, rdata_struct, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Convert from type-specific struct into uncompressed wire form. + */ + isc_buffer_init(&target, buf, sizeof(buf)); + result = dns_rdata_fromstruct(NULL, rdclass, type, rdata_struct, + &target); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Ensure results are consistent. + */ + assert_int_equal(isc_buffer_usedlength(&target), rdata->length); + + assert_memory_equal(buf, rdata->data, rdata->length); + + /* + * Check that one can walk hip rendezvous servers and + * https/svcb parameters. + */ + switch (type) { + case dns_rdatatype_hip: { + dns_rdata_hip_t *hip = rdata_struct; + + for (result = dns_rdata_hip_first(hip); result == ISC_R_SUCCESS; + result = dns_rdata_hip_next(hip)) + { + dns_name_t name; + dns_name_init(&name, NULL); + dns_rdata_hip_current(hip, &name); + assert_int_not_equal(dns_name_countlabels(&name), 0); + assert_true(dns_name_isabsolute(&name)); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } + case dns_rdatatype_https: { + dns_rdata_in_https_t *https = rdata_struct; + + for (result = dns_rdata_in_https_first(https); + result == ISC_R_SUCCESS; + result = dns_rdata_in_https_next(https)) + { + isc_region_t region; + dns_rdata_in_https_current(https, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } + case dns_rdatatype_svcb: { + dns_rdata_in_svcb_t *svcb = rdata_struct; + + for (result = dns_rdata_in_svcb_first(svcb); + result == ISC_R_SUCCESS; + result = dns_rdata_in_svcb_next(svcb)) + { + isc_region_t region; + dns_rdata_in_svcb_current(svcb, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } + } + + isc_mem_free(dt_mctx, rdata_struct); +} + +/* + * Check whether converting supplied text form RDATA into uncompressed wire + * form succeeds (tests fromtext_*()). If so, try converting it back into text + * form and see if it results in the original text (tests totext_*()). + */ +static void +check_text_ok_single(const text_ok_t *text_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type, size_t structsize) { + unsigned char buf_fromtext[1024], buf_fromwire[1024], buf_towire[1024]; + dns_rdata_t rdata = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; + char buf_totext[1024] = { 0 }; + isc_buffer_t target; + isc_result_t result; + size_t length = 0; + + if (debug) { + fprintf(stdout, "#check_text_ok_single(%s)\n", + text_ok->text_in); + } + /* + * Try converting text form RDATA into uncompressed wire form. + */ + result = dns_test_rdatafromstring(&rdata, rdclass, type, buf_fromtext, + sizeof(buf_fromtext), + text_ok->text_in, false); + /* + * Check whether result is as expected. + */ + if (text_ok->text_out != NULL) { + if (debug && result != ISC_R_SUCCESS) { + fprintf(stdout, "# '%s'\n", text_ok->text_in); + fprintf(stdout, "# result=%s\n", + dns_result_totext(result)); + } + assert_int_equal(result, ISC_R_SUCCESS); + } else { + if (debug && result == ISC_R_SUCCESS) { + fprintf(stdout, "#'%s'\n", text_ok->text_in); + } + assert_int_not_equal(result, ISC_R_SUCCESS); + } + + /* + * If text form RDATA was not parsed correctly, performing any + * additional checks is pointless. + */ + if (result != ISC_R_SUCCESS) { + return; + } + + /* + * Try converting uncompressed wire form RDATA back into text form and + * check whether the resulting text is the same as the original one. + */ + isc_buffer_init(&target, buf_totext, sizeof(buf_totext)); + result = dns_rdata_totext(&rdata, NULL, &target); + if (result != ISC_R_SUCCESS && debug) { + size_t i; + fprintf(stdout, "# dns_rdata_totext -> %s", + dns_result_totext(result)); + for (i = 0; i < rdata.length; i++) { + if ((i % 16) == 0) { + fprintf(stdout, "\n#"); + } + fprintf(stdout, " %02x", rdata.data[i]); + } + fprintf(stdout, "\n"); + } + assert_int_equal(result, ISC_R_SUCCESS); + /* + * Ensure buf_totext is properly NUL terminated as dns_rdata_totext() + * may attempt different output formats writing into the apparently + * unused part of the buffer. + */ + isc_buffer_putuint8(&target, 0); + if (debug && strcmp(buf_totext, text_ok->text_out) != 0) { + fprintf(stdout, "# '%s' != '%s'\n", buf_totext, + text_ok->text_out); + } + assert_string_equal(buf_totext, text_ok->text_out); + + if (debug) { + fprintf(stdout, "#dns_rdata_totext -> '%s'\n", buf_totext); + } + + /* + * Ensure that fromtext_*() output is valid input for fromwire_*(). + */ + result = wire_to_rdata(rdata.data, rdata.length, rdclass, type, + buf_fromwire, sizeof(buf_fromwire), &rdata2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdata.length, rdata2.length); + assert_memory_equal(rdata.data, buf_fromwire, rdata.length); + + /* + * Ensure that fromtext_*() output is valid input for towire_*(). + */ + result = rdata_towire(&rdata, buf_towire, sizeof(buf_towire), &length); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdata.length, length); + assert_memory_equal(rdata.data, buf_towire, length); + + /* + * Test that additionaldata_*() succeeded. + */ + result = rdata_additionadata(&rdata); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Exercise checknames_*(). + */ + rdata_checknames(&rdata); + + /* + * Perform two-way conversion checks between uncompressed wire form and + * type-specific struct. + */ + check_struct_conversions(&rdata, structsize, text_ok->loop); +} + +/* + * Test whether converting rdata to text form and then parsing the result of + * that conversion again results in the same uncompressed wire form. This + * checks whether totext_*() output is parsable by fromtext_*() for given RR + * class and type. + * + * This function is called for every input RDATA which is successfully parsed + * by check_wire_ok_single() and whose type is not a meta-type. + */ +static void +check_text_conversions(dns_rdata_t *rdata) { + char buf_totext[1024] = { 0 }; + unsigned char buf_fromtext[1024]; + isc_result_t result; + isc_buffer_t target; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + + /* + * Convert uncompressed wire form RDATA into text form. This + * conversion must succeed since input RDATA was successfully + * parsed by check_wire_ok_single(). + */ + isc_buffer_init(&target, buf_totext, sizeof(buf_totext)); + result = dns_rdata_totext(rdata, NULL, &target); + assert_int_equal(result, ISC_R_SUCCESS); + /* + * Ensure buf_totext is properly NUL terminated as dns_rdata_totext() + * may attempt different output formats writing into the apparently + * unused part of the buffer. + */ + isc_buffer_putuint8(&target, 0); + if (debug) { + fprintf(stdout, "#'%s'\n", buf_totext); + } + + /* + * Try parsing text form RDATA output by dns_rdata_totext() again. + */ + result = dns_test_rdatafromstring(&rdata2, rdata->rdclass, rdata->type, + buf_fromtext, sizeof(buf_fromtext), + buf_totext, false); + if (debug && result != ISC_R_SUCCESS) { + fprintf(stdout, "# result = %s\n", dns_result_totext(result)); + fprintf(stdout, "# '%s'\n", buf_fromtext); + } + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdata2.length, rdata->length); + assert_memory_equal(buf_fromtext, rdata->data, rdata->length); +} + +/* + * Test whether converting rdata to multi-line text form and then parsing the + * result of that conversion again results in the same uncompressed wire form. + * This checks whether multi-line totext_*() output is parsable by fromtext_*() + * for given RR class and type. + * + * This function is called for every input RDATA which is successfully parsed + * by check_wire_ok_single() and whose type is not a meta-type. + */ +static void +check_multiline_text_conversions(dns_rdata_t *rdata) { + char buf_totext[1024] = { 0 }; + unsigned char buf_fromtext[1024]; + isc_result_t result; + isc_buffer_t target; + dns_rdata_t rdata2 = DNS_RDATA_INIT; + unsigned int flags; + + /* + * Convert uncompressed wire form RDATA into multi-line text form. + * This conversion must succeed since input RDATA was successfully + * parsed by check_wire_ok_single(). + */ + isc_buffer_init(&target, buf_totext, sizeof(buf_totext)); + flags = dns_master_styleflags(&dns_master_style_default); + result = dns_rdata_tofmttext(rdata, dns_rootname, flags, 80 - 32, 4, + "\n", &target); + assert_int_equal(result, ISC_R_SUCCESS); + /* + * Ensure buf_totext is properly NUL terminated as + * dns_rdata_tofmttext() may attempt different output formats + * writing into the apparently unused part of the buffer. + */ + isc_buffer_putuint8(&target, 0); + if (debug) { + fprintf(stdout, "#'%s'\n", buf_totext); + } + + /* + * Try parsing multi-line text form RDATA output by + * dns_rdata_tofmttext() again. + */ + result = dns_test_rdatafromstring(&rdata2, rdata->rdclass, rdata->type, + buf_fromtext, sizeof(buf_fromtext), + buf_totext, false); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdata2.length, rdata->length); + assert_memory_equal(buf_fromtext, rdata->data, rdata->length); +} + +/* + * Test whether supplied wire form RDATA is properly handled as being either + * valid or invalid for an RR of given rdclass and type. + */ +static void +check_wire_ok_single(const wire_ok_t *wire_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type, size_t structsize) { + unsigned char buf[1024], buf_towire[1024]; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + size_t length = 0; + + /* + * Try converting wire data into uncompressed wire form. + */ + result = wire_to_rdata(wire_ok->data, wire_ok->len, rdclass, type, buf, + sizeof(buf), &rdata); + /* + * Check whether result is as expected. + */ + if (wire_ok->ok) { + assert_int_equal(result, ISC_R_SUCCESS); + } else { + assert_int_not_equal(result, ISC_R_SUCCESS); + } + + if (result != ISC_R_SUCCESS) { + return; + } + + /* + * If data was parsed correctly, perform two-way conversion checks + * between uncompressed wire form and type-specific struct. + * + * If the RR type is not a meta-type, additionally perform two-way + * conversion checks between: + * + * - uncompressed wire form and text form, + * - uncompressed wire form and multi-line text form. + */ + check_struct_conversions(&rdata, structsize, wire_ok->loop); + if (!dns_rdatatype_ismeta(rdata.type)) { + check_text_conversions(&rdata); + check_multiline_text_conversions(&rdata); + } + + /* + * Ensure that fromwire_*() output is valid input for towire_*(). + */ + result = rdata_towire(&rdata, buf_towire, sizeof(buf_towire), &length); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(rdata.length, length); + assert_memory_equal(rdata.data, buf_towire, length); + + /* + * Test that additionaldata_*() succeeded. + */ + result = rdata_additionadata(&rdata); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Exercise checknames_*(). + */ + rdata_checknames(&rdata); +} + +/* + * Test fromtext_*() and totext_*() routines for given RR class and type for + * each text form RDATA in the supplied array. See the comment for + * check_text_ok_single() for an explanation of how exactly these routines are + * tested. + */ +static void +check_text_ok(const text_ok_t *text_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type, size_t structsize) { + size_t i; + + /* + * Check all entries in the supplied array. + */ + for (i = 0; text_ok[i].text_in != NULL; i++) { + check_text_ok_single(&text_ok[i], rdclass, type, structsize); + } +} + +/* + * For each wire form RDATA in the supplied array, check whether it is properly + * handled as being either valid or invalid for an RR of given rdclass and + * type, then check whether trying to process a zero-length wire data buffer + * yields the expected result. This checks whether the fromwire_*() routine + * for given RR class and type behaves as expected. + */ +static void +check_wire_ok(const wire_ok_t *wire_ok, bool empty_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type, size_t structsize) { + wire_ok_t empty_wire = WIRE_TEST(empty_ok, 0); + size_t i; + + /* + * Check all entries in the supplied array. + */ + for (i = 0; wire_ok[i].len != 0; i++) { + if (debug) { + fprintf(stderr, "calling check_wire_ok_single on %zu\n", + i); + } + check_wire_ok_single(&wire_ok[i], rdclass, type, structsize); + } + + /* + * Check empty wire data. + */ + check_wire_ok_single(&empty_wire, rdclass, type, structsize); +} + +/* + * Check that two records compare as expected with dns_rdata_compare(). + */ +static void +check_compare_ok_single(const compare_ok_t *compare_ok, + dns_rdataclass_t rdclass, dns_rdatatype_t type) { + dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; + unsigned char buf1[1024], buf2[1024]; + isc_result_t result; + int answer; + + result = dns_test_rdatafromstring(&rdata1, rdclass, type, buf1, + sizeof(buf1), compare_ok->text1, + false); + if (result != ISC_R_SUCCESS) { + fail_msg("# line %d: '%s': expected success, got failure", + compare_ok->lineno, compare_ok->text1); + } + + result = dns_test_rdatafromstring(&rdata2, rdclass, type, buf2, + sizeof(buf2), compare_ok->text2, + false); + + if (result != ISC_R_SUCCESS) { + fail_msg("# line %d: '%s': expected success, got failure", + compare_ok->lineno, compare_ok->text2); + } + + answer = dns_rdata_compare(&rdata1, &rdata2); + if (compare_ok->answer == 0 && answer != 0) { + fail_msg("# line %d: dns_rdata_compare('%s', '%s'): " + "expected equal, got %s", + compare_ok->lineno, compare_ok->text1, + compare_ok->text2, + (answer > 0) ? "greater than" : "less than"); + } + if (compare_ok->answer < 0 && answer >= 0) { + fail_msg("# line %d: dns_rdata_compare('%s', '%s'): " + "expected less than, got %s", + compare_ok->lineno, compare_ok->text1, + compare_ok->text2, + (answer == 0) ? "equal" : "greater than"); + } + if (compare_ok->answer > 0 && answer <= 0) { + fail_msg("line %d: dns_rdata_compare('%s', '%s'): " + "expected greater than, got %s", + compare_ok->lineno, compare_ok->text1, + compare_ok->text2, + (answer == 0) ? "equal" : "less than"); + } +} + +/* + * Check that all the records sets in compare_ok compare as expected + * with dns_rdata_compare(). + */ +static void +check_compare_ok(const compare_ok_t *compare_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type) { + size_t i; + /* + * Check all entries in the supplied array. + */ + for (i = 0; compare_ok[i].text1 != NULL; i++) { + check_compare_ok_single(&compare_ok[i], rdclass, type); + } +} + +/* + * Test whether supplied sets of text form and/or wire form RDATA are handled + * as expected. + * + * The empty_ok argument denotes whether an attempt to parse a zero-length wire + * data buffer should succeed or not (it is valid for some RR types). There is + * no point in performing a similar check for empty text form RDATA, because + * dns_rdata_fromtext() returns ISC_R_UNEXPECTEDEND before calling fromtext_*() + * for the given RR class and type. + */ +static void +check_rdata(const text_ok_t *text_ok, const wire_ok_t *wire_ok, + const compare_ok_t *compare_ok, bool empty_ok, + dns_rdataclass_t rdclass, dns_rdatatype_t type, size_t structsize) { + if (text_ok != NULL) { + check_text_ok(text_ok, rdclass, type, structsize); + } + if (wire_ok != NULL) { + check_wire_ok(wire_ok, empty_ok, rdclass, type, structsize); + } + if (compare_ok != NULL) { + check_compare_ok(compare_ok, rdclass, type); + } +} + +/* + * Check presentation vs unknown format of the record. + */ +static void +check_textvsunknown_single(const textvsunknown_t *textvsunknown, + dns_rdataclass_t rdclass, dns_rdatatype_t type) { + dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; + unsigned char buf1[1024], buf2[1024]; + isc_result_t result; + + result = dns_test_rdatafromstring(&rdata1, rdclass, type, buf1, + sizeof(buf1), textvsunknown->text1, + false); + if (debug && result != ISC_R_SUCCESS) { + fprintf(stdout, "# '%s'\n", textvsunknown->text1); + fprintf(stdout, "# result=%s\n", dns_result_totext(result)); + } + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_test_rdatafromstring(&rdata2, rdclass, type, buf2, + sizeof(buf2), textvsunknown->text2, + false); + if (debug && result != ISC_R_SUCCESS) { + fprintf(stdout, "# '%s'\n", textvsunknown->text2); + fprintf(stdout, "# result=%s\n", dns_result_totext(result)); + } + assert_int_equal(result, ISC_R_SUCCESS); + if (debug && rdata1.length != rdata2.length) { + fprintf(stdout, "# '%s'\n", textvsunknown->text1); + fprintf(stdout, "# rdata1.length (%u) != rdata2.length (%u)\n", + rdata1.length, rdata2.length); + } + assert_int_equal(rdata1.length, rdata2.length); + if (debug && memcmp(rdata1.data, rdata2.data, rdata1.length) != 0) { + unsigned int i; + fprintf(stdout, "# '%s'\n", textvsunknown->text1); + for (i = 0; i < rdata1.length; i++) { + if (rdata1.data[i] != rdata2.data[i]) { + fprintf(stderr, "# %u: %02x != %02x\n", i, + rdata1.data[i], rdata2.data[i]); + } + } + } + assert_memory_equal(rdata1.data, rdata2.data, rdata1.length); +} + +static void +check_textvsunknown(const textvsunknown_t *textvsunknown, + dns_rdataclass_t rdclass, dns_rdatatype_t type) { + size_t i; + + /* + * Check all entries in the supplied array. + */ + for (i = 0; textvsunknown[i].text1 != NULL; i++) { + check_textvsunknown_single(&textvsunknown[i], rdclass, type); + } +} + +/* + * Common tests for RR types based on KEY that require key data: + * + * - CDNSKEY (RFC 7344) + * - DNSKEY (RFC 4034) + * - RKEY (draft-reid-dnsext-rkey-00) + */ +static void +key_required(void **state, dns_rdatatype_t type, size_t size) { + wire_ok_t wire_ok[] = { /* + * RDATA must be at least 5 octets in size: + * + * - 2 octets for Flags, + * - 1 octet for Protocol, + * - 1 octet for Algorithm, + * - Public Key must not be empty. + * + * RFC 2535 section 3.1.2 allows the Public Key + * to be empty if bits 0-1 of Flags are both + * set, but that only applies to KEY records: + * for the RR types tested here, the Public Key + * must not be empty. + */ + WIRE_INVALID(0x00), + WIRE_INVALID(0x00, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00), + WIRE_INVALID(0xc0, 0x00, 0x00, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00, 0x00), + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00), + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in, type, size); +} + +/* APL RDATA manipulations */ +static void +apl(void **state) { + text_ok_t text_ok[] = { + /* empty list */ + TEXT_VALID(""), + /* min,max prefix IPv4 */ + TEXT_VALID("1:0.0.0.0/0"), TEXT_VALID("1:127.0.0.1/32"), + /* min,max prefix IPv6 */ + TEXT_VALID("2:::/0"), TEXT_VALID("2:::1/128"), + /* negated */ + TEXT_VALID("!1:0.0.0.0/0"), TEXT_VALID("!1:127.0.0.1/32"), + TEXT_VALID("!2:::/0"), TEXT_VALID("!2:::1/128"), + /* bits set after prefix length - not disallowed */ + TEXT_VALID("1:127.0.0.0/0"), TEXT_VALID("2:8000::/0"), + /* multiple */ + TEXT_VALID("1:0.0.0.0/0 1:127.0.0.1/32"), + TEXT_VALID("1:0.0.0.0/0 !1:127.0.0.1/32"), + /* family 0, prefix 0, positive */ + TEXT_VALID("\\# 4 00000000"), + /* family 0, prefix 0, negative */ + TEXT_VALID("\\# 4 00000080"), + /* prefix too long */ + TEXT_INVALID("1:0.0.0.0/33"), TEXT_INVALID("2:::/129"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { /* zero length */ + WIRE_VALID(), + /* prefix too big IPv4 */ + WIRE_INVALID(0x00, 0x01, 33U, 0x00), + /* prefix too big IPv6 */ + WIRE_INVALID(0x00, 0x02, 129U, 0x00), + /* trailing zero octet in afdpart */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, true, dns_rdataclass_in, + dns_rdatatype_apl, sizeof(dns_rdata_in_apl_t)); +} + +/* + * http://broadband-forum.org/ftp/pub/approved-specs/af-saa-0069.000.pdf + * + * ATMA RR’s have the following RDATA format: + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | FORMAT | | + * +--+--+--+--+--+--+--+--+ | + * / ADDRESS / + * | | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * The fields have the following meaning: + * + * * FORMAT: One octet that indicates the format of ADDRESS. The two + * possible values for FORMAT are value 0 indicating ATM End System Address + * (AESA) format and value 1 indicating E.164 format. + * + * * ADDRESS: Variable length string of octets containing the ATM address of + * the node to which this RR pertains. + * + * When the format value is 0, indicating that the address is in AESA format, + * the address is coded as described in ISO 8348/AD 2 using the preferred + * binary encoding of the ISO NSAP format. When the format value is 1, + * indicating that the address is in E.164 format, the Address/Number Digits + * appear in the order in which they would be entered on a numeric keypad. + * Digits are coded in IA5 characters with the leftmost bit of each digit set + * to 0. This ATM address appears in ATM End System Address Octets field (AESA + * format) or the Address/Number Digits field (E.164 format) of the Called + * party number information element [ATMUNI3.1]. Subaddress information is + * intentionally not included because E.164 subaddress information is used for + * routing. + * + * ATMA RRs cause no additional section processing. + */ +static void +atma(void **state) { + text_ok_t text_ok[] = { TEXT_VALID("00"), + TEXT_VALID_CHANGED("0.0", "00"), + /* + * multiple consecutive periods + */ + TEXT_INVALID("0..0"), + /* + * trailing period + */ + TEXT_INVALID("00."), + /* + * leading period + */ + TEXT_INVALID(".00"), + /* + * Not full octets. + */ + TEXT_INVALID("000"), + /* + * E.164 + */ + TEXT_VALID("+61200000000"), + /* + * E.164 with periods + */ + TEXT_VALID_CHANGED("+61.2.0000.0000", "+6120000" + "0000"), + /* + * E.164 with period at end + */ + TEXT_INVALID("+61200000000."), + /* + * E.164 with multiple consecutive periods + */ + TEXT_INVALID("+612..00000000"), + /* + * E.164 with period before the leading digit. + */ + TEXT_INVALID("+.61200000000"), + /* + * Sentinel. + */ + TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { + /* + * Too short. + */ + WIRE_INVALID(0x00), WIRE_INVALID(0x01), + /* + * all digits + */ + WIRE_VALID(0x01, '6', '1', '2', '0', '0', '0'), + /* + * non digit + */ + WIRE_INVALID(0x01, '+', '6', '1', '2', '0', '0', '0'), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_atma, sizeof(dns_rdata_in_atma_t)); +} + +/* AMTRELAY RDATA manipulations */ +static void +amtrelay(void **state) { + text_ok_t text_ok[] = { + TEXT_INVALID(""), TEXT_INVALID("0"), TEXT_INVALID("0 0"), + /* gateway type 0 */ + TEXT_VALID("0 0 0"), TEXT_VALID("0 1 0"), + TEXT_INVALID("0 2 0"), /* discovery out of range */ + TEXT_VALID("255 1 0"), /* max precedence */ + TEXT_INVALID("256 1 0"), /* precedence out of range */ + + /* IPv4 gateway */ + TEXT_INVALID("0 0 1"), /* no address */ + TEXT_VALID("0 0 1 0.0.0.0"), + TEXT_INVALID("0 0 1 0.0.0.0 x"), /* extra */ + TEXT_INVALID("0 0 1 0.0.0.0.0"), /* bad address */ + TEXT_INVALID("0 0 1 ::"), /* bad address */ + TEXT_INVALID("0 0 1 ."), /* bad address */ + + /* IPv6 gateway */ + TEXT_INVALID("0 0 2"), /* no address */ + TEXT_VALID("0 0 2 ::"), TEXT_INVALID("0 0 2 :: xx"), /* extra */ + TEXT_INVALID("0 0 2 0.0.0.0"), /* bad address */ + TEXT_INVALID("0 0 2 ."), /* bad address */ + + /* hostname gateway */ + TEXT_INVALID("0 0 3"), /* no name */ + /* IPv4 is a valid name */ + TEXT_VALID_CHANGED("0 0 3 0.0.0.0", "0 0 3 0.0.0.0."), + /* IPv6 is a valid name */ + TEXT_VALID_CHANGED("0 0 3 ::", "0 0 3 ::."), + TEXT_VALID_CHANGED("0 0 3 example", "0 0 3 example."), + TEXT_VALID("0 0 3 example."), + TEXT_INVALID("0 0 3 example. x"), /* extra */ + + /* unknown gateway */ + TEXT_VALID("\\# 2 0004"), TEXT_VALID("\\# 2 0084"), + TEXT_VALID("\\# 2 007F"), TEXT_VALID("\\# 3 000400"), + TEXT_VALID("\\# 3 008400"), TEXT_VALID("\\# 3 00FF00"), + + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { + WIRE_INVALID(0x00), WIRE_VALID(0x00, 0x00), + WIRE_VALID(0x00, 0x80), WIRE_INVALID(0x00, 0x00, 0x00), + WIRE_INVALID(0x00, 0x80, 0x00), + + WIRE_INVALID(0x00, 0x01), WIRE_INVALID(0x00, 0x01, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00), + WIRE_VALID(0x00, 0x01, 0x00, 0x00, 0x00, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), + + WIRE_INVALID(0x00, 0x02), WIRE_INVALID(0x00, 0x02, 0x00), + WIRE_VALID(0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15), + WIRE_INVALID(0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16), + + WIRE_INVALID(0x00, 0x03), WIRE_VALID(0x00, 0x03, 0x00), + WIRE_INVALID(0x00, 0x03, 0x00, 0x00), /* extra */ + + WIRE_VALID(0x00, 0x04), WIRE_VALID(0x00, 0x04, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_amtrelay, sizeof(dns_rdata_amtrelay_t)); +} + +static void +cdnskey(void **state) { + key_required(state, dns_rdatatype_cdnskey, sizeof(dns_rdata_cdnskey_t)); +} + +/* + * CSYNC tests. + * + * RFC 7477: + * + * 2.1. The CSYNC Resource Record Format + * + * 2.1.1. The CSYNC Resource Record Wire Format + * + * The CSYNC RDATA consists of the following fields: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SOA Serial | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Flags | Type Bit Map / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / Type Bit Map (continued) / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 2.1.1.1. The SOA Serial Field + * + * The SOA Serial field contains a copy of the 32-bit SOA serial number + * from the child zone. If the soaminimum flag is set, parental agents + * querying children's authoritative servers MUST NOT act on data from + * zones advertising an SOA serial number less than this value. See + * [RFC1982] for properly implementing "less than" logic. If the + * soaminimum flag is not set, parental agents MUST ignore the value in + * the SOA Serial field. Clients can set the field to any value if the + * soaminimum flag is unset, such as the number zero. + * + * (...) + * + * 2.1.1.2. The Flags Field + * + * The Flags field contains 16 bits of boolean flags that define + * operations that affect the processing of the CSYNC record. The flags + * defined in this document are as follows: + * + * 0x00 0x01: "immediate" + * + * 0x00 0x02: "soaminimum" + * + * The definitions for how the flags are to be used can be found in + * Section 3. + * + * The remaining flags are reserved for use by future specifications. + * Undefined flags MUST be set to 0 by CSYNC publishers. Parental + * agents MUST NOT process a CSYNC record if it contains a 1 value for a + * flag that is unknown to or unsupported by the parental agent. + * + * 2.1.1.2.1. The Type Bit Map Field + * + * The Type Bit Map field indicates the record types to be processed by + * the parental agent, according to the procedures in Section 3. The + * Type Bit Map field is encoded in the same way as the Type Bit Map + * field of the NSEC record, described in [RFC4034], Section 4.1.2. If + * a bit has been set that a parental agent implementation does not + * understand, the parental agent MUST NOT act upon the record. + * Specifically, a parental agent must not simply copy the data, and it + * must understand the semantics associated with a bit in the Type Bit + * Map field that has been set to 1. + */ +static void +csync(void **state) { + text_ok_t text_ok[] = { TEXT_INVALID(""), + TEXT_INVALID("0"), + TEXT_VALID("0 0"), + TEXT_VALID("0 0 A"), + TEXT_VALID("0 0 NS"), + TEXT_VALID("0 0 AAAA"), + TEXT_VALID("0 0 A AAAA"), + TEXT_VALID("0 0 A NS AAAA"), + TEXT_INVALID("0 0 A NS AAAA BOGUS"), + TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { + /* + * Short. + */ + WIRE_INVALID(0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Serial + flags only. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Bad type map. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Bad type map. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Good type map. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_csync, sizeof(dns_rdata_csync_t)); +} + +static void +dnskey(void **state) { + key_required(state, dns_rdatatype_dnskey, sizeof(dns_rdata_dnskey_t)); +} + +/* + * DOA tests. + * + * draft-durand-doa-over-dns-03: + * + * 3.2. DOA RDATA Wire Format + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0: | | + * | DOA-ENTERPRISE | + * | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 4: | | + * | DOA-TYPE | + * | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 8: | DOA-LOCATION | DOA-MEDIA-TYPE / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 10: / / + * / DOA-MEDIA-TYPE (continued) / + * / / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * / / + * / DOA-DATA / + * / / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * DOA-ENTERPRISE: a 32-bit unsigned integer in network order. + * + * DOA-TYPE: a 32-bit unsigned integer in network order. + * + * DOA-LOCATION: an 8-bit unsigned integer. + * + * DOA-MEDIA-TYPE: A (see [RFC1035]). The first + * octet of the contains the number of characters to + * follow. + * + * DOA-DATA: A variable length blob of binary data. The length of the + * DOA-DATA is not contained within the wire format of the RR and has to + * be computed from the RDLENGTH of the entire RR once other fields have + * been taken into account. + * + * 3.3. DOA RDATA Presentation Format + * + * The DOA-ENTERPRISE field is presented as an unsigned 32-bit decimal + * integer with range 0 - 4,294,967,295. + * + * The DOA-TYPE field is presented as an unsigned 32-bit decimal integer + * with range 0 - 4,294,967,295. + * + * The DOA-LOCATION field is presented as an unsigned 8-bit decimal + * integer with range 0 - 255. + * + * The DOA-MEDIA-TYPE field is presented as a single . + * + * The DOA-DATA is presented as Base64 encoded data [RFC4648] unless the + * DOA-DATA is empty in which case it is presented as a single dash + * character ("-", ASCII 45). White space is permitted within Base64 + * data. + */ +static void +doa(void **state) { + text_ok_t text_ok[] = { + /* + * Valid, non-empty DOA-DATA. + */ + TEXT_VALID("0 0 1 \"text/plain\" Zm9v"), + /* + * Valid, non-empty DOA-DATA with whitespace in between. + */ + TEXT_VALID_CHANGED("0 0 1 \"text/plain\" Zm 9v", "0 0 1 " + "\"text/" + "plain\" " + "Zm9v"), + /* + * Valid, unquoted DOA-MEDIA-TYPE, non-empty DOA-DATA. + */ + TEXT_VALID_CHANGED("0 0 1 text/plain Zm9v", "0 0 1 " + "\"text/plain\" " + "Zm9v"), + /* + * Invalid, quoted non-empty DOA-DATA. + */ + TEXT_INVALID("0 0 1 \"text/plain\" \"Zm9v\""), + /* + * Valid, empty DOA-DATA. + */ + TEXT_VALID("0 0 1 \"text/plain\" -"), + /* + * Invalid, quoted empty DOA-DATA. + */ + TEXT_INVALID("0 0 1 \"text/plain\" \"-\""), + /* + * Invalid, missing "-" in empty DOA-DATA. + */ + TEXT_INVALID("0 0 1 \"text/plain\""), + /* + * Valid, undefined DOA-LOCATION. + */ + TEXT_VALID("0 0 100 \"text/plain\" Zm9v"), + /* + * Invalid, DOA-LOCATION too big. + */ + TEXT_INVALID("0 0 256 \"text/plain\" ZM9v"), + /* + * Valid, empty DOA-MEDIA-TYPE, non-empty DOA-DATA. + */ + TEXT_VALID("0 0 2 \"\" aHR0cHM6Ly93d3cuaXNjLm9yZy8="), + /* + * Valid, empty DOA-MEDIA-TYPE, empty DOA-DATA. + */ + TEXT_VALID("0 0 1 \"\" -"), + /* + * Valid, DOA-MEDIA-TYPE with a space. + */ + TEXT_VALID("0 0 1 \"plain text\" Zm9v"), + /* + * Invalid, missing DOA-MEDIA-TYPE. + */ + TEXT_INVALID("1234567890 1234567890 1"), + /* + * Valid, DOA-DATA over 255 octets. + */ + TEXT_VALID("1234567890 1234567890 1 \"image/gif\" " + "R0lGODlhKAAZAOMCAGZmZgBmmf///zOZzMz//5nM/zNmmWbM" + "/5nMzMzMzACZ/////////////////////yH5BAEKAA8ALAAA" + "AAAoABkAAATH8IFJK5U2a4337F5ogRkpnoCJrly7PrCKyh8c" + "3HgAhzT35MDbbtO7/IJIHbGiOiaTxVTpSVWWLqNq1UVyapNS" + "1wd3OAxug0LhnCubcVhsxysQnOt4ATpvvzHlFzl1AwODhWeF" + "AgRpen5/UhheAYMFdUB4SFcpGEGGdQeCAqBBLTuSk30EeXd9" + "pEsAbKGxjHqDSE0Sp6ixN4N1BJmbc7lIhmsBich1awPAjkY1" + "SZR8bJWrz382SGqIBQQFQd4IsUTaX+ceuudPEQA7"), + /* + * Invalid, bad Base64 in DOA-DATA. + */ + TEXT_INVALID("1234567890 1234567890 1 \"image/gif\" R0lGODl"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { + /* + * Valid, empty DOA-MEDIA-TYPE, empty DOA-DATA. + */ + WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01, + 0x00), + /* + * Invalid, missing DOA-MEDIA-TYPE. + */ + WIRE_INVALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, + 0x01), + /* + * Invalid, malformed DOA-MEDIA-TYPE length. + */ + WIRE_INVALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, + 0x01, 0xff), + /* + * Valid, empty DOA-DATA. + */ + WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01, + 0x03, 0x66, 0x6f, 0x6f), + /* + * Valid, non-empty DOA-DATA. + */ + WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01, + 0x03, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72), + /* + * Valid, DOA-DATA over 255 octets. + */ + WIRE_VALID(0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x01, + 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x00, 0x66, + 0x99, 0xff, 0xff, 0xff, 0x33, 0x99, 0xcc, 0xcc, 0xff, + 0xff, 0x99, 0xcc, 0xff, 0x33, 0x66, 0x99, 0x66, 0xcc, + 0xff, 0x99, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x99, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21, 0xf9, + 0x04, 0x01, 0x0a, 0x00, 0x0f, 0x00, 0x2c, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x19, 0x00, 0x00, 0x04, 0xc7, + 0xf0, 0x81, 0x49, 0x2b, 0x95, 0x36, 0x6b, 0x8d, 0xf7, + 0xec, 0x5e, 0x68, 0x81, 0x19, 0x29, 0x9e, 0x80, 0x89, + 0xae, 0x5c, 0xbb, 0x3e, 0xb0, 0x8a, 0xca, 0x1f, 0x1c, + 0xdc, 0x78, 0x00, 0x87, 0x34, 0xf7, 0xe4, 0xc0, 0xdb, + 0x6e, 0xd3, 0xbb, 0xfc, 0x82, 0x48, 0x1d, 0xb1, 0xa2, + 0x3a, 0x26, 0x93, 0xc5, 0x54, 0xe9, 0x49, 0x55, 0x96, + 0x2e, 0xa3, 0x6a, 0xd5, 0x45, 0x72, 0x6a, 0x93, 0x52, + 0xd7, 0x07, 0x77, 0x38, 0x0c, 0x6e, 0x83, 0x42, 0xe1, + 0x9c, 0x2b, 0x9b, 0x71, 0x58, 0x6c, 0xc7, 0x2b, 0x10, + 0x9c, 0xeb, 0x78, 0x01, 0x3a, 0x6f, 0xbf, 0x31, 0xe5, + 0x17, 0x39, 0x75, 0x03, 0x03, 0x83, 0x85, 0x67, 0x85, + 0x02, 0x04, 0x69, 0x7a, 0x7e, 0x7f, 0x52, 0x18, 0x5e, + 0x01, 0x83, 0x05, 0x75, 0x40, 0x78, 0x48, 0x57, 0x29, + 0x18, 0x41, 0x86, 0x75, 0x07, 0x82, 0x02, 0xa0, 0x41, + 0x2d, 0x3b, 0x92, 0x93, 0x7d, 0x04, 0x79, 0x77, 0x7d, + 0xa4, 0x4b, 0x00, 0x6c, 0xa1, 0xb1, 0x8c, 0x7a, 0x83, + 0x48, 0x4d, 0x12, 0xa7, 0xa8, 0xb1, 0x37, 0x83, 0x75, + 0x04, 0x99, 0x9b, 0x73, 0xb9, 0x48, 0x86, 0x6b, 0x01, + 0x89, 0xc8, 0x75, 0x6b, 0x03, 0xc0, 0x8e, 0x46, 0x35, + 0x49, 0x94, 0x7c, 0x6c, 0x95, 0xab, 0xcf, 0x7f, 0x36, + 0x48, 0x6a, 0x88, 0x05, 0x04, 0x05, 0x41, 0xde, 0x08, + 0xb1, 0x44, 0xda, 0x5f, 0xe7, 0x1e, 0xba, 0xe7, 0x4f, + 0x11, 0x00, 0x3b), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_doa, sizeof(dns_rdata_doa_t)); +} + +/* + * DS tests. + * + * RFC 4034: + * + * 5.1. DS RDATA Wire Format + * + * The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet + * Algorithm field, a 1 octet Digest Type field, and a Digest field. + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Key Tag | Algorithm | Digest Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / / + * / Digest / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 5.1.1. The Key Tag Field + * + * The Key Tag field lists the key tag of the DNSKEY RR referred to by + * the DS record, in network byte order. + * + * The Key Tag used by the DS RR is identical to the Key Tag used by + * RRSIG RRs. Appendix B describes how to compute a Key Tag. + * + * 5.1.2. The Algorithm Field + * + * The Algorithm field lists the algorithm number of the DNSKEY RR + * referred to by the DS record. + * + * The algorithm number used by the DS RR is identical to the algorithm + * number used by RRSIG and DNSKEY RRs. Appendix A.1 lists the + * algorithm number types. + * + * 5.1.3. The Digest Type Field + * + * The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY + * RR. The Digest Type field identifies the algorithm used to construct + * the digest. Appendix A.2 lists the possible digest algorithm types. + * + * 5.1.4. The Digest Field + * + * The DS record refers to a DNSKEY RR by including a digest of that + * DNSKEY RR. + * + * The digest is calculated by concatenating the canonical form of the + * fully qualified owner name of the DNSKEY RR with the DNSKEY RDATA, + * and then applying the digest algorithm. + * + * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); + * + * "|" denotes concatenation + * + * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. + * + * The size of the digest may vary depending on the digest algorithm and + * DNSKEY RR size. As of the time of this writing, the only defined + * digest algorithm is SHA-1, which produces a 20 octet digest. + */ +static void +ds(void **state) { + text_ok_t text_ok[] = { + /* + * Invalid, empty record. + */ + TEXT_INVALID(""), + /* + * Invalid, no algorithm. + */ + TEXT_INVALID("0"), + /* + * Invalid, no digest type. + */ + TEXT_INVALID("0 0"), + /* + * Invalid, no digest. + */ + TEXT_INVALID("0 0 0"), + /* + * Valid, 1-octet digest for a reserved digest type. + */ + TEXT_VALID("0 0 0 00"), + /* + * Invalid, short SHA-1 digest. + */ + TEXT_INVALID("0 0 1 00"), + TEXT_INVALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B"), + /* + * Valid, 20-octet SHA-1 digest. + */ + TEXT_VALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B6A"), + /* + * Invalid, excessively long SHA-1 digest. + */ + TEXT_INVALID("0 0 1 4FDCE83016EDD29077621FE568F8DADDB5809B" + "6A00"), + /* + * Invalid, short SHA-256 digest. + */ + TEXT_INVALID("0 0 2 00"), + TEXT_INVALID("0 0 2 D001BD422FFDA9B745425B71DC17D007E69186" + "9BD59C5F237D9BF85434C313"), + /* + * Valid, 32-octet SHA-256 digest. + */ + TEXT_VALID_CHANGED("0 0 2 " + "D001BD422FFDA9B745425B71DC17D007E691869B" + "D59C5F237D9BF85434C3133F", + "0 0 2 " + "D001BD422FFDA9B745425B71DC17D007E691869B" + "D59C5F237D9BF854 34C3133F"), + /* + * Invalid, excessively long SHA-256 digest. + */ + TEXT_INVALID("0 0 2 D001BD422FFDA9B745425B71DC17D007E69186" + "9BD59C5F237D9BF85434C3133F00"), + /* + * Valid, GOST is no longer supported, hence no length checks. + */ + TEXT_VALID("0 0 3 00"), + /* + * Invalid, short SHA-384 digest. + */ + TEXT_INVALID("0 0 4 00"), + TEXT_INVALID("0 0 4 AC748D6C5AA652904A8763D64B7DFFFFA98152" + "BE12128D238BEBB4814B648F5A841E15CAA2DE348891" + "A37A699F65E5"), + /* + * Valid, 48-octet SHA-384 digest. + */ + TEXT_VALID_CHANGED("0 0 4 " + "AC748D6C5AA652904A8763D64B7DFFFFA98152BE" + "12128D238BEBB4814B648F5A841E15CAA2DE348891A" + "37A" + "699F65E54D", + "0 0 4 " + "AC748D6C5AA652904A8763D64B7DFFFFA98152BE" + "12128D238BEBB481 " + "4B648F5A841E15CAA2DE348891A37A" + "699F65E54D"), + /* + * Invalid, excessively long SHA-384 digest. + */ + TEXT_INVALID("0 0 4 AC748D6C5AA652904A8763D64B7DFFFFA98152" + "BE12128D238BEBB4814B648F5A841E15CAA2DE348891" + "A37A699F65E54D00"), + /* + * Valid, 1-octet digest for an unassigned digest type. + */ + TEXT_VALID("0 0 5 00"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { + /* + * Invalid, truncated key tag. + */ + WIRE_INVALID(0x00), + /* + * Invalid, no algorithm. + */ + WIRE_INVALID(0x00, 0x00), + /* + * Invalid, no digest type. + */ + WIRE_INVALID(0x00, 0x00, 0x00), + /* + * Invalid, no digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00), + /* + * Valid, 1-octet digest for a reserved digest type. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Invalid, short SHA-1 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30, + 0x16, 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5, + 0x68, 0xF8, 0xDA, 0xDD, 0xB5, 0x80, 0x9B), + /* + * Valid, 20-octet SHA-1 digest. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30, 0x16, + 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5, 0x68, 0xF8, + 0xDA, 0xDD, 0xB5, 0x80, 0x9B, 0x6A), + /* + * Invalid, excessively long SHA-1 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x01, 0x4F, 0xDC, 0xE8, 0x30, + 0x16, 0xED, 0xD2, 0x90, 0x77, 0x62, 0x1F, 0xE5, + 0x68, 0xF8, 0xDA, 0xDD, 0xB5, 0x80, 0x9B, 0x6A, + 0x00), + /* + * Invalid, short SHA-256 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42, + 0x2F, 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71, + 0xDC, 0x17, 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B, + 0xD5, 0x9C, 0x5F, 0x23, 0x7D, 0x9B, 0xF8, 0x54, + 0x34, 0xC3, 0x13), + /* + * Valid, 32-octet SHA-256 digest. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42, 0x2F, + 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71, 0xDC, 0x17, + 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B, 0xD5, 0x9C, 0x5F, + 0x23, 0x7D, 0x9B, 0xF8, 0x54, 0x34, 0xC3, 0x13, + 0x3F), + /* + * Invalid, excessively long SHA-256 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x02, 0xD0, 0x01, 0xBD, 0x42, + 0x2F, 0xFD, 0xA9, 0xB7, 0x45, 0x42, 0x5B, 0x71, + 0xDC, 0x17, 0xD0, 0x07, 0xE6, 0x91, 0x86, 0x9B, + 0xD5, 0x9C, 0x5F, 0x23, 0x7D, 0x9B, 0xF8, 0x54, + 0x34, 0xC3, 0x13, 0x3F, 0x00), + /* + * Valid, GOST is no longer supported, hence no length checks. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x03, 0x00), + /* + * Invalid, short SHA-384 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C, + 0x5A, 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6, + 0x4B, 0x7D, 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE, + 0x12, 0x12, 0x8D, 0x23, 0x8B, 0xEB, 0xB4, 0x81, + 0x4B, 0x64, 0x8F, 0x5A, 0x84, 0x1E, 0x15, 0xCA, + 0xA2, 0xDE, 0x34, 0x88, 0x91, 0xA3, 0x7A, 0x69, + 0x9F, 0x65, 0xE5), + /* + * Valid, 48-octet SHA-384 digest. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C, 0x5A, + 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6, 0x4B, 0x7D, + 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE, 0x12, 0x12, 0x8D, + 0x23, 0x8B, 0xEB, 0xB4, 0x81, 0x4B, 0x64, 0x8F, 0x5A, + 0x84, 0x1E, 0x15, 0xCA, 0xA2, 0xDE, 0x34, 0x88, 0x91, + 0xA3, 0x7A, 0x69, 0x9F, 0x65, 0xE5, 0x4D), + /* + * Invalid, excessively long SHA-384 digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x04, 0xAC, 0x74, 0x8D, 0x6C, + 0x5A, 0xA6, 0x52, 0x90, 0x4A, 0x87, 0x63, 0xD6, + 0x4B, 0x7D, 0xFF, 0xFF, 0xA9, 0x81, 0x52, 0xBE, + 0x12, 0x12, 0x8D, 0x23, 0x8B, 0xEB, 0xB4, 0x81, + 0x4B, 0x64, 0x8F, 0x5A, 0x84, 0x1E, 0x15, 0xCA, + 0xA2, 0xDE, 0x34, 0x88, 0x91, 0xA3, 0x7A, 0x69, + 0x9F, 0x65, 0xE5, 0x4D, 0x00), + WIRE_VALID(0x00, 0x00, 0x04, 0x00, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_ds, sizeof(dns_rdata_ds_t)); +} + +/* + * EDNS Client Subnet tests. + * + * RFC 7871: + * + * 6. Option Format + * + * This protocol uses an EDNS0 [RFC6891] option to include client + * address information in DNS messages. The option is structured as + * follows: + * + * +0 (MSB) +1 (LSB) + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0: | OPTION-CODE | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2: | OPTION-LENGTH | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 4: | FAMILY | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 6: | SOURCE PREFIX-LENGTH | SCOPE PREFIX-LENGTH | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 8: | ADDRESS... / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * o (Defined in [RFC6891]) OPTION-CODE, 2 octets, for ECS is 8 (0x00 + * 0x08). + * + * o (Defined in [RFC6891]) OPTION-LENGTH, 2 octets, contains the + * length of the payload (everything after OPTION-LENGTH) in octets. + * + * o FAMILY, 2 octets, indicates the family of the address contained in + * the option, using address family codes as assigned by IANA in + * Address Family Numbers [Address_Family_Numbers]. + * + * The format of the address part depends on the value of FAMILY. This + * document only defines the format for FAMILY 1 (IPv4) and FAMILY 2 + * (IPv6), which are as follows: + * + * o SOURCE PREFIX-LENGTH, an unsigned octet representing the leftmost + * number of significant bits of ADDRESS to be used for the lookup. + * In responses, it mirrors the same value as in the queries. + * + * o SCOPE PREFIX-LENGTH, an unsigned octet representing the leftmost + * number of significant bits of ADDRESS that the response covers. + * In queries, it MUST be set to 0. + * + * o ADDRESS, variable number of octets, contains either an IPv4 or + * IPv6 address, depending on FAMILY, which MUST be truncated to the + * number of bits indicated by the SOURCE PREFIX-LENGTH field, + * padding with 0 bits to pad to the end of the last octet needed. + * + * o A server receiving an ECS option that uses either too few or too + * many ADDRESS octets, or that has non-zero ADDRESS bits set beyond + * SOURCE PREFIX-LENGTH, SHOULD return FORMERR to reject the packet, + * as a signal to the software developer making the request to fix + * their implementation. + * + * All fields are in network byte order ("big-endian", per [RFC1700], + * Data Notation). + */ +static void +edns_client_subnet(void **state) { + wire_ok_t wire_ok[] = { + /* + * Option code with no content. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 0x00), + /* + * Option code family 0, source 0, scope 0. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00), + /* + * Option code family 1 (IPv4), source 0, scope 0. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00), + /* + * Option code family 2 (IPv6) , source 0, scope 0. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00), + /* + * Extra octet. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00), + /* + * Source too long for IPv4. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 8, 0x00, 0x01, 33, 0x00, 0x00, + 0x00, 0x00, 0x00), + /* + * Source too long for IPv6. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 20, 0x00, 0x02, 129, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Scope too long for IPv4. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 8, 0x00, 0x01, 0x00, 33, 0x00, + 0x00, 0x00, 0x00), + /* + * Scope too long for IPv6. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 20, 0x00, 0x02, 0x00, 129, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * When family=0, source and scope should be 0. + */ + WIRE_VALID(0x00, 0x08, 0x00, 4, 0x00, 0x00, 0x00, 0x00), + /* + * When family=0, source and scope should be 0. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 5, 0x00, 0x00, 0x01, 0x00, 0x00), + /* + * When family=0, source and scope should be 0. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 5, 0x00, 0x00, 0x00, 0x01, 0x00), + /* + * Length too short for source IPv4. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 7, 0x00, 0x01, 32, 0x00, 0x00, + 0x00, 0x00), + /* + * Length too short for source IPv6. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 19, 0x00, 0x02, 128, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(NULL, wire_ok, NULL, true, dns_rdataclass_in, + dns_rdatatype_opt, sizeof(dns_rdata_opt_t)); +} + +/* + * http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + * + * The RDATA portion of both the NIMLOC and EID records contains + * uninterpreted binary data. The representation in the text master file + * is an even number of hex characters (0 to 9, a to f), case is not + * significant. For readability, whitespace may be included in the value + * field and should be ignored when reading a master file. + */ +static void +eid(void **state) { + text_ok_t text_ok[] = { TEXT_VALID("AABBCC"), + TEXT_VALID_CHANGED("AA bb cc", "AABBCC"), + TEXT_INVALID("aab"), + /* + * Sentinel. + */ + TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { WIRE_VALID(0x00), WIRE_VALID(0xAA, 0xBB, 0xCC), + /* + * Sentinel. + */ + WIRE_SENTINEL() }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_eid, sizeof(dns_rdata_in_eid_t)); +} + +/* + * test that an oversized HIP record will be rejected + */ +static void +hip(void **state) { + text_ok_t text_ok[] = { + /* RFC 8005 examples. */ + TEXT_VALID_LOOP(0, "2 200100107B1A74DF365639CC39F1D578 " + "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI" + "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW" + "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b" + "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx" + "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D"), + TEXT_VALID_LOOP(1, "2 200100107B1A74DF365639CC39F1D578 " + "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI" + "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW" + "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b" + "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx" + "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D " + "rvs1.example.com."), + TEXT_VALID_LOOP(2, "2 200100107B1A74DF365639CC39F1D578 " + "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cI" + "vM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbW" + "Iy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+b" + "SRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWx" + "Z48AWkskmdHaVDP4BcelrTI3rMXdXF5D " + "rvs1.example.com. rvs2.example.com."), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + unsigned char hipwire[DNS_RDATA_MAXLENGTH] = { 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x04, 0x41, + 0x42, 0x43, 0x44, 0x00 }; + unsigned char buf[1024 * 1024]; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + size_t i; + + UNUSED(state); + + /* + * Fill the rest of input buffer with compression pointers. + */ + for (i = 12; i < sizeof(hipwire) - 2; i += 2) { + hipwire[i] = 0xc0; + hipwire[i + 1] = 0x06; + } + + result = wire_to_rdata(hipwire, sizeof(hipwire), dns_rdataclass_in, + dns_rdatatype_hip, buf, sizeof(buf), &rdata); + assert_int_equal(result, DNS_R_FORMERR); + check_text_ok(text_ok, dns_rdataclass_in, dns_rdatatype_hip, + sizeof(dns_rdata_hip_t)); +} + +/* + * ISDN tests. + * + * RFC 1183: + * + * 3.2. The ISDN RR + * + * The ISDN RR is defined with mnemonic ISDN and type code 20 (decimal). + * + * An ISDN (Integrated Service Digital Network) number is simply a + * telephone number. The intent of the members of the CCITT is to + * upgrade all telephone and data network service to a common service. + * + * The numbering plan (E.163/E.164) is the same as the familiar + * international plan for POTS (an un-official acronym, meaning Plain + * Old Telephone Service). In E.166, CCITT says "An E.163/E.164 + * telephony subscriber may become an ISDN subscriber without a number + * change." + * + * ISDN has the following format: + * + * ISDN + * + * The field is required; is optional. + * + * identifies the ISDN number of and DDI (Direct + * Dial In) if any, as defined by E.164 [8] and E.163 [7], the ISDN and + * PSTN (Public Switched Telephone Network) numbering plan. E.163 + * defines the country codes, and E.164 the form of the addresses. Its + * format in master files is a syntactically + * identical to that used in TXT and HINFO. + * + * specifies the subaddress (SA). The format of in master + * files is a syntactically identical to that used in + * TXT and HINFO. + * + * The format of ISDN is class insensitive. ISDN RRs cause no + * additional section processing. + * + * The is a string of characters, normally decimal + * digits, beginning with the E.163 country code and ending with the DDI + * if any. Note that ISDN, in Q.931, permits any IA5 character in the + * general case. + * + * The is a string of hexadecimal digits. For digits 0-9, the + * concrete encoding in the Q.931 call setup information element is + * identical to BCD. + * + * For example: + * + * Relay.Prime.COM. IN ISDN 150862028003217 + * sh.Prime.COM. IN ISDN 150862028003217 004 + * + * (Note: "1" is the country code for the North American Integrated + * Numbering Area, i.e., the system of "area codes" familiar to people + * in those countries.) + * + * The RR data is the ASCII representation of the digits. It is encoded + * as one or two s, i.e., count followed by + * characters. + */ +static void +isdn(void **state) { + wire_ok_t wire_ok[] = { /* + * "". + */ + WIRE_VALID(0x00), + /* + * "\001". + */ + WIRE_VALID(0x01, 0x01), + /* + * "\001" "". + */ + WIRE_VALID(0x01, 0x01, 0x00), + /* + * "\001" "\001". + */ + WIRE_VALID(0x01, 0x01, 0x01, 0x01), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_isdn, sizeof(dns_rdata_isdn_t)); +} + +/* + * KEY tests. + */ +static void +key(void **state) { + wire_ok_t wire_ok[] = { /* + * RDATA is comprised of: + * + * - 2 octets for Flags, + * - 1 octet for Protocol, + * - 1 octet for Algorithm, + * - variable number of octets for Public Key. + * + * RFC 2535 section 3.1.2 states that if bits + * 0-1 of Flags are both set, the RR stops after + * the algorithm octet and thus its length must + * be 4 octets. In any other case, though, the + * Public Key part must not be empty. + */ + WIRE_INVALID(0x00), + WIRE_INVALID(0x00, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00), + WIRE_VALID(0xc0, 0x00, 0x00, 0x00), + WIRE_INVALID(0xc0, 0x00, 0x00, 0x00, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00, 0x00), + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00), + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_key, sizeof(dns_rdata_key_t)); +} + +/* + * LOC tests. + */ +static void +loc(void **state) { + text_ok_t text_ok[] = { + TEXT_VALID_CHANGED("0 N 0 E 0", "0 0 0.000 N 0 0 0.000 E 0.00m " + "1m 10000m 10m"), + TEXT_VALID_CHANGED("0 S 0 W 0", "0 0 0.000 N 0 0 0.000 E 0.00m " + "1m 10000m 10m"), + TEXT_VALID_CHANGED("0 0 N 0 0 E 0", "0 0 0.000 N 0 0 0.000 E " + "0.00m 1m 10000m 10m"), + TEXT_VALID_CHANGED("0 0 0 N 0 0 0 E 0", + "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m " + "10m"), + TEXT_VALID_CHANGED("0 0 0 N 0 0 0 E 0", + "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m " + "10m"), + TEXT_VALID_CHANGED("0 0 0. N 0 0 0. E 0", + "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m " + "10m"), + TEXT_VALID_CHANGED("0 0 .0 N 0 0 .0 E 0", + "0 0 0.000 N 0 0 0.000 E 0.00m 1m 10000m " + "10m"), + TEXT_INVALID("0 North 0 East 0"), + TEXT_INVALID("0 South 0 West 0"), + TEXT_INVALID("0 0 . N 0 0 0. E 0"), + TEXT_INVALID("0 0 0. N 0 0 . E 0"), + TEXT_INVALID("0 0 0. N 0 0 0. E m"), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 ."), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 m"), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 ."), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 m"), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 0 ."), + TEXT_INVALID("0 0 0. N 0 0 0. E 0 0 0 m"), + TEXT_VALID_CHANGED("90 N 180 E 0", "90 0 0.000 N 180 0 0.000 E " + "0.00m 1m 10000m 10m"), + TEXT_INVALID("90 1 N 180 E 0"), + TEXT_INVALID("90 0 1 N 180 E 0"), + TEXT_INVALID("90 N 180 1 E 0"), + TEXT_INVALID("90 N 180 0 1 E 0"), + TEXT_VALID_CHANGED("90 S 180 W 0", "90 0 0.000 S 180 0 0.000 W " + "0.00m 1m 10000m 10m"), + TEXT_INVALID("90 1 S 180 W 0"), + TEXT_INVALID("90 0 1 S 180 W 0"), + TEXT_INVALID("90 S 180 1 W 0"), + TEXT_INVALID("90 S 180 0 1 W 0"), + TEXT_INVALID("0 0 0.000 E 0 0 0.000 E -0.95m 1m 10000m 10m"), + TEXT_VALID("0 0 0.000 N 0 0 0.000 E -0.95m 1m 10000m 10m"), + TEXT_VALID("0 0 0.000 N 0 0 0.000 E -0.05m 1m 10000m 10m"), + TEXT_VALID("0 0 0.000 N 0 0 0.000 E -100000.00m 1m 10000m 10m"), + TEXT_VALID("0 0 0.000 N 0 0 0.000 E 42849672.95m 1m 10000m " + "10m"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, 0, NULL, false, dns_rdataclass_in, + dns_rdatatype_loc, sizeof(dns_rdata_loc_t)); +} + +/* + * http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt + * + * The RDATA portion of both the NIMLOC and EID records contains + * uninterpreted binary data. The representation in the text master file + * is an even number of hex characters (0 to 9, a to f), case is not + * significant. For readability, whitespace may be included in the value + * field and should be ignored when reading a master file. + */ +static void +nimloc(void **state) { + text_ok_t text_ok[] = { TEXT_VALID("AABBCC"), + TEXT_VALID_CHANGED("AA bb cc", "AABBCC"), + TEXT_INVALID("aab"), + /* + * Sentinel. + */ + TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { WIRE_VALID(0x00), WIRE_VALID(0xAA, 0xBB, 0xCC), + /* + * Sentinel. + */ + WIRE_SENTINEL() }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_nimloc, sizeof(dns_rdata_in_nimloc_t)); +} + +/* + * NSEC tests. + * + * RFC 4034: + * + * 4.1. NSEC RDATA Wire Format + * + * The RDATA of the NSEC RR is as shown below: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / Next Domain Name / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * / Type Bit Maps / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 4.1.1. The Next Domain Name Field + * + * The Next Domain field contains the next owner name (in the canonical + * ordering of the zone) that has authoritative data or contains a + * delegation point NS RRset; see Section 6.1 for an explanation of + * canonical ordering. The value of the Next Domain Name field in the + * last NSEC record in the zone is the name of the zone apex (the owner + * name of the zone's SOA RR). This indicates that the owner name of + * the NSEC RR is the last name in the canonical ordering of the zone. + * + * A sender MUST NOT use DNS name compression on the Next Domain Name + * field when transmitting an NSEC RR. + * + * Owner names of RRsets for which the given zone is not authoritative + * (such as glue records) MUST NOT be listed in the Next Domain Name + * unless at least one authoritative RRset exists at the same owner + * name. + * + * 4.1.2. The Type Bit Maps Field + * + * The Type Bit Maps field identifies the RRset types that exist at the + * NSEC RR's owner name. + * + * The RR type space is split into 256 window blocks, each representing + * the low-order 8 bits of the 16-bit RR type space. Each block that + * has at least one active RR type is encoded using a single octet + * window number (from 0 to 255), a single octet bitmap length (from 1 + * to 32) indicating the number of octets used for the window block's + * bitmap, and up to 32 octets (256 bits) of bitmap. + * + * Blocks are present in the NSEC RR RDATA in increasing numerical + * order. + * + * Type Bit Maps Field = ( Window Block # | Bitmap Length | Bitmap )+ + * + * where "|" denotes concatenation. + * + * Each bitmap encodes the low-order 8 bits of RR types within the + * window block, in network bit order. The first bit is bit 0. For + * window block 0, bit 1 corresponds to RR type 1 (A), bit 2 corresponds + * to RR type 2 (NS), and so forth. For window block 1, bit 1 + * corresponds to RR type 257, and bit 2 to RR type 258. If a bit is + * set, it indicates that an RRset of that type is present for the NSEC + * RR's owner name. If a bit is clear, it indicates that no RRset of + * that type is present for the NSEC RR's owner name. + * + * Bits representing pseudo-types MUST be clear, as they do not appear + * in zone data. If encountered, they MUST be ignored upon being read. + */ +static void +nsec(void **state) { + text_ok_t text_ok[] = { TEXT_INVALID(""), TEXT_INVALID("."), + TEXT_VALID(". RRSIG"), TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { WIRE_INVALID(0x00), WIRE_INVALID(0x00, 0x00), + WIRE_INVALID(0x00, 0x00, 0x00), + WIRE_VALID(0x00, 0x00, 0x01, 0x02), + WIRE_SENTINEL() }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_nsec, sizeof(dns_rdata_nsec_t)); +} + +/* + * NSEC3 tests. + * + * RFC 5155. + */ +static void +nsec3(void **state) { + text_ok_t text_ok[] = { TEXT_INVALID(""), + TEXT_INVALID("."), + TEXT_INVALID(". RRSIG"), + TEXT_INVALID("1 0 10 76931F"), + TEXT_INVALID("1 0 10 76931F " + "IMQ912BREQP1POLAH3RMONG&" + "UED541AS"), + TEXT_INVALID("1 0 10 76931F " + "IMQ912BREQP1POLAH3RMONGAUED541AS " + "A RRSIG BADTYPE"), + TEXT_VALID("1 0 10 76931F " + "AJHVGTICN6K0VDA53GCHFMT219SRRQLM A " + "RRSIG"), + TEXT_VALID("1 0 10 76931F " + "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"), + TEXT_VALID("1 0 10 - " + "AJHVGTICN6K0VDA53GCHFMT219SRRQLM"), + TEXT_SENTINEL() }; + + UNUSED(state); + + check_rdata(text_ok, NULL, NULL, false, dns_rdataclass_in, + dns_rdatatype_nsec3, sizeof(dns_rdata_nsec3_t)); +} + +/* NXT RDATA manipulations */ +static void +nxt(void **state) { + compare_ok_t compare_ok[] = { + COMPARE("a. A SIG", "a. A SIG", 0), + /* + * Records that differ only in the case of the next + * name should be equal. + */ + COMPARE("A. A SIG", "a. A SIG", 0), + /* + * Sorting on name field. + */ + COMPARE("A. A SIG", "b. A SIG", -1), + COMPARE("b. A SIG", "A. A SIG", 1), + /* bit map differs */ + COMPARE("b. A SIG", "b. A AAAA SIG", -1), + /* order of bit map does not matter */ + COMPARE("b. A SIG AAAA", "b. A AAAA SIG", 0), COMPARE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(NULL, NULL, compare_ok, false, dns_rdataclass_in, + dns_rdatatype_nxt, sizeof(dns_rdata_nxt_t)); +} + +static void +rkey(void **state) { + text_ok_t text_ok[] = { /* + * Valid, flags set to 0 and a key is present. + */ + TEXT_VALID("0 0 0 aaaa"), + /* + * Invalid, non-zero flags. + */ + TEXT_INVALID("1 0 0 aaaa"), + TEXT_INVALID("65535 0 0 aaaa"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { /* + * Valid, flags set to 0 and a key is present. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Invalid, non-zero flags. + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x00), + WIRE_INVALID(0xff, 0xff, 0x00, 0x00, 0x00), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + key_required(state, dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t)); + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t)); +} + +/* SSHFP RDATA manipulations */ +static void +sshfp(void **state) { + text_ok_t text_ok[] = { TEXT_INVALID(""), /* too short */ + TEXT_INVALID("0"), /* reserved, too short */ + TEXT_VALID("0 0"), /* no finger print */ + TEXT_VALID("0 0 AA"), /* reserved */ + TEXT_INVALID("0 1 AA"), /* too short SHA 1 + * digest */ + TEXT_INVALID("0 2 AA"), /* too short SHA 256 + * digest */ + TEXT_VALID("0 3 AA"), /* unknown finger print + * type */ + /* good length SHA 1 digest */ + TEXT_VALID("1 1 " + "00112233445566778899AABBCCDDEEFF171" + "81920"), + /* good length SHA 256 digest */ + TEXT_VALID("4 2 " + "A87F1B687AC0E57D2A081A2F282672334D9" + "0ED316D2B818CA9580EA3 84D92401"), + /* + * totext splits the fingerprint into chunks and + * emits uppercase hex. + */ + TEXT_VALID_CHANGED("1 2 " + "00112233445566778899aabbccd" + "deeff " + "00112233445566778899AABBCCD" + "DEEFF", + "1 2 " + "00112233445566778899AABBCCD" + "DEEFF" + "00112233445566778899AABB " + "CCDDEEFF"), + TEXT_SENTINEL() }; + wire_ok_t wire_ok[] = { + WIRE_INVALID(0x00), /* reserved too short */ + WIRE_VALID(0x00, 0x00), /* reserved no finger print */ + WIRE_VALID(0x00, 0x00, 0x00), /* reserved */ + + /* too short SHA 1 digests */ + WIRE_INVALID(0x00, 0x01), WIRE_INVALID(0x00, 0x01, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0x17, 0x18, 0x19), + /* good length SHA 1 digest */ + WIRE_VALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x17, 0x18, 0x19, 0x20), + /* too long SHA 1 digest */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21), + /* too short SHA 256 digests */ + WIRE_INVALID(0x00, 0x02), WIRE_INVALID(0x00, 0x02, 0x00), + WIRE_INVALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, + 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, + 0x31), + /* good length SHA 256 digest */ + WIRE_VALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32), + /* too long SHA 256 digest */ + WIRE_INVALID(0x00, 0x02, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, + 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, + 0x31, 0x32, 0x33), + /* unknown digest, * no fingerprint */ + WIRE_VALID(0x00, 0x03), WIRE_VALID(0x00, 0x03, 0x00), /* unknown + * digest + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_sshfp, sizeof(dns_rdata_sshfp_t)); +} + +/* + * WKS tests. + * + * RFC 1035: + * + * 3.4.2. WKS RDATA format + * + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | ADDRESS | + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * | PROTOCOL | | + * +--+--+--+--+--+--+--+--+ | + * | | + * / / + * / / + * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * + * where: + * + * ADDRESS An 32 bit Internet address + * + * PROTOCOL An 8 bit IP protocol number + * + * A variable length bit map. The bit map must be a + * multiple of 8 bits long. + * + * The WKS record is used to describe the well known services supported by + * a particular protocol on a particular internet address. The PROTOCOL + * field specifies an IP protocol number, and the bit map has one bit per + * port of the specified protocol. The first bit corresponds to port 0, + * the second to port 1, etc. If the bit map does not include a bit for a + * protocol of interest, that bit is assumed zero. The appropriate values + * and mnemonics for ports and protocols are specified in [RFC-1010]. + * + * For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port + * 25 (SMTP). If this bit is set, a SMTP server should be listening on TCP + * port 25; if zero, SMTP service is not supported on the specified + * address. + */ +static void +wks(void **state) { + text_ok_t text_ok[] = { /* + * Valid, IPv4 address in dotted-quad form. + */ + TEXT_VALID("127.0.0.1 6"), + /* + * Invalid, IPv4 address not in dotted-quad + * form. + */ + TEXT_INVALID("127.1 6"), + /* + * Sentinel. + */ + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { /* + * Too short. + */ + WIRE_INVALID(0x00, 0x08, 0x00, 0x00), + /* + * Minimal TCP. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x00, 6), + /* + * Minimal UDP. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x00, 17), + /* + * Minimal other. + */ + WIRE_VALID(0x00, 0x08, 0x00, 0x00, 1), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_wks, sizeof(dns_rdata_in_wks_t)); +} + +static void +https_svcb(void **state) { + /* + * Known keys: mandatory, apln, no-default-alpn, port, + * ipv4hint, port, ipv6hint, dohpath. + */ + text_ok_t text_ok[] = { + /* unknown key invalid */ + TEXT_INVALID("1 . unknown="), + /* no domain */ + TEXT_INVALID("0"), + /* minimal record */ + TEXT_VALID_LOOP(0, "0 ."), + /* Alias form requires SvcFieldValue to be empty */ + TEXT_INVALID("0 . alpn=\"h2\""), + /* no "key" prefix */ + TEXT_INVALID("2 svc.example.net. 0=\"2222\""), + /* no key value */ + TEXT_INVALID("2 svc.example.net. key"), + /* no key value */ + TEXT_INVALID("2 svc.example.net. key=\"2222\""), + /* zero pad invalid */ + TEXT_INVALID("2 svc.example.net. key07=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key8=2222", + "2 svc.example.net. key8=\"2222\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2", + "2 svc.example.net. alpn=\"h2\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h3", + "2 svc.example.net. alpn=\"h3\""), + /* alpn has 2 sub field "h2" and "h3" */ + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2,h3", + "2 svc.example.net. alpn=\"h2,h3\""), + /* apln has 2 sub fields "h1,h2" and "h3" (comma escaped) */ + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h1\\\\,h2,h3", + "2 svc.example.net. alpn=\"h1\\\\,h2,h3\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. port=50"), + /* no-default-alpn, alpn is required */ + TEXT_INVALID("2 svc.example.net. no-default-alpn"), + /* no-default-alpn with alpn present */ + TEXT_VALID_LOOPCHG( + 2, "2 svc.example.net. no-default-alpn alpn=h2", + "2 svc.example.net. alpn=\"h2\" no-default-alpn"), + /* empty hint */ + TEXT_INVALID("2 svc.example.net. ipv4hint="), + TEXT_VALID_LOOP(1, "2 svc.example.net. " + "ipv4hint=10.50.0.1,10.50.0.2"), + /* empty hint */ + TEXT_INVALID("2 svc.example.net. ipv6hint="), + TEXT_VALID_LOOP(1, "2 svc.example.net. ipv6hint=::1,2002::1"), + TEXT_VALID_LOOP(1, "2 svc.example.net. ech=abcdefghijkl"), + /* bad base64 */ + TEXT_INVALID("2 svc.example.net. ech=abcdefghijklm"), + TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""), + /* Out of key order on input (alpn == key1). */ + TEXT_VALID_LOOPCHG(2, + "2 svc.example.net. key8=\"2222\" alpn=h2", + "2 svc.example.net. alpn=\"h2\" " + "key8=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key65535=\"2222\""), + TEXT_INVALID("2 svc.example.net. key65536=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key10"), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key11=", + "2 svc.example.net. key11"), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key12=\"\"", + "2 svc.example.net. key12"), + /* empty alpn-id sub fields */ + TEXT_INVALID("2 svc.example.net. alpn"), + TEXT_INVALID("2 svc.example.net. alpn="), + TEXT_INVALID("2 svc.example.net. alpn=,h1"), + TEXT_INVALID("2 svc.example.net. alpn=h1,"), + TEXT_INVALID("2 svc.example.net. alpn=h1,,h2"), + /* mandatory */ + TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=alpn " + "alpn=\"h2\""), + TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=alpn,port " + "alpn=\"h2\" port=443"), + TEXT_VALID_LOOPCHG(3, + "2 svc.example.net. mandatory=port,alpn " + "alpn=\"h2\" port=443", + "2 svc.example.net. mandatory=alpn,port " + "alpn=\"h2\" port=443"), + TEXT_INVALID("2 svc.example.net. mandatory=mandatory"), + TEXT_INVALID("2 svc.example.net. mandatory=port"), + TEXT_INVALID("2 svc.example.net. mandatory=,port port=433"), + TEXT_INVALID("2 svc.example.net. mandatory=port, port=433"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=alpn,,port alpn=h2 port=433"), + /* mandatory w/ unknown key values */ + TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=key8 key8"), + TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=key8,key9 " + "key8 key9"), + TEXT_VALID_LOOPCHG( + 3, "2 svc.example.net. mandatory=key9,key8 key8 key9", + "2 svc.example.net. mandatory=key8,key9 key8 key9"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=key8,key8"), + TEXT_INVALID("2 svc.example.net. mandatory=,key8"), + TEXT_INVALID("2 svc.example.net. mandatory=key8,"), + TEXT_INVALID("2 svc.example.net. " + "mandatory=key8,,key8"), + /* Invalid test vectors */ + TEXT_INVALID("1 foo.example.com. ( key123=abc key123=def )"), + TEXT_INVALID("1 foo.example.com. mandatory"), + TEXT_INVALID("1 foo.example.com. alpn"), + TEXT_INVALID("1 foo.example.com. port"), + TEXT_INVALID("1 foo.example.com. ipv4hint"), + TEXT_INVALID("1 foo.example.com. ipv6hint"), + TEXT_INVALID("1 foo.example.com. no-default-alpn=abc"), + TEXT_INVALID("1 foo.example.com. mandatory=key123"), + TEXT_INVALID("1 foo.example.com. mandatory=mandatory"), + TEXT_INVALID("1 foo.example.com. ( mandatory=key123,key123 " + "key123=abc)"), + /* dohpath tests */ + TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{?dns}", + "1 example.net. key7=\"/{?dns}\""), + TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/some/path{?dns}", + "1 example.net. key7=\"/some/path{?dns}\""), + TEXT_INVALID("1 example.com. dohpath=no-slash"), + TEXT_INVALID("1 example.com. dohpath=/{?notdns}"), + TEXT_INVALID("1 example.com. dohpath=/notvariable"), + TEXT_SENTINEL() + + }; + wire_ok_t wire_ok[] = { + /* + * Too short + */ + WIRE_INVALID(0x00, 0x00), + /* + * Minimal length record. + */ + WIRE_VALID(0x00, 0x00, 0x00), + /* + * Alias with non-empty SvcFieldValue (key7=""). + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00), + /* + * Bad key7= length (longer than rdata). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x01), + /* + * Port (0x03) too small (zero and one octets). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00), + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00), + /* Valid port */ + WIRE_VALID_LOOP(1, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x02, + 0x00, 0x00), + /* + * Port (0x03) too big (three octets). + */ + WIRE_INVALID(0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x00), + /* + * Duplicate keys. + */ + WIRE_INVALID(0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Out of order keys. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Empty of mandatory key list. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * "mandatory=mandatory" is invalid + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00), + /* + * Out of order mandatory key list. + */ + WIRE_INVALID(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x80, 0x00, 0x71, 0x00, 0x71, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00), + /* + * Alpn(0x00 0x01) (length 0x00 0x09) "h1,h2" + "h3" + */ + WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09, + 5, 'h', '1', ',', 'h', '2', 2, 'h', '3'), + /* + * Alpn(0x00 0x01) (length 0x00 0x09) "h1\h2" + "h3" + */ + WIRE_VALID_LOOP(0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09, + 5, 'h', '1', '\\', 'h', '2', 2, 'h', '3'), + /* + * no-default-alpn (0x00 0x02) without alpn, alpn is required. + */ + WIRE_INVALID(0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00), + /* + * Alpn(0x00 0x01) with zero length elements is invalid + */ + WIRE_INVALID(0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00), + WIRE_SENTINEL() + }; + /* Test vectors from RFCXXXX */ + textvsunknown_t textvsunknown[] = { + /* AliasForm */ + { "0 foo.example.com", "\\# 19 ( 00 00 03 66 6f 6f 07 65 78 61 " + "6d 70 6c 65 03 63 6f 6d 00)" }, + /* ServiceForm */ + { "1 .", "\\# 3 ( 00 01 00)" }, + /* Port example */ + { "16 foo.example.com port=53", + "\\# 25 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f " + "6d 00 00 03 00 02 00 35 )" }, + /* Unregistered keys with unquoted value. */ + { "1 foo.example.com key667=hello", + "\\# 28 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f " + "6d 00 02 9b 00 05 68 65 6c 6c 6f )" }, + /* + * Quoted decimal-escaped character. + * 1 foo.example.com key667="hello\210qoo" + */ + { "1 foo.example.com key667=\"hello\\210qoo\"", + "\\# 32 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f " + "6d 00 02 9b 00 09 68 65 6c 6c 6f d2 71 6f 6f )" }, + /* + * IPv6 hints example, quoted. + * 1 foo.example.com ipv6hint="2001:db8::1,2001:db8::53:1" + */ + { "1 foo.example.com ipv6hint=\"2001:db8::1,2001:db8::53:1\"", + "\\# 55 ( 00 01 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f " + "6d 00 00 06 00 20 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 " + "00 01 20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01 )" }, + /* SvcParamValues and mandatory out of order. */ + { "16 foo.example.org alpn=h2,h3-19 mandatory=ipv4hint,alpn " + "ipv4hint=192.0.2.1", + "\\# 48 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 " + "67 00 00 00 00 04 00 01 00 04 00 01 00 09 02 68 32 05 68 33 " + "2d 31 39 00 04 00 04 c0 00 02 01 )" }, + /* + * Quoted ALPN with escaped comma and backslash. + * 16 foo.example.org alpn="f\\\\oo\\,bar,h2" + */ + { "16 foo.example.org alpn=\"f\\\\\\\\oo\\\\,bar,h2\"", + "\\# 35 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 " + "67 00 00 01 00 0c 08 66 5c 6f 6f 2c 62 61 72 02 68 32 )" }, + /* + * Unquoted ALPN with escaped comma and backslash. + * 16 foo.example.org alpn=f\\\092oo\092,bar,h2 + */ + { "16 foo.example.org alpn=f\\\\\\092oo\\092,bar,h2", + "\\# 35 ( 00 10 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 " + "67 00 00 01 00 0c 08 66 5c 6f 6f 2c 62 61 72 02 68 32 )" }, + { NULL, NULL } + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_svcb, sizeof(dns_rdata_in_svcb_t)); + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_https, sizeof(dns_rdata_in_https_t)); + + check_textvsunknown(textvsunknown, dns_rdataclass_in, + dns_rdatatype_svcb); + check_textvsunknown(textvsunknown, dns_rdataclass_in, + dns_rdatatype_https); +} + +/* + * ZONEMD tests. + * + * Excerpted from RFC 8976: + * + * The ZONEMD RDATA wire format is encoded as follows: + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Serial | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Scheme |Hash Algorithm | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | Digest | + * / / + * / / + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 2.2.1. The Serial Field + * + * The Serial field is a 32-bit unsigned integer in network byte order. + * It is the serial number from the zone's SOA record ([RFC1035], + * Section 3.3.13) for which the zone digest was generated. + * + * It is included here to clearly bind the ZONEMD RR to a particular + * version of the zone's content. Without the serial number, a stand- + * alone ZONEMD digest has no obvious association to any particular + * instance of a zone. + * + * 2.2.2. The Scheme Field + * + * The Scheme field is an 8-bit unsigned integer that identifies the + * methods by which data is collated and presented as input to the + * hashing function. + * + * Herein, SIMPLE, with Scheme value 1, is the only standardized Scheme + * defined for ZONEMD records and it MUST be supported by + * implementations. The "ZONEMD Schemes" registry is further described + * in Section 5. + * + * Scheme values 240-254 are allocated for Private Use. + * + * 2.2.3. The Hash Algorithm Field + * + * The Hash Algorithm field is an 8-bit unsigned integer that identifies + * the cryptographic hash algorithm used to construct the digest. + * + * Herein, SHA384 ([RFC6234]), with Hash Algorithm value 1, is the only + * standardized Hash Algorithm defined for ZONEMD records that MUST be + * supported by implementations. When SHA384 is used, the size of the + * Digest field is 48 octets. The result of the SHA384 digest algorithm + * MUST NOT be truncated, and the entire 48-octet digest is published in + * the ZONEMD record. + * + * SHA512 ([RFC6234]), with Hash Algorithm value 2, is also defined for + * ZONEMD records and SHOULD be supported by implementations. When + * SHA512 is used, the size of the Digest field is 64 octets. The + * result of the SHA512 digest algorithm MUST NOT be truncated, and the + * entire 64-octet digest is published in the ZONEMD record. + * + * Hash Algorithm values 240-254 are allocated for Private Use. + * + * The "ZONEMD Hash Algorithms" registry is further described in + * Section 5. + * + * 2.2.4. The Digest Field + * + * The Digest field is a variable-length sequence of octets containing + * the output of the hash algorithm. The length of the Digest field is + * determined by deducting the fixed size of the Serial, Scheme, and + * Hash Algorithm fields from the RDATA size in the ZONEMD RR header. + * + * The Digest field MUST NOT be shorter than 12 octets. Digests for the + * SHA384 and SHA512 hash algorithms specified herein are never + * truncated. Digests for future hash algorithms MAY be truncated but + * MUST NOT be truncated to a length that results in less than 96 bits + * (12 octets) of equivalent strength. + * + * Section 3 describes how to calculate the digest for a zone. + * Section 4 describes how to use the digest to verify the contents of a + * zone. + * + */ + +static void +zonemd(void **state) { + text_ok_t text_ok[] = { + TEXT_INVALID(""), + /* No digest scheme or digest type*/ + TEXT_INVALID("0"), + /* No digest type */ + TEXT_INVALID("0 0"), + /* No digest */ + TEXT_INVALID("0 0 0"), + /* No digest */ + TEXT_INVALID("99999999 0 0"), + /* No digest */ + TEXT_INVALID("2019020700 0 0"), + /* Digest too short */ + TEXT_INVALID("2019020700 1 1 DEADBEEF"), + /* Digest too short */ + TEXT_INVALID("2019020700 1 2 DEADBEEF"), + /* Digest too short */ + TEXT_INVALID("2019020700 1 3 DEADBEEFDEADBEEFDEADBE"), + /* Digest type unknown */ + TEXT_VALID("2019020700 1 3 DEADBEEFDEADBEEFDEADBEEF"), + /* Digest type max */ + TEXT_VALID("2019020700 1 255 DEADBEEFDEADBEEFDEADBEEF"), + /* Digest type too big */ + TEXT_INVALID("2019020700 0 256 DEADBEEFDEADBEEFDEADBEEF"), + /* Scheme max */ + TEXT_VALID("2019020700 255 3 DEADBEEFDEADBEEFDEADBEEF"), + /* Scheme too big */ + TEXT_INVALID("2019020700 256 3 DEADBEEFDEADBEEFDEADBEEF"), + /* SHA384 */ + TEXT_VALID("2019020700 1 1 " + "7162D2BB75C047A53DE98767C9192BEB" + "14DB01E7E2267135DAF0230A 19BA4A31" + "6AF6BF64AA5C7BAE24B2992850300509"), + /* SHA512 */ + TEXT_VALID("2019020700 1 2 " + "08CFA1115C7B948C4163A901270395EA" + "226A930CD2CBCF2FA9A5E6EB 85F37C8A" + "4E114D884E66F176EAB121CB02DB7D65" + "2E0CC4827E7A3204 F166B47E5613FD27"), + /* SHA384 too short and with private scheme */ + TEXT_INVALID("2021042801 0 1 " + "7162D2BB75C047A53DE98767C9192BEB" + "6AF6BF64AA5C7BAE24B2992850300509"), + /* SHA512 too short and with private scheme */ + TEXT_INVALID("2021042802 5 2 " + "A897B40072ECAE9E4CA3F1F227DE8F5E" + "480CDEBB16DFC64C1C349A7B5F6C71AB" + "E8A88B76EF0BA1604EC25752E946BF98"), + TEXT_SENTINEL() + }; + wire_ok_t wire_ok[] = { + /* + * Short. + */ + WIRE_INVALID(0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Short. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00), + /* + * Short 11-octet digest. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00), + /* + * Minimal, 12-octet hash for an undefined digest type. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00), + /* + * SHA-384 is defined, so we insist there be a digest of + * the expected length. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00), + /* + * 48-octet digest, valid for SHA-384. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, + 0xce), + /* + * 56-octet digest, too long for SHA-384. + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce), + /* + * 56-octet digest, too short for SHA-512 + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad), + /* + * 64-octet digest, just right for SHA-512 + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef), + /* + * 72-octet digest, too long for SHA-512 + */ + WIRE_INVALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, + 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce), + /* + * 56-octet digest, valid for an undefined digest type. + */ + WIRE_VALID(0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, + 0xef, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, + 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce), + /* + * Sentinel. + */ + WIRE_SENTINEL() + }; + + UNUSED(state); + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_zonemd, sizeof(dns_rdata_zonemd_t)); +} + +static void +atcname(void **state) { + unsigned int i; + UNUSED(state); +#define UNR "# Unexpected result from dns_rdatatype_atcname for type %u\n" + for (i = 0; i < 0xffffU; i++) { + bool tf = dns_rdatatype_atcname((dns_rdatatype_t)i); + switch (i) { + case dns_rdatatype_nsec: + case dns_rdatatype_key: + case dns_rdatatype_rrsig: + if (!tf) { + print_message(UNR, i); + } + assert_true(tf); + break; + default: + if (tf) { + print_message(UNR, i); + } + assert_false(tf); + break; + } + } +#undef UNR +} + +static void +atparent(void **state) { + unsigned int i; + UNUSED(state); +#define UNR "# Unexpected result from dns_rdatatype_atparent for type %u\n" + for (i = 0; i < 0xffffU; i++) { + bool tf = dns_rdatatype_atparent((dns_rdatatype_t)i); + switch (i) { + case dns_rdatatype_ds: + if (!tf) { + print_message(UNR, i); + } + assert_true(tf); + break; + default: + if (tf) { + print_message(UNR, i); + } + assert_false(tf); + break; + } + } +#undef UNR +} + +static void +iszonecutauth(void **state) { + unsigned int i; + UNUSED(state); +#define UNR "# Unexpected result from dns_rdatatype_iszonecutauth for type %u\n" + for (i = 0; i < 0xffffU; i++) { + bool tf = dns_rdatatype_iszonecutauth((dns_rdatatype_t)i); + switch (i) { + case dns_rdatatype_ns: + case dns_rdatatype_ds: + case dns_rdatatype_nsec: + case dns_rdatatype_key: + case dns_rdatatype_rrsig: + if (!tf) { + print_message(UNR, i); + } + assert_true(tf); + break; + default: + if (tf) { + print_message(UNR, i); + } + assert_false(tf); + break; + } + } +#undef UNR +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + /* types */ + cmocka_unit_test_setup_teardown(amtrelay, _setup, _teardown), + cmocka_unit_test_setup_teardown(apl, _setup, _teardown), + cmocka_unit_test_setup_teardown(atma, _setup, _teardown), + cmocka_unit_test_setup_teardown(cdnskey, _setup, _teardown), + cmocka_unit_test_setup_teardown(csync, _setup, _teardown), + cmocka_unit_test_setup_teardown(dnskey, _setup, _teardown), + cmocka_unit_test_setup_teardown(doa, _setup, _teardown), + cmocka_unit_test_setup_teardown(ds, _setup, _teardown), + cmocka_unit_test_setup_teardown(eid, _setup, _teardown), + cmocka_unit_test_setup_teardown(hip, _setup, _teardown), + cmocka_unit_test_setup_teardown(https_svcb, _setup, _teardown), + cmocka_unit_test_setup_teardown(isdn, _setup, _teardown), + cmocka_unit_test_setup_teardown(key, _setup, _teardown), + cmocka_unit_test_setup_teardown(loc, _setup, _teardown), + cmocka_unit_test_setup_teardown(nimloc, _setup, _teardown), + cmocka_unit_test_setup_teardown(nsec, _setup, _teardown), + cmocka_unit_test_setup_teardown(nsec3, _setup, _teardown), + cmocka_unit_test_setup_teardown(nxt, _setup, _teardown), + cmocka_unit_test_setup_teardown(rkey, _setup, _teardown), + cmocka_unit_test_setup_teardown(sshfp, _setup, _teardown), + cmocka_unit_test_setup_teardown(wks, _setup, _teardown), + cmocka_unit_test_setup_teardown(zonemd, _setup, _teardown), + /* other tests */ + cmocka_unit_test_setup_teardown(edns_client_subnet, _setup, + _teardown), + cmocka_unit_test_setup_teardown(atcname, NULL, NULL), + cmocka_unit_test_setup_teardown(atparent, NULL, NULL), + cmocka_unit_test_setup_teardown(iszonecutauth, NULL, NULL), + }; + struct CMUnitTest selected[sizeof(tests) / sizeof(tests[0])]; + size_t i; + int c; + + memset(selected, 0, sizeof(selected)); + + while ((c = isc_commandline_parse(argc, argv, "dlt:")) != -1) { + switch (c) { + case 'd': + debug = true; + break; + case 'l': + for (i = 0; i < (sizeof(tests) / sizeof(tests[0])); i++) + { + if (tests[i].name != NULL) { + fprintf(stdout, "%s\n", tests[i].name); + } + } + return (0); + case 't': + if (!cmocka_add_test_byname( + tests, isc_commandline_argument, selected)) + { + fprintf(stderr, "unknown test '%s'\n", + isc_commandline_argument); + exit(1); + } + break; + default: + break; + } + } + + if (selected[0].name != NULL) { + return (cmocka_run_group_tests(selected, NULL, NULL)); + } else { + return (cmocka_run_group_tests(tests, NULL, NULL)); + } +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rdataset_test.c b/lib/dns/tests/rdataset_test.c new file mode 100644 index 0000000..ebdd128 --- /dev/null +++ b/lib/dns/tests/rdataset_test.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. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include + +#include +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/* test trimming of rdataset TTLs */ +static void +trimttl(void **state) { + dns_rdataset_t rdataset, sigrdataset; + dns_rdata_rrsig_t rrsig; + isc_stdtime_t ttltimenow, ttltimeexpire; + + ttltimenow = 10000000; + ttltimeexpire = ttltimenow + 800; + + UNUSED(state); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&sigrdataset); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimeexpire; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true); + assert_int_equal(rdataset.ttl, 800); + assert_int_equal(sigrdataset.ttl, 800); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true); + assert_int_equal(rdataset.ttl, 120); + assert_int_equal(sigrdataset.ttl, 120); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + false); + assert_int_equal(rdataset.ttl, 0); + assert_int_equal(sigrdataset.ttl, 0); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimeexpire; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true); + assert_int_equal(rdataset.ttl, 800); + assert_int_equal(sigrdataset.ttl, 800); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, true); + assert_int_equal(rdataset.ttl, 120); + assert_int_equal(sigrdataset.ttl, 120); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + false); + assert_int_equal(rdataset.ttl, 0); + assert_int_equal(sigrdataset.ttl, 0); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(trimttl, _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rdatasetstats_test.c b/lib/dns/tests/rdatasetstats_test.c new file mode 100644 index 0000000..20b333d --- /dev/null +++ b/lib/dns/tests/rdatasetstats_test.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include + +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static void +set_typestats(dns_stats_t *stats, dns_rdatatype_t type) { + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = 0; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); + + attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); +} + +static void +set_nxdomainstats(dns_stats_t *stats) { + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN; + which = DNS_RDATASTATSTYPE_VALUE(0, attributes); + dns_rdatasetstats_increment(stats, which); +} + +static void +mark_stale(dns_stats_t *stats, dns_rdatatype_t type, int from, int to) { + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = from; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_decrement(stats, which); + + attributes |= to; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); + + attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET | from; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_decrement(stats, which); + + attributes |= to; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); +} + +static void +mark_nxdomain_stale(dns_stats_t *stats, int from, int to) { + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN | from; + which = DNS_RDATASTATSTYPE_VALUE(0, attributes); + dns_rdatasetstats_decrement(stats, which); + + attributes |= to; + which = DNS_RDATASTATSTYPE_VALUE(0, attributes); + dns_rdatasetstats_increment(stats, which); +} + +#define ATTRIBUTE_SET(y) ((attributes & (y)) != 0) +static void +verify_active_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) { + unsigned int attributes; +#if debug + unsigned int type; +#endif /* if debug */ + + UNUSED(which); + UNUSED(arg); + + attributes = DNS_RDATASTATSTYPE_ATTR(which); +#if debug + type = DNS_RDATASTATSTYPE_BASE(which); + + fprintf(stderr, "%s%s%s%s%s/%u, %u\n", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ", + type, (unsigned)value); +#endif /* if debug */ + if ((attributes & DNS_RDATASTATSTYPE_ATTR_ANCIENT) == 0 && + (attributes & DNS_RDATASTATSTYPE_ATTR_STALE) == 0) + { + assert_int_equal(value, 1); + } else { + assert_int_equal(value, 0); + } +} + +static void +verify_stale_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) { + unsigned int attributes; +#if debug + unsigned int type; +#endif /* if debug */ + + UNUSED(which); + UNUSED(arg); + + attributes = DNS_RDATASTATSTYPE_ATTR(which); +#if debug + type = DNS_RDATASTATSTYPE_BASE(which); + + fprintf(stderr, "%s%s%s%s%s/%u, %u\n", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ", + type, (unsigned)value); +#endif /* if debug */ + if ((attributes & DNS_RDATASTATSTYPE_ATTR_STALE) != 0) { + assert_int_equal(value, 1); + } else { + assert_int_equal(value, 0); + } +} + +static void +verify_ancient_counters(dns_rdatastatstype_t which, uint64_t value, void *arg) { + unsigned int attributes; +#if debug + unsigned int type; +#endif /* if debug */ + + UNUSED(which); + UNUSED(arg); + + attributes = DNS_RDATASTATSTYPE_ATTR(which); +#if debug + type = DNS_RDATASTATSTYPE_BASE(which); + + fprintf(stderr, "%s%s%s%s%s/%u, %u\n", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_OTHERTYPE) ? "O" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXRRSET) ? "!" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_ANCIENT) ? "~" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_STALE) ? "#" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ", + type, (unsigned)value); +#endif /* if debug */ + if ((attributes & DNS_RDATASTATSTYPE_ATTR_ANCIENT) != 0) { + assert_int_equal(value, 1); + } else { + assert_int_equal(value, 0); + } +} +/* + * Individual unit tests + */ + +/* + * Test that rdatasetstats counters are properly set when moving from + * active -> stale -> ancient. + */ +static void +rdatasetstats(void **state, bool servestale) { + unsigned int i; + unsigned int from = 0; + dns_stats_t *stats = NULL; + isc_result_t result; + + UNUSED(state); + + result = dns_rdatasetstats_create(dt_mctx, &stats); + assert_int_equal(result, ISC_R_SUCCESS); + + /* First 255 types. */ + for (i = 1; i <= 255; i++) { + set_typestats(stats, (dns_rdatatype_t)i); + } + /* Specials */ + set_typestats(stats, (dns_rdatatype_t)1000); + set_nxdomainstats(stats); + + /* Check that all active counters are set to appropriately. */ + dns_rdatasetstats_dump(stats, verify_active_counters, NULL, 1); + + if (servestale) { + /* Mark stale */ + for (i = 1; i <= 255; i++) { + mark_stale(stats, (dns_rdatatype_t)i, 0, + DNS_RDATASTATSTYPE_ATTR_STALE); + } + mark_stale(stats, (dns_rdatatype_t)1000, 0, + DNS_RDATASTATSTYPE_ATTR_STALE); + mark_nxdomain_stale(stats, 0, DNS_RDATASTATSTYPE_ATTR_STALE); + + /* Check that all counters are set to appropriately. */ + dns_rdatasetstats_dump(stats, verify_stale_counters, NULL, 1); + + /* Set correct staleness state */ + from = DNS_RDATASTATSTYPE_ATTR_STALE; + } + + /* Mark ancient */ + for (i = 1; i <= 255; i++) { + mark_stale(stats, (dns_rdatatype_t)i, from, + DNS_RDATASTATSTYPE_ATTR_ANCIENT); + } + mark_stale(stats, (dns_rdatatype_t)1000, from, + DNS_RDATASTATSTYPE_ATTR_ANCIENT); + mark_nxdomain_stale(stats, from, DNS_RDATASTATSTYPE_ATTR_ANCIENT); + + /* + * Check that all counters are set to appropriately. + */ + dns_rdatasetstats_dump(stats, verify_ancient_counters, NULL, 1); + + dns_stats_detach(&stats); +} + +/* + * Test that rdatasetstats counters are properly set when moving from + * active -> stale -> ancient. + */ +static void +test_rdatasetstats_active_stale_ancient(void **state) { + rdatasetstats(state, true); +} + +/* + * Test that rdatasetstats counters are properly set when moving from + * active -> ancient. + */ +static void +test_rdatasetstats_active_ancient(void **state) { + rdatasetstats(state, false); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_rdatasetstats_active_stale_ancient, _setup, + _teardown), + cmocka_unit_test_setup_teardown( + test_rdatasetstats_active_ancient, _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/resolver_test.c b/lib/dns/tests/resolver_test.c new file mode 100644 index 0000000..ed89d01 --- /dev/null +++ b/lib/dns/tests/resolver_test.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dnstest.h" + +static dns_dispatchmgr_t *dispatchmgr = NULL; +static dns_dispatch_t *dispatch = NULL; +static dns_view_t *view = NULL; + +static int +_setup(void **state) { + isc_result_t result; + isc_sockaddr_t local; + + UNUSED(state); + + result = dns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dispatchmgr_create(dt_mctx, &dispatchmgr); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_test_makeview("view", &view); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_sockaddr_any(&local); + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &local, + 4096, 100, 100, 100, 500, 0, 0, &dispatch); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_dispatch_detach(&dispatch); + dns_view_detach(&view); + dns_dispatchmgr_destroy(&dispatchmgr); + dns_test_end(); + + return (0); +} + +static void +mkres(dns_resolver_t **resolverp) { + isc_result_t result; + + result = dns_resolver_create(view, taskmgr, 1, 1, socketmgr, timermgr, + 0, dispatchmgr, dispatch, NULL, resolverp); + assert_int_equal(result, ISC_R_SUCCESS); +} + +static void +destroy_resolver(dns_resolver_t **resolverp) { + dns_resolver_shutdown(*resolverp); + dns_resolver_detach(resolverp); +} + +/* dns_resolver_create */ +static void +create_test(void **state) { + dns_resolver_t *resolver = NULL; + + UNUSED(state); + + mkres(&resolver); + destroy_resolver(&resolver); +} + +/* dns_resolver_gettimeout */ +static void +gettimeout_test(void **state) { + dns_resolver_t *resolver = NULL; + unsigned int timeout; + + UNUSED(state); + + mkres(&resolver); + + timeout = dns_resolver_gettimeout(resolver); + assert_true(timeout > 0); + + destroy_resolver(&resolver); +} + +/* dns_resolver_settimeout */ +static void +settimeout_test(void **state) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(state); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, default_timeout + 1); + timeout = dns_resolver_gettimeout(resolver); + assert_true(timeout == default_timeout + 1); + + destroy_resolver(&resolver); +} + +/* dns_resolver_settimeout */ +static void +settimeout_default_test(void **state) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(state); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, default_timeout + 100); + + timeout = dns_resolver_gettimeout(resolver); + assert_int_equal(timeout, default_timeout + 100); + + dns_resolver_settimeout(resolver, 0); + timeout = dns_resolver_gettimeout(resolver); + assert_int_equal(timeout, default_timeout); + + destroy_resolver(&resolver); +} + +/* dns_resolver_settimeout below minimum */ +static void +settimeout_belowmin_test(void **state) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(state); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, 9000); + + timeout = dns_resolver_gettimeout(resolver); + assert_int_equal(timeout, default_timeout); + + destroy_resolver(&resolver); +} + +/* dns_resolver_settimeout over maximum */ +static void +settimeout_overmax_test(void **state) { + dns_resolver_t *resolver = NULL; + unsigned int timeout; + + UNUSED(state); + + mkres(&resolver); + + dns_resolver_settimeout(resolver, 4000000); + timeout = dns_resolver_gettimeout(resolver); + assert_in_range(timeout, 0, 3999999); + destroy_resolver(&resolver); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(create_test, _setup, _teardown), + cmocka_unit_test_setup_teardown(gettimeout_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(settimeout_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(settimeout_default_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(settimeout_belowmin_test, + _setup, _teardown), + cmocka_unit_test_setup_teardown(settimeout_overmax_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/result_test.c b/lib/dns/tests/result_test.c new file mode 100644 index 0000000..3a05c71 --- /dev/null +++ b/lib/dns/tests/result_test.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include + +#include + +/* + * Check ids array is populated. + */ +static void +ids(void **state) { + const char *str; + isc_result_t result; + + UNUSED(state); + + dns_result_register(); + dst_result_register(); + + for (result = ISC_RESULTCLASS_DNS; + result < (ISC_RESULTCLASS_DNS + DNS_R_NRESULTS); result++) + { + str = isc_result_toid(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + } + + str = isc_result_toid(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + for (result = ISC_RESULTCLASS_DST; + result < (ISC_RESULTCLASS_DST + DST_R_NRESULTS); result++) + { + str = isc_result_toid(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + } + + str = isc_result_toid(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + for (result = ISC_RESULTCLASS_DNSRCODE; + result < (ISC_RESULTCLASS_DNSRCODE + DNS_R_NRCODERESULTS); + result++) + { + str = isc_result_toid(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + } + + str = isc_result_toid(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(ids), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/rsa_test.c b/lib/dns/tests/rsa_test.c new file mode 100644 index 0000000..7d8897b --- /dev/null +++ b/lib/dns/tests/rsa_test.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include + +#include "../dst_internal.h" +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +static unsigned char d[10] = { 0xa, 0x10, 0xbb, 0, 0xfe, + 0x15, 0x1, 0x88, 0xcc, 0x7d }; + +static unsigned char sigsha1[256] = { + 0x45, 0x55, 0xd6, 0xf8, 0x05, 0xd2, 0x2e, 0x79, 0x14, 0x2b, 0x1b, 0xd1, + 0x4b, 0xb7, 0xcd, 0xc0, 0xa2, 0xf3, 0x85, 0x32, 0x1f, 0xa3, 0xfd, 0x1f, + 0x30, 0xe0, 0xde, 0xb2, 0x6f, 0x3c, 0x8e, 0x2b, 0x82, 0x92, 0xcd, 0x1c, + 0x1b, 0xdf, 0xe6, 0xd5, 0x4d, 0x93, 0xe6, 0xaa, 0x40, 0x28, 0x1b, 0x7b, + 0x2e, 0x40, 0x4d, 0xb5, 0x4d, 0x43, 0xe8, 0xfc, 0x93, 0x86, 0x68, 0xe3, + 0xbf, 0x73, 0x9a, 0x1e, 0x6b, 0x5d, 0x52, 0xb8, 0x98, 0x1c, 0x94, 0xe1, + 0x85, 0x8b, 0xee, 0xb1, 0x4f, 0x22, 0x71, 0xcb, 0xfd, 0xb2, 0xa8, 0x88, + 0x64, 0xb4, 0xb1, 0x4a, 0xa1, 0x7a, 0xce, 0x52, 0x83, 0xd8, 0xf2, 0x9e, + 0x67, 0x4c, 0xc3, 0x37, 0x74, 0xfe, 0xe0, 0x25, 0x2a, 0xfd, 0xa3, 0x09, + 0xff, 0x8a, 0x92, 0x0d, 0xa9, 0xb3, 0x90, 0x23, 0xbe, 0x6a, 0x2c, 0x9e, + 0x5c, 0x6d, 0xb4, 0xa7, 0xd7, 0x97, 0xdd, 0xc6, 0xb8, 0xae, 0xd4, 0x88, + 0x64, 0x63, 0x1e, 0x85, 0x20, 0x09, 0xea, 0xc4, 0x0b, 0xca, 0xbf, 0x83, + 0x5c, 0x89, 0xae, 0x64, 0x15, 0x76, 0x06, 0x51, 0xb6, 0xa1, 0x99, 0xb2, + 0x3c, 0x50, 0x99, 0x86, 0x7d, 0xc7, 0xca, 0x4e, 0x1d, 0x2c, 0x17, 0xbb, + 0x6c, 0x7a, 0xc9, 0x3f, 0x5e, 0x28, 0x57, 0x2c, 0xda, 0x01, 0x1d, 0xe8, + 0x01, 0xf8, 0xf6, 0x37, 0xe1, 0x34, 0x56, 0xae, 0x6e, 0xb1, 0xd4, 0xa2, + 0xc4, 0x02, 0xc1, 0xca, 0x96, 0xb0, 0x06, 0x72, 0x2a, 0x27, 0xaa, 0xc8, + 0xd5, 0x50, 0x81, 0x49, 0x46, 0x33, 0xf8, 0xf7, 0x6b, 0xf4, 0x9c, 0x30, + 0x90, 0x50, 0xf6, 0x16, 0x76, 0x9d, 0xc6, 0x73, 0xb5, 0xbc, 0x8a, 0xb6, + 0x1d, 0x98, 0xcb, 0xce, 0x36, 0x6f, 0x60, 0xec, 0x96, 0x49, 0x08, 0x85, + 0x5b, 0xc1, 0x8e, 0xb0, 0xea, 0x9e, 0x1f, 0xd6, 0x27, 0x7f, 0xb6, 0xe0, + 0x04, 0x12, 0xd2, 0x81 +}; + +static unsigned char sigsha256[256] = { + 0x83, 0x53, 0x15, 0xfc, 0xca, 0xdb, 0xf6, 0x0d, 0x53, 0x24, 0x5b, 0x5a, + 0x8e, 0xd0, 0xbe, 0x5e, 0xbc, 0xe8, 0x9e, 0x92, 0x3c, 0xfa, 0x93, 0x03, + 0xce, 0x2f, 0xc7, 0x6d, 0xd0, 0xbb, 0x9d, 0x06, 0x83, 0xc6, 0xd3, 0xc0, + 0xc1, 0x57, 0x9c, 0x82, 0x17, 0x7f, 0xb5, 0xf8, 0x31, 0x18, 0xda, 0x46, + 0x05, 0x2c, 0xf8, 0xea, 0xaa, 0xcd, 0x99, 0x18, 0xff, 0x23, 0x5e, 0xef, + 0xf0, 0x87, 0x47, 0x6e, 0x91, 0xfd, 0x19, 0x0b, 0x39, 0x19, 0x6a, 0xc8, + 0xdf, 0x71, 0x66, 0x8e, 0xa9, 0xa0, 0x79, 0x5c, 0x2c, 0x52, 0x00, 0x61, + 0x17, 0x86, 0x66, 0x03, 0x52, 0xad, 0xec, 0x06, 0x53, 0xd9, 0x6d, 0xe3, + 0xe3, 0xea, 0x28, 0x15, 0xb3, 0x75, 0xf4, 0x61, 0x7d, 0xed, 0x69, 0x2c, + 0x24, 0xf3, 0x21, 0xb1, 0x8a, 0xea, 0x60, 0xa2, 0x9e, 0x6a, 0xa6, 0x53, + 0x12, 0xf6, 0x5c, 0xef, 0xd7, 0x49, 0x4a, 0x02, 0xe7, 0xf8, 0x64, 0x89, + 0x13, 0xac, 0xd5, 0x1e, 0x58, 0xff, 0xa1, 0x63, 0xdd, 0xa0, 0x1f, 0x44, + 0x99, 0x6a, 0x59, 0x7f, 0x35, 0xbd, 0xf1, 0xf3, 0x7a, 0x28, 0x44, 0xe3, + 0x4c, 0x68, 0xb1, 0xb3, 0x97, 0x3c, 0x46, 0xe3, 0xc2, 0x12, 0x9e, 0x68, + 0x0b, 0xa6, 0x6c, 0x8f, 0x58, 0x48, 0x44, 0xa4, 0xf7, 0xa7, 0xc2, 0x91, + 0x8f, 0xbf, 0x00, 0xd0, 0x01, 0x35, 0xd4, 0x86, 0x6e, 0x1f, 0xea, 0x42, + 0x60, 0xb1, 0x84, 0x27, 0xf4, 0x99, 0x36, 0x06, 0x98, 0x12, 0x83, 0x32, + 0x9f, 0xcd, 0x50, 0x5a, 0x5e, 0xb8, 0x8e, 0xfe, 0x8d, 0x8d, 0x33, 0x2d, + 0x45, 0xe1, 0xc9, 0xdf, 0x2a, 0xd8, 0x38, 0x1d, 0x95, 0xd4, 0x42, 0xee, + 0x93, 0x5b, 0x0f, 0x1e, 0x07, 0x06, 0x3a, 0x92, 0xf1, 0x59, 0x1d, 0x6e, + 0x1c, 0x31, 0xf3, 0xce, 0xa9, 0x1f, 0xad, 0x4d, 0x76, 0x4d, 0x24, 0x98, + 0xe2, 0x0e, 0x8c, 0x35 +}; + +static unsigned char sigsha512[512] = { + 0x4e, 0x2f, 0x63, 0x42, 0xc5, 0xf3, 0x05, 0x4a, 0xa6, 0x3a, 0x93, 0xa0, + 0xd9, 0x33, 0xa0, 0xd1, 0x46, 0x33, 0x42, 0xe8, 0x74, 0xeb, 0x3b, 0x10, + 0x82, 0xd7, 0xcf, 0x39, 0x23, 0xb3, 0xe9, 0x23, 0x53, 0x87, 0x8c, 0xee, + 0x78, 0xcb, 0xb3, 0xd9, 0xd2, 0x6d, 0x1a, 0x7c, 0x01, 0x4f, 0xed, 0x8d, + 0xf2, 0x72, 0xe4, 0x6a, 0x00, 0x8a, 0x60, 0xa6, 0xd5, 0x9c, 0x43, 0x6c, + 0xef, 0x38, 0x0c, 0x74, 0x82, 0x5d, 0x22, 0xaa, 0x87, 0x81, 0x90, 0x9c, + 0x64, 0x07, 0x9b, 0x13, 0x51, 0xe0, 0xa5, 0xc2, 0x83, 0x78, 0x2b, 0x9b, + 0xb3, 0x8a, 0x9d, 0x36, 0x33, 0xbd, 0x0d, 0x53, 0x84, 0xae, 0xe8, 0x13, + 0x36, 0xf6, 0xdf, 0x96, 0xe9, 0xda, 0xc3, 0xd7, 0xa9, 0x2f, 0xf3, 0x5e, + 0x5f, 0x1f, 0x7f, 0x38, 0x7e, 0x8d, 0xbe, 0x90, 0x5e, 0x13, 0xb2, 0x20, + 0xbb, 0x9d, 0xfe, 0xe1, 0x52, 0xce, 0xe6, 0x80, 0xa7, 0x95, 0x24, 0x59, + 0xe3, 0xac, 0x24, 0xc4, 0xfa, 0x1c, 0x44, 0x34, 0x29, 0x8d, 0xb1, 0xd0, + 0xd9, 0x4c, 0xff, 0xc4, 0xdb, 0xca, 0xc4, 0x3f, 0x38, 0xf9, 0xe4, 0xaf, + 0x75, 0x0a, 0x67, 0x4d, 0xa0, 0x2b, 0xb0, 0x83, 0xce, 0x53, 0xc4, 0xb9, + 0x2e, 0x61, 0xb6, 0x64, 0xe5, 0xb5, 0xe5, 0xac, 0x9d, 0x51, 0xec, 0x58, + 0x42, 0x90, 0x78, 0xf6, 0x46, 0x96, 0xef, 0xb6, 0x97, 0xb7, 0x54, 0x28, + 0x1a, 0x4c, 0x29, 0xf4, 0x7a, 0x33, 0xc6, 0x07, 0xfd, 0xec, 0x97, 0x36, + 0x1d, 0x42, 0x88, 0x94, 0x27, 0xc2, 0xa3, 0xe1, 0xd4, 0x87, 0xa1, 0x8a, + 0x2b, 0xff, 0x47, 0x60, 0xfe, 0x1f, 0xaf, 0xc2, 0xeb, 0x17, 0xdd, 0x56, + 0xc5, 0x94, 0x5c, 0xcb, 0x23, 0xe5, 0x49, 0x4d, 0x99, 0x06, 0x02, 0x5a, + 0xfc, 0xfc, 0xdc, 0xee, 0x49, 0xbc, 0x47, 0x60, 0xff, 0x6a, 0x63, 0x8b, + 0xe1, 0x2e, 0xa3, 0xa7 +}; + +/* RSA verify */ +static void +isc_rsa_verify_test(void **state) { + isc_result_t ret; + dns_fixedname_t fname; + isc_buffer_t buf; + dns_name_t *name; + dst_key_t *key = NULL; + dst_context_t *ctx = NULL; + isc_region_t r; + + UNUSED(state); + + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&buf, "rsa.", 4); + isc_buffer_add(&buf, 4); + ret = dns_name_fromtext(name, &buf, NULL, 0, NULL); + assert_int_equal(ret, ISC_R_SUCCESS); + + ret = dst_key_fromfile(name, 29238, DST_ALG_RSASHA256, DST_TYPE_PUBLIC, + "./", dt_mctx, &key); + assert_int_equal(ret, ISC_R_SUCCESS); + + /* RSASHA1 - May not be supported by the OS */ + if (dst_algorithm_supported(DST_ALG_RSASHA1)) { + key->key_alg = DST_ALG_RSASHA1; + + ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC, + false, 0, &ctx); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = sigsha1; + r.length = 256; + ret = dst_context_verify(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + } + + /* RSASHA256 */ + + key->key_alg = DST_ALG_RSASHA256; + + ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC, false, 0, + &ctx); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = sigsha256; + r.length = 256; + ret = dst_context_verify(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + + /* RSASHA512 */ + + key->key_alg = DST_ALG_RSASHA512; + + ret = dst_context_create(key, dt_mctx, DNS_LOGCATEGORY_DNSSEC, false, 0, + &ctx); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + r.base = sigsha512; + r.length = 256; + ret = dst_context_verify(ctx, &r); + assert_int_equal(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + + dst_key_free(&key); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_rsa_verify_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/dns/tests/sigs_test.c b/lib/dns/tests/sigs_test.c new file mode 100644 index 0000000..8e438cb --- /dev/null +++ b/lib/dns/tests/sigs_test.c @@ -0,0 +1,462 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../zone_p.h" +#include "dnstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = dns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + dns_test_end(); + + return (0); +} + +/*% + * Structure characterizing a single diff tuple in the dns_diff_t structure + * prepared by dns__zone_updatesigs(). + */ +typedef struct { + dns_diffop_t op; + const char *owner; + dns_ttl_t ttl; + const char *type; +} zonediff_t; + +#define ZONEDIFF_SENTINEL \ + { \ + 0, NULL, 0, NULL \ + } + +/*% + * Structure defining a dns__zone_updatesigs() test. + */ +typedef struct { + const char *description; /* test description */ + const zonechange_t *changes; /* array of "raw" zone changes */ + const zonediff_t *zonediff; /* array of "processed" zone changes + * */ +} updatesigs_test_params_t; + +/*% + * Check whether the 'found' tuple matches the 'expected' tuple. 'found' is + * the 'index'th tuple output by dns__zone_updatesigs() in test 'test'. + */ +static void +compare_tuples(const zonediff_t *expected, dns_difftuple_t *found, + size_t index) { + char found_covers[DNS_RDATATYPE_FORMATSIZE] = {}; + char found_type[DNS_RDATATYPE_FORMATSIZE] = {}; + char found_name[DNS_NAME_FORMATSIZE]; + isc_consttextregion_t typeregion; + dns_fixedname_t expected_fname; + dns_rdatatype_t expected_type; + dns_name_t *expected_name; + dns_rdata_rrsig_t rrsig; + isc_buffer_t typebuf; + isc_result_t result; + + REQUIRE(expected != NULL); + REQUIRE(found != NULL); + REQUIRE(index > 0); + + /* + * Check operation. + */ + assert_int_equal(expected->op, found->op); + + /* + * Check owner name. + */ + expected_name = dns_fixedname_initname(&expected_fname); + result = dns_name_fromstring(expected_name, expected->owner, 0, + dt_mctx); + assert_int_equal(result, ISC_R_SUCCESS); + dns_name_format(&found->name, found_name, sizeof(found_name)); + assert_true(dns_name_equal(expected_name, &found->name)); + + /* + * Check TTL. + */ + assert_int_equal(expected->ttl, found->ttl); + + /* + * Parse expected RR type. + */ + typeregion.base = expected->type; + typeregion.length = strlen(expected->type); + result = dns_rdatatype_fromtext(&expected_type, + (isc_textregion_t *)&typeregion); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Format found RR type for reporting purposes. + */ + isc_buffer_init(&typebuf, found_type, sizeof(found_type)); + result = dns_rdatatype_totext(found->rdata.type, &typebuf); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Check RR type. + */ + switch (expected->op) { + case DNS_DIFFOP_ADDRESIGN: + case DNS_DIFFOP_DELRESIGN: + /* + * Found tuple must be of type RRSIG. + */ + assert_int_equal(found->rdata.type, dns_rdatatype_rrsig); + if (found->rdata.type != dns_rdatatype_rrsig) { + break; + } + /* + * The signature must cover an RRset of type 'expected->type'. + */ + result = dns_rdata_tostruct(&found->rdata, &rrsig, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&typebuf, found_covers, sizeof(found_covers)); + result = dns_rdatatype_totext(rrsig.covered, &typebuf); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(expected_type, rrsig.covered); + break; + default: + /* + * Found tuple must be of type 'expected->type'. + */ + assert_int_equal(expected_type, found->rdata.type); + break; + } +} + +/*% + * Perform a single dns__zone_updatesigs() test defined in 'test'. All other + * arguments are expected to remain constant between subsequent invocations of + * this function. + */ +static void +updatesigs_test(const updatesigs_test_params_t *test, dns_zone_t *zone, + dns_db_t *db, dst_key_t *zone_keys[], unsigned int nkeys, + isc_stdtime_t now) { + size_t tuples_expected, tuples_found, index; + dns_dbversion_t *version = NULL; + dns_diff_t raw_diff, zone_diff; + const zonediff_t *expected; + dns_difftuple_t *found; + isc_result_t result; + + dns__zonediff_t zonediff = { + .diff = &zone_diff, + .offline = false, + }; + + REQUIRE(test != NULL); + REQUIRE(test->description != NULL); + REQUIRE(test->changes != NULL); + REQUIRE(zone != NULL); + REQUIRE(db != NULL); + REQUIRE(zone_keys != NULL); + + /* + * Create a new version of the zone's database. + */ + result = dns_db_newversion(db, &version); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Create a diff representing the supplied changes. + */ + result = dns_test_difffromchanges(&raw_diff, test->changes, false); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Apply the "raw" diff to the new version of the zone's database as + * this is what dns__zone_updatesigs() expects to happen before it is + * called. + */ + dns_diff_apply(&raw_diff, db, version); + + /* + * Initialize the structure dns__zone_updatesigs() will modify. + */ + dns_diff_init(dt_mctx, &zone_diff); + + /* + * Check whether dns__zone_updatesigs() behaves as expected. + */ + result = dns__zone_updatesigs(&raw_diff, db, version, zone_keys, nkeys, + zone, now - 3600, now + 3600, 0, now, + true, false, &zonediff); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(ISC_LIST_EMPTY(raw_diff.tuples)); + assert_false(ISC_LIST_EMPTY(zone_diff.tuples)); + + /* + * Ensure that the number of tuples in the zone diff is as expected. + */ + + tuples_expected = 0; + for (expected = test->zonediff; expected->owner != NULL; expected++) { + tuples_expected++; + } + + tuples_found = 0; + for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL; + found = ISC_LIST_NEXT(found, link)) + { + tuples_found++; + } + + assert_int_equal(tuples_expected, tuples_found); + + /* + * Ensure that every tuple in the zone diff matches expectations. + */ + expected = test->zonediff; + index = 1; + for (found = ISC_LIST_HEAD(zone_diff.tuples); found != NULL; + found = ISC_LIST_NEXT(found, link)) + { + compare_tuples(expected, found, index); + expected++; + index++; + } + + /* + * Apply changes to zone database contents and clean up. + */ + dns_db_closeversion(db, &version, true); + dns_diff_clear(&zone_diff); + dns_diff_clear(&raw_diff); +} + +/* dns__zone_updatesigs() tests */ +static void +updatesigs_next_test(void **state) { + dst_key_t *zone_keys[DNS_MAXZONEKEYS]; + dns_zone_t *zone = NULL; + dns_db_t *db = NULL; + isc_result_t result; + unsigned int nkeys; + isc_stdtime_t now; + size_t i; + + UNUSED(state); + + /* + * Prepare a zone along with its signing keys. + */ + + result = dns_test_makezone("example", &zone, NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_zone, "example", + "testdata/master/master18.data"); + assert_int_equal(result, DNS_R_SEENINCLUDE); + + result = dns_zone_setkeydirectory(zone, "testkeys"); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_stdtime_get(&now); + result = dns__zone_findkeys(zone, db, NULL, now, dt_mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(nkeys, 2); + + /* + * Define the tests to be run. Note that changes to zone database + * contents introduced by each test are preserved between tests. + */ + + const zonechange_t changes_add[] = { + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo" }, + { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "bar" }, + ZONECHANGE_SENTINEL, + }; + const zonediff_t zonediff_add[] = { + { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" }, + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, + { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" }, + { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" }, + ZONEDIFF_SENTINEL, + }; + const updatesigs_test_params_t test_add = { + .description = "add new RRsets", + .changes = changes_add, + .zonediff = zonediff_add, + }; + + const zonechange_t changes_append[] = { + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo1" }, + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT", "foo2" }, + ZONECHANGE_SENTINEL, + }; + const zonediff_t zonediff_append[] = { + { DNS_DIFFOP_DELRESIGN, "foo.example", 300, "TXT" }, + { DNS_DIFFOP_ADDRESIGN, "foo.example", 300, "TXT" }, + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, + { DNS_DIFFOP_ADD, "foo.example", 300, "TXT" }, + ZONEDIFF_SENTINEL, + }; + const updatesigs_test_params_t test_append = { + .description = "append multiple RRs to an existing RRset", + .changes = changes_append, + .zonediff = zonediff_append, + }; + + const zonechange_t changes_replace[] = { + { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "bar" }, + { DNS_DIFFOP_ADD, "bar.example", 600, "TXT", "rab" }, + ZONECHANGE_SENTINEL, + }; + const zonediff_t zonediff_replace[] = { + { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" }, + { DNS_DIFFOP_ADDRESIGN, "bar.example", 600, "TXT" }, + { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" }, + { DNS_DIFFOP_ADD, "bar.example", 600, "TXT" }, + ZONEDIFF_SENTINEL, + }; + const updatesigs_test_params_t test_replace = { + .description = "replace an existing RRset", + .changes = changes_replace, + .zonediff = zonediff_replace, + }; + + const zonechange_t changes_delete[] = { + { DNS_DIFFOP_DEL, "bar.example", 600, "TXT", "rab" }, + ZONECHANGE_SENTINEL, + }; + const zonediff_t zonediff_delete[] = { + { DNS_DIFFOP_DELRESIGN, "bar.example", 600, "TXT" }, + { DNS_DIFFOP_DEL, "bar.example", 600, "TXT" }, + ZONEDIFF_SENTINEL, + }; + const updatesigs_test_params_t test_delete = { + .description = "delete an existing RRset", + .changes = changes_delete, + .zonediff = zonediff_delete, + }; + + const zonechange_t changes_mixed[] = { + { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz1" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "A", "127.0.0.1" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "TXT", "baz2" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA", "::1" }, + ZONECHANGE_SENTINEL, + }; + const zonediff_t zonediff_mixed[] = { + { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "TXT" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "TXT" }, + { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "A" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "A" }, + { DNS_DIFFOP_ADDRESIGN, "baz.example", 900, "AAAA" }, + { DNS_DIFFOP_ADD, "baz.example", 900, "AAAA" }, + ZONEDIFF_SENTINEL, + }; + const updatesigs_test_params_t test_mixed = { + .description = "add different RRsets with common owner name", + .changes = changes_mixed, + .zonediff = zonediff_mixed, + }; + + const updatesigs_test_params_t *tests[] = { + &test_add, &test_append, &test_replace, + &test_delete, &test_mixed, + }; + + /* + * Run tests. + */ + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + updatesigs_test(tests[i], zone, db, zone_keys, nkeys, now); + } + + /* + * Clean up. + */ + for (i = 0; i < nkeys; i++) { + dst_key_free(&zone_keys[i]); + } + dns_db_detach(&db); + dns_zone_detach(&zone); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(updatesigs_next_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/dns/tests/testdata/db/data.db b/lib/dns/tests/testdata/db/data.db new file mode 100644 index 0000000..67a4fba --- /dev/null +++ b/lib/dns/tests/testdata/db/data.db @@ -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. + +$TTL 1000 +@ in soa localhost. postmaster.localhost. ( + 1993050801 ;serial + 3600 ;refresh + 1800 ;retry + 604800 ;expiration + 3600 ) ;minimum +a in ns ns.vix.com. +a in ns ns2.vix.com. +a in ns ns3.vix.com. +b in a 1.2.3.4 diff --git a/lib/dns/tests/testdata/dbiterator/zone1.data b/lib/dns/tests/testdata/dbiterator/zone1.data new file mode 100644 index 0000000..c380d39 --- /dev/null +++ b/lib/dns/tests/testdata/dbiterator/zone1.data @@ -0,0 +1,30 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("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. + +$TTL 600 +@ in soa localhost. postmaster.localhost. ( + 2011080901 ;serial + 3600 ;refresh + 1800 ;retry + 604800 ;expiration + 600 ) ;minimum + in ns ns + in ns ns2 +ns in a 10.0.0.1 +ns2 in a 10.0.0.2 + +a in txt "test" +b in txt "test" +c in txt "test" +d.e.f in txt "test" +e in txt "test" +f.g.h in txt "test" +f.g.i in txt "test" +f.g.j in txt "test" +k in txt "test" diff --git a/lib/dns/tests/testdata/dbiterator/zone2.data b/lib/dns/tests/testdata/dbiterator/zone2.data new file mode 100644 index 0000000..7265c27 --- /dev/null +++ b/lib/dns/tests/testdata/dbiterator/zone2.data @@ -0,0 +1,319 @@ +; File written on Mon Aug 15 16:51:56 2011 +; dnssec_signzone version 9.7.3rc1 +test. 600 IN SOA localhost. postmaster.localhost. ( + 2011080901 ; serial + 3600 ; refresh (1 hour) + 1800 ; retry (30 minutes) + 604800 ; expire (1 week) + 600 ; minimum (10 minutes) + ) + 600 RRSIG SOA 7 1 600 20110914225156 ( + 20110815225156 39833 test. + IoQPcpx+Y2btVBBdM2H/9ppRMjphB1thwrdh + midhKH+MXDAauUIENucugi3zLsc1o2ke8LnQ + v3lCLd/bb5MD1otuS8vOw1GWEFhXOUBZU6wS + QwEIcG4BiSlz7/GvOlRa2znkOmZ3c8bD/J3Y + XUWDI3BEDPgrZqfxEvoMyPEWjO8= ) + 600 NS ns.test. + 600 NS ns2.test. + 600 RRSIG NS 7 1 600 20110914225156 ( + 20110815225156 39833 test. + OgEimhmFIAqlH0hyQy3pTsveBHKyqs9WfO1S + uDPRj3DFgFEAjoY473T8GxG2C+jTVL/UMVcb + BTZ8wIAiUHhqKLcmr0q/1X+kNUs7tNi+6oMn + /jxaOuRL6c8Kf2gl2t4g6JTwQqLQhUHTfQP+ + bEfKUr75VsVfxCQZIHlZ3/AlxZM= ) + 600 DNSKEY 256 3 7 ( + AwEAAc0FzrE7jUiaKIGZpIaFE8E989topAJN + dWIQUQ7BSKabmpBP2M+SXHwIiQ/yC25iqudO + IxjRcK7nHB1VoP84xU2oMj6eeSqQHf/bYaji + Y8IfR7lgrzoDWzq+0rtnKMJc/JM8SMkcoBAS + llvxarDJTZheZjlrCvhpRJC+FAkBsx81 + ) ; key id = 39833 + 600 DNSKEY 257 3 7 ( + AwEAAc55LPDhBLqfDUpjYYbBt+N63CiZtKrD + UDGeFAerbw0MWIUi3PgMr7yGVrj8e5Qjp9UN + zBUax6NdhlYVtFA8CwMTXGBjxgyqUoWpce08 + lswxfE70BpgUA6w5efs0/mYtX9/A76etCaSI + oNH2vfa47BCdCPDfC1uTgyeuNuDvhszHaSiD + 8OY7tLa/voecUlq38sdqi2raf2DvgOm7rdFa + reXOS/WIj7zd4XYrV1JGthxOMVlQ7zdv9rVd + UNUIF2d4hwCZJQr0ejhmvB3m/DuNmNOPYmnv + KTmLSE+IJ6baqYvKOVxwV+SaCnuJEjv+3Yrx + 8WQYD/iS9WBhC9FUit0dy+0= + ) ; key id = 57183 + 600 RRSIG DNSKEY 7 1 600 20110914225156 ( + 20110815225156 39833 test. + xPV+bSGUlbxA5MKBeeRbwUDh3Qc+dm77+OHQ + BHIr1L8/kRP5o5J7MqPA37kea6nhyltYf9xM + RsxyiaBGUUeLyWg/q6hTtkNgAHifOPAhiDz8 + AJDSTdSsq9RVtjdobAD0jyzz9sWnB+TPSOmj + Nlyd7VtPVEuSYljgawwfBBO3Kho= ) + 600 RRSIG DNSKEY 7 1 600 20110914225156 ( + 20110815225156 57183 test. + S3jkC7AvyFc4ShfHt6AWgS4zpx9DzWHBK9gV + 2H23OJzy8H1At/CjKxWVHLJ/io+ygryVnt/I + 47Jyhh9i43TnXj8il475YsweGnXGZSorrcXA + 3IsD2lOuRYnp3yetxe2ZrMGNDqqImE6X4x1a + UJI0cbE2UMZfUt8Rm5USiGzwAEgFD1OXxvMD + UT3flyp+Ote9FConK8gewV4wlJuBFemWT7BZ + lUYnoqfuAeEn2+1pIBS0iA0LNFjNBaEgtcjo + QeweN32yKoApau47Dl/Klw7KFT8+PLZ0QPbt + XAkJU7q94Q5aucDuHCSCTCc+2vZxdEnXKvRY + rfLuG8r/V5Kn+1iYrQ== ) + 0 NSEC3PARAM 1 0 10 - + 0 RRSIG NSEC3PARAM 7 1 0 20110914225156 ( + 20110815225156 39833 test. + kghSSeP8AZiQ/zmxgxAyG0itoUMo5adG5pxD + p8T3ZmbxEUSyG5acxBFkmeY39wVU0Cda8tWc + HHrMbB5e2GN8z6xJ0A4rVyXfKSYJSz+iKWfk + 7sOFRjd8OLYE3di6PwIpk6ORUiRPMFLDQCH0 + Q27hLsSoKyd50orKKI+ncjz7WzU= ) +a.test. 600 IN TXT "test" + 600 RRSIG TXT 7 2 600 20110914225156 ( + 20110815225156 39833 test. + UEVOlnL6CDRNCfk/Xge2oaGYCV1+ewwi5zJ0 + CX4DdwiNEkItL4HgBe8xXfxgFC3qySdsSYPE + 1krdFyIkAclMCwHECd1UwZbGlMTEUGrE1KOB + 8vQY+OhIV9TAhqNwnjbu7s2ZdNUv3wiUPcfk + hCJ4rzP6yeV2inLwZulXnhxb6Pk= ) +b.test. 600 IN TXT "test" + 600 RRSIG TXT 7 2 600 20110914225156 ( + 20110815225156 39833 test. + HcyQlO9io6Rc5e4vVqlRmK5PacOaFQJmdERG + 5Aobpgm1FuCLC7F+IMZ0d1XvBWnsw9iDzV43 + UKzTGqUSmDiSBzs4QzHlacGickIW8EOV4xyJ + +mcJ0FZh4YNbkt6CiX+8SF6IxfCMhRMjpSsK + rWqJMG3LXkI6W9stShzsYAFBOzQ= ) +e.test. 600 IN TXT "test" + 600 RRSIG TXT 7 2 600 20110914225156 ( + 20110815225156 39833 test. + jUn5FGRTL9OcFU7tvfkUnSwY8jA+8JynE0hi + ZJbYXDU5CiWGmR2B3yPHxUCewRqouyVCV8bc + xZsSuBxvcdYKryYDbjsmB83GlSEuxE9J7XZs + 8SxUP8PobLVqzXgEZS/XRU2G+R915ZDP9/iL + z9oYwc9TkeyXbp8J/ZsH88tG980= ) +c.test. 600 IN TXT "test" + 600 RRSIG TXT 7 2 600 20110914225156 ( + 20110815225156 39833 test. + cRxAj45oFDDCd8xQXxD1F0Qq8XeBWAj8EYS3 + 7nFXAgAy8sTczFvYCNGj79o7BALJwM4vc/wx + 6rjsiO/sHgfTMEBDq6lH9Wql72uhwavI2SrL + /h/wBP5q4BXlQ4xp6cLhhdifOWhNTvLP+Fe5 + U6yjvqneiKspze9SiFbcmRDiJds= ) +d.e.f.test. 600 IN TXT "test" + 600 RRSIG TXT 7 4 600 20110914225156 ( + 20110815225156 39833 test. + ENjCzr/P9rJmj5OJLzYwWtHtBg2Uz+qJDucz + I97Pq9F819/c5sxNfT4hgICCw6ZfT4ffbzye + fFJ0JVrh2cYOzu68ozlgek/Uml1UW0pDQVdI + s4zEgp4XK9wXUxtWChSqp5YXMdeHegZFu32i + IMNTbJDudwYSwhr2FyG92ZRi8Y8= ) +f.g.h.test. 600 IN TXT "test" + 600 RRSIG TXT 7 4 600 20110914225156 ( + 20110815225156 39833 test. + HT7iocFsfDjeX6j9RJdE3xfVGkIxhajFHgM/ + T/mJj/al4HKV6Ajia8DhpdfDrgM2m7r+Pgcn + FSIstfebQsuFCnHX/gIalDND/grHKsetQnMP + Y7O4QLsRnTV53fdlqQ4eT+jBW6fzJdGySVN+ + bg6kNJZS8DebjmlKtZz7tXjkP+4= ) +f.g.i.test. 600 IN TXT "test" + 600 RRSIG TXT 7 4 600 20110914225156 ( + 20110815225156 39833 test. + kHJJeNSL1rz4QRYqOzhGMQl1yIdio7l8Lg8H + f0TsvFLa6BudVtwKUm+Kz2QiDn7/Lew8w0KX + vVHxX/Vwl3Ixk54YgMKLNogz2TEvnh/VGiS7 + 8r0oSUrg0CFd+xDfxnLeRqX5NNfMuSJap5WH + Aw7IVeRjXDwJFYnytMEnTrhHHHg= ) +f.g.j.test. 600 IN TXT "test" + 600 RRSIG TXT 7 4 600 20110914225156 ( + 20110815225156 39833 test. + lIEHEhDFhOWK8W/F2xWELU2p/X77S2KTivm9 + sY4k3RPsLNHE7p+lF8p72Lcb79rtltnoVYtE + pTIiaUcmgGwfaI4cwfXbeuEgnuTiLg7Xrefx + 3GT86Q+8gfgbMXUmRA/eouWZhCOaYJN99gYz + urzDMiRLYmILHmLlnvo82SgXeuk= ) +k.test. 600 IN TXT "test" + 600 RRSIG TXT 7 2 600 20110914225156 ( + 20110815225156 39833 test. + wC3zgYWsuLga8Vu3QFu/Ci8SzRbA5bvjSmDj + NzcpjU5cvJBxtgzatCr02AaUC94bI0JzNrEB + nFyWCYw55lyy+bAHU1u05UcQmz0n5yxkvmHX + i8ZjMyQkAvNKodJHaFQqUKKIDuSHD2EziKqg + eNn55YRS11ihkODehUVNl7TnYeA= ) +ns.test. 600 IN A 10.0.0.1 + 600 RRSIG A 7 2 600 20110914225156 ( + 20110815225156 39833 test. + VyK/WlQ6ikXdjF/arGzyAyYhOc8IYNBp4QLW + gtYjvbjIcV5+9JINWmUs61VjJ14nES1sI0xb + 9vQJuiPXTM1awUAnvOKLhaX6fbJaEiR1w6Cf + RT5QKBMxNBKVStqdabHcigY4DUuc1PQk1vCw + yMUJt3nHNVMZk+XAycNHzBeYjik= ) +ns2.test. 600 IN A 10.0.0.2 + 600 RRSIG A 7 2 600 20110914225156 ( + 20110815225156 39833 test. + CX6UlZL+5NQJViKfbe/E3uIJk/wjUzoiHBhY + B6gS8nxZzlRPdTTXyMZoRa4etTZEbrRjnyXk + 1rP47faCUwbh//XqukN9f7FZ4Y39NpPS2XpX + 0Lx6M93Jz46lbzmseMFs2YmNMzzhN4uhRvl/ + 8gPtYsn9KMXnAlFfa4XrE5LNVyY= ) +1F3JQ6EANHNHOCMUPQTVNM339VDTR51C.test. 600 IN NSEC3 1 0 10 - 7QKPELF33JOK9BVJ7CKE99AHG40B0SH7 A RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + w7aS12lxLNh+G1B/2kEq1BO6IzYvyC8n/MGV + 0jvFnapNXGZMPrPxGeO2wkw1JXepuXCv98be + M4SjQywaH+VP6ZMTIfjxRxtcCM+aLAFhiz0l + /MILEkjemmxjAfvV7emRVMwCGcoGI7qC3Xxq + q5g8EzJiYyTCOnI5LKRggn97wGg= ) +7QKPELF33JOK9BVJ7CKE99AHG40B0SH7.test. 600 IN NSEC3 1 0 10 - 94Q15K1V1VE5F87EI37T2B9A39EEC368 TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + J4ObL3p4eN0jWh06M+rX2SSPANQoKfnosElB + KcKE7fLqEjKK7N6Yh6KUlbEP25tfeZ7W6GBJ + b7q6Nh0Ax8fYdc/6JVvmxcwWcx5Lw1TfITGB + ttFntJlbp1A8lwP3pn8Ksql1X2ogh78AsgTb + X5kmXVukC1oEzt98EAa/V/an8QA= ) +CS8M3UVG0UJDR6USBES4U9SNUGQI2RJE.test. 600 IN NSEC3 1 0 10 - ETEQB5V431INUIIE547FKSOF7O4DJ62J A RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + Vyd/2b0S15fACJ8TiPXKtScV9A/ZztVumZAm + o2S6jaVJKWik+8orDW+WiJ4/PEl26PK2m1uv + HD2beuUCHj9EnYkN/dzL3Bsc302qr9xqsh0q + VFS2moznoNG415ZV3vgYR7L9DAp43ZeFuw6I + 7sr21hLYLUeo31xBsJg7RlOL+4s= ) +ETEQB5V431INUIIE547FKSOF7O4DJ62J.test. 600 IN NSEC3 1 0 10 - F8G1MB0JUEU3FBI11CAVFIPGEA3POOIM + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + oOHs1eb3JYeOMOnzE2PS6NIXBNzSoTYPIxo/ + P0d/ihsLKra3yNJNPTlu4kf+FZoNYAGtMK/D + 6dZWFvtdswDdi2C5WSgsanuHqXq5Lr3A1nCe + cQI5PO4RrLymB+MtYg15CNKcnc0WmJO8deSR + WzNOarC+Iz1Xj3FkKDS4FFr+02Q= ) +94Q15K1V1VE5F87EI37T2B9A39EEC368.test. 600 IN NSEC3 1 0 10 - CS8M3UVG0UJDR6USBES4U9SNUGQI2RJE + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + K0PvN7YtHQ63x/x2yXXa2S9GBGuTNJywDZ8M + wyMSwytCb9mn4hnKD5mJHaXGTw3YX7usbnEO + ce6hiJdN/VhMfbRMOvUpgyblOj4kXiYVZY1a + SyycfugK/Hu1j4az7lIhhnnx58GChA6mg8Vx + 3Uz6cNDDCSTBTl09NyeUUrKWsHQ= ) +FBH6B0LHT9PPQB1P98D228HA1H52L8PO.test. 600 IN NSEC3 1 0 10 - JGU2L7C3LKLHAKC5RHUOORTI2DCKK3KL + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + giXRE+4ZeIzDrhx1XkFSpIKGFd3UGzlrLZnO + Ur9nMUfwvU5A3fitEkdayo3ZDH7MQGpSotaH + ReiFXx3Z6Hm2NIN/RHYZQr9e0vbMYSjkANdu + HWBA1SrSq5SHyuy970mPd4jfTHiABCo6fJGB + ykGClZGou0WSaB+Ak19fMbeQ2Wo= ) +JGU2L7C3LKLHAKC5RHUOORTI2DCKK3KL.test. 600 IN NSEC3 1 0 10 - KFMJ88CKMKUQQJE59IKFBOLLLD4DF55H TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + BHTDUgZdWNLgz3xHYMqvlWK/IJ0xrXESoREc + 6D3sO9bcLTMYPO9t80itOlipwp4AmaVOBXPt + cKSdgsUXDEtHqNSxtGbNr5xQ+Aqsep0GX71V + HkcIuiNdTUw83dkajCHMkmQCbEjp9mbdiTmS + haNW2EsscldfaS1aq5tYUhCT3l4= ) +L993U6VC0DUV5QJ8TRPD2IQLM8FJ7AT9.test. 600 IN NSEC3 1 0 10 - LSMRLLNBQGGK8J6V40KLM2LG5TE4FS0P + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + vE7K0Nrju4qLFDYkIyMY5bIMT0wu8MJdxL6u + 7WVA4HepccKQcUnvVoBAcrA9+MUeteyrad8Y + SJvQIt7sz5t7FViWSq5IMPVPujWtW5J30LhJ + mOLd1KmnFWoVthJ1oFNzBM80A60seKNnEw1M + lV6Y+v0gNYIQensUb9w6SVMTpxE= ) +F8G1MB0JUEU3FBI11CAVFIPGEA3POOIM.test. 600 IN NSEC3 1 0 10 - FA1T7MKUUV9SD4VDBJQ3GRFK1IDTCKL7 + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + DkL9ONc0vpsKdG20ol8XPAaVfLb7kf1wnKbR + rQUB1trGSHm/Igo06of43zm9J+56htFJg1xD + I2de0sCUBQYyHVBBDiBAd1g+ZvcpUlLP0w8M + NxMviMiG/WQAdGXHwYfUimwMWD7gNGl1m05H + HwYmzGs+d1bClDNBrFhdfdL2+iA= ) +LSMRLLNBQGGK8J6V40KLM2LG5TE4FS0P.test. 600 IN NSEC3 1 0 10 - LUAN2Q3I2OCVSD41MP08HNA9JP22D38K + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + ZgiWuMqodQuhwuAF6CIiJTsdRahi+poOiZAM + WXNP0wXfdptcG2uhbdDwy+0crhe3tuybhwcb + CuiaQUh0XNPhgF+qmXpGobaqBhCEvCF4K9qY + OCIoMfsI1pIBVbMw0+YXVarFZ8+mfNU/+6n6 + yy2+1nCg3k4XR2Dpv4CeDBfcAuM= ) +NAL1UIEBM38NKMN6RQOKE8T781IA7UKI.test. 600 IN NSEC3 1 0 10 - OUSGP0LO9FGAROHDULQVSTI3OLQIBB39 TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + x8JiXPI+EXHz8ZO/VW0/+9wWsBNqeSMxXZIV + ibOnogSg7Wi7Yq1xftKC2+xEevNxSZnBibEy + Sgro5xKTf0n7pD9hHVBLoYmOOnbXY3QNQ2EQ + y3LdPT355WmwVddVOOxNpNRp2zQyqg7BhVA3 + wxY7tyVQd4x1+95ATUQBnFditdE= ) +KFMJ88CKMKUQQJE59IKFBOLLLD4DF55H.test. 600 IN NSEC3 1 0 10 - L993U6VC0DUV5QJ8TRPD2IQLM8FJ7AT9 TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + KQPaN2Ecebifbl4Bz5Yo0x2DgGmZiVhpSydm + oy/5NtMjt7G472JrKlqByap+VxW0bpzo3IER + 3P8Dsv7pfBD4/Cl5sFqwZL7wYy7RB4dQLVCi + Pepc/Mr3gR2XmL91fpGttMj5jGscnVQJCyFa + obzhsVaVImUQZFDPb0UQUHwIhOA= ) +LUAN2Q3I2OCVSD41MP08HNA9JP22D38K.test. 600 IN NSEC3 1 0 10 - NAL1UIEBM38NKMN6RQOKE8T781IA7UKI TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + NJ+X3d0qh2+fbSnG0iQPxAeDIOzX5NTmY9fS + x7IO/DDcgUhPvl1YYdz5J999cec1zzOKp10J + YbsIAzg0w/Y4D4CBUw3IkcOrUFOODb6eJQGb + rVFRqmp3BUP4qOAWUZvx4oQ0KG4K/h/KJMbU + Vcdl7PF7G5O5hMyR9UWg4zal7Sk= ) +OUSGP0LO9FGAROHDULQVSTI3OLQIBB39.test. 600 IN NSEC3 1 0 10 - PQQ28M3U2MM08GGFV3JKR76G2H9IUJPC TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + A/qxYrSE/smBGbST8j8eGPCrRnwvVa25kDha + IuA3nv0vzXhFvlruc9f0HRGwsq6A2pw3I5W+ + xo2/JxsNyFOotdwaDDEBzqPkJmrzupxQS4Hm + rHSLnRnNw4QzvzNjAGWMYAoe3OeHC47wmAtI + qE91EHZTlPP28CUXOMo+7sCaOa8= ) +U0UVS2SUP89P2TM3PJO4TC1GPJ2O6519.test. 600 IN NSEC3 1 0 10 - VA2VG5BEMCKQP6MS5NHHGL18031BIA7M NS SOA RRSIG DNSKEY NSEC3PARAM + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + rahhkfiF+Rk6oqbWTdu9qcwhmj5hbDuIFdiJ + GmaG+cFSv5Mjp+txNVCvBK9Hq/VpW0ypen/3 + JC0sVAugSX+HAKAgyaMKmgWCvoQZ6ZSJUh7o + LRPcT+oxVXQAqjovxpaV8k6sYo44tpljPdOD + UluWAP5SrmJKjzCxs27KGRx8MK4= ) +VA2VG5BEMCKQP6MS5NHHGL18031BIA7M.test. 600 IN NSEC3 1 0 10 - VAKOQ2TPD7S25NFBJT73J3C4OGU10RJ5 TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + XcBeZ8lo9Qo8z56+1FdGDjh6ZHCfO+MQ/wnY + TEUo/aWLkPTyq39nLhe0qVBJxmDpM+KQFuG9 + cjQT5fvrlrY+lv6dedB64EBMYy4kKbIv7N5+ + r6+sfWlvtKsfXxysLSk2+jLEm5NuLFrOdNas + WLVsq741D3YcWt4kM1HCyk3DNF8= ) +FA1T7MKUUV9SD4VDBJQ3GRFK1IDTCKL7.test. 600 IN NSEC3 1 0 10 - FBH6B0LHT9PPQB1P98D228HA1H52L8PO TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + jB/vLrvx4sQQD7J3ZacAAyhcFmIPh7LH3ljw + IAIaeLb10oX5q1/nQKYdfq976TMy5sWpBcmd + i91WLxd+T/gOSumyP8bC3g+SUoyZ9wxY6A6a + MMx1rn0QA9IKrxMqojs9M3urJ8QAeIS+KyAn + rbyyJuG+EVm0prqlPZtzUi28WCI= ) +PQQ28M3U2MM08GGFV3JKR76G2H9IUJPC.test. 600 IN NSEC3 1 0 10 - U0UVS2SUP89P2TM3PJO4TC1GPJ2O6519 + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + asCOU9OkVWMvUU2IUpwMgdYf0faA04zPbaFf + qywYsv3NH01Lky6G3a0WUPAbBm7TAYx/ln8a + 559vlpp/gpXEl9CcLrjO6wy5i0ryp8gVHtKJ + rQlEc/uw4SY+S5t7FuZc2rNRdAbxVMYuwrvm + HBsKDPblre3e06ZZFEmnGFzCgmg= ) +VAKOQ2TPD7S25NFBJT73J3C4OGU10RJ5.test. 600 IN NSEC3 1 0 10 - VNCCJH8JPOLGLAGVMV3FKS09M7RRDU47 TXT RRSIG + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + Pt4tKB1p/jsyLYab9LSt5MF1KTRT18nRTOox + q0IACkXkKx7W5xv6nSYXIB+nQzNp1Y1hhoXn + 9IFi0liPnIAOp73w4vybhfIdTFiEmHPHT6O9 + VIx5cSriqBI6Qda8GtfeIb96P8SojbUk5BDI + g18iYjviGhQYRgpU3tg1qd7pbcc= ) +VNCCJH8JPOLGLAGVMV3FKS09M7RRDU47.test. 600 IN NSEC3 1 0 10 - 1F3JQ6EANHNHOCMUPQTVNM339VDTR51C + 600 RRSIG NSEC3 7 2 600 20110914225156 ( + 20110815225156 39833 test. + ZMZPHawhkuzSV7C7zkgghH/jpw9CQVR1JUXq + pAeY2iIIWwNhfuskJaLgtu/5SuKnJtrv6D4N + g+lfEkBReia5xO/SCcHv8/hXEPH8vZ4xe1C9 + 6GVB6ip2hKw2g5HpyF7X18WgwZ0cqPWVg+Q+ + xRLpXH+53391Wt5rG7qJswn5RLE= ) diff --git a/lib/dns/tests/testdata/diff/zone1.data b/lib/dns/tests/testdata/diff/zone1.data new file mode 100644 index 0000000..8ddf669 --- /dev/null +++ b/lib/dns/tests/testdata/diff/zone1.data @@ -0,0 +1,13 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("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. + +@ 0 SOA . . 0 0 0 0 0 +@ 0 NS @ +@ 0 A 1.2.3.4 +remove 0 A 5.6.7.8 diff --git a/lib/dns/tests/testdata/diff/zone2.data b/lib/dns/tests/testdata/diff/zone2.data new file mode 100644 index 0000000..363af42 --- /dev/null +++ b/lib/dns/tests/testdata/diff/zone2.data @@ -0,0 +1,14 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("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. + +@ 0 SOA . . 0 0 0 0 0 +@ 0 NS @ +@ 0 A 1.2.3.4 +remove 0 A 5.6.7.8 +added 0 A 5.6.7.8 diff --git a/lib/dns/tests/testdata/diff/zone3.data b/lib/dns/tests/testdata/diff/zone3.data new file mode 100644 index 0000000..ae3a60e --- /dev/null +++ b/lib/dns/tests/testdata/diff/zone3.data @@ -0,0 +1,12 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("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. + +@ 0 SOA . . 0 0 0 0 0 +@ 0 NS @ +@ 0 A 1.2.3.4 diff --git a/lib/dns/tests/testdata/dnstap/dnstap.saved b/lib/dns/tests/testdata/dnstap/dnstap.saved new file mode 100644 index 0000000..c657f41 Binary files /dev/null and b/lib/dns/tests/testdata/dnstap/dnstap.saved differ diff --git a/lib/dns/tests/testdata/dnstap/dnstap.text b/lib/dns/tests/testdata/dnstap/dnstap.text new file mode 100644 index 0000000..71977e4 --- /dev/null +++ b/lib/dns/tests/testdata/dnstap/dnstap.text @@ -0,0 +1,96 @@ +03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 SR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 CR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 AR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 RR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 FR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 UDP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 15:47:16.000 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TQ 10.53.0.1:2112 -> 10.53.0.2:2112 TCP 40b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 UDP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 17:47:16.000 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A +03-Feb-2017 16:47:16.830 TR 10.53.0.1:2112 <- 10.53.0.2:2112 TCP 287b www.isc.org/IN/A diff --git a/lib/dns/tests/testdata/dnstap/query.auth b/lib/dns/tests/testdata/dnstap/query.auth new file mode 100644 index 0000000..a14f850 --- /dev/null +++ b/lib/dns/tests/testdata/dnstap/query.auth @@ -0,0 +1,4 @@ +# authoritative query, www.isc.org/A +8d 24 00 20 00 01 00 00 00 00 00 01 03 77 77 77 +03 69 73 63 03 6f 72 67 00 00 01 00 01 00 00 29 +10 00 00 00 00 00 00 00 diff --git a/lib/dns/tests/testdata/dnstap/query.recursive b/lib/dns/tests/testdata/dnstap/query.recursive new file mode 100644 index 0000000..8ee705f --- /dev/null +++ b/lib/dns/tests/testdata/dnstap/query.recursive @@ -0,0 +1,4 @@ +# recursive query for www.isc.org/A +bf 08 01 20 00 01 00 00 00 00 00 01 03 77 77 77 +03 69 73 63 03 6f 72 67 00 00 01 00 01 00 00 29 +10 00 00 00 00 00 00 00 diff --git a/lib/dns/tests/testdata/dnstap/response.auth b/lib/dns/tests/testdata/dnstap/response.auth new file mode 100644 index 0000000..4d0ea81 --- /dev/null +++ b/lib/dns/tests/testdata/dnstap/response.auth @@ -0,0 +1,19 @@ +# authoritative response, www.isc.org/A +8d 24 84 00 00 01 00 01 00 04 00 07 03 77 77 77 +03 69 73 63 03 6f 72 67 00 00 01 00 01 c0 0c 00 +01 00 01 00 00 00 3c 00 04 95 14 40 45 c0 10 00 +02 00 01 00 00 1c 20 00 0d 03 61 6d 73 06 73 6e +73 2d 70 62 c0 10 c0 10 00 02 00 01 00 00 1c 20 +00 07 04 73 66 62 61 c0 3d c0 10 00 02 00 01 00 +00 1c 20 00 19 02 6e 73 03 69 73 63 0b 61 66 69 +6c 69 61 73 2d 6e 73 74 04 69 6e 66 6f 00 c0 10 +00 02 00 01 00 00 1c 20 00 06 03 6f 72 64 c0 3d +c0 39 00 01 00 01 00 00 1c 20 00 04 c7 06 01 1e +c0 39 00 1c 00 01 00 00 1c 20 00 10 20 01 05 00 +00 60 00 00 00 00 00 00 00 00 00 30 c0 8a 00 01 +00 01 00 00 1c 20 00 04 c7 06 00 1e c0 8a 00 1c +00 01 00 00 1c 20 00 10 20 01 05 00 00 71 00 00 +00 00 00 00 00 00 00 30 c0 52 00 01 00 01 00 00 +1c 20 00 04 95 14 40 03 c0 52 00 1c 00 01 00 00 +1c 20 00 10 20 01 04 f8 00 00 00 02 00 00 00 00 +00 00 00 19 00 00 29 10 00 00 00 00 00 00 00 diff --git a/lib/dns/tests/testdata/dnstap/response.recursive b/lib/dns/tests/testdata/dnstap/response.recursive new file mode 100644 index 0000000..6e3a3cf --- /dev/null +++ b/lib/dns/tests/testdata/dnstap/response.recursive @@ -0,0 +1,19 @@ +# recursive response, www.isc.org/A +bf 08 81 a0 00 01 00 01 00 04 00 07 03 77 77 77 +03 69 73 63 03 6f 72 67 00 00 01 00 01 c0 0c 00 +01 00 01 00 00 00 15 00 04 95 14 40 45 c0 10 00 +02 00 01 00 00 1b a6 00 0e 04 73 66 62 61 06 73 +6e 73 2d 70 62 c0 10 c0 10 00 02 00 01 00 00 1b +a6 00 06 03 6f 72 64 c0 3e c0 10 00 02 00 01 00 +00 1b a6 00 19 02 6e 73 03 69 73 63 0b 61 66 69 +6c 69 61 73 2d 6e 73 74 04 69 6e 66 6f 00 c0 10 +00 02 00 01 00 00 1b a6 00 06 03 61 6d 73 c0 3e +c0 8a 00 01 00 01 00 00 b1 d5 00 04 c7 06 01 1e +c0 8a 00 1c 00 01 00 00 b1 d5 00 10 20 01 05 00 +00 60 00 00 00 00 00 00 00 00 00 30 c0 53 00 01 +00 01 00 00 b1 d5 00 04 c7 06 00 1e c0 53 00 1c +00 01 00 00 b1 d5 00 10 20 01 05 00 00 71 00 00 +00 00 00 00 00 00 00 30 c0 39 00 01 00 01 00 00 +b1 d5 00 04 95 14 40 03 c0 39 00 1c 00 01 00 00 +b1 d5 00 10 20 01 04 f8 00 00 00 02 00 00 00 00 +00 00 00 19 00 00 29 10 00 00 00 00 00 00 00 diff --git a/lib/dns/tests/testdata/dst/Ktest.+008+11349.key b/lib/dns/tests/testdata/dst/Ktest.+008+11349.key new file mode 100644 index 0000000..a1bd768 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+008+11349.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 11349, for test. +; Created: 20181025090713 (Thu Oct 25 11:07:13 2018) +; Publish: 20181025090713 (Thu Oct 25 11:07:13 2018) +; Activate: 20181025090713 (Thu Oct 25 11:07:13 2018) +test. IN DNSKEY 256 3 8 AwEAAdqPwPScyURzeCUzEadKNYgQW50LPDV/ir9nWIbiSn2yMkymxiby BQH+Hk1neE9qa9X4XaEnKf5YZx7o14rRikmOb2lomtOkI9ovh1K/SvLO Zd1E3e61F29g1eCq52mMY3xAdEcBNqEq+6mgEwGmwl83+mAh5anxXNHa 2rcfdG+L diff --git a/lib/dns/tests/testdata/dst/Ktest.+008+11349.private b/lib/dns/tests/testdata/dst/Ktest.+008+11349.private new file mode 100644 index 0000000..5dfef79 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+008+11349.private @@ -0,0 +1,13 @@ +Private-key-format: v1.3 +Algorithm: 8 (RSASHA256) +Modulus: 2o/A9JzJRHN4JTMRp0o1iBBbnQs8NX+Kv2dYhuJKfbIyTKbGJvIFAf4eTWd4T2pr1fhdoScp/lhnHujXitGKSY5vaWia06Qj2i+HUr9K8s5l3UTd7rUXb2DV4KrnaYxjfEB0RwE2oSr7qaATAabCXzf6YCHlqfFc0dratx90b4s= +PublicExponent: AQAB +PrivateExponent: a4qmX/YxlmvWpz8spYr/MhcSbQCVPKGoLKv2RFBeZODknRDGmW0mh6d5U47hBPqRWvRdZak2oX7wJqZdQGIAT25bC09rLNMctfxXKtzwSaXFjXZGHGv+bDHcqIltvIYmRbb0pK/LinFaLZqfpVe0WOfKuT9BT03BlwSZV8GKgZE= +Prime1: 8oZLQoVpIqsiQw7bX5pTm/O0gEUnEzNOVEoLGsfIl68Lz/1CBm9ypTp8QOB0B9IpnH8vOS+NJM1az1d0RhqKow== +Prime2: 5rSbE6duWIb90uICkAUJn4OztHX0fkd9GKNYdsHVReFBH2poXGojVGkW6i/IaYl4NEXXr5Z89dWtR+RNH2Z9+Q== +Exponent1: 2IcuCmYyR9Gi9Vv+YIzYuRQMw7j5+hqEhJzW7UIRxdtzIG9s03INWZet9/5tmc35eM/Uyam6ynDN8vCRz0VDIQ== +Exponent2: vKcdVKIKWrvwXXzRaaGk79rLnZsDFiwxQG96TIpOczkyfpUNx9xHDaRtx4zRTnPKZrxiFkRx5LkZXHt1EWNHSQ== +Coefficient: pb9dFRZA2IRXDCGCM1ikp+QCs72wNn3hgURZLRLmtcBbQcYhP/dcp80SpInviwJPNRcKrfxninqygEARzfHtqQ== +Created: 20181025090713 +Publish: 20181025090713 +Activate: 20181025090713 diff --git a/lib/dns/tests/testdata/dst/Ktest.+013+49130.key b/lib/dns/tests/testdata/dst/Ktest.+013+49130.key new file mode 100644 index 0000000..e3ff931 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+013+49130.key @@ -0,0 +1,5 @@ +; This is a zone-signing key, keyid 49130, for test. +; Created: 20181025090718 (Thu Oct 25 11:07:18 2018) +; Publish: 20181025090718 (Thu Oct 25 11:07:18 2018) +; Activate: 20181025090718 (Thu Oct 25 11:07:18 2018) +test. IN DNSKEY 256 3 13 uP04fwB/DuBBqdjPLseIoFT7vgtP8Lr/be1NhRBvibwQ+Hr+3GQhIKIK XbamgOUxXJ9JDjWFAT2KXw0V3sAN9w== diff --git a/lib/dns/tests/testdata/dst/Ktest.+013+49130.private b/lib/dns/tests/testdata/dst/Ktest.+013+49130.private new file mode 100644 index 0000000..754d9f9 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+013+49130.private @@ -0,0 +1,6 @@ +Private-key-format: v1.3 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: feGDRABRCbcsCqssKK5B5518y95smrv/cJnz2pa/UVA= +Created: 20181025090718 +Publish: 20181025090718 +Activate: 20181025090718 diff --git a/lib/dns/tests/testdata/dst/test1.data b/lib/dns/tests/testdata/dst/test1.data new file mode 100644 index 0000000..cf84e9f --- /dev/null +++ b/lib/dns/tests/testdata/dst/test1.data @@ -0,0 +1,3077 @@ +Network Working Group P. Mockapetris +Request for Comments: 1035 ISI + November 1987 +Obsoletes: RFCs 882, 883, 973 + + DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION + + +1. STATUS OF THIS MEMO + +This RFC describes the details of the domain system and protocol, and +assumes that the reader is familiar with the concepts discussed in a +companion RFC, "Domain Names - Concepts and Facilities" [RFC-1034]. + +The domain system is a mixture of functions and data types which are an +official protocol and functions and data types which are still +experimental. Since the domain system is intentionally extensible, new +data types and experimental behavior should always be expected in parts +of the system beyond the official protocol. The official protocol parts +include standard queries, responses and the Internet class RR data +formats (e.g., host addresses). Since the previous RFC set, several +definitions have changed, so some previous definitions are obsolete. + +Experimental or obsolete features are clearly marked in these RFCs, and +such information should be used with caution. + +The reader is especially cautioned not to depend on the values which +appear in examples to be current or complete, since their purpose is +primarily pedagogical. Distribution of this memo is unlimited. + + Table of Contents + + 1. STATUS OF THIS MEMO 1 + 2. INTRODUCTION 3 + 2.1. Overview 3 + 2.2. Common configurations 4 + 2.3. Conventions 7 + 2.3.1. Preferred name syntax 7 + 2.3.2. Data Transmission Order 8 + 2.3.3. Character Case 9 + 2.3.4. Size limits 10 + 3. DOMAIN NAME SPACE AND RR DEFINITIONS 10 + 3.1. Name space definitions 10 + 3.2. RR definitions 11 + 3.2.1. Format 11 + 3.2.2. TYPE values 12 + 3.2.3. QTYPE values 12 + 3.2.4. CLASS values 13 + + + +Mockapetris [Page 1] + +RFC 1035 Domain Implementation and Specification November 1987 + + + 3.2.5. QCLASS values 13 + 3.3. Standard RRs 13 + 3.3.1. CNAME RDATA format 14 + 3.3.2. HINFO RDATA format 14 + 3.3.3. MB RDATA format (EXPERIMENTAL) 14 + 3.3.4. MD RDATA format (Obsolete) 15 + 3.3.5. MF RDATA format (Obsolete) 15 + 3.3.6. MG RDATA format (EXPERIMENTAL) 16 + 3.3.7. MINFO RDATA format (EXPERIMENTAL) 16 + 3.3.8. MR RDATA format (EXPERIMENTAL) 17 + 3.3.9. MX RDATA format 17 + 3.3.10. NULL RDATA format (EXPERIMENTAL) 17 + 3.3.11. NS RDATA format 18 + 3.3.12. PTR RDATA format 18 + 3.3.13. SOA RDATA format 19 + 3.3.14. TXT RDATA format 20 + 3.4. ARPA Internet specific RRs 20 + 3.4.1. A RDATA format 20 + 3.4.2. WKS RDATA format 21 + 3.5. IN-ADDR.ARPA domain 22 + 3.6. Defining new types, classes, and special namespaces 24 + 4. MESSAGES 25 + 4.1. Format 25 + 4.1.1. Header section format 26 + 4.1.2. Question section format 28 + 4.1.3. Resource record format 29 + 4.1.4. Message compression 30 + 4.2. Transport 32 + 4.2.1. UDP usage 32 + 4.2.2. TCP usage 32 + 5. MASTER FILES 33 + 5.1. Format 33 + 5.2. Use of master files to define zones 35 + 5.3. Master file example 36 + 6. NAME SERVER IMPLEMENTATION 37 + 6.1. Architecture 37 + 6.1.1. Control 37 + 6.1.2. Database 37 + 6.1.3. Time 39 + 6.2. Standard query processing 39 + 6.3. Zone refresh and reload processing 39 + 6.4. Inverse queries (Optional) 40 + 6.4.1. The contents of inverse queries and responses 40 + 6.4.2. Inverse query and response example 41 + 6.4.3. Inverse query processing 42 + + + + + + +Mockapetris [Page 2] + +RFC 1035 Domain Implementation and Specification November 1987 + + + 6.5. Completion queries and responses 42 + 7. RESOLVER IMPLEMENTATION 43 + 7.1. Transforming a user request into a query 43 + 7.2. Sending the queries 44 + 7.3. Processing responses 46 + 7.4. Using the cache 47 + 8. MAIL SUPPORT 47 + 8.1. Mail exchange binding 48 + 8.2. Mailbox binding (Experimental) 48 + 9. REFERENCES and BIBLIOGRAPHY 50 + Index 54 + +2. INTRODUCTION + +2.1. Overview + +The goal of domain names is to provide a mechanism for naming resources +in such a way that the names are usable in different hosts, networks, +protocol families, internets, and administrative organizations. + +From the user's point of view, domain names are useful as arguments to a +local agent, called a resolver, which retrieves information associated +with the domain name. Thus a user might ask for the host address or +mail information associated with a particular domain name. To enable +the user to request a particular type of information, an appropriate +query type is passed to the resolver with the domain name. To the user, +the domain tree is a single information space; the resolver is +responsible for hiding the distribution of data among name servers from +the user. + +From the resolver's point of view, the database that makes up the domain +space is distributed among various name servers. Different parts of the +domain space are stored in different name servers, although a particular +data item will be stored redundantly in two or more name servers. The +resolver starts with knowledge of at least one name server. When the +resolver processes a user query it asks a known name server for the +information; in return, the resolver either receives the desired +information or a referral to another name server. Using these +referrals, resolvers learn the identities and contents of other name +servers. Resolvers are responsible for dealing with the distribution of +the domain space and dealing with the effects of name server failure by +consulting redundant databases in other servers. + +Name servers manage two kinds of data. The first kind of data held in +sets called zones; each zone is the complete database for a particular +"pruned" subtree of the domain space. This data is called +authoritative. A name server periodically checks to make sure that its +zones are up to date, and if not, obtains a new copy of updated zones + + + +Mockapetris [Page 3] + +RFC 1035 Domain Implementation and Specification November 1987 + + +from master files stored locally or in another name server. The second +kind of data is cached data which was acquired by a local resolver. +This data may be incomplete, but improves the performance of the +retrieval process when non-local data is repeatedly accessed. Cached +data is eventually discarded by a timeout mechanism. + +This functional structure isolates the problems of user interface, +failure recovery, and distribution in the resolvers and isolates the +database update and refresh problems in the name servers. + +2.2. Common configurations + +A host can participate in the domain name system in a number of ways, +depending on whether the host runs programs that retrieve information +from the domain system, name servers that answer queries from other +hosts, or various combinations of both functions. The simplest, and +perhaps most typical, configuration is shown below: + + Local Host | Foreign + | + +---------+ +----------+ | +--------+ + | | user queries | |queries | | | + | User |-------------->| |---------|->|Foreign | + | Program | | Resolver | | | Name | + | |<--------------| |<--------|--| Server | + | | user responses| |responses| | | + +---------+ +----------+ | +--------+ + | A | + cache additions | | references | + V | | + +----------+ | + | cache | | + +----------+ | + +User programs interact with the domain name space through resolvers; the +format of user queries and user responses is specific to the host and +its operating system. User queries will typically be operating system +calls, and the resolver and its cache will be part of the host operating +system. Less capable hosts may choose to implement the resolver as a +subroutine to be linked in with every program that needs its services. +Resolvers answer user queries with information they acquire via queries +to foreign name servers and the local cache. + +Note that the resolver may have to make several queries to several +different foreign name servers to answer a particular user query, and +hence the resolution of a user query may involve several network +accesses and an arbitrary amount of time. The queries to foreign name +servers and the corresponding responses have a standard format described + + + +Mockapetris [Page 4] + +RFC 1035 Domain Implementation and Specification November 1987 + + +in this memo, and may be datagrams. + +Depending on its capabilities, a name server could be a stand alone +program on a dedicated machine or a process or processes on a large +timeshared host. A simple configuration might be: + + Local Host | Foreign + | + +---------+ | + / /| | + +---------+ | +----------+ | +--------+ + | | | | |responses| | | + | | | | Name |---------|->|Foreign | + | Master |-------------->| Server | | |Resolver| + | files | | | |<--------|--| | + | |/ | | queries | +--------+ + +---------+ +----------+ | + +Here a primary name server acquires information about one or more zones +by reading master files from its local file system, and answers queries +about those zones that arrive from foreign resolvers. + +The DNS requires that all zones be redundantly supported by more than +one name server. Designated secondary servers can acquire zones and +check for updates from the primary server using the zone transfer +protocol of the DNS. This configuration is shown below: + + Local Host | Foreign + | + +---------+ | + / /| | + +---------+ | +----------+ | +--------+ + | | | | |responses| | | + | | | | Name |---------|->|Foreign | + | Master |-------------->| Server | | |Resolver| + | files | | | |<--------|--| | + | |/ | | queries | +--------+ + +---------+ +----------+ | + A |maintenance | +--------+ + | +------------|->| | + | queries | |Foreign | + | | | Name | + +------------------|--| Server | + maintenance responses | +--------+ + +In this configuration, the name server periodically establishes a +virtual circuit to a foreign name server to acquire a copy of a zone or +to check that an existing copy has not changed. The messages sent for + + + +Mockapetris [Page 5] + +RFC 1035 Domain Implementation and Specification November 1987 + + +these maintenance activities follow the same form as queries and +responses, but the message sequences are somewhat different. + +The information flow in a host that supports all aspects of the domain +name system is shown below: + + Local Host | Foreign + | + +---------+ +----------+ | +--------+ + | | user queries | |queries | | | + | User |-------------->| |---------|->|Foreign | + | Program | | Resolver | | | Name | + | |<--------------| |<--------|--| Server | + | | user responses| |responses| | | + +---------+ +----------+ | +--------+ + | A | + cache additions | | references | + V | | + +----------+ | + | Shared | | + | database | | + +----------+ | + A | | + +---------+ refreshes | | references | + / /| | V | + +---------+ | +----------+ | +--------+ + | | | | |responses| | | + | | | | Name |---------|->|Foreign | + | Master |-------------->| Server | | |Resolver| + | files | | | |<--------|--| | + | |/ | | queries | +--------+ + +---------+ +----------+ | + A |maintenance | +--------+ + | +------------|->| | + | queries | |Foreign | + | | | Name | + +------------------|--| Server | + maintenance responses | +--------+ + +The shared database holds domain space data for the local name server +and resolver. The contents of the shared database will typically be a +mixture of authoritative data maintained by the periodic refresh +operations of the name server and cached data from previous resolver +requests. The structure of the domain data and the necessity for +synchronization between name servers and resolvers imply the general +characteristics of this database, but the actual format is up to the +local implementor. + + + + +Mockapetris [Page 6] + +RFC 1035 Domain Implementation and Specification November 1987 + + +Information flow can also be tailored so that a group of hosts act +together to optimize activities. Sometimes this is done to offload less +capable hosts so that they do not have to implement a full resolver. +This can be appropriate for PCs or hosts which want to minimize the +amount of new network code which is required. This scheme can also +allow a group of hosts can share a small number of caches rather than +maintaining a large number of separate caches, on the premise that the +centralized caches will have a higher hit ratio. In either case, +resolvers are replaced with stub resolvers which act as front ends to +resolvers located in a recursive server in one or more name servers +known to perform that service: + + Local Hosts | Foreign + | + +---------+ | + | | responses | + | Stub |<--------------------+ | + | Resolver| | | + | |----------------+ | | + +---------+ recursive | | | + queries | | | + V | | + +---------+ recursive +----------+ | +--------+ + | | queries | |queries | | | + | Stub |-------------->| Recursive|---------|->|Foreign | + | Resolver| | Server | | | Name | + | |<--------------| |<--------|--| Server | + +---------+ responses | |responses| | | + +----------+ | +--------+ + | Central | | + | cache | | + +----------+ | + +In any case, note that domain components are always replicated for +reliability whenever possible. + +2.3. Conventions + +The domain system has several conventions dealing with low-level, but +fundamental, issues. While the implementor is free to violate these +conventions WITHIN HIS OWN SYSTEM, he must observe these conventions in +ALL behavior observed from other hosts. + +2.3.1. Preferred name syntax + +The DNS specifications attempt to be as general as possible in the rules +for constructing domain names. The idea is that the name of any +existing object can be expressed as a domain name with minimal changes. + + + +Mockapetris [Page 7] + +RFC 1035 Domain Implementation and Specification November 1987 + + +However, when assigning a domain name for an object, the prudent user +will select a name which satisfies both the rules of the domain system +and any existing rules for the object, whether these rules are published +or implied by existing programs. + +For example, when naming a mail domain, the user should satisfy both the +rules of this memo and those in RFC-822. When creating a new host name, +the old rules for HOSTS.TXT should be followed. This avoids problems +when old software is converted to use domain names. + +The following syntax will result in fewer problems with many + +applications that use domain names (e.g., mail, TELNET). + + ::= | " " + + ::=