From ea648e70a989cca190cd7403fe892fd2dcc290b4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 5 May 2024 20:37:14 +0200 Subject: Adding upstream version 1:9.11.5.P4+dfsg. Signed-off-by: Daniel Baumann --- lib/dns/Atffile | 5 + lib/dns/Kyuafile | 4 + lib/dns/Makefile.in | 227 + lib/dns/acache.c | 1821 ++ lib/dns/acl.c | 728 + lib/dns/adb.c | 4823 +++++ lib/dns/api | 13 + lib/dns/badcache.c | 440 + lib/dns/byaddr.c | 315 + lib/dns/cache.c | 1575 ++ lib/dns/callbacks.c | 110 + lib/dns/catz.c | 1992 ++ lib/dns/client.c | 3243 +++ lib/dns/clientinfo.c | 32 + lib/dns/compress.c | 400 + lib/dns/db.c | 1132 ++ lib/dns/dbiterator.c | 138 + lib/dns/dbtable.c | 279 + lib/dns/diff.c | 673 + lib/dns/dispatch.c | 3906 ++++ lib/dns/dlz.c | 549 + lib/dns/dns64.c | 297 + lib/dns/dnssec.c | 2241 +++ lib/dns/dnstap.c | 1251 ++ lib/dns/dnstap.proto | 268 + lib/dns/ds.c | 151 + lib/dns/dst_api.c | 2029 ++ lib/dns/dst_gost.h | 58 + lib/dns/dst_internal.h | 306 + lib/dns/dst_lib.c | 56 + lib/dns/dst_openssl.h | 71 + lib/dns/dst_parse.c | 856 + lib/dns/dst_parse.h | 142 + lib/dns/dst_pkcs11.h | 38 + lib/dns/dst_result.c | 110 + lib/dns/dyndb.c | 467 + lib/dns/ecdb.c | 835 + lib/dns/fixedname.c | 39 + lib/dns/forward.c | 257 + lib/dns/gen-unix.h | 90 + lib/dns/gen-win32.h | 273 + lib/dns/gen.c | 923 + lib/dns/geoip.c | 881 + lib/dns/gssapi_link.c | 388 + lib/dns/gssapictx.c | 894 + lib/dns/hmac_link.c | 1811 ++ lib/dns/include/Makefile.in | 17 + lib/dns/include/dns/Makefile.in | 60 + lib/dns/include/dns/acache.h | 442 + lib/dns/include/dns/acl.h | 282 + lib/dns/include/dns/adb.h | 841 + lib/dns/include/dns/badcache.h | 151 + lib/dns/include/dns/bit.h | 30 + lib/dns/include/dns/byaddr.h | 166 + lib/dns/include/dns/cache.h | 342 + lib/dns/include/dns/callbacks.h | 102 + lib/dns/include/dns/catz.h | 469 + lib/dns/include/dns/cert.h | 62 + lib/dns/include/dns/client.h | 674 + lib/dns/include/dns/clientinfo.h | 82 + lib/dns/include/dns/compress.h | 291 + lib/dns/include/dns/db.h | 1690 ++ lib/dns/include/dns/dbiterator.h | 294 + lib/dns/include/dns/dbtable.h | 158 + lib/dns/include/dns/diff.h | 279 + lib/dns/include/dns/dispatch.h | 614 + lib/dns/include/dns/dlz.h | 339 + lib/dns/include/dns/dlz_dlopen.h | 172 + lib/dns/include/dns/dns64.h | 171 + lib/dns/include/dns/dnssec.h | 383 + lib/dns/include/dns/dnstap.h | 377 + lib/dns/include/dns/ds.h | 52 + lib/dns/include/dns/dsdigest.h | 71 + lib/dns/include/dns/dyndb.h | 161 + lib/dns/include/dns/ecdb.h | 48 + lib/dns/include/dns/edns.h | 27 + lib/dns/include/dns/events.h | 85 + lib/dns/include/dns/fixedname.h | 84 + lib/dns/include/dns/forward.h | 139 + lib/dns/include/dns/geoip.h | 115 + lib/dns/include/dns/ipkeylist.h | 87 + lib/dns/include/dns/iptable.h | 72 + lib/dns/include/dns/journal.h | 298 + lib/dns/include/dns/keydata.h | 51 + lib/dns/include/dns/keyflags.h | 47 + lib/dns/include/dns/keytable.h | 439 + lib/dns/include/dns/keyvalues.h | 113 + lib/dns/include/dns/lib.h | 52 + lib/dns/include/dns/log.h | 111 + lib/dns/include/dns/lookup.h | 130 + lib/dns/include/dns/master.h | 382 + lib/dns/include/dns/masterdump.h | 428 + lib/dns/include/dns/message.h | 1436 ++ lib/dns/include/dns/name.h | 1423 ++ lib/dns/include/dns/ncache.h | 186 + lib/dns/include/dns/nsec.h | 111 + lib/dns/include/dns/nsec3.h | 271 + lib/dns/include/dns/nta.h | 204 + lib/dns/include/dns/opcode.h | 44 + lib/dns/include/dns/order.h | 92 + lib/dns/include/dns/peer.h | 263 + lib/dns/include/dns/portlist.h | 100 + lib/dns/include/dns/private.h | 67 + lib/dns/include/dns/rbt.h | 1144 ++ lib/dns/include/dns/rcode.h | 106 + lib/dns/include/dns/rdata.h | 774 + lib/dns/include/dns/rdataclass.h | 94 + lib/dns/include/dns/rdatalist.h | 123 + lib/dns/include/dns/rdataset.h | 710 + lib/dns/include/dns/rdatasetiter.h | 163 + lib/dns/include/dns/rdataslab.h | 178 + lib/dns/include/dns/rdatatype.h | 97 + lib/dns/include/dns/request.h | 403 + lib/dns/include/dns/resolver.h | 714 + lib/dns/include/dns/result.h | 201 + lib/dns/include/dns/rootns.h | 38 + lib/dns/include/dns/rpz.h | 380 + lib/dns/include/dns/rriterator.h | 184 + lib/dns/include/dns/rrl.h | 277 + lib/dns/include/dns/sdb.h | 214 + lib/dns/include/dns/sdlz.h | 374 + lib/dns/include/dns/secalg.h | 71 + lib/dns/include/dns/secproto.h | 64 + lib/dns/include/dns/soa.h | 98 + lib/dns/include/dns/ssu.h | 259 + lib/dns/include/dns/stats.h | 466 + lib/dns/include/dns/tcpmsg.h | 142 + lib/dns/include/dns/time.h | 73 + lib/dns/include/dns/timer.h | 47 + lib/dns/include/dns/tkey.h | 248 + lib/dns/include/dns/tsec.h | 131 + lib/dns/include/dns/tsig.h | 293 + lib/dns/include/dns/ttl.h | 82 + lib/dns/include/dns/types.h | 437 + lib/dns/include/dns/update.h | 66 + lib/dns/include/dns/validator.h | 259 + lib/dns/include/dns/version.h | 27 + lib/dns/include/dns/view.h | 1353 ++ lib/dns/include/dns/xfrin.h | 112 + lib/dns/include/dns/zone.h | 2515 +++ lib/dns/include/dns/zonekey.h | 37 + lib/dns/include/dns/zt.h | 234 + lib/dns/include/dst/Makefile.in | 34 + lib/dns/include/dst/dst.h | 995 + lib/dns/include/dst/gssapi.h | 215 + lib/dns/include/dst/lib.h | 34 + lib/dns/include/dst/result.h | 67 + lib/dns/ipkeylist.c | 248 + lib/dns/iptable.c | 182 + lib/dns/journal.c | 2364 +++ lib/dns/key.c | 187 + lib/dns/keydata.c | 84 + lib/dns/keytable.c | 768 + lib/dns/lib.c | 161 + lib/dns/log.c | 100 + lib/dns/lookup.c | 491 + lib/dns/mapapi | 16 + lib/dns/master.c | 3242 +++ lib/dns/masterdump.c | 2127 ++ lib/dns/message.c | 4362 +++++ lib/dns/name.c | 2734 +++ lib/dns/ncache.c | 750 + lib/dns/nsec.c | 445 + lib/dns/nsec3.c | 2113 ++ lib/dns/nta.c | 722 + lib/dns/openssl_link.c | 464 + lib/dns/openssldh_link.c | 793 + lib/dns/openssldsa_link.c | 815 + lib/dns/opensslecdsa_link.c | 657 + lib/dns/openssleddsa_link.c | 676 + lib/dns/opensslgost_link.c | 630 + lib/dns/opensslrsa_link.c | 1830 ++ lib/dns/order.c | 163 + lib/dns/peer.c | 887 + lib/dns/pkcs11.c | 45 + lib/dns/pkcs11dh_link.c | 1136 ++ lib/dns/pkcs11dsa_link.c | 1126 ++ lib/dns/pkcs11ecdsa_link.c | 1198 ++ lib/dns/pkcs11eddsa_link.c | 1185 ++ lib/dns/pkcs11gost_link.c | 957 + lib/dns/pkcs11rsa_link.c | 2237 +++ lib/dns/portlist.c | 261 + lib/dns/private.c | 367 + lib/dns/rbt.c | 3672 ++++ lib/dns/rbtdb.c | 10431 ++++++++++ lib/dns/rbtdb.h | 50 + lib/dns/rbtdb64.c | 17 + lib/dns/rbtdb64.h | 39 + lib/dns/rcode.c | 593 + lib/dns/rdata.c | 2432 +++ lib/dns/rdata/any_255/tsig_250.c | 598 + lib/dns/rdata/any_255/tsig_250.h | 31 + lib/dns/rdata/ch_3/a_1.c | 314 + lib/dns/rdata/ch_3/a_1.h | 28 + lib/dns/rdata/generic/afsdb_18.c | 304 + lib/dns/rdata/generic/afsdb_18.h | 27 + lib/dns/rdata/generic/avc_258.c | 163 + lib/dns/rdata/generic/avc_258.h | 30 + lib/dns/rdata/generic/caa_257.c | 365 + lib/dns/rdata/generic/caa_257.h | 26 + lib/dns/rdata/generic/cdnskey_60.c | 170 + lib/dns/rdata/generic/cdnskey_60.h | 18 + lib/dns/rdata/generic/cds_59.c | 174 + lib/dns/rdata/generic/cds_59.h | 18 + lib/dns/rdata/generic/cert_37.c | 277 + lib/dns/rdata/generic/cert_37.h | 27 + lib/dns/rdata/generic/cname_5.c | 228 + lib/dns/rdata/generic/cname_5.h | 22 + lib/dns/rdata/generic/csync_62.c | 269 + lib/dns/rdata/generic/csync_62.h | 28 + lib/dns/rdata/generic/dlv_32769.c | 170 + lib/dns/rdata/generic/dlv_32769.h | 19 + lib/dns/rdata/generic/dname_39.c | 228 + lib/dns/rdata/generic/dname_39.h | 25 + lib/dns/rdata/generic/dnskey_48.c | 172 + lib/dns/rdata/generic/dnskey_48.h | 21 + lib/dns/rdata/generic/doa_259.c | 358 + lib/dns/rdata/generic/doa_259.h | 27 + lib/dns/rdata/generic/ds_43.c | 400 + lib/dns/rdata/generic/ds_43.h | 28 + lib/dns/rdata/generic/eui48_108.c | 210 + lib/dns/rdata/generic/eui48_108.h | 21 + lib/dns/rdata/generic/eui64_109.c | 215 + lib/dns/rdata/generic/eui64_109.h | 21 + lib/dns/rdata/generic/gpos_27.c | 247 + lib/dns/rdata/generic/gpos_27.h | 30 + lib/dns/rdata/generic/hinfo_13.c | 216 + lib/dns/rdata/generic/hinfo_13.h | 25 + lib/dns/rdata/generic/hip_55.c | 497 + lib/dns/rdata/generic/hip_55.h | 41 + lib/dns/rdata/generic/ipseckey_45.c | 496 + lib/dns/rdata/generic/ipseckey_45.h | 29 + lib/dns/rdata/generic/isdn_20.c | 237 + lib/dns/rdata/generic/isdn_20.h | 28 + lib/dns/rdata/generic/key_25.c | 436 + lib/dns/rdata/generic/key_25.h | 30 + lib/dns/rdata/generic/keydata_65533.c | 441 + lib/dns/rdata/generic/keydata_65533.h | 29 + lib/dns/rdata/generic/l32_105.c | 228 + lib/dns/rdata/generic/l32_105.h | 22 + lib/dns/rdata/generic/l64_106.c | 223 + lib/dns/rdata/generic/l64_106.h | 22 + lib/dns/rdata/generic/loc_29.c | 811 + lib/dns/rdata/generic/loc_29.h | 36 + lib/dns/rdata/generic/lp_107.c | 271 + lib/dns/rdata/generic/lp_107.h | 23 + lib/dns/rdata/generic/mb_7.c | 230 + lib/dns/rdata/generic/mb_7.h | 23 + lib/dns/rdata/generic/md_3.c | 232 + lib/dns/rdata/generic/md_3.h | 24 + lib/dns/rdata/generic/mf_4.c | 231 + lib/dns/rdata/generic/mf_4.h | 23 + lib/dns/rdata/generic/mg_8.c | 226 + lib/dns/rdata/generic/mg_8.h | 23 + lib/dns/rdata/generic/minfo_14.c | 321 + lib/dns/rdata/generic/minfo_14.h | 24 + lib/dns/rdata/generic/mr_9.c | 227 + lib/dns/rdata/generic/mr_9.h | 23 + lib/dns/rdata/generic/mx_15.c | 336 + lib/dns/rdata/generic/mx_15.h | 24 + lib/dns/rdata/generic/naptr_35.c | 662 + lib/dns/rdata/generic/naptr_35.h | 33 + lib/dns/rdata/generic/nid_104.c | 223 + lib/dns/rdata/generic/nid_104.h | 22 + lib/dns/rdata/generic/ninfo_56.c | 191 + lib/dns/rdata/generic/ninfo_56.h | 34 + lib/dns/rdata/generic/ns_2.c | 247 + lib/dns/rdata/generic/ns_2.h | 24 + lib/dns/rdata/generic/nsec3_50.c | 402 + lib/dns/rdata/generic/nsec3_50.h | 112 + lib/dns/rdata/generic/nsec3param_51.c | 313 + lib/dns/rdata/generic/nsec3param_51.h | 32 + lib/dns/rdata/generic/nsec_47.c | 282 + lib/dns/rdata/generic/nsec_47.h | 27 + lib/dns/rdata/generic/null_10.c | 183 + lib/dns/rdata/generic/null_10.h | 25 + lib/dns/rdata/generic/nxt_30.c | 325 + lib/dns/rdata/generic/nxt_30.h | 27 + lib/dns/rdata/generic/openpgpkey_61.c | 242 + lib/dns/rdata/generic/openpgpkey_61.h | 22 + lib/dns/rdata/generic/opt_41.c | 413 + lib/dns/rdata/generic/opt_41.h | 48 + lib/dns/rdata/generic/proforma.c | 183 + lib/dns/rdata/generic/proforma.h | 23 + lib/dns/rdata/generic/ptr_12.c | 268 + lib/dns/rdata/generic/ptr_12.h | 23 + lib/dns/rdata/generic/rkey_57.c | 168 + lib/dns/rdata/generic/rkey_57.h | 17 + lib/dns/rdata/generic/rp_17.c | 312 + lib/dns/rdata/generic/rp_17.h | 27 + lib/dns/rdata/generic/rrsig_46.c | 613 + lib/dns/rdata/generic/rrsig_46.h | 34 + lib/dns/rdata/generic/rt_21.c | 307 + lib/dns/rdata/generic/rt_21.h | 26 + lib/dns/rdata/generic/sig_24.c | 575 + lib/dns/rdata/generic/sig_24.h | 35 + lib/dns/rdata/generic/sink_40.c | 278 + lib/dns/rdata/generic/sink_40.h | 25 + lib/dns/rdata/generic/smimea_53.c | 156 + lib/dns/rdata/generic/smimea_53.h | 17 + lib/dns/rdata/generic/soa_6.c | 438 + lib/dns/rdata/generic/soa_6.h | 30 + lib/dns/rdata/generic/spf_99.c | 163 + lib/dns/rdata/generic/spf_99.h | 34 + lib/dns/rdata/generic/sshfp_44.c | 263 + lib/dns/rdata/generic/sshfp_44.h | 28 + lib/dns/rdata/generic/ta_32768.c | 165 + lib/dns/rdata/generic/ta_32768.h | 20 + lib/dns/rdata/generic/talink_58.c | 264 + lib/dns/rdata/generic/talink_58.h | 24 + lib/dns/rdata/generic/tkey_249.c | 556 + lib/dns/rdata/generic/tkey_249.h | 34 + lib/dns/rdata/generic/tlsa_52.c | 334 + lib/dns/rdata/generic/tlsa_52.h | 29 + lib/dns/rdata/generic/txt_16.c | 360 + lib/dns/rdata/generic/txt_16.h | 45 + lib/dns/rdata/generic/unspec_103.c | 187 + lib/dns/rdata/generic/unspec_103.h | 24 + lib/dns/rdata/generic/uri_256.c | 308 + lib/dns/rdata/generic/uri_256.h | 25 + lib/dns/rdata/generic/x25_19.c | 214 + lib/dns/rdata/generic/x25_19.h | 26 + lib/dns/rdata/hs_4/a_1.c | 227 + lib/dns/rdata/hs_4/a_1.h | 22 + lib/dns/rdata/in_1/a6_38.c | 460 + lib/dns/rdata/in_1/a6_38.h | 27 + lib/dns/rdata/in_1/a_1.c | 245 + lib/dns/rdata/in_1/a_1.h | 22 + lib/dns/rdata/in_1/aaaa_28.c | 241 + lib/dns/rdata/in_1/aaaa_28.h | 24 + lib/dns/rdata/in_1/apl_42.c | 456 + lib/dns/rdata/in_1/apl_42.h | 52 + lib/dns/rdata/in_1/dhcid_49.c | 232 + lib/dns/rdata/in_1/dhcid_49.h | 24 + lib/dns/rdata/in_1/kx_36.c | 284 + lib/dns/rdata/in_1/kx_36.h | 26 + lib/dns/rdata/in_1/nsap-ptr_23.c | 241 + lib/dns/rdata/in_1/nsap-ptr_23.h | 25 + lib/dns/rdata/in_1/nsap_22.c | 251 + lib/dns/rdata/in_1/nsap_22.h | 26 + lib/dns/rdata/in_1/px_26.c | 370 + lib/dns/rdata/in_1/px_26.h | 27 + lib/dns/rdata/in_1/srv_33.c | 394 + lib/dns/rdata/in_1/srv_33.h | 27 + lib/dns/rdata/in_1/wks_11.c | 412 + lib/dns/rdata/in_1/wks_11.h | 25 + lib/dns/rdata/rdatastructpre.h | 35 + lib/dns/rdata/rdatastructsuf.h | 15 + lib/dns/rdatalist.c | 415 + lib/dns/rdatalist_p.h | 63 + lib/dns/rdataset.c | 834 + lib/dns/rdatasetiter.c | 73 + lib/dns/rdataslab.c | 1134 ++ lib/dns/request.c | 1604 ++ lib/dns/resolver.c | 10327 ++++++++++ lib/dns/result.c | 443 + lib/dns/rootns.c | 528 + lib/dns/rpz.c | 2413 +++ lib/dns/rriterator.c | 204 + lib/dns/rrl.c | 1320 ++ lib/dns/sdb.c | 1593 ++ lib/dns/sdlz.c | 2172 +++ lib/dns/soa.c | 142 + lib/dns/spnego.asn1 | 50 + lib/dns/spnego.c | 1825 ++ lib/dns/spnego.h | 65 + lib/dns/spnego_asn1.c | 862 + lib/dns/spnego_asn1.pl | 193 + lib/dns/ssu.c | 711 + lib/dns/ssu_external.c | 262 + lib/dns/stats.c | 475 + lib/dns/tcpmsg.c | 239 + lib/dns/tests/Atffile | 34 + lib/dns/tests/Kdh.+002+18602.key | 1 + lib/dns/tests/Krsa.+005+29235.key | 5 + lib/dns/tests/Kyuafile | 33 + lib/dns/tests/Makefile.in | 266 + lib/dns/tests/acl_test.c | 374 + lib/dns/tests/db_test.c | 212 + lib/dns/tests/dbdiff_test.c | 166 + lib/dns/tests/dbiterator_test.c | 425 + lib/dns/tests/dbversion_test.c | 735 + lib/dns/tests/dh_test.c | 92 + lib/dns/tests/dispatch_test.c | 341 + lib/dns/tests/dnstap_test.c | 383 + lib/dns/tests/dnstest.c | 596 + lib/dns/tests/dnstest.h | 135 + lib/dns/tests/dst_test.c | 263 + lib/dns/tests/geoip_test.c | 711 + lib/dns/tests/gost_test.c | 379 + lib/dns/tests/keytable_test.c | 622 + lib/dns/tests/master_test.c | 684 + lib/dns/tests/mkraw.pl | 24 + lib/dns/tests/name_test.c | 772 + lib/dns/tests/nsec3_test.c | 207 + lib/dns/tests/peer_test.c | 140 + lib/dns/tests/private_test.c | 220 + lib/dns/tests/rbt_serialize_test.c | 456 + lib/dns/tests/rbt_test.c | 1437 ++ lib/dns/tests/rdata_test.c | 1214 ++ lib/dns/tests/rdataset_test.c | 125 + lib/dns/tests/rdatasetstats_test.c | 172 + lib/dns/tests/resolver_test.c | 228 + lib/dns/tests/rsa_test.c | 312 + lib/dns/tests/sigs_test.c | 479 + lib/dns/tests/testdata/db/data.db | 20 + 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.+001+00002.key | 1 + lib/dns/tests/testdata/dst/Ktest.+001+54622.key | 1 + .../tests/testdata/dst/Ktest.+001+54622.private | 10 + lib/dns/tests/testdata/dst/Ktest.+003+23616.key | 1 + .../tests/testdata/dst/Ktest.+003+23616.private | 7 + lib/dns/tests/testdata/dst/Ktest.+003+49667.key | 1 + lib/dns/tests/testdata/dst/test1.data | 3077 +++ lib/dns/tests/testdata/dst/test1.dsasig | 3 + lib/dns/tests/testdata/dst/test1.rsasig | 5 + lib/dns/tests/testdata/dst/test2.data | 3077 +++ 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 | 14 + lib/dns/tests/testdata/nsec3/2048.db | 14 + lib/dns/tests/testdata/nsec3/4096.db | 14 + lib/dns/tests/testdata/nsec3/min-1024.db | 18 + lib/dns/tests/testdata/nsec3/min-2048.db | 16 + lib/dns/tests/testdata/zt/zone1.db | 20 + 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 | 213 + lib/dns/tests/tsig_test.c | 491 + lib/dns/tests/update_test.c | 343 + lib/dns/tests/zonemgr_test.c | 253 + lib/dns/tests/zt_test.c | 342 + lib/dns/time.c | 205 + lib/dns/timer.c | 55 + lib/dns/tkey.c | 1526 ++ lib/dns/tsec.c | 158 + lib/dns/tsig.c | 2019 ++ lib/dns/ttl.c | 222 + lib/dns/update.c | 2077 ++ lib/dns/validator.c | 3985 ++++ lib/dns/version.c | 23 + lib/dns/view.c | 2411 +++ lib/dns/win32/DLLMain.c | 50 + lib/dns/win32/gen.dsp.in | 107 + lib/dns/win32/gen.dsw | 29 + lib/dns/win32/gen.mak.in | 267 + lib/dns/win32/gen.vcxproj.filters.in | 27 + lib/dns/win32/gen.vcxproj.in | 127 + lib/dns/win32/gen.vcxproj.user | 3 + lib/dns/win32/libdns.def.in | 1504 ++ lib/dns/win32/libdns.dsp.in | 937 + lib/dns/win32/libdns.dsw | 29 + lib/dns/win32/libdns.mak.in | 2890 +++ lib/dns/win32/libdns.vcxproj.filters.in | 690 + lib/dns/win32/libdns.vcxproj.in | 346 + lib/dns/win32/libdns.vcxproj.user | 3 + lib/dns/win32/version.c | 23 + lib/dns/xfrin.c | 1618 ++ lib/dns/zone.c | 19485 +++++++++++++++++++ lib/dns/zone_p.h | 46 + lib/dns/zonekey.c | 50 + lib/dns/zt.c | 601 + 490 files changed, 240839 insertions(+) create mode 100644 lib/dns/Atffile create mode 100644 lib/dns/Kyuafile create mode 100644 lib/dns/Makefile.in create mode 100644 lib/dns/acache.c create mode 100644 lib/dns/acl.c create mode 100644 lib/dns/adb.c create mode 100644 lib/dns/api 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/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_gost.h create mode 100644 lib/dns/dst_internal.h create mode 100644 lib/dns/dst_lib.c 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/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/geoip.c create mode 100644 lib/dns/gssapi_link.c create mode 100644 lib/dns/gssapictx.c create mode 100644 lib/dns/hmac_link.c create mode 100644 lib/dns/include/Makefile.in create mode 100644 lib/dns/include/dns/Makefile.in create mode 100644 lib/dns/include/dns/acache.h 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/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/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/keydata.h create mode 100644 lib/dns/include/dns/keyflags.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/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/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/lib.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/key.c create mode 100644 lib/dns/keydata.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/openssldsa_link.c create mode 100644 lib/dns/opensslecdsa_link.c create mode 100644 lib/dns/openssleddsa_link.c create mode 100644 lib/dns/opensslgost_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/pkcs11dh_link.c create mode 100644 lib/dns/pkcs11dsa_link.c create mode 100644 lib/dns/pkcs11ecdsa_link.c create mode 100644 lib/dns/pkcs11eddsa_link.c create mode 100644 lib/dns/pkcs11gost_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/rbtdb64.c create mode 100644 lib/dns/rbtdb64.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/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/unspec_103.c create mode 100644 lib/dns/rdata/generic/unspec_103.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/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/dhcid_49.c create mode 100644 lib/dns/rdata/in_1/dhcid_49.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/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/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/spnego.asn1 create mode 100644 lib/dns/spnego.c create mode 100644 lib/dns/spnego.h create mode 100644 lib/dns/spnego_asn1.c create mode 100644 lib/dns/spnego_asn1.pl 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/Atffile create mode 100644 lib/dns/tests/Kdh.+002+18602.key create mode 100644 lib/dns/tests/Krsa.+005+29235.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/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/gost_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/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/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/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.+001+00002.key create mode 100644 lib/dns/tests/testdata/dst/Ktest.+001+54622.key create mode 100644 lib/dns/tests/testdata/dst/Ktest.+001+54622.private create mode 100644 lib/dns/tests/testdata/dst/Ktest.+003+23616.key create mode 100644 lib/dns/tests/testdata/dst/Ktest.+003+23616.private create mode 100644 lib/dns/tests/testdata/dst/Ktest.+003+49667.key create mode 100644 lib/dns/tests/testdata/dst/test1.data create mode 100644 lib/dns/tests/testdata/dst/test1.dsasig create mode 100644 lib/dns/tests/testdata/dst/test1.rsasig create mode 100644 lib/dns/tests/testdata/dst/test2.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/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/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.dsp.in create mode 100644 lib/dns/win32/gen.dsw create mode 100644 lib/dns/win32/gen.mak.in 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.dsp.in create mode 100644 lib/dns/win32/libdns.dsw create mode 100644 lib/dns/win32/libdns.mak.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/zt.c (limited to 'lib/dns') diff --git a/lib/dns/Atffile b/lib/dns/Atffile new file mode 100644 index 0000000..1edb838 --- /dev/null +++ b/lib/dns/Atffile @@ -0,0 +1,5 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = bind9 + +tp: tests diff --git a/lib/dns/Kyuafile b/lib/dns/Kyuafile new file mode 100644 index 0000000..0739e3a --- /dev/null +++ b/lib/dns/Kyuafile @@ -0,0 +1,4 @@ +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..4a8549e --- /dev/null +++ b/lib/dns/Makefile.in @@ -0,0 +1,227 @@ +# 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 http://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@ + +@LIBDNS_API@ + +@BIND9_MAKE_INCLUDES@ + +USE_ISC_SPNEGO = @USE_ISC_SPNEGO@ + +CINCLUDES = -I. -I${top_srcdir}/lib/dns -Iinclude ${DNS_INCLUDES} \ + ${ISC_INCLUDES} @DST_OPENSSL_INC@ @DST_GSSAPI_INC@ + +CDEFINES = -DUSE_MD5 @CRYPTO@ @USE_GSSAPI@ ${USE_ISC_SPNEGO} + +CWARNINGS = + +ISCLIBS = ../../lib/isc/libisc.@A@ + +ISCDEPLIBS = ../../lib/isc/libisc.@A@ + +LIBS = @LIBS@ + +# Alphabetically + +OPENSSLGOSTLINKOBJS = opensslgost_link.@O@ +OPENSSLECDSALINKOBJS = opensslecdsa_link.@O@ +OPENSSLEDDSALINKOBJS = openssleddsa_link.@O@ +OPENSSLLINKOBJS = openssl_link.@O@ openssldh_link.@O@ openssldsa_link.@O@ \ + @OPENSSLECDSALINKOBJS@ @OPENSSLEDDSALINKOBJS@ \ + @OPENSSLGOSTLINKOBJS@ opensslrsa_link.@O@ + +PKCS11LINKOBJS = pkcs11dh_link.@O@ pkcs11dsa_link.@O@ pkcs11rsa_link.@O@ \ + pkcs11ecdsa_link.@O@ pkcs11eddsa_link.@O@ \ + pkcs11gost_link.@O@ pkcs11.@O@ + +DSTOBJS = @DST_EXTRA_OBJS@ @OPENSSLLINKOBJS@ @PKCS11LINKOBJS@ \ + dst_api.@O@ dst_lib.@O@ dst_parse.@O@ dst_result.@O@ \ + gssapi_link.@O@ gssapictx.@O@ hmac_link.@O@ key.@O@ + +GEOIPLINKOBJS = geoip.@O@ + +DNSTAPOBJS = dnstap.@O@ dnstap.pb-c.@O@ + +# Alphabetically +DNSOBJS = acache.@O@ 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@ dnssec.@O@ ds.@O@ dyndb.@O@ \ + fixedname.@O@ forward.@O@ \ + ipkeylist.@O@ iptable.@O@ journal.@O@ keydata.@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@ rbtdb64.@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@ zt.@O@ +PORTDNSOBJS = client.@O@ ecdb.@O@ + +OBJS= @DNSTAPOBJS@ ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS} \ + ${PORTDNSOBJS} @GEOIPLINKOBJS@ + + +# Alphabetically +OPENSSLGOSTLINKSRCS = opensslgost_link.c +OPENSSLECDSALINKSRCS = opensslecdsa_link.c +OPENSSLEDDSALINKSRCS = openssleddsa_link.c +OPENSSLLINKSRCS = openssl_link.c openssldh_link.c openssldsa_link.c \ + @OPENSSLECDSALINKSRCS@ @OPENSSLEDDSALINKSRCS@ \ + @OPENSSLGOSTLINKSRCS@ opensslrsa_link.c + +PKCS11LINKSRCS = pkcs11dh_link.c pkcs11dsa_link.c pkcs11rsa_link.c \ + pkcs11ecdsa_link.c pkcs11eddsa_link.c \ + pkcs11gost_link.c pkcs11.c + +DSTSRCS = @DST_EXTRA_SRCS@ @OPENSSLLINKSRCS@ @PKCS11LINKSRCS@ \ + dst_api.c dst_lib.c dst_parse.c \ + dst_result.c gssapi_link.c gssapictx.c \ + hmac_link.c key.c + +GEOIPLINKSRCS = geoip.c + +DNSTAPSRCS = dnstap.c dnstap.pb-c.c + +DNSSRCS = acache.c acl.c adb.c badcache. byaddr.c \ + cache.c callbacks.c clientinfo.c compress.c \ + db.c dbiterator.c dbtable.c diff.c dispatch.c \ + dlz.c dns64.c dnssec.c ds.c dyndb.c fixedname.c forward.c \ + ipkeylist.c iptable.c journal.c keydata.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 rbtdb64.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 zonekey.c zt.c ${OTHERSRCS} +PORTDNSSRCS = client.c ecdb.c + +SRCS = ${DSTSRCS} ${DNSSRCS} ${PORTDNSSRCS} @DNSTAPSRCS@ @GEOIPLINKSRCS@ + +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}\" \ + -DLIBINTERFACE=${LIBINTERFACE} \ + -DLIBREVISION=${LIBREVISION} \ + -DLIBAGE=${LIBAGE} \ + -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} \ + -version-info ${LIBINTERFACE}:${LIBREVISION}:${LIBAGE} \ + ${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 \ + ${BUILD_CPPFLAGS} ${BUILD_LDFLAGS} -o $@ ${srcdir}/gen.c ${BUILD_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 include/dns/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 + +rbtdb64.@O@: rbtdb64.c rbtdb.c + +depend: include +subdirs: include +${OBJS}: include + +# dnstap +dnstap.@O@: dnstap.c dnstap.pb-c.c + +dnstap.pb-c.c dnstap.pb-c.h include/dns/dnstap.pb-c.h: dnstap.proto + $(PROTOC_C) --c_out=. dnstap.proto + cp -f dnstap.pb-c.h include/dns + +dnstap.pb-c.@O@: dnstap.pb-c.c + +spnego.@O@: spnego_asn1.c spnego.h diff --git a/lib/dns/acache.c b/lib/dns/acache.c new file mode 100644 index 0000000..4e5fd40 --- /dev/null +++ b/lib/dns/acache.c @@ -0,0 +1,1821 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: acache.c,v 1.22 2008/02/07 23:46:54 tbox Exp $ */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(ISC_PLATFORM_HAVESTDATOMIC) +#if defined(__cplusplus) +#include +#else +#include +#endif +#endif + +#define ACACHE_MAGIC ISC_MAGIC('A', 'C', 'H', 'E') +#define DNS_ACACHE_VALID(acache) ISC_MAGIC_VALID(acache, ACACHE_MAGIC) + +#define ACACHEENTRY_MAGIC ISC_MAGIC('A', 'C', 'E', 'T') +#define DNS_ACACHEENTRY_VALID(entry) ISC_MAGIC_VALID(entry, ACACHEENTRY_MAGIC) + +#define DBBUCKETS 67 + +#if 0 +#define ATRACE(m) isc_log_write(dns_lctx, \ + DNS_LOGCATEGORY_DATABASE, \ + DNS_LOGMODULE_ACACHE, \ + ISC_LOG_DEBUG(3), \ + "acache %p: %s", acache, (m)) +#define AATRACE(a,m) isc_log_write(dns_lctx, \ + DNS_LOGCATEGORY_DATABASE, \ + DNS_LOGMODULE_ACACHE, \ + ISC_LOG_DEBUG(3), \ + "acache %p: %s", (a), (m)) +#else +#define ATRACE(m) +#define AATRACE(a, m) +#endif + +/* + * The following variables control incremental cleaning. + * MINSIZE is how many bytes is the floor for dns_acache_setcachesize(). + * CLEANERINCREMENT is how many entries are examined in one pass. + * (XXX simply derived from definitions in cache.c There may be better + * constants here.) + */ +#define DNS_ACACHE_MINSIZE 2097152U /* Bytes. 2097152 = 2 MB */ +#define DNS_ACACHE_CLEANERINCREMENT 1000 /* Number of entries. */ + +#define DEFAULT_ACACHE_ENTRY_LOCK_COUNT 1009 /*%< Should be prime. */ + +#if defined(ISC_RWLOCK_USEATOMIC) && \ + ((defined(ISC_PLATFORM_HAVESTDATOMIC) && defined(ATOMIC_LONG_LOCK_FREE)) || \ + defined(ISC_PLATFORM_HAVEATOMICSTORE)) +#define ACACHE_USE_RWLOCK 1 +#if (defined(ISC_PLATFORM_HAVESTDATOMIC) && defined(ATOMIC_LONG_LOCK_FREE)) +#define ACACHE_HAVESTDATOMIC 1 +#endif +#endif + +#ifdef ACACHE_USE_RWLOCK +#define ACACHE_INITLOCK(l) isc_rwlock_init((l), 0, 0) +#define ACACHE_DESTROYLOCK(l) isc_rwlock_destroy(l) +#define ACACHE_LOCK(l, t) RWLOCK((l), (t)) +#define ACACHE_UNLOCK(l, t) RWUNLOCK((l), (t)) + +#ifdef ACACHE_HAVESTDATOMIC +#define acache_storetime(entry, t) \ + atomic_store_explicit(&(entry)->lastused, (t), \ + memory_order_relaxed); +#else +#define acache_storetime(entry, t) \ + (isc_atomic_store((int32_t *)&(entry)->lastused, (t))) +#endif + +#else +#define ACACHE_INITLOCK(l) isc_mutex_init(l) +#define ACACHE_DESTROYLOCK(l) DESTROYLOCK(l) +#define ACACHE_LOCK(l, t) LOCK(l) +#define ACACHE_UNLOCK(l, t) UNLOCK(l) + +#define acache_storetime(entry, t) ((entry)->lastused = (t)) +#endif + +/* Locked by acache lock */ +typedef struct dbentry { + ISC_LINK(struct dbentry) link; + + dns_db_t *db; + ISC_LIST(dns_acacheentry_t) originlist; + ISC_LIST(dns_acacheentry_t) referlist; +} dbentry_t; + +typedef ISC_LIST(dbentry_t) dbentrylist_t; + +typedef struct acache_cleaner acache_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)->resched_event == NULL) + +struct acache_cleaner { + isc_mutex_t lock; + /* + * Locks overmem_event, overmem. (See cache.c) + */ + + dns_acache_t *acache; + unsigned int cleaning_interval; /* The cleaning-interval + from named.conf, + in seconds. */ + + isc_stdtime_t last_cleanup_time; /* The time when the last + cleanup task completed */ + + isc_timer_t *cleaning_timer; + isc_event_t *resched_event; /* Sent by cleaner task to + itself to reschedule */ + isc_event_t *overmem_event; + + dns_acacheentry_t *current_entry; /* The bookmark entry to + restart the cleaning. + Locked by acache lock. */ + int increment; /* Number of entries to + clean in one increment */ + + unsigned long ncleaned; /* Number of entries cleaned + up (for logging purposes) */ + cleaner_state_t state; /* Idle/Busy/Done. */ + bool overmem; /* The acache is in an overmem + state. */ +}; + +struct dns_acachestats { + unsigned int hits; + unsigned int queries; + unsigned int misses; + unsigned int adds; + unsigned int deleted; + unsigned int cleaned; + unsigned int cleaner_runs; + unsigned int overmem; + unsigned int overmem_nocreates; + unsigned int nomem; +}; + +/* + * The actual acache object. + */ + +struct dns_acache { + unsigned int magic; + + isc_mem_t *mctx; + isc_refcount_t refs; + +#ifdef ACACHE_USE_RWLOCK + isc_rwlock_t *entrylocks; +#else + isc_mutex_t *entrylocks; +#endif + + isc_mutex_t lock; + + int live_cleaners; + acache_cleaner_t cleaner; + ISC_LIST(dns_acacheentry_t) entries; + unsigned int dbentries; + dbentrylist_t dbbucket[DBBUCKETS]; + + bool shutting_down; + + isc_task_t *task; + isc_event_t cevent; + bool cevent_sent; + + dns_acachestats_t stats; +}; + +struct dns_acacheentry { + unsigned int magic; + + unsigned int locknum; + isc_refcount_t references; + + dns_acache_t *acache; + + /* Data for Management of cache entries */ + ISC_LINK(dns_acacheentry_t) link; + ISC_LINK(dns_acacheentry_t) olink; + ISC_LINK(dns_acacheentry_t) rlink; + + dns_db_t *origdb; /* reference to the DB + holding this entry */ + + /* Cache data */ + dns_zone_t *zone; /* zone this entry + belongs to */ + dns_db_t *db; /* DB this entry belongs to */ + dns_dbversion_t *version; /* the version of the DB */ + dns_dbnode_t *node; /* node this entry + belongs to */ + dns_name_t *foundname; /* corresponding DNS name + and rdataset */ + + /* Callback function and its argument */ + void (*callback)(dns_acacheentry_t *, void **); + void *cbarg; + + /* Timestamp of the last time this entry is referred to */ +#ifdef ACACHE_HAVESTDATOMIC + atomic_uint_fast32_t lastused; +#else + isc_stdtime32_t lastused; +#endif +}; + +/* + * Internal functions (and prototypes). + */ +static inline bool check_noentry(dns_acache_t *acache); +static void destroy(dns_acache_t *acache); +static void shutdown_entries(dns_acache_t *acache); +static void shutdown_buckets(dns_acache_t *acache); +static void destroy_entry(dns_acacheentry_t *ent); +static inline void unlink_dbentries(dns_acache_t *acache, + dns_acacheentry_t *ent); +static inline isc_result_t finddbent(dns_acache_t *acache, + dns_db_t *db, dbentry_t **dbentryp); +static inline void clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry); +static isc_result_t acache_cleaner_init(dns_acache_t *acache, + isc_timermgr_t *timermgr, + acache_cleaner_t *cleaner); +static void acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event); +static void acache_incremental_cleaning_action(isc_task_t *task, + isc_event_t *event); +static void acache_overmem_cleaning_action(isc_task_t *task, + isc_event_t *event); +static void acache_cleaner_shutdown_action(isc_task_t *task, + isc_event_t *event); + +/* + * acache should be locked. If it is not, the stats can get out of whack, + * which is not a big deal for us since this is for debugging / stats + */ +static void +reset_stats(dns_acache_t *acache) { + acache->stats.hits = 0; + acache->stats.queries = 0; + acache->stats.misses = 0; + acache->stats.adds = 0; + acache->stats.deleted = 0; + acache->stats.cleaned = 0; + acache->stats.overmem = 0; + acache->stats.overmem_nocreates = 0; + acache->stats.nomem = 0; +} + +/* + * The acache must be locked before calling. + */ +static inline bool +check_noentry(dns_acache_t *acache) { + if (ISC_LIST_EMPTY(acache->entries) && acache->dbentries == 0) { + return (true); + } + + return (false); +} + +/* + * The acache must be locked before calling. + */ +static void +shutdown_entries(dns_acache_t *acache) { + dns_acacheentry_t *entry, *entry_next; + + REQUIRE(DNS_ACACHE_VALID(acache)); + INSIST(acache->shutting_down); + + /* + * Release the dependency of all entries, and detach them. + */ + for (entry = ISC_LIST_HEAD(acache->entries); + entry != NULL; + entry = entry_next) { + entry_next = ISC_LIST_NEXT(entry, link); + + ACACHE_LOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + /* + * If the cleaner holds this entry, it will be unlinked and + * freed in the cleaner later. + */ + if (acache->cleaner.current_entry != entry) + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + if (entry->callback != NULL) { + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + } + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + if (acache->cleaner.current_entry != entry) + dns_acache_detachentry(&entry); + } +} + +/* + * The acache must be locked before calling. + */ +static void +shutdown_buckets(dns_acache_t *acache) { + int i; + dbentry_t *dbent; + + REQUIRE(DNS_ACACHE_VALID(acache)); + INSIST(acache->shutting_down); + + for (i = 0; i < DBBUCKETS; i++) { + while ((dbent = ISC_LIST_HEAD(acache->dbbucket[i])) != NULL) { + INSIST(ISC_LIST_EMPTY(dbent->originlist) && + ISC_LIST_EMPTY(dbent->referlist)); + ISC_LIST_UNLINK(acache->dbbucket[i], dbent, link); + + dns_db_detach(&dbent->db); + + isc_mem_put(acache->mctx, dbent, sizeof(*dbent)); + + acache->dbentries--; + } + } + + INSIST(acache->dbentries == 0); +} + +static void +shutdown_task(isc_task_t *task, isc_event_t *ev) { + dns_acache_t *acache; + + UNUSED(task); + + acache = ev->ev_arg; + INSIST(DNS_ACACHE_VALID(acache)); + + isc_event_free(&ev); + + LOCK(&acache->lock); + + shutdown_entries(acache); + shutdown_buckets(acache); + + UNLOCK(&acache->lock); + + dns_acache_detach(&acache); +} + +/* The acache and the entry must be locked before calling. */ +static inline void +unlink_dbentries(dns_acache_t *acache, dns_acacheentry_t *ent) { + isc_result_t result; + dbentry_t *dbent; + + if (ISC_LINK_LINKED(ent, olink)) { + INSIST(ent->origdb != NULL); + dbent = NULL; + result = finddbent(acache, ent->origdb, &dbent); + INSIST(result == ISC_R_SUCCESS); + + ISC_LIST_UNLINK(dbent->originlist, ent, olink); + } + if (ISC_LINK_LINKED(ent, rlink)) { + INSIST(ent->db != NULL); + dbent = NULL; + result = finddbent(acache, ent->db, &dbent); + INSIST(result == ISC_R_SUCCESS); + + ISC_LIST_UNLINK(dbent->referlist, ent, rlink); + } +} + +/* There must not be a reference to this entry. */ +static void +destroy_entry(dns_acacheentry_t *entry) { + dns_acache_t *acache; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + acache = entry->acache; + REQUIRE(DNS_ACACHE_VALID(acache)); + + /* + * Since there is no reference to this entry, it is safe to call + * clear_entry() here. + */ + clear_entry(acache, entry); + + isc_mem_put(acache->mctx, entry, sizeof(*entry)); + + dns_acache_detach(&acache); +} + +static void +destroy(dns_acache_t *acache) { + int i; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + ATRACE("destroy"); + + isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0); + + if (acache->cleaner.overmem_event != NULL) + isc_event_free(&acache->cleaner.overmem_event); + + if (acache->cleaner.resched_event != NULL) + isc_event_free(&acache->cleaner.resched_event); + + if (acache->task != NULL) + isc_task_detach(&acache->task); + + for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++) + ACACHE_DESTROYLOCK(&acache->entrylocks[i]); + isc_mem_put(acache->mctx, acache->entrylocks, + sizeof(*acache->entrylocks) * + DEFAULT_ACACHE_ENTRY_LOCK_COUNT); + + DESTROYLOCK(&acache->cleaner.lock); + + DESTROYLOCK(&acache->lock); + acache->magic = 0; + + isc_mem_putanddetach(&acache->mctx, acache, sizeof(*acache)); +} + +static inline isc_result_t +finddbent(dns_acache_t *acache, dns_db_t *db, dbentry_t **dbentryp) { + int bucket; + dbentry_t *dbentry; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + REQUIRE(dbentryp != NULL && *dbentryp == NULL); + + /* + * The caller must be holding the acache lock. + */ + + bucket = isc_hash_function(&db, sizeof(db), true, NULL) % DBBUCKETS; + + for (dbentry = ISC_LIST_HEAD(acache->dbbucket[bucket]); + dbentry != NULL; + dbentry = ISC_LIST_NEXT(dbentry, link)) { + if (dbentry->db == db) + break; + } + + *dbentryp = dbentry; + + if (dbentry == NULL) + return (ISC_R_NOTFOUND); + else + return (ISC_R_SUCCESS); +} + +static inline void +clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry) { + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + /* + * The caller must be holing the entry lock. + */ + + if (entry->foundname) { + dns_rdataset_t *rdataset, *rdataset_next; + + for (rdataset = ISC_LIST_HEAD(entry->foundname->list); + rdataset != NULL; + rdataset = rdataset_next) { + rdataset_next = ISC_LIST_NEXT(rdataset, link); + ISC_LIST_UNLINK(entry->foundname->list, + rdataset, link); + dns_rdataset_disassociate(rdataset); + isc_mem_put(acache->mctx, rdataset, sizeof(*rdataset)); + } + if (dns_name_dynamic(entry->foundname)) + dns_name_free(entry->foundname, acache->mctx); + isc_mem_put(acache->mctx, entry->foundname, + sizeof(*entry->foundname)); + entry->foundname = NULL; + } + + if (entry->node != NULL) { + INSIST(entry->db != NULL); + dns_db_detachnode(entry->db, &entry->node); + } + if (entry->version != NULL) { + INSIST(entry->db != NULL); + dns_db_closeversion(entry->db, &entry->version, false); + } + if (entry->db != NULL) + dns_db_detach(&entry->db); + if (entry->zone != NULL) + dns_zone_detach(&entry->zone); + + if (entry->origdb != NULL) + dns_db_detach(&entry->origdb); +} + +static isc_result_t +acache_cleaner_init(dns_acache_t *acache, isc_timermgr_t *timermgr, + acache_cleaner_t *cleaner) +{ + int result; + + ATRACE("acache cleaner init"); + + result = isc_mutex_init(&cleaner->lock); + if (result != ISC_R_SUCCESS) + goto fail; + + cleaner->increment = DNS_ACACHE_CLEANERINCREMENT; + cleaner->state = cleaner_s_idle; + cleaner->acache = acache; + cleaner->overmem = false; + + cleaner->cleaning_timer = NULL; + cleaner->resched_event = NULL; + cleaner->overmem_event = NULL; + cleaner->current_entry = NULL; + + if (timermgr != NULL) { + cleaner->acache->live_cleaners++; + + result = isc_task_onshutdown(acache->task, + acache_cleaner_shutdown_action, + acache); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "acache cleaner: " + "isc_task_onshutdown() failed: %s", + dns_result_totext(result)); + goto cleanup; + } + + cleaner->cleaning_interval = 0; /* Initially turned off. */ + isc_stdtime_get(&cleaner->last_cleanup_time); + result = isc_timer_create(timermgr, isc_timertype_inactive, + NULL, NULL, + acache->task, + acache_cleaning_timer_action, + cleaner, &cleaner->cleaning_timer); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_create() failed: %s", + dns_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + cleaner->resched_event = + isc_event_allocate(acache->mctx, cleaner, + DNS_EVENT_ACACHECLEAN, + acache_incremental_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->resched_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + cleaner->overmem_event = + isc_event_allocate(acache->mctx, cleaner, + DNS_EVENT_ACACHEOVERMEM, + acache_overmem_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->overmem_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } + + 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->cleaning_timer != NULL) + isc_timer_detach(&cleaner->cleaning_timer); + cleaner->acache->live_cleaners--; + DESTROYLOCK(&cleaner->lock); + fail: + return (result); +} + +static void +begin_cleaning(acache_cleaner_t *cleaner) { + dns_acacheentry_t *head; + dns_acache_t *acache = cleaner->acache; + + /* + * This function does not have to lock the cleaner, since critical + * parameters (except current_entry, which is locked by acache lock,) + * are only used in a single task context. + */ + + REQUIRE(CLEANER_IDLE(cleaner)); + INSIST(DNS_ACACHE_VALID(acache)); + INSIST(cleaner->current_entry == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1), + "begin acache cleaning, mem inuse %lu", + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + LOCK(&acache->lock); + + head = ISC_LIST_HEAD(acache->entries); + if (head != NULL) + dns_acache_attachentry(head, &cleaner->current_entry); + + UNLOCK(&acache->lock); + + if (cleaner->current_entry != NULL) { + cleaner->ncleaned = 0; + cleaner->state = cleaner_s_busy; + isc_task_send(acache->task, &cleaner->resched_event); + } + + return; +} + +static void +end_cleaning(acache_cleaner_t *cleaner, isc_event_t *event) { + dns_acache_t *acache = cleaner->acache; + + REQUIRE(CLEANER_BUSY(cleaner)); + REQUIRE(event != NULL); + REQUIRE(DNS_ACACHEENTRY_VALID(cleaner->current_entry)); + + /* No need to lock the cleaner (see begin_cleaning()). */ + + LOCK(&acache->lock); + + /* + * Even if the cleaner has the last reference to the entry, which means + * the entry has been unused, it may still be linked if unlinking the + * entry has been delayed due to the reference. + */ + if (isc_refcount_current(&cleaner->current_entry->references) == 1) { + INSIST(cleaner->current_entry->callback == NULL); + + if (ISC_LINK_LINKED(cleaner->current_entry, link)) { + ISC_LIST_UNLINK(acache->entries, + cleaner->current_entry, link); + } + } + dns_acache_detachentry(&cleaner->current_entry); + + if (cleaner->overmem) + acache->stats.overmem++; + acache->stats.cleaned += cleaner->ncleaned; + acache->stats.cleaner_runs++; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_NOTICE, + "acache %p stats: hits=%d misses=%d queries=%d " + "adds=%d deleted=%d " + "cleaned=%d cleaner_runs=%d overmem=%d " + "overmem_nocreates=%d nomem=%d", + acache, + acache->stats.hits, acache->stats.misses, + acache->stats.queries, + acache->stats.adds, acache->stats.deleted, + acache->stats.cleaned, acache->stats.cleaner_runs, + acache->stats.overmem, acache->stats.overmem_nocreates, + acache->stats.nomem); + reset_stats(acache); + + isc_stdtime_get(&cleaner->last_cleanup_time); + + UNLOCK(&acache->lock); + + dns_acache_setcleaninginterval(cleaner->acache, + cleaner->cleaning_interval); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "end acache cleaning, " + "%lu entries cleaned, mem inuse %lu", + cleaner->ncleaned, + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + if (cleaner->overmem) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_NOTICE, + "acache is still in overmem state " + "after cleaning"); + } + + cleaner->ncleaned = 0; + cleaner->state = cleaner_s_idle; + cleaner->resched_event = event; +} + +/* + * This is run once for every acache-cleaning-interval as defined + * in named.conf. + */ +static void +acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + + UNUSED(task); + + INSIST(event->ev_type == ISC_TIMEREVENT_TICK); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "acache cleaning timer fired, " + "cleaner state = %d", cleaner->state); + + if (cleaner->state == cleaner_s_idle) + begin_cleaning(cleaner); + + isc_event_free(&event); +} + +/* The caller must hold entry lock. */ +static inline bool +entry_stale(acache_cleaner_t *cleaner, dns_acacheentry_t *entry, + isc_stdtime32_t now32, unsigned int interval) +{ + /* + * If the callback has been canceled, we definitely do not need the + * entry. + */ + if (entry->callback == NULL) + return (true); + + if (interval > cleaner->cleaning_interval) + interval = cleaner->cleaning_interval; + + if (entry->lastused + interval < now32) + return (true); + + /* + * If the acache is in the overmem state, probabilistically decide if + * the entry should be purged, based on the time passed from its last + * use and the cleaning interval. + */ + if (cleaner->overmem) { + unsigned int passed; + uint32_t val; + + if (isc_serial_ge(now32, entry->lastused)) + passed = now32 - entry->lastused; /* <= interval */ + else + passed = 0; + + if (passed > interval / 2) + return (true); + isc_random_get(&val); + if (passed > interval / 4) + return (val % 4 == 0); + return (val % 8 == 0); + } + + return (false); +} + +/* + * Do incremental cleaning. + */ +static void +acache_incremental_cleaning_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + dns_acache_t *acache = cleaner->acache; + dns_acacheentry_t *entry, *next = NULL; + int n_entries; + isc_stdtime32_t now32, last32; + isc_stdtime_t now; + unsigned int interval; + + INSIST(DNS_ACACHE_VALID(acache)); + INSIST(task == acache->task); + INSIST(event->ev_type == DNS_EVENT_ACACHECLEAN); + + if (cleaner->state == cleaner_s_done) { + cleaner->state = cleaner_s_busy; + end_cleaning(cleaner, event); + return; + } + + INSIST(CLEANER_BUSY(cleaner)); + + n_entries = cleaner->increment; + + isc_stdtime_get(&now); + isc_stdtime_convert32(now, &now32); + + LOCK(&acache->lock); + + entry = cleaner->current_entry; + isc_stdtime_convert32(cleaner->last_cleanup_time, &last32); + if (isc_serial_ge(now32, last32)) + interval = now32 - last32; + else + interval = 0; + + while (n_entries-- > 0) { + bool is_stale = false; + + INSIST(entry != NULL); + + next = ISC_LIST_NEXT(entry, link); + + ACACHE_LOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + is_stale = entry_stale(cleaner, entry, now32, interval); + if (is_stale) { + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + cleaner->ncleaned++; + } + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + if (is_stale) + dns_acache_detachentry(&entry); + + if (next == NULL) { + if (cleaner->overmem) { + entry = ISC_LIST_HEAD(acache->entries); + if (entry != NULL) { + /* + * If we are still in the overmem + * state, keep cleaning. In case we + * exit from the loop immediately after + * this, reset next to the head entry + * as we'll expect it will be never + * NULL. + */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), + "acache cleaner: " + "still overmem, " + "reset and try again"); + next = entry; + continue; + } + } + + UNLOCK(&acache->lock); + end_cleaning(cleaner, event); + return; + } + + entry = next; + } + + /* + * We have successfully performed a cleaning increment but have + * not gone through the entire cache. Remember the entry that will + * be the starting point in the next clean-up, and reschedule another + * batch. If it fails, just try to continue anyway. + */ + INSIST(next != NULL); + dns_acache_detachentry(&cleaner->current_entry); + dns_acache_attachentry(next, &cleaner->current_entry); + + UNLOCK(&acache->lock); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + ISC_LOG_DEBUG(1), "acache cleaner: checked %d entries, " + "mem inuse %lu, sleeping", cleaner->increment, + (unsigned long)isc_mem_inuse(cleaner->acache->mctx)); + + isc_task_send(task, &event); + INSIST(CLEANER_BUSY(cleaner)); + + return; +} + +/* + * This is called when the acache either surpasses its upper limit + * or shrinks beyond its lower limit. + */ +static void +acache_overmem_cleaning_action(isc_task_t *task, isc_event_t *event) { + acache_cleaner_t *cleaner = event->ev_arg; + bool want_cleaning = false; + + UNUSED(task); + + INSIST(event->ev_type == DNS_EVENT_ACACHEOVERMEM); + INSIST(cleaner->overmem_event == NULL); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE, + 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 acache_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); +} + +static void +water(void *arg, int mark) { + dns_acache_t *acache = arg; + bool overmem = (mark == ISC_MEM_HIWATER); + + REQUIRE(DNS_ACACHE_VALID(acache)); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1), + "acache memory reaches %s watermark, mem inuse %lu", + overmem ? "high" : "low", + (unsigned long)isc_mem_inuse(acache->mctx)); + + LOCK(&acache->cleaner.lock); + + if (acache->cleaner.overmem != overmem) { + acache->cleaner.overmem = overmem; + + if (acache->cleaner.overmem_event != NULL) + isc_task_send(acache->task, + &acache->cleaner.overmem_event); + isc_mem_waterack(acache->mctx, mark); + } + + UNLOCK(&acache->cleaner.lock); +} + +/* + * The cleaner task is shutting down; do the necessary cleanup. + */ +static void +acache_cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) { + dns_acache_t *acache = event->ev_arg; + bool should_free = false; + + INSIST(task == acache->task); + INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN); + INSIST(DNS_ACACHE_VALID(acache)); + + ATRACE("acache cleaner shutdown"); + + if (CLEANER_BUSY(&acache->cleaner)) + end_cleaning(&acache->cleaner, event); + else + isc_event_free(&event); + + LOCK(&acache->lock); + + acache->live_cleaners--; + INSIST(acache->live_cleaners == 0); + + if (isc_refcount_current(&acache->refs) == 0) { + INSIST(check_noentry(acache) == true); + should_free = true; + } + + /* + * By detaching the timer in the context of its task, + * we are guaranteed that there will be no further timer + * events. + */ + if (acache->cleaner.cleaning_timer != NULL) + isc_timer_detach(&acache->cleaner.cleaning_timer); + + /* Make sure we don't reschedule anymore. */ + (void)isc_task_purge(task, NULL, DNS_EVENT_ACACHECLEAN, NULL); + + UNLOCK(&acache->lock); + + if (should_free) + destroy(acache); +} + +/* + * Public functions. + */ + +isc_result_t +dns_acache_create(dns_acache_t **acachep, isc_mem_t *mctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr) +{ + int i; + isc_result_t result; + dns_acache_t *acache; + + REQUIRE(acachep != NULL && *acachep == NULL); + REQUIRE(mctx != NULL); + REQUIRE(taskmgr != NULL); + + acache = isc_mem_get(mctx, sizeof(*acache)); + if (acache == NULL) + return (ISC_R_NOMEMORY); + + ATRACE("create"); + + result = isc_refcount_init(&acache->refs, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, acache, sizeof(*acache)); + return (result); + } + + result = isc_mutex_init(&acache->lock); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement(&acache->refs, NULL); + isc_refcount_destroy(&acache->refs); + isc_mem_put(mctx, acache, sizeof(*acache)); + return (result); + } + + acache->mctx = NULL; + isc_mem_attach(mctx, &acache->mctx); + ISC_LIST_INIT(acache->entries); + + acache->shutting_down = false; + + acache->task = NULL; + acache->entrylocks = NULL; + + result = isc_task_create(taskmgr, 1, &acache->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_task_setname(acache->task, "acachetask", acache); + ISC_EVENT_INIT(&acache->cevent, sizeof(acache->cevent), 0, NULL, + DNS_EVENT_ACACHECONTROL, shutdown_task, NULL, + NULL, NULL, NULL); + acache->cevent_sent = false; + + acache->dbentries = 0; + for (i = 0; i < DBBUCKETS; i++) + ISC_LIST_INIT(acache->dbbucket[i]); + + acache->entrylocks = isc_mem_get(mctx, sizeof(*acache->entrylocks) * + DEFAULT_ACACHE_ENTRY_LOCK_COUNT); + if (acache->entrylocks == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++) { + result = ACACHE_INITLOCK(&acache->entrylocks[i]); + if (result != ISC_R_SUCCESS) { + while (i-- > 0) + ACACHE_DESTROYLOCK(&acache->entrylocks[i]); + isc_mem_put(mctx, acache->entrylocks, + sizeof(*acache->entrylocks) * + DEFAULT_ACACHE_ENTRY_LOCK_COUNT); + acache->entrylocks = NULL; + goto cleanup; + } + } + + acache->live_cleaners = 0; + result = acache_cleaner_init(acache, timermgr, &acache->cleaner); + if (result != ISC_R_SUCCESS) + goto cleanup; + + acache->stats.cleaner_runs = 0; + reset_stats(acache); + + acache->magic = ACACHE_MAGIC; + + *acachep = acache; + return (ISC_R_SUCCESS); + + cleanup: + if (acache->task != NULL) + isc_task_detach(&acache->task); + DESTROYLOCK(&acache->lock); + isc_refcount_decrement(&acache->refs, NULL); + isc_refcount_destroy(&acache->refs); + if (acache->entrylocks != NULL) { + for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++) + ACACHE_DESTROYLOCK(&acache->entrylocks[i]); + isc_mem_put(mctx, acache->entrylocks, + sizeof(*acache->entrylocks) * + DEFAULT_ACACHE_ENTRY_LOCK_COUNT); + } + isc_mem_put(mctx, acache, sizeof(*acache)); + isc_mem_detach(&mctx); + + return (result); +} + +void +dns_acache_attach(dns_acache_t *source, dns_acache_t **targetp) { + REQUIRE(DNS_ACACHE_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + AATRACE(source, "attach"); + + isc_refcount_increment(&source->refs, NULL); + + *targetp = source; +} + +void +dns_acache_countquerymiss(dns_acache_t *acache) { + acache->stats.misses++; /* XXXSK danger: unlocked! */ + acache->stats.queries++; /* XXXSK danger: unlocked! */ +} + +void +dns_acache_detach(dns_acache_t **acachep) { + dns_acache_t *acache; + unsigned int refs; + bool should_free = false; + + REQUIRE(acachep != NULL && DNS_ACACHE_VALID(*acachep)); + acache = *acachep; + + ATRACE("detach"); + + isc_refcount_decrement(&acache->refs, &refs); + if (refs == 0) { + INSIST(check_noentry(acache) == true); + should_free = true; + } + + *acachep = NULL; + + /* + * If we're exiting and the cleaner task exists, let it free the cache. + */ + if (should_free && acache->live_cleaners > 0) { + isc_task_shutdown(acache->task); + should_free = false; + } + + if (should_free) + destroy(acache); +} + +void +dns_acache_shutdown(dns_acache_t *acache) { + REQUIRE(DNS_ACACHE_VALID(acache)); + + LOCK(&acache->lock); + + ATRACE("shutdown"); + + if (!acache->shutting_down) { + isc_event_t *event; + dns_acache_t *acache_evarg = NULL; + + INSIST(!acache->cevent_sent); + + acache->shutting_down = true; + + isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0); + + /* + * Self attach the object in order to prevent it from being + * destroyed while waiting for the event. + */ + dns_acache_attach(acache, &acache_evarg); + event = &acache->cevent; + event->ev_arg = acache_evarg; + isc_task_send(acache->task, &event); + acache->cevent_sent = true; + } + + UNLOCK(&acache->lock); +} + +isc_result_t +dns_acache_setdb(dns_acache_t *acache, dns_db_t *db) { + int bucket; + dbentry_t *dbentry; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + + ATRACE("setdb"); + + LOCK(&acache->lock); + + dbentry = NULL; + result = finddbent(acache, db, &dbentry); + if (result == ISC_R_SUCCESS) { + result = ISC_R_EXISTS; + goto end; + } + result = ISC_R_SUCCESS; + + dbentry = isc_mem_get(acache->mctx, sizeof(*dbentry)); + if (dbentry == NULL) { + result = ISC_R_NOMEMORY; + goto end; + } + + ISC_LINK_INIT(dbentry, link); + ISC_LIST_INIT(dbentry->originlist); + ISC_LIST_INIT(dbentry->referlist); + + dbentry->db = NULL; + dns_db_attach(db, &dbentry->db); + + bucket = isc_hash_function(&db, sizeof(db), true, NULL) % DBBUCKETS; + + ISC_LIST_APPEND(acache->dbbucket[bucket], dbentry, link); + + acache->dbentries++; + + end: + UNLOCK(&acache->lock); + + return (result); +} + +isc_result_t +dns_acache_putdb(dns_acache_t *acache, dns_db_t *db) { + int bucket; + isc_result_t result; + dbentry_t *dbentry; + dns_acacheentry_t *entry; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(db != NULL); + + ATRACE("putdb"); + + LOCK(&acache->lock); + + dbentry = NULL; + result = finddbent(acache, db, &dbentry); + if (result != ISC_R_SUCCESS) { + /* + * The entry may have not been created due to memory shortage. + */ + UNLOCK(&acache->lock); + return (ISC_R_NOTFOUND); + } + + /* + * Release corresponding cache entries: for each entry, release all + * links the entry has, and then callback to the entry holder (if any). + * If no other external references exist (this can happen if the + * original holder has canceled callback,) destroy it here. + */ + while ((entry = ISC_LIST_HEAD(dbentry->originlist)) != NULL) { + ACACHE_LOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + /* + * Releasing olink first would avoid finddbent() in + * unlink_dbentries(). + */ + ISC_LIST_UNLINK(dbentry->originlist, entry, olink); + if (acache->cleaner.current_entry != entry) + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + if (acache->cleaner.current_entry != entry) + dns_acache_detachentry(&entry); + } + while ((entry = ISC_LIST_HEAD(dbentry->referlist)) != NULL) { + ACACHE_LOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + ISC_LIST_UNLINK(dbentry->referlist, entry, rlink); + if (acache->cleaner.current_entry != entry) + ISC_LIST_UNLINK(acache->entries, entry, link); + unlink_dbentries(acache, entry); + + if (entry->callback != NULL) + (entry->callback)(entry, &entry->cbarg); + entry->callback = NULL; + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + if (acache->cleaner.current_entry != entry) + dns_acache_detachentry(&entry); + } + + INSIST(ISC_LIST_EMPTY(dbentry->originlist) && + ISC_LIST_EMPTY(dbentry->referlist)); + + bucket = isc_hash_function(&db, sizeof(db), true, NULL) % DBBUCKETS; + + ISC_LIST_UNLINK(acache->dbbucket[bucket], dbentry, link); + dns_db_detach(&dbentry->db); + + isc_mem_put(acache->mctx, dbentry, sizeof(*dbentry)); + + acache->dbentries--; + + acache->stats.deleted++; + + UNLOCK(&acache->lock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_acache_createentry(dns_acache_t *acache, dns_db_t *origdb, + void (*callback)(dns_acacheentry_t *, void **), + void *cbarg, dns_acacheentry_t **entryp) +{ + dns_acacheentry_t *newentry; + isc_result_t result; + uint32_t r; + isc_stdtime_t tmptime; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(entryp != NULL && *entryp == NULL); + REQUIRE(origdb != NULL); + + /* + * Should we exceed our memory limit for some reason (for + * example, if the cleaner does not run aggressively enough), + * then we will not create additional entries. + * + * XXXSK: It might be better to lock the acache->cleaner->lock, + * but locking may be an expensive bottleneck. If we misread + * the value, we will occasionally refuse to create a few + * cache entries, or create a few that we should not. I do not + * expect this to happen often, and it will not have very bad + * effects when it does. So no lock for now. + */ + if (acache->cleaner.overmem) { + acache->stats.overmem_nocreates++; /* XXXSK danger: unlocked! */ + return (ISC_R_NORESOURCES); + } + + newentry = isc_mem_get(acache->mctx, sizeof(*newentry)); + if (newentry == NULL) { + acache->stats.nomem++; /* XXXMLG danger: unlocked! */ + return (ISC_R_NOMEMORY); + } + + isc_random_get(&r); + newentry->locknum = r % DEFAULT_ACACHE_ENTRY_LOCK_COUNT; + + result = isc_refcount_init(&newentry->references, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(acache->mctx, newentry, sizeof(*newentry)); + return (result); + }; + + ISC_LINK_INIT(newentry, link); + ISC_LINK_INIT(newentry, olink); + ISC_LINK_INIT(newentry, rlink); + + newentry->acache = NULL; + dns_acache_attach(acache, &newentry->acache); + + newentry->zone = NULL; + newentry->db = NULL; + newentry->version = NULL; + newentry->node = NULL; + newentry->foundname = NULL; + + newentry->callback = callback; + newentry->cbarg = cbarg; + newentry->origdb = NULL; + dns_db_attach(origdb, &newentry->origdb); + + isc_stdtime_get(&tmptime); + acache_storetime(newentry, tmptime); + + newentry->magic = ACACHEENTRY_MAGIC; + + *entryp = newentry; + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_acache_getentry(dns_acacheentry_t *entry, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_name_t *fname, + dns_message_t *msg, isc_stdtime_t now) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_rdataset_t *erdataset; + isc_stdtime32_t now32; + dns_acache_t *acache; + int locknum; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + REQUIRE(zonep == NULL || *zonep == NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(versionp != NULL && *versionp == NULL); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(fname != NULL); + REQUIRE(msg != NULL); + acache = entry->acache; + REQUIRE(DNS_ACACHE_VALID(acache)); + + locknum = entry->locknum; + ACACHE_LOCK(&acache->entrylocks[locknum], isc_rwlocktype_read); + + isc_stdtime_convert32(now, &now32); + acache_storetime(entry, now32); + + if (entry->zone != NULL && zonep != NULL) + dns_zone_attach(entry->zone, zonep); + + if (entry->db == NULL) { + *dbp = NULL; + *versionp = NULL; + } else { + dns_db_attach(entry->db, dbp); + dns_db_attachversion(entry->db, entry->version, versionp); + } + if (entry->node == NULL) + *nodep = NULL; + else { + dns_db_attachnode(entry->db, entry->node, nodep); + + INSIST(entry->foundname != NULL); + dns_name_copy(entry->foundname, fname, NULL); + for (erdataset = ISC_LIST_HEAD(entry->foundname->list); + erdataset != NULL; + erdataset = ISC_LIST_NEXT(erdataset, link)) { + dns_rdataset_t *ardataset; + + ardataset = NULL; + result = dns_message_gettemprdataset(msg, &ardataset); + if (result != ISC_R_SUCCESS) { + ACACHE_UNLOCK(&acache->entrylocks[locknum], + isc_rwlocktype_read); + goto fail; + } + + /* + * XXXJT: if we simply clone the rdataset, we'll get + * lost wrt cyclic ordering. We'll need an additional + * trick to get the latest counter from the original + * header. + */ + dns_rdataset_clone(erdataset, ardataset); + ISC_LIST_APPEND(fname->list, ardataset, link); + } + } + + entry->acache->stats.hits++; /* XXXMLG danger: unlocked! */ + entry->acache->stats.queries++; + + ACACHE_UNLOCK(&acache->entrylocks[locknum], isc_rwlocktype_read); + + return (result); + + fail: + while ((erdataset = ISC_LIST_HEAD(fname->list)) != NULL) { + ISC_LIST_UNLINK(fname->list, erdataset, link); + dns_rdataset_disassociate(erdataset); + dns_message_puttemprdataset(msg, &erdataset); + } + if (*nodep != NULL) + dns_db_detachnode(*dbp, nodep); + if (*versionp != NULL) + dns_db_closeversion(*dbp, versionp, false); + if (*dbp != NULL) + dns_db_detach(dbp); + if (zonep != NULL && *zonep != NULL) + dns_zone_detach(zonep); + + return (result); +} + +isc_result_t +dns_acache_setentry(dns_acache_t *acache, dns_acacheentry_t *entry, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, dns_name_t *fname) +{ + isc_result_t result; + dbentry_t *odbent; + dbentry_t *rdbent = NULL; + bool close_version = false; + dns_acacheentry_t *dummy_entry = NULL; + + REQUIRE(DNS_ACACHE_VALID(acache)); + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + LOCK(&acache->lock); /* XXX: need to lock it here for ordering */ + ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write); + + /* Set zone */ + if (zone != NULL) + dns_zone_attach(zone, &entry->zone); + /* Set DB */ + if (db != NULL) + dns_db_attach(db, &entry->db); + /* + * Set DB version. If the version is not given by the caller, + * which is the case for glue or cache DBs, use the current version. + */ + if (version == NULL) { + if (db != NULL) { + dns_db_currentversion(db, &version); + close_version = true; + } + } + if (version != NULL) { + INSIST(db != NULL); + dns_db_attachversion(db, version, &entry->version); + } + if (close_version) + dns_db_closeversion(db, &version, false); + /* Set DB node. */ + if (node != NULL) { + INSIST(db != NULL); + dns_db_attachnode(db, node, &entry->node); + } + + /* + * Set list of the corresponding rdatasets, if given. + * To minimize the overhead and memory consumption, we'll do this for + * positive cache only, in which case the DB node is non NULL. + * We do not want to cache incomplete information, so give up the + * entire entry when a memory shortage happen during the process. + */ + if (node != NULL) { + dns_rdataset_t *ardataset, *crdataset; + + entry->foundname = isc_mem_get(acache->mctx, + sizeof(*entry->foundname)); + + if (entry->foundname == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_name_init(entry->foundname, NULL); + result = dns_name_dup(fname, acache->mctx, + entry->foundname); + if (result != ISC_R_SUCCESS) + goto fail; + + for (ardataset = ISC_LIST_HEAD(fname->list); + ardataset != NULL; + ardataset = ISC_LIST_NEXT(ardataset, link)) { + crdataset = isc_mem_get(acache->mctx, + sizeof(*crdataset)); + if (crdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + + dns_rdataset_init(crdataset); + dns_rdataset_clone(ardataset, crdataset); + ISC_LIST_APPEND(entry->foundname->list, crdataset, + link); + } + } + + odbent = NULL; + result = finddbent(acache, entry->origdb, &odbent); + if (result != ISC_R_SUCCESS) + goto fail; + if (db != NULL) { + rdbent = NULL; + result = finddbent(acache, db, &rdbent); + if (result != ISC_R_SUCCESS) + goto fail; + } + + ISC_LIST_APPEND(acache->entries, entry, link); + ISC_LIST_APPEND(odbent->originlist, entry, olink); + if (rdbent != NULL) + ISC_LIST_APPEND(rdbent->referlist, entry, rlink); + + /* + * The additional cache needs an implicit reference to entries in its + * link. + */ + dns_acache_attachentry(entry, &dummy_entry); + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + + acache->stats.adds++; + UNLOCK(&acache->lock); + + return (ISC_R_SUCCESS); + + fail: + clear_entry(acache, entry); + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + UNLOCK(&acache->lock); + + return (result); +} + +bool +dns_acache_cancelentry(dns_acacheentry_t *entry) { + dns_acache_t *acache; + bool callback_active; + + REQUIRE(DNS_ACACHEENTRY_VALID(entry)); + + acache = entry->acache; + + INSIST(DNS_ACACHE_VALID(entry->acache)); + + LOCK(&acache->lock); + ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write); + + callback_active = (entry->cbarg != NULL); + + /* + * Release dependencies stored in this entry as much as possible. + * The main link cannot be released, since the acache object has + * a reference to this entry; the empty entry will be released in + * the next cleaning action. + */ + unlink_dbentries(acache, entry); + clear_entry(entry->acache, entry); + + entry->callback = NULL; + entry->cbarg = NULL; + + ACACHE_UNLOCK(&acache->entrylocks[entry->locknum], + isc_rwlocktype_write); + UNLOCK(&acache->lock); + + return (callback_active); +} + +void +dns_acache_attachentry(dns_acacheentry_t *source, + dns_acacheentry_t **targetp) +{ + REQUIRE(DNS_ACACHEENTRY_VALID(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references, NULL); + + *targetp = source; +} + +void +dns_acache_detachentry(dns_acacheentry_t **entryp) { + dns_acacheentry_t *entry; + unsigned int refs; + + REQUIRE(entryp != NULL && DNS_ACACHEENTRY_VALID(*entryp)); + entry = *entryp; + + isc_refcount_decrement(&entry->references, &refs); + + /* + * If there are no references to the entry, the entry must have been + * unlinked and can be destroyed safely. + */ + if (refs == 0) { + INSIST(!ISC_LINK_LINKED(entry, link)); + (*entryp)->acache->stats.deleted++; + destroy_entry(entry); + } + + *entryp = NULL; +} + +void +dns_acache_setcleaninginterval(dns_acache_t *acache, unsigned int t) { + isc_interval_t interval; + isc_result_t result; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + ATRACE("dns_acache_setcleaninginterval"); + + LOCK(&acache->lock); + + /* + * It may be the case that the acache has already shut down. + * If so, it has no timer. (Not sure if this can really happen.) + */ + if (acache->cleaner.cleaning_timer == NULL) + goto unlock; + + acache->cleaner.cleaning_interval = t; + + if (t == 0) { + result = isc_timer_reset(acache->cleaner.cleaning_timer, + isc_timertype_inactive, + NULL, NULL, true); + } else { + isc_interval_set(&interval, acache->cleaner.cleaning_interval, + 0); + result = isc_timer_reset(acache->cleaner.cleaning_timer, + isc_timertype_ticker, + NULL, &interval, false); + } + if (result != ISC_R_SUCCESS) + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_WARNING, + "could not set acache cleaning interval: %s", + isc_result_totext(result)); + else + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ACACHE, ISC_LOG_NOTICE, + "acache %p cleaning interval set to %d.", + acache, t); + + unlock: + UNLOCK(&acache->lock); +} + +/* + * This function was derived from cache.c:dns_cache_setcachesize(). See the + * function for more details about the logic. + */ +void +dns_acache_setcachesize(dns_acache_t *acache, size_t size) { + size_t hiwater, lowater; + + REQUIRE(DNS_ACACHE_VALID(acache)); + + if (size != 0U && size < DNS_ACACHE_MINSIZE) + size = DNS_ACACHE_MINSIZE; + + hiwater = size - (size >> 3); + lowater = size - (size >> 2); + + if (size == 0U || hiwater == 0U || lowater == 0U) + isc_mem_setwater(acache->mctx, water, acache, 0, 0); + else + isc_mem_setwater(acache->mctx, water, acache, + hiwater, lowater); +} diff --git a/lib/dns/acl.c b/lib/dns/acl.c new file mode 100644 index 0000000..c1108e8 --- /dev/null +++ b/lib/dns/acl.c @@ -0,0 +1,728 @@ +/* + * 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 http://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)); + if (acl == NULL) + return (ISC_R_NOMEMORY); + + acl->mctx = NULL; + isc_mem_attach(mctx, &acl->mctx); + + acl->name = NULL; + + result = isc_refcount_init(&acl->refcount, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, acl, sizeof(*acl)); + return (result); + } + + 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)); + if (acl->elements == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + acl->alloc = n; + memset(acl->elements, 0, n * sizeof(dns_aclelement_t)); + *target = acl; + return (ISC_R_SUCCESS); + + cleanup: + dns_acl_detach(&acl); + return (result); +} + +/* + * 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 || acl->node_count != 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) +{ + return (dns_acl_match2(reqaddr, reqsigner, NULL, 0, NULL, acl, env, + match, matchelt)); +} + +isc_result_t +dns_acl_match2(const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, + const isc_netaddr_t *ecs, + uint8_t ecslen, + uint8_t *scope, + 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); + REQUIRE(ecs != NULL || scope == 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, false); + + /* 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); + + /* + * If ecs is not NULL, we search the radix tree again to + * see if we find a better match on an ECS node + */ + if (ecs != NULL) { + node = NULL; + addr = ecs; + + if (env != NULL && env->match_mapped && + addr->family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&addr->type.in6)) + { + isc_netaddr_fromv4mapped(&v4addr, addr); + addr = &v4addr; + } + + NETADDR_TO_PREFIX_T(addr, pfx, ecslen, true); + + result = isc_radix_search(acl->iptable->radix, &node, &pfx); + if (result == ISC_R_SUCCESS && node != NULL) { + int off = ISC_RADIX_FAMILY(&pfx); + if (match_num == -1 || + node->node_num[off] < match_num) + { + match_num = node->node_num[off]; + if (scope != NULL) { + *scope = node->bit; + } + if (*(bool *) node->data[off]) { + *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_match2(reqaddr, reqsigner, ecs, ecslen, + scope, 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)); + if (newmem == NULL) + return (ISC_R_NOMEMORY); + + /* 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 + dest->node_count; + + /* 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); + result = dns_name_dup(&source->elements[i].keyname, + dest->mctx, + &dest->elements[nelem+i].keyname); + if (result != ISC_R_SUCCESS) + return result; + } + +#ifdef HAVE_GEOIP + /* Duplicate GeoIP data */ + if (source->elements[i].type == dns_aclelementtype_geoip) { + dest->elements[nelem + i].geoip_elem = + source->elements[i].geoip_elem; + } +#endif + + /* 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 + dest->node_count; + result = dns_iptable_merge(dest->iptable, source->iptable, pos); + if (result != ISC_R_SUCCESS) + return (result); + if (nodes > dest->node_count) + dest->node_count = 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) +{ + return (dns_aclelement_match2(reqaddr, reqsigner, NULL, 0, NULL, + e, env, matchelt)); +} + +bool +dns_aclelement_match2(const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, + const isc_netaddr_t *ecs, + uint8_t ecslen, + uint8_t *scope, + 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; +#ifdef HAVE_GEOIP + const isc_netaddr_t *addr = NULL; +#endif + + REQUIRE(ecs != NULL || scope == NULL); + + 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; + +#ifdef HAVE_GEOIP + case dns_aclelementtype_geoip: + if (env == NULL || env->geoip == NULL) + return (false); + addr = (env->geoip_use_ecs && ecs != NULL) ? ecs : reqaddr; + return (dns_geoip_match(addr, scope, env->geoip, + &e->geoip_elem)); +#endif + default: + /* Should be impossible. */ + INSIST(0); + } + + result = dns_acl_match2(reqaddr, reqsigner, ecs, ecslen, scope, + 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, NULL); + *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) { + dns_acl_t *acl = *aclp; + unsigned int refs; + + REQUIRE(DNS_ACL_VALID(acl)); + + isc_refcount_decrement(&acl->refcount, &refs); + if (refs == 0) + destroy(acl); + *aclp = NULL; +} + + +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) { + RUNTIME_CHECK(isc_mutex_init(&insecure_prefix_lock) == ISC_R_SUCCESS); +} + +/* + * 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]) && + (data[2] == NULL || !* (bool *) data[2]) && + (data[3] == NULL || !* (bool *) data[3])) + return; + + /* + * If a loopback address found and the other family and + * ecs 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]) && + (data[2] == NULL || !* (bool *) data[2]) && + (data[3] == NULL || !* (bool *) data[3])) + return; + + if (prefix->bitlen == 128 && + IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) && + (data[0] == NULL || !* (bool *) data[0]) && + (data[2] == NULL || !* (bool *) data[2]) && + (data[3] == NULL || !* (bool *) data[3])) + 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; + +#ifdef HAVE_GEOIP + case dns_aclelementtype_geoip: +#endif + case dns_aclelementtype_localnets: + return (true); + + default: + INSIST(0); + return (true); + } + } + + /* No insecure elements were found. */ + 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; +#ifdef HAVE_GEOIP + env->geoip = NULL; + env->geoip_use_ecs = false; +#endif + 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; +#ifdef HAVE_GEOIP + t->geoip_use_ecs = s->geoip_use_ecs; +#endif +} + +void +dns_aclenv_destroy(dns_aclenv_t *env) { + dns_acl_detach(&env->localhost); + dns_acl_detach(&env->localnets); +} diff --git a/lib/dns/adb.c b/lib/dns/adb.c new file mode 100644 index 0000000..956f368 --- /dev/null +++ b/lib/dns/adb.c @@ -0,0 +1,4823 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file + * + * \note + * In finds, if task == NULL, no events will be generated, and no events + * have been sent. If task != NULL but taskaction == NULL, an event has been + * posted but not yet freed. If neither are NULL, no event was posted. + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + +#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_mutex_t mplock; + isc_mempool_t *nmp; /*%< dns_adbname_t */ + isc_mempool_t *nhmp; /*%< dns_adbnamehook_t */ + isc_mempool_t *limp; /*%< dns_adblameinfo_t */ + isc_mempool_t *emp; /*%< dns_adbentry_t */ + isc_mempool_t *ahmp; /*%< dns_adbfind_t */ + isc_mempool_t *aimp; /*%< dns_adbaddrinfo_t */ + isc_mempool_t *afmp; /*%< dns_adbfetch_t */ + + /*! + * 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; + uint32_t quota; + uint32_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 inline dns_adbname_t *new_adbname(dns_adb_t *, dns_name_t *); +static inline void free_adbname(dns_adb_t *, dns_adbname_t **); +static inline dns_adbnamehook_t *new_adbnamehook(dns_adb_t *, + dns_adbentry_t *); +static inline void free_adbnamehook(dns_adb_t *, dns_adbnamehook_t **); +static inline dns_adblameinfo_t *new_adblameinfo(dns_adb_t *, dns_name_t *, + dns_rdatatype_t); +static inline void free_adblameinfo(dns_adb_t *, dns_adblameinfo_t **); +static inline dns_adbentry_t *new_adbentry(dns_adb_t *); +static inline void free_adbentry(dns_adb_t *, dns_adbentry_t **); +static inline dns_adbfind_t *new_adbfind(dns_adb_t *); +static inline bool free_adbfind(dns_adb_t *, dns_adbfind_t **); +static inline dns_adbaddrinfo_t *new_adbaddrinfo(dns_adb_t *, dns_adbentry_t *, + in_port_t); +static inline dns_adbfetch_t *new_adbfetch(dns_adb_t *); +static inline void free_adbfetch(dns_adb_t *, dns_adbfetch_t **); +static inline dns_adbname_t *find_name_and_lock(dns_adb_t *, dns_name_t *, + unsigned int, int *); +static inline dns_adbentry_t *find_entry_and_lock(dns_adb_t *, + 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 *, 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 inline bool dec_adb_irefcnt(dns_adb_t *); +static inline void inc_adb_irefcnt(dns_adb_t *); +static inline void inc_adb_erefcnt(dns_adb_t *); +static inline void inc_entry_refcnt(dns_adb_t *, dns_adbentry_t *, + bool); +static inline bool dec_entry_refcnt(dns_adb_t *, bool, + dns_adbentry_t *, bool); +static inline 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 inline 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 inline void link_name(dns_adb_t *, int, dns_adbname_t *); +static inline bool unlink_name(dns_adb_t *, dns_adbname_t *); +static inline void link_entry(dns_adb_t *, int, dns_adbentry_t *); +static inline 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) + +/* + * 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 inline 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 inline 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 inline void +dec_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { + if (adb->view->adbstats != NULL) + isc_stats_decrement(adb->view->adbstats, counter); +} + +static inline void +inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { + if (adb->view->adbstats != NULL) + isc_stats_increment(adb->view->adbstats, counter); +} + +static inline 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. + */ + 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); + if (newentries == NULL || newdeadentries == NULL || + newentrylocks == NULL || newentry_sd == NULL || + newentry_refcnt == NULL) + goto cleanup; + + /* + * Initialise the new resources. + */ + result = isc_mutexblock_init(newentrylocks, n); + if (result != ISC_R_SUCCESS) + goto cleanup; + + 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. + */ + DESTROYMUTEXBLOCK(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, 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. + */ + 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); + if (newnames == NULL || newdeadnames == NULL || + newnamelocks == NULL || newname_sd == NULL || + newname_refcnt == NULL) + goto cleanup; + + /* + * Initialise the new resources. + */ + result = isc_mutexblock_init(newnamelocks, n); + if (result != ISC_R_SUCCESS) + goto cleanup; + + 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. + */ + DESTROYMUTEXBLOCK(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 == false); + 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 inline 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 inline 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 inline 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 inline 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 inline 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 == false); + 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 == false); + 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, dns_name_t *name, 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); + result = dns_name_dup(&cname.cname, adb->mctx, target); + dns_rdata_freestruct(&cname); + if (result != ISC_R_SUCCESS) + return (result); + } 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); + result = dns_name_dup(new_target, adb->mctx, target); + if (result != ISC_R_SUCCESS) + return (result); + } + + 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 inline 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 inline 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 inline void +inc_adb_irefcnt(dns_adb_t *adb) { + LOCK(&adb->reflock); + adb->irefcnt++; + UNLOCK(&adb->reflock); +} + +static inline void +inc_adb_erefcnt(dns_adb_t *adb) { + LOCK(&adb->reflock); + adb->erefcnt++; + UNLOCK(&adb->reflock); +} + +static inline 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 inline 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 inline dns_adbname_t * +new_adbname(dns_adb_t *adb, dns_name_t *dnsname) { + dns_adbname_t *name; + + name = isc_mempool_get(adb->nmp); + if (name == NULL) + return (NULL); + + dns_name_init(&name->name, NULL); + if (dns_name_dup(dnsname, adb->mctx, &name->name) != ISC_R_SUCCESS) { + isc_mempool_put(adb->nmp, name); + return (NULL); + } + 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 inline 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_mempool_put(adb->nmp, n); + LOCK(&adb->namescntlock); + adb->namescnt--; + dec_adbstats(adb, dns_adbstats_namescnt); + UNLOCK(&adb->namescntlock); +} + +static inline dns_adbnamehook_t * +new_adbnamehook(dns_adb_t *adb, dns_adbentry_t *entry) { + dns_adbnamehook_t *nh; + + nh = isc_mempool_get(adb->nhmp); + if (nh == NULL) + return (NULL); + + nh->magic = DNS_ADBNAMEHOOK_MAGIC; + nh->entry = entry; + ISC_LINK_INIT(nh, plink); + + return (nh); +} + +static inline 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_mempool_put(adb->nhmp, nh); +} + +static inline dns_adblameinfo_t * +new_adblameinfo(dns_adb_t *adb, dns_name_t *qname, dns_rdatatype_t qtype) { + dns_adblameinfo_t *li; + + li = isc_mempool_get(adb->limp); + if (li == NULL) + return (NULL); + + dns_name_init(&li->qname, NULL); + if (dns_name_dup(qname, adb->mctx, &li->qname) != ISC_R_SUCCESS) { + isc_mempool_put(adb->limp, li); + return (NULL); + } + li->magic = DNS_ADBLAMEINFO_MAGIC; + li->lame_timer = 0; + li->qtype = qtype; + ISC_LINK_INIT(li, plink); + + return (li); +} + +static inline 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_mempool_put(adb->limp, li); +} + +static inline dns_adbentry_t * +new_adbentry(dns_adb_t *adb) { + dns_adbentry_t *e; + uint32_t r; + + e = isc_mempool_get(adb->emp); + if (e == NULL) + return (NULL); + + 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; + isc_random_get(&r); + e->srtt = (r & 0x1f) + 1; + e->lastage = 0; + e->expires = 0; + e->active = 0; + e->mode = 0; + 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 inline 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_mempool_put(adb->emp, e); + LOCK(&adb->entriescntlock); + adb->entriescnt--; + dec_adbstats(adb, dns_adbstats_entriescnt); + UNLOCK(&adb->entriescntlock); +} + +static inline dns_adbfind_t * +new_adbfind(dns_adb_t *adb) { + dns_adbfind_t *h; + isc_result_t result; + + h = isc_mempool_get(adb->ahmp); + if (h == NULL) + return (NULL); + + /* + * 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 + */ + result = isc_mutex_init(&h->lock); + if (result != ISC_R_SUCCESS) { + isc_mempool_put(adb->ahmp, h); + return (NULL); + } + + 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 inline dns_adbfetch_t * +new_adbfetch(dns_adb_t *adb) { + dns_adbfetch_t *f; + + f = isc_mempool_get(adb->afmp); + if (f == NULL) + return (NULL); + + f->magic = 0; + f->fetch = NULL; + + dns_rdataset_init(&f->rdataset); + + f->magic = DNS_ADBFETCH_MAGIC; + + return (f); +} + +static inline 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_mempool_put(adb->afmp, f); +} + +static inline 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; + + DESTROYLOCK(&find->lock); + isc_mempool_put(adb->ahmp, 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 inline dns_adbaddrinfo_t * +new_adbaddrinfo(dns_adb_t *adb, dns_adbentry_t *entry, in_port_t port) { + dns_adbaddrinfo_t *ai; + + ai = isc_mempool_get(adb->aimp); + if (ai == NULL) + return (NULL); + + 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 inline 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_mempool_put(adb->aimp, 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 inline dns_adbname_t * +find_name_and_lock(dns_adb_t *adb, 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 inline dns_adbentry_t * +find_entry_and_lock(dns_adb_t *adb, 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, 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 (%u/%u): %s", + addrbuf, entry->active, entry->quota, msgbuf); +} + +static void +copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, 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) { + 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 (entry->quota != 0 && + entry->active >= entry->quota) + { + 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) { + 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 (entry->quota != 0 && + entry->active >= entry->quota) + { + 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. + */ + result = kill_name(&name, DNS_EVENT_ADBEXPIRED); + *namep = NULL; + + /* + * 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) == + false); + 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. + */ + 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); + *entryp = NULL; + 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 == false); + 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 == false); + 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_mempool_destroy(&adb->nmp); + isc_mempool_destroy(&adb->nhmp); + isc_mempool_destroy(&adb->limp); + isc_mempool_destroy(&adb->emp); + isc_mempool_destroy(&adb->ahmp); + isc_mempool_destroy(&adb->aimp); + isc_mempool_destroy(&adb->afmp); + + DESTROYMUTEXBLOCK(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); + + DESTROYMUTEXBLOCK(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); + + DESTROYLOCK(&adb->reflock); + DESTROYLOCK(&adb->lock); + DESTROYLOCK(&adb->mplock); + DESTROYLOCK(&adb->overmemlock); + DESTROYLOCK(&adb->entriescntlock); + DESTROYLOCK(&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)); + if (adb == NULL) + return (ISC_R_NOMEMORY); + + /* + * 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->nmp = NULL; + adb->nhmp = NULL; + adb->limp = NULL; + adb->emp = NULL; + adb->ahmp = NULL; + adb->aimp = NULL; + adb->afmp = NULL; + 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, " + "intializing table sizes to %u\n", + nbuckets[11]); + adb->nentries = nbuckets[11]; + adb->nnames = nbuckets[11]; + } + + isc_mem_attach(mem, &adb->mctx); + + result = isc_mutex_init(&adb->lock); + if (result != ISC_R_SUCCESS) + goto fail0b; + + result = isc_mutex_init(&adb->mplock); + if (result != ISC_R_SUCCESS) + goto fail0c; + + result = isc_mutex_init(&adb->reflock); + if (result != ISC_R_SUCCESS) + goto fail0d; + + result = isc_mutex_init(&adb->overmemlock); + if (result != ISC_R_SUCCESS) + goto fail0e; + + result = isc_mutex_init(&adb->entriescntlock); + if (result != ISC_R_SUCCESS) + goto fail0f; + + result = isc_mutex_init(&adb->namescntlock); + if (result != ISC_R_SUCCESS) + goto fail0g; + +#define ALLOCENTRY(adb, el) \ + do { \ + (adb)->el = isc_mem_get((adb)->mctx, \ + sizeof(*(adb)->el) * (adb)->nentries); \ + if ((adb)->el == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto fail1; \ + }\ + } 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); \ + if ((adb)->el == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto fail1; \ + }\ + } 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. + */ + result = isc_mutexblock_init(adb->namelocks, adb->nnames); + if (result != ISC_R_SUCCESS) + goto fail1; + 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++; + } + result = isc_mutexblock_init(adb->entrylocks, adb->nentries); + if (result != ISC_R_SUCCESS) + goto fail2; + + /* + * Memory pools + */ +#define MPINIT(t, p, n) do { \ + result = isc_mempool_create(mem, sizeof(t), &(p)); \ + if (result != ISC_R_SUCCESS) \ + goto fail3; \ + isc_mempool_setfreemax((p), FREE_ITEMS); \ + isc_mempool_setfillcount((p), FILL_COUNT); \ + isc_mempool_setname((p), n); \ + isc_mempool_associatelock((p), &adb->mplock); \ +} while (0) + + MPINIT(dns_adbname_t, adb->nmp, "adbname"); + MPINIT(dns_adbnamehook_t, adb->nhmp, "adbnamehook"); + MPINIT(dns_adblameinfo_t, adb->limp, "adblameinfo"); + MPINIT(dns_adbentry_t, adb->emp, "adbentry"); + MPINIT(dns_adbfind_t, adb->ahmp, "adbfind"); + MPINIT(dns_adbaddrinfo_t, adb->aimp, "adbaddrinfo"); + MPINIT(dns_adbfetch_t, adb->afmp, "adbfetch"); + +#undef MPINIT + + /* + * Allocate an internal task. + */ + result = isc_task_create(adb->taskmgr, 0, &adb->task); + if (result != ISC_R_SUCCESS) + goto fail3; + + isc_task_setname(adb->task, "ADB", adb); + + result = isc_stats_create(adb->mctx, &view->adbstats, dns_adbstats_max); + if (result != ISC_R_SUCCESS) + goto fail3; + + 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); + + fail3: + if (adb->task != NULL) + isc_task_detach(&adb->task); + + /* clean up entrylocks */ + DESTROYMUTEXBLOCK(adb->entrylocks, adb->nentries); + + fail2: /* clean up namelocks */ + DESTROYMUTEXBLOCK(adb->namelocks, adb->nnames); + + fail1: /* clean up only allocated memory */ + 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); + if (adb->nmp != NULL) + isc_mempool_destroy(&adb->nmp); + if (adb->nhmp != NULL) + isc_mempool_destroy(&adb->nhmp); + if (adb->limp != NULL) + isc_mempool_destroy(&adb->limp); + if (adb->emp != NULL) + isc_mempool_destroy(&adb->emp); + if (adb->ahmp != NULL) + isc_mempool_destroy(&adb->ahmp); + if (adb->aimp != NULL) + isc_mempool_destroy(&adb->aimp); + if (adb->afmp != NULL) + isc_mempool_destroy(&adb->afmp); + + DESTROYLOCK(&adb->namescntlock); + fail0g: + DESTROYLOCK(&adb->entriescntlock); + fail0f: + DESTROYLOCK(&adb->overmemlock); + fail0e: + DESTROYLOCK(&adb->reflock); + fail0d: + DESTROYLOCK(&adb->mplock); + fail0c: + DESTROYLOCK(&adb->lock); + fail0b: + 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; + + INSIST(adb->erefcnt > 0); + + LOCK(&adb->reflock); + 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_mempool_getallocated(adb->ahmp) == 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, dns_name_t *name, dns_name_t *qname, + dns_rdatatype_t qtype, unsigned int options, + isc_stdtime_t now, dns_name_t *target, + in_port_t port, dns_adbfind_t **findp) +{ + return (dns_adb_createfind2(adb, task, action, arg, name, + qname, qtype, options, now, + target, port, 0, NULL, findp)); +} + +isc_result_t +dns_adb_createfind2(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action, + void *arg, dns_name_t *name, 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) == false); + 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) == false); + 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) == false); + + /* + * 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)) { + /* + * 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; + 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", + find, adbname); + } 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) { + result = dns_name_copy(&adbname->target, target, NULL); + if (result != ISC_R_SUCCESS) + goto out; + } + 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) == + false); + 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) == false); + for (i = 0; i < adb->nentries; i++) + RUNTIME_CHECK(cleanup_entries(adb, i, now) == false); + + 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) { + unsigned int i; + 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) + fprintf(f, "; addr %p, erefcnt %u, irefcnt %u, finds out %u\n", + adb, adb->erefcnt, adb->irefcnt, + isc_mempool_getallocated(adb->nhmp)); + + for (i = 0; i < adb->nnames; i++) + LOCK(&adb->namelocks[i]); + for (i = 0; i < adb->nentries; i++) + LOCK(&adb->entrylocks[i]); + + /* + * Dump the names + */ + for (i = 0; i < adb->nnames; i++) { + name = ISC_LIST_HEAD(adb->names[i]); + if (name == NULL) + continue; + if (debug) + fprintf(f, "; bucket %u\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); + } + } + } + + fprintf(f, ";\n; Unassociated entries\n;\n"); + + for (i = 0; i < adb->nentries; i++) { + 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); + } + } + + /* + * Unlock everything + */ + for (i = 0; i < adb->nentries; i++) + UNLOCK(&adb->entrylocks[i]); + for (i = 0; i < adb->nnames; i++) + UNLOCK(&adb->namelocks[i]); +} + +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) { + fprintf(f, " [atr %0.2f] [quota %u]", + entry->atr, entry->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, 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); + dump_entry(f, adb, nh->entry, debug, now); + } +} + +static inline 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_find2(adb->view, &adbname->name, rdtype, now, + NAME_GLUEOK(adbname) ? DNS_DBFIND_GLUEOK : 0, + NAME_HINTOK(adbname), + (adbname->flags & NAME_STARTATZONE) != 0 ? + true : false, + 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_findzonecut2(adb->view, &adbname->name, name, + 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; + + result = dns_resolver_createfetch3(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) + 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, 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]); +} + +/* + * (10000 / ((10 + n) / 10)^(3/2)) for n in 0..99. + * 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) { + addr->entry->quota = adb->quota * + quota_adj[--addr->entry->mode] / 10000; + log_quota(addr->entry, "atr %0.2f, quota increased to %u", + addr->entry->atr, addr->entry->quota); + } else if (addr->entry->atr > adb->atr_high && + addr->entry->mode < (QUOTA_ADJ_SIZE - 1)) { + addr->entry->quota = adb->quota * + quota_adj[++addr->entry->mode] / 10000; + log_quota(addr->entry, "atr %0.2f, quota decreased to %u", + addr->entry->atr, addr->entry->quota); + } + + /* Ensure we don't drop to zero */ + if (addr->entry->quota == 0) + addr->entry->quota = 1; +} + +#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) { + return dns_adb_probesize2(adb, addr, 0); +} + +unsigned int +dns_adb_probesize2(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); + if (addr->entry->cookie != NULL) + 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, 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; + REQUIRE(DNS_ADBADDRINFO_VALID(addr)); + entry = addr->entry; + REQUIRE(DNS_ADBENTRY_VALID(entry)); + + *addrp = NULL; + 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) == false); + for (i = 0; i < adb->nentries; i++) + RUNTIME_CHECK(cleanup_entries(adb, i, INT_MAX) == false); + +#ifdef DUMP_ADB_AFTER_CLEANING + dump_adb(adb, stdout, true, INT_MAX); +#endif + + UNLOCK(&adb->lock); +} + +void +dns_adb_flushname(dns_adb_t *adb, dns_name_t *name) { + dns_adbname_t *adbname; + dns_adbname_t *nextname; + 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) == + false); + } + adbname = nextname; + } + UNLOCK(&adb->namelocks[bucket]); + UNLOCK(&adb->lock); +} + +void +dns_adb_flushnames(dns_adb_t *adb, 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 == false); + } + 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) { + bool block; + REQUIRE(DNS_ADBENTRY_VALID(entry)); + block = (entry->quota != 0 && entry->active >= entry->quota); + return (block); +} + +void +dns_adb_beginudpfetch(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]); + addr->entry->active++; + UNLOCK(&adb->entrylocks[bucket]); +} + +void +dns_adb_endudpfetch(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]); + if (addr->entry->active > 0) + addr->entry->active--; + UNLOCK(&adb->entrylocks[bucket]); +} diff --git a/lib/dns/api b/lib/dns/api new file mode 100644 index 0000000..d94b0e6 --- /dev/null +++ b/lib/dns/api @@ -0,0 +1,13 @@ +# LIBINTERFACE ranges +# 9.6: 50-59, 110-119 +# 9.7: 60-79 +# 9.8: 80-89, 120-129 +# 9.9: 90-109, 170-179 +# 9.9-sub: 130-139, 150-159, 200-209 +# 9.10: 140-149, 190-199 +# 9.10-sub: 180-189 +# 9.11: 160-169,1100-1199 +# 9.12: 1200-1299 +LIBINTERFACE = 1104 +LIBREVISION = 2 +LIBAGE = 0 diff --git a/lib/dns/badcache.c b/lib/dns/badcache.c new file mode 100644 index 0000000..5c784be --- /dev/null +++ b/lib/dns/badcache.c @@ -0,0 +1,440 @@ +/* + * 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 http://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_mutex_t lock; + isc_mem_t *mctx; + + dns_bcentry_t **table; + unsigned int count; + unsigned int minsize; + unsigned int size; + unsigned int sweep; +}; + +#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 isc_result_t +badcache_resize(dns_badcache_t *bc, isc_time_t *now, bool grow); + +isc_result_t +dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) { + isc_result_t result; + dns_badcache_t *bc = NULL; + + REQUIRE(bcp != NULL && *bcp == NULL); + REQUIRE(mctx != NULL); + + bc = isc_mem_get(mctx, sizeof(dns_badcache_t)); + if (bc == NULL) + return (ISC_R_NOMEMORY); + memset(bc, 0, sizeof(dns_badcache_t)); + + isc_mem_attach(mctx, &bc->mctx); + result = isc_mutex_init(&bc->lock); + if (result != ISC_R_SUCCESS) + goto cleanup; + + bc->table = isc_mem_get(bc->mctx, sizeof(*bc->table) * size); + if (bc->table == NULL) { + result = ISC_R_NOMEMORY; + goto destroy_lock; + } + + bc->size = bc->minsize = size; + memset(bc->table, 0, bc->size * sizeof(dns_bcentry_t *)); + + bc->count = 0; + bc->sweep = 0; + bc->magic = BADCACHE_MAGIC; + + *bcp = bc; + return (ISC_R_SUCCESS); + + destroy_lock: + DESTROYLOCK(&bc->lock); + cleanup: + isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t)); + return (result); +} + +void +dns_badcache_destroy(dns_badcache_t **bcp) { + dns_badcache_t *bc; + + REQUIRE(bcp != NULL && *bcp != NULL); + bc = *bcp; + + dns_badcache_flush(bc); + + bc->magic = 0; + DESTROYLOCK(&bc->lock); + isc_mem_put(bc->mctx, bc->table, sizeof(dns_bcentry_t *) * bc->size); + isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t)); + *bcp = NULL; +} + +static isc_result_t +badcache_resize(dns_badcache_t *bc, isc_time_t *now, bool grow) { + dns_bcentry_t **newtable, *bad, *next; + unsigned int newsize, i; + + if (grow) + newsize = bc->size * 2 + 1; + else + newsize = (bc->size - 1) / 2; + + newtable = isc_mem_get(bc->mctx, sizeof(dns_bcentry_t *) * newsize); + if (newtable == NULL) + return (ISC_R_NOMEMORY); + memset(newtable, 0, sizeof(dns_bcentry_t *) * newsize); + + for (i = 0; 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); + bc->count--; + } else { + bad->next = newtable[bad->hashval % newsize]; + newtable[bad->hashval % newsize] = bad; + } + } + bc->table[i] = NULL; + } + + isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size); + bc->size = newsize; + bc->table = newtable; + + return (ISC_R_SUCCESS); +} + +void +dns_badcache_add(dns_badcache_t *bc, dns_name_t *name, + dns_rdatatype_t type, bool update, + uint32_t flags, isc_time_t *expire) +{ + isc_result_t result; + unsigned int i, hashval; + dns_bcentry_t *bad, *prev, *next; + isc_time_t now; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + REQUIRE(expire != NULL); + + LOCK(&bc->lock); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) + isc_time_settoepoch(&now); + + hashval = dns_name_hash(name, false); + i = hashval % bc->size; + prev = NULL; + for (bad = bc->table[i]; 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[i] = bad->next; + else + prev->next = bad->next; + isc_mem_put(bc->mctx, bad, + sizeof(*bad) + bad->name.length); + bc->count--; + } else + prev = bad; + } + + if (bad == NULL) { + isc_buffer_t buffer; + bad = isc_mem_get(bc->mctx, sizeof(*bad) + name->length); + if (bad == NULL) + goto cleanup; + 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[i]; + bc->table[i] = bad; + bc->count++; + if (bc->count > bc->size * 8) + badcache_resize(bc, &now, true); + if (bc->count < bc->size * 2 && bc->size > bc->minsize) + badcache_resize(bc, &now, false); + } else + bad->expire = *expire; + + cleanup: + UNLOCK(&bc->lock); +} + +bool +dns_badcache_find(dns_badcache_t *bc, 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; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + REQUIRE(now != NULL); + + LOCK(&bc->lock); + + /* + * 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 (bc->count == 0) + goto skip; + + i = dns_name_hash(name, false) % bc->size; + prev = NULL; + for (bad = bc->table[i]; 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[i] = bad->next; + + isc_mem_put(bc->mctx, bad, sizeof(*bad) + + bad->name.length); + bc->count--; + continue; + } + if (bad->type == type && dns_name_equal(name, &bad->name)) { + if (flagp != NULL) + *flagp = bad->flags; + answer = true; + break; + } + prev = bad; + } + skip: + + /* + * Slow sweep to clean out stale records. + */ + i = bc->sweep++ % bc->size; + 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); + bc->count--; + } + + UNLOCK(&bc->lock); + return (answer); +} + +void +dns_badcache_flush(dns_badcache_t *bc) { + dns_bcentry_t *entry, *next; + unsigned int i; + + REQUIRE(VALID_BADCACHE(bc)); + + for (i = 0; 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); + bc->count--; + } + bc->table[i] = NULL; + } +} + +void +dns_badcache_flushname(dns_badcache_t *bc, dns_name_t *name) { + dns_bcentry_t *bad, *prev, *next; + isc_result_t result; + isc_time_t now; + unsigned int i; + + REQUIRE(VALID_BADCACHE(bc)); + REQUIRE(name != NULL); + + LOCK(&bc->lock); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) + isc_time_settoepoch(&now); + i = dns_name_hash(name, false) % bc->size; + prev = NULL; + for (bad = bc->table[i]; 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[i] = bad->next; + else + prev->next = bad->next; + + isc_mem_put(bc->mctx, bad, sizeof(*bad) + + bad->name.length); + bc->count--; + } else + prev = bad; + } + + UNLOCK(&bc->lock); +} + +void +dns_badcache_flushtree(dns_badcache_t *bc, 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); + + LOCK(&bc->lock); + + result = isc_time_now(&now); + if (result != ISC_R_SUCCESS) + isc_time_settoepoch(&now); + + for (i = 0; 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); + bc->count--; + } else + prev = bad; + } + } + + UNLOCK(&bc->lock); +} + + +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); + + LOCK(&bc->lock); + fprintf(fp, ";\n; %s\n;\n", cachename); + + TIME_NOW(&now); + for (i = 0; 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); + bc->count--; + 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); + } + } + UNLOCK(&bc->lock); +} diff --git a/lib/dns/byaddr.c b/lib/dns/byaddr.c new file mode 100644 index 0000000..9eece30 --- /dev/null +++ b/lib/dns/byaddr.c @@ -0,0 +1,315 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include + +#include + +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(isc_netaddr_t *address, bool nibble, + dns_name_t *name) +{ + /* + * We dropped bitstring labels, so all lookups will use nibbles. + */ + UNUSED(nibble); + + return (dns_byaddr_createptrname2(address, + DNS_BYADDROPT_IPV6INT, name)); +} + +isc_result_t +dns_byaddr_createptrname2(isc_netaddr_t *address, unsigned int options, + dns_name_t *name) +{ + char textname[128]; + unsigned char *bytes; + int i; + char *cp; + isc_buffer_t buffer; + unsigned int len; + + REQUIRE(address != NULL); + + /* + * 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 = (unsigned char *)(&address->type); + if (address->family == AF_INET) { + (void)snprintf(textname, sizeof(textname), + "%u.%u.%u.%u.in-addr.arpa.", + (bytes[3] & 0xffU), + (bytes[2] & 0xffU), + (bytes[1] & 0xffU), + (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); + if ((options & DNS_BYADDROPT_IPV6INT) != 0) { + strlcpy(cp, "ip6.int.", remaining); + } else { + 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 inline 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)); + if (name == NULL) { + dns_rdata_freestruct(&ptr); + return (ISC_R_NOMEMORY); + } + dns_name_init(name, NULL); + result = dns_name_dup(&ptr.ptr, byaddr->mctx, name); + dns_rdata_freestruct(&ptr); + if (result != ISC_R_SUCCESS) { + isc_mem_put(byaddr->mctx, name, sizeof(*name)); + return (ISC_R_NOMEMORY); + } + 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, 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)); + if (byaddr == NULL) + return (ISC_R_NOMEMORY); + byaddr->mctx = NULL; + isc_mem_attach(mctx, &byaddr->mctx); + byaddr->options = options; + + byaddr->event = isc_mem_get(mctx, sizeof(*byaddr->event)); + if (byaddr->event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_byaddr; + } + 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); + + result = isc_mutex_init(&byaddr->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_event; + + dns_fixedname_init(&byaddr->name); + + result = dns_byaddr_createptrname2(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: + DESTROYLOCK(&byaddr->lock); + + cleanup_event: + ievent = (isc_event_t *)byaddr->event; + isc_event_free(&ievent); + byaddr->event = NULL; + + isc_task_detach(&byaddr->task); + + cleanup_byaddr: + 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; + REQUIRE(VALID_BYADDR(byaddr)); + REQUIRE(byaddr->event == NULL); + REQUIRE(byaddr->task == NULL); + dns_lookup_destroy(&byaddr->lookup); + + DESTROYLOCK(&byaddr->lock); + byaddr->magic = 0; + isc_mem_putanddetach(&byaddr->mctx, byaddr, sizeof(*byaddr)); + + *byaddrp = NULL; +} diff --git a/lib/dns/cache.c b/lib/dns/cache.c new file mode 100644 index 0000000..4701ff8 --- /dev/null +++ b/lib/dns/cache.c @@ -0,0 +1,1575 @@ +/* + * 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 http://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 "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; + unsigned int cleaning_interval; /*% The cleaning-interval from + named.conf, in seconds. */ + isc_timer_t *cleaning_timer; + 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; + + /* Locked by 'lock'. */ + int references; + int live_tasks; + dns_rdataclass_t rdclass; + dns_db_t *db; + cache_cleaner_t cleaner; + char *db_type; + int db_argc; + char **db_argv; + size_t size; + 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 +cleaning_timer_action(isc_task_t *task, isc_event_t *event); + +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 inline isc_result_t +cache_create_db(dns_cache_t *cache, dns_db_t **db) { + return (dns_db_create(cache->mctx, cache->db_type, dns_rootname, + dns_dbtype_cache, cache->rdclass, + cache->db_argc, cache->db_argv, db)); +} + +isc_result_t +dns_cache_create(isc_mem_t *cmctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *db_type, unsigned int db_argc, char **db_argv, + dns_cache_t **cachep) +{ + return (dns_cache_create3(cmctx, cmctx, taskmgr, timermgr, rdclass, "", + db_type, db_argc, db_argv, cachep)); +} + +isc_result_t +dns_cache_create2(isc_mem_t *cmctx, 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) +{ + return (dns_cache_create3(cmctx, cmctx, taskmgr, timermgr, rdclass, + cachename, db_type, db_argc, db_argv, + cachep)); +} + +isc_result_t +dns_cache_create3(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)); + if (cache == NULL) + return (ISC_R_NOMEMORY); + + 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); + if (cache->name == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_mem; + } + } + + result = isc_mutex_init(&cache->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_mem; + + result = isc_mutex_init(&cache->filelock); + if (result != ISC_R_SUCCESS) + goto cleanup_lock; + + cache->references = 1; + cache->live_tasks = 0; + cache->rdclass = rdclass; + + 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); + if (cache->db_type == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_stats; + } + + /* + * 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 *)); + if (cache->db_argv == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_dbtype; + } + + 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]); + if (cache->db_argv[i] == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_dbargv; + } + } + } + + /* + * 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 *)); + cleanup_dbtype: + isc_mem_free(cmctx, cache->db_type); + cleanup_filelock: + DESTROYLOCK(&cache->filelock); + cleanup_stats: + isc_stats_detach(&cache->stats); + cleanup_lock: + DESTROYLOCK(&cache->lock); + cleanup_mem: + 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) { + int i; + + REQUIRE(VALID_CACHE(cache)); + REQUIRE(cache->references == 0); + + 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); + + DESTROYLOCK(&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 (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); + + DESTROYLOCK(&cache->lock); + DESTROYLOCK(&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); + + LOCK(&cache->lock); + cache->references++; + UNLOCK(&cache->lock); + + *targetp = cache; +} + +void +dns_cache_detach(dns_cache_t **cachep) { + dns_cache_t *cache; + bool free_cache = false; + + REQUIRE(cachep != NULL); + cache = *cachep; + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + REQUIRE(cache->references > 0); + cache->references--; + if (cache->references == 0) { + cache->cleaner.overmem = false; + free_cache = true; + } + + *cachep = NULL; + + if (free_cache) { + /* + * 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 (cache->live_tasks > 0) { + isc_task_shutdown(cache->cleaner.task); + free_cache = false; + } + } + + UNLOCK(&cache->lock); + + if (free_cache) + 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); + if (newname == NULL) + return (ISC_R_NOMEMORY); + + 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); + 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); + UNLOCK(&cache->filelock); + return (result); + +} + +void +dns_cache_setcleaninginterval(dns_cache_t *cache, unsigned int t) { + isc_interval_t interval; + isc_result_t result; + + LOCK(&cache->lock); + + /* + * It may be the case that the cache has already shut down. + * If so, it has no timer. + */ + if (cache->cleaner.cleaning_timer == NULL) + goto unlock; + + cache->cleaner.cleaning_interval = t; + + if (t == 0) { + result = isc_timer_reset(cache->cleaner.cleaning_timer, + isc_timertype_inactive, + NULL, NULL, true); + } else { + isc_interval_set(&interval, cache->cleaner.cleaning_interval, + 0); + result = isc_timer_reset(cache->cleaner.cleaning_timer, + isc_timertype_ticker, + NULL, &interval, false); + } + if (result != ISC_R_SUCCESS) + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, ISC_LOG_WARNING, + "could not set cache cleaning interval: %s", + isc_result_totext(result)); + + unlock: + UNLOCK(&cache->lock); +} + +unsigned int +dns_cache_getcleaninginterval(dns_cache_t *cache) { + unsigned int t; + + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + t = cache->cleaner.cleaning_interval; + UNLOCK(&cache->lock); + + return (t); +} + +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; + + result = isc_mutex_init(&cleaner->lock); + if (result != ISC_R_SUCCESS) + goto fail; + + 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->cleaning_timer = NULL; + cleaner->resched_event = NULL; + cleaner->overmem_event = NULL; + cleaner->cleaning_interval = 0; /* Initially turned off. */ + + 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; + } + 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) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "cache cleaner: " + "isc_task_onshutdown() failed: %s", + dns_result_totext(result)); + goto cleanup; + } + + result = isc_timer_create(timermgr, isc_timertype_inactive, + NULL, NULL, cleaner->task, + cleaning_timer_action, cleaner, + &cleaner->cleaning_timer); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_timer_create() failed: %s", + dns_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + cleaner->resched_event = + isc_event_allocate(cache->mctx, cleaner, + DNS_EVENT_CACHECLEAN, + incremental_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->resched_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + cleaner->overmem_event = + isc_event_allocate(cache->mctx, cleaner, + DNS_EVENT_CACHEOVERMEM, + overmem_cleaning_action, + cleaner, sizeof(isc_event_t)); + if (cleaner->overmem_event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } + + 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->cleaning_timer != NULL) + isc_timer_detach(&cleaner->cleaning_timer); + if (cleaner->task != NULL) + isc_task_detach(&cleaner->task); + if (cleaner->iterator != NULL) + dns_dbiterator_destroy(&cleaner->iterator); + DESTROYLOCK(&cleaner->lock); + fail: + 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); + + dns_cache_setcleaninginterval(cleaner->cache, + cleaner->cleaning_interval); + + 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 run once for every cache-cleaning-interval as defined in named.conf. + */ +static void +cleaning_timer_action(isc_task_t *task, isc_event_t *event) { + cache_cleaner_t *cleaner = event->ev_arg; + + UNUSED(task); + + INSIST(task == cleaner->task); + INSIST(event->ev_type == ISC_TIMEREVENT_TICK); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE, + ISC_LOG_DEBUG(1), "cache cleaning timer fired, " + "cleaner state = %d", cleaner->state); + + if (cleaner->state == cleaner_s_idle) + begin_cleaning(cleaner); + + isc_event_free(&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); +} + +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); +} + +/* + * 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; + bool should_free = false; + + 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); + + LOCK(&cache->lock); + + cache->live_tasks--; + INSIST(cache->live_tasks == 0); + + if (cache->references == 0) + should_free = true; + + /* + * By detaching the timer in the context of its task, + * we are guaranteed that there will be no further timer + * events. + */ + if (cache->cleaner.cleaning_timer != NULL) + isc_timer_detach(&cache->cleaner.cleaning_timer); + + /* Make sure we don't reschedule anymore. */ + (void)isc_task_purge(task, NULL, DNS_EVENT_CACHECLEAN, NULL); + + UNLOCK(&cache->lock); + + if (should_free) + 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, (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, 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, dns_name_t *name) { + return (dns_cache_flushnode(cache, name, false)); +} + +isc_result_t +dns_cache_flushnode(dns_cache_t *cache, 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, xmlTextWriterPtr writer) { + int indices[dns_cachestatscounter_max]; + uint64_t values[dns_cachestatscounter_max]; + int xmlrc; + + 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_JSON +#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, json_object *cstats) { + isc_result_t result = ISC_R_SUCCESS; + int indices[dns_cachestatscounter_max]; + uint64_t values[dns_cachestatscounter_max]; + json_object *obj; + + 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 diff --git a/lib/dns/callbacks.c b/lib/dns/callbacks.c new file mode 100644 index 0000000..42dae51 --- /dev/null +++ b/lib/dns/callbacks.c @@ -0,0 +1,110 @@ +/* + * 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 http://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 + +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..b750d66 --- /dev/null +++ b/lib/dns/catz.c @@ -0,0 +1,1992 @@ +/* + * 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 http://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 + + +/*% + * Single member zone in a catalog + */ +struct dns_catz_entry { + dns_name_t name; + dns_catz_options_t opts; + isc_refcount_t refs; +}; + +/*% + * Catalog zone + */ +struct dns_catz_zone { + 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); + +/*% + * Collection of catalog zones for a view + */ +struct dns_catz_zones { + 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) { + 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) { + 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(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) +{ + 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; + isc_result_t result; + + REQUIRE(nentryp != NULL && *nentryp == NULL); + + nentry = isc_mem_get(mctx, sizeof(dns_catz_entry_t)); + if (nentry == NULL) + return (ISC_R_NOMEMORY); + + dns_name_init(&nentry->name, NULL); + if (domain != NULL) { + result = dns_name_dup(domain, mctx, &nentry->name); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + + dns_catz_options_init(&nentry->opts); + isc_refcount_init(&nentry->refs, 1); + *nentryp = nentry; + return (ISC_R_SUCCESS); + +cleanup: + isc_mem_put(mctx, nentry, sizeof(dns_catz_entry_t)); + return (result); +} + +dns_name_t * +dns_catz_entry_getname(dns_catz_entry_t *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; + + 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(entryp != NULL && *entryp == NULL); + isc_refcount_increment(&entry->refs, NULL); + *entryp = entry; +} + +void +dns_catz_entry_detach(dns_catz_zone_t *zone, dns_catz_entry_t **entryp) { + dns_catz_entry_t *entry; + isc_mem_t *mctx; + unsigned int refs; + + REQUIRE(entryp != NULL && *entryp != NULL); + + entry = *entryp; + *entryp = NULL; + + mctx = zone->catzs->mctx; + + isc_refcount_decrement(&entry->refs, &refs); + if (refs == 0) { + dns_catz_options_free(&entry->opts, mctx); + if (dns_name_dynamic(&entry->name)) + dns_name_free(&entry->name, mctx); + isc_refcount_destroy(&entry->refs); + isc_mem_put(mctx, entry, sizeof(dns_catz_entry_t)); + } +} + +bool +dns_catz_entry_validate(const dns_catz_entry_t *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; + + 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); + + /* 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/keys! */ + return (true); +} + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + return (&zone->name); +} + +dns_catz_options_t * +dns_catz_zone_getdefoptions(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + return (&zone->defoptions); +} + +void +dns_catz_zone_resetdefoptions(dns_catz_zone_t *zone) { + REQUIRE(zone != NULL); + + 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(target != NULL); + REQUIRE(newzone != NULL); + + /* 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); + + result = isc_ht_init(&toadd, target->catzs->mctx, 16); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = isc_ht_init(&tomod, target->catzs->mctx, 16); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = isc_ht_iter_create(newzone->entries, &iter1); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = isc_ht_iter_create(target->entries, &iter2); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * We can create those iterators now, even though toadd and tomod are + * empty + */ + result = isc_ht_iter_create(toadd, &iteradd); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = isc_ht_iter_create(tomod, &itermod); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * 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; + dns_catz_entry_t *oentry; + unsigned char * key; + 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) { + result = isc_ht_add(toadd, 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 adding zone '%s' " + "from catalog '%s' - %s", + zname, czname, + isc_result_totext(result)); + continue; + } + + if (dns_catz_entry_cmp(oentry, nentry) != true) { + result = isc_ht_add(tomod, 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 modifying zone '%s' " + "from catalog '%s' - %s", + zname, czname, + isc_result_totext(result)); + } + 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; + 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; + 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; + isc_ht_iter_current(itermod, (void **) &entry); + 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; + +cleanup: + if (iter1 != NULL) + isc_ht_iter_destroy(&iter1); + if (iter2 != NULL) + isc_ht_iter_destroy(&iter2); + if (iteradd != NULL) + isc_ht_iter_destroy(&iteradd); + if (itermod != NULL) + isc_ht_iter_destroy(&itermod); + if (toadd != NULL) + isc_ht_destroy(&toadd); + if (tomod != NULL) + 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)); + if (new_zones == NULL) + return (ISC_R_NOMEMORY); + memset(new_zones, 0, sizeof(*new_zones)); + + result = isc_mutex_init(&new_zones->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_newzones; + + result = isc_refcount_init(&new_zones->refs, 1); + if (result != ISC_R_SUCCESS) + goto cleanup_mutex; + + result = isc_ht_init(&new_zones->zones, mctx, 4); + if (result != ISC_R_SUCCESS) + goto cleanup_refcount; + + 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; + + *catzsp = new_zones; + return (ISC_R_SUCCESS); + + cleanup_ht: + isc_ht_destroy(&new_zones->zones); + cleanup_refcount: + isc_refcount_destroy(&new_zones->refs); + cleanup_mutex: + isc_mutex_destroy(&new_zones->lock); + cleanup_newzones: + isc_mem_put(mctx, new_zones, sizeof(*new_zones)); + + return (result); +} + +void +dns_catz_catzs_set_view(dns_catz_zones_t *catzs, dns_view_t *view) { + REQUIRE(catzs != NULL); + REQUIRE(view != NULL); + /* 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(zonep != NULL && *zonep == NULL); + + new_zone = isc_mem_get(catzs->mctx, sizeof(*new_zone)); + if (new_zone == NULL) + return (ISC_R_NOMEMORY); + + memset(new_zone, 0, sizeof(*new_zone)); + + dns_name_init(&new_zone->name, NULL); + + result = dns_name_dup(name, catzs->mctx, &new_zone->name); + if (result != ISC_R_SUCCESS) + goto cleanup_newzone; + + result = isc_ht_init(&new_zone->entries, catzs->mctx, 4); + if (result != ISC_R_SUCCESS) + goto cleanup_name; + + 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); + + *zonep = new_zone; + + return (ISC_R_SUCCESS); + + cleanup_ht: + isc_ht_destroy(&new_zone->entries); + cleanup_name: + dns_name_free(&new_zone->name, catzs->mctx); + cleanup_newzone: + 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(catzs != NULL); + REQUIRE(name != NULL); + 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; + + result = isc_ht_find(catzs->zones, name->ndata, name->length, + (void **) &found); + 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(catzsp != NULL && *catzsp == NULL); + + isc_refcount_increment(&catzs->refs, NULL); + *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, NULL); + *zonep = zone; +} + +void +dns_catz_zone_detach(dns_catz_zone_t **zonep) { + isc_result_t result; + dns_catz_zone_t *zone; + isc_ht_iter_t *iter = NULL; + isc_mem_t *mctx; + unsigned int refs; + + REQUIRE(zonep != NULL && *zonep != NULL); + + zone = *zonep; + *zonep = NULL; + isc_refcount_decrement(&zone->refs, &refs); + if (refs == 0) { + if (zone->entries != NULL) { + result = isc_ht_iter_create(zone->entries, &iter); + INSIST(result == ISC_R_SUCCESS); + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_entry_t *entry; + + 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); + } + mctx = zone->catzs->mctx; + isc_timer_detach(&zone->updatetimer); + isc_refcount_destroy(&zone->refs); + if (zone->db_registered == true) { + result = dns_db_updatenotify_unregister(zone->db, + dns_catz_dbupdate_callback, + zone->catzs); + INSIST(result == ISC_R_SUCCESS); + } + 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; + isc_ht_iter_t *iter = NULL; + isc_result_t result; + unsigned int refs; + dns_catz_zone_t *zone; + + + REQUIRE(catzsp != NULL); + catzs = *catzsp; + REQUIRE(catzs != NULL); + + *catzsp = NULL; + isc_refcount_decrement(&catzs->refs, &refs); + + if (refs == 0) { + DESTROYLOCK(&catzs->lock); + if (catzs->zones != NULL) { + result = isc_ht_iter_create(catzs->zones, &iter); + INSIST(result == ISC_R_SUCCESS); + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS;) + { + 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_task_destroy(&catzs->updater); + 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(zone != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(name != NULL); + + 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 { + result = dns_name_dup(&ptr.ptr, zone->catzs->mctx, + &entry->name); + if (result != ISC_R_SUCCESS) { + dns_rdata_freestruct(&ptr); + return (result); + } + } + } 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(zone != NULL); + 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(zone != NULL); + REQUIRE(ipkl != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + REQUIRE(dns_rdataset_isassociated(value)); + REQUIRE(name != NULL); + + 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)); + if (keyname == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (ipkl->labels[i] == NULL) { + if (keyname != NULL) { + dns_name_free(keyname, mctx); + isc_mem_put(mctx, keyname, + sizeof(dns_name_t)); + } + return (ISC_R_NOMEMORY); + } + dns_name_init(ipkl->labels[i], NULL); + result = dns_name_dup(name, mctx, ipkl->labels[i]); + if (result != ISC_R_SUCCESS) { + if (keyname != NULL) { + dns_name_free(keyname, mctx); + isc_mem_put(mctx, keyname, + sizeof(dns_name_t)); + } + return (result); + } + + 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(zone != NULL); + 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); + result = isc_buffer_allocate(zone->catzs->mctx, &aclb, 16); + isc_buffer_setautorealloc(aclb, true); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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(zone != NULL); + REQUIRE(mhash != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + 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 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(zone != NULL); + REQUIRE(name != NULL); + 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, + 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(catzs != NULL); + REQUIRE(zone != NULL); + + 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); +} + +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_sha256_t sha256; + isc_region_t r; + isc_result_t result; + size_t rlen; + + REQUIRE(zone != NULL); + REQUIRE(entry != NULL); + REQUIRE(buffer != NULL && *buffer != NULL); + + result = isc_buffer_allocate(zone->catzs->mctx, &tbuf, + strlen(zone->catzs->view->name) + + 2 * DNS_NAME_FORMATSIZE + 2); + if (result != ISC_R_SUCCESS) + return (result); + INSIST(tbuf != NULL); + + 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; + + /* __catz__.db */ + rlen = ISC_SHA256_DIGESTSTRINGLENGTH + 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 (tbuf->used > ISC_SHA256_DIGESTSTRINGLENGTH) { + isc_sha256_init(&sha256); + isc_sha256_update(&sha256, r.base, r.length); + /* we can do that because digest string < 2 * DNS_NAME */ + isc_sha256_end(&sha256, (char *) r.base); + 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(zone != NULL); + REQUIRE(entry != NULL); + REQUIRE(buf != NULL && *buf == NULL); + + /* + * The buffer will be reallocated if something won't fit, + * ISC_BUFFER_INCR seems like a good start. + */ + result = isc_buffer_allocate(zone->catzs->mctx, &buffer, + ISC_BUFFER_INCR); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + 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 overriden 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 == false) { + 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: + if (buffer != NULL) + 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(zone != NULL); + + 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(fn_arg != NULL); + 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_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 == false) { + 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); +} + +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(catzs != NULL); + + /* + * 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; + } + + 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 %d", + 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, + &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); + 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)); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_SUCCESS) { + break; + } + 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 == false) { + 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; + dns_catz_zone_t *zone; + + REQUIRE(catzs != NULL); + + result = isc_ht_iter_create(catzs->zones, &iter); + INSIST(result == ISC_R_SUCCESS); + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS; + result = isc_ht_iter_next(iter)) + { + isc_ht_iter_current(iter, (void **) &zone); + zone->active = false; + } + 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; + dns_catz_zone_t *zone; + + LOCK(&catzs->lock); + result = isc_ht_iter_create(catzs->zones, &iter); + INSIST(result == ISC_R_SUCCESS); + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS;) + { + isc_ht_iter_current(iter, (void **) &zone); + if (zone->active == false) { + 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); +} + +isc_result_t +dns_catz_get_iterator(dns_catz_zone_t *catz, isc_ht_iter_t **itp) { + return (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..a6d0e21 --- /dev/null +++ b/lib/dns/client.c @@ -0,0 +1,3243 @@ +/* + * 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 http://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 + +#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 REQCTX_MAGIC ISC_MAGIC('R', 'q', 'c', 'x') +#define REQCTX_VALID(c) ISC_MAGIC_VALID(c, REQCTX_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 +#define RESOLVER_NTASKS 31 +#endif /* TUNE_LARGE */ + +/*% + * 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 update_timeout; + unsigned int update_udptimeout; + unsigned int update_udpretries; + unsigned int find_timeout; + unsigned int find_udpretries; + + /* Locked */ + unsigned int references; + dns_viewlist_t viewlist; + ISC_LIST(struct resctx) resctxs; + ISC_LIST(struct reqctx) reqctxs; + ISC_LIST(struct updatectx) updatectxs; +}; + +/*% + * Timeout/retry constants for dynamic update borrowed from nsupdate + */ +#define DEF_UPDATE_TIMEOUT 300 +#define MIN_UPDATE_TIMEOUT 30 +#define DEF_UPDATE_UDPTIMEOUT 3 +#define DEF_UPDATE_UDPRETRIES 3 + +#define DEF_FIND_TIMEOUT 5 +#define DEF_FIND_UDPRETRIES 3 + +#define DNS_CLIENTATTR_OWNCTX 0x01 + +#define DNS_CLIENTVIEW_NAME "dnsclient" + +/*% + * 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; + +/*% + * Internal state for a single DNS request + */ +typedef struct reqctx { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; + dns_client_t *client; + unsigned int parseoptions; + + /* Locked */ + ISC_LINK(struct reqctx) link; + bool canceled; + dns_tsigkey_t *tsigkey; + dns_request_t *request; + dns_clientreqevent_t *event; +} reqctx_t; + +/*% + * Argument of an internal event for synchronous DNS request. + */ +typedef struct reqarg { + /* Unlocked */ + isc_appctx_t *actx; + dns_client_t *client; + isc_mutex_t lock; + + /* Locked */ + isc_result_t result; + dns_clientreqtrans_t *trans; + bool canceled; +} reqarg_t; + +/*% + * Argument of an internal event for synchronous name resolution. + */ +typedef struct updatearg { + /* Unlocked */ + isc_appctx_t *actx; + dns_client_t *client; + isc_mutex_t lock; + + /* Locked */ + isc_result_t result; + dns_clientupdatetrans_t *trans; + bool canceled; +} updatearg_t; + +/*% + * Internal state for a single dynamic update procedure + */ +typedef struct updatectx { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; + dns_client_t *client; + bool want_tcp; + + /* Locked */ + dns_request_t *updatereq; + dns_request_t *soareq; + dns_clientrestrans_t *restrans; + dns_clientrestrans_t *restrans2; + bool canceled; + + /* Task Locked */ + ISC_LINK(struct updatectx) link; + dns_clientupdatestate_t state; + dns_rdataclass_t rdclass; + dns_view_t *view; + dns_message_t *updatemsg; + dns_message_t *soaquery; + dns_clientupdateevent_t *event; + dns_tsigkey_t *tsigkey; + dst_key_t *sig0key; + dns_name_t *firstname; + dns_name_t soaqname; + dns_fixedname_t zonefname; + dns_name_t *zonename; + isc_sockaddrlist_t servers; + unsigned int nservers; + isc_sockaddr_t *currentserver; + struct updatectx *bp4; + struct updatectx *bp6; +} updatectx_t; + +static isc_result_t request_soa(updatectx_t *uctx); +static void client_resfind(resctx_t *rctx, dns_fetchevent_t *event); +static isc_result_t send_update(updatectx_t *uctx); + +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, + 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: + INSIST(0); + } + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + + if (localaddr == NULL) { + localaddr = &anyaddr; + isc_sockaddr_anyofpf(localaddr, family); + } + + 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(dns_client_t **clientp, unsigned int options) { + isc_result_t result; + isc_mem_t *mctx = NULL; + isc_appctx_t *actx = NULL; + isc_taskmgr_t *taskmgr = NULL; + isc_socketmgr_t *socketmgr = NULL; + isc_timermgr_t *timermgr = NULL; +#if 0 + /* XXXMPA add debug logging support */ + isc_log_t *lctx = NULL; + isc_logconfig_t *logconfig = NULL; + unsigned int logdebuglevel = 0; +#endif + + result = isc_mem_create(0, 0, &mctx); + if (result != ISC_R_SUCCESS) + return (result); + result = isc_appctx_create(mctx, &actx); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = isc_app_ctxstart(actx); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = isc_taskmgr_createinctx(mctx, actx, 1, 0, &taskmgr); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = isc_socketmgr_createinctx(mctx, actx, &socketmgr); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = isc_timermgr_createinctx(mctx, actx, &timermgr); + if (result != ISC_R_SUCCESS) + goto cleanup; +#if 0 + result = isc_log_create(mctx, &lctx, &logconfig); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + result = isc_log_usechannel(logconfig, "default_debug", NULL, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_log_setdebuglevel(lctx, logdebuglevel); +#endif + result = dns_client_createx(mctx, actx, taskmgr, socketmgr, timermgr, + options, clientp); + if (result != ISC_R_SUCCESS) + goto cleanup; + + (*clientp)->attributes |= DNS_CLIENTATTR_OWNCTX; + + /* client has its own reference to mctx, so we can detach it here */ + isc_mem_detach(&mctx); + + return (ISC_R_SUCCESS); + + cleanup: + if (taskmgr != NULL) + isc_taskmgr_destroy(&taskmgr); + if (timermgr != NULL) + isc_timermgr_destroy(&timermgr); + if (socketmgr != NULL) + isc_socketmgr_destroy(&socketmgr); + if (actx != NULL) + isc_appctx_destroy(&actx); + isc_mem_detach(&mctx); + + return (result); +} + +isc_result_t +dns_client_createx(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) +{ + isc_result_t result; + result = dns_client_createx2(mctx, actx, taskmgr, socketmgr, timermgr, + options, clientp, NULL, NULL); + return (result); +} + +isc_result_t +dns_client_createx2(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, isc_sockaddr_t *localaddr4, + isc_sockaddr_t *localaddr6) +{ + dns_client_t *client; + isc_result_t result; + 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)); + if (client == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&client->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, client, sizeof(*client)); + return (result); + } + + 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; + + result = dns_dispatchmgr_create(mctx, NULL, &dispatchmgr); + if (result != ISC_R_SUCCESS) + goto cleanup; + client->dispatchmgr = 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; + } + + /* 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; + ISC_LIST_INIT(client->viewlist); + ISC_LIST_APPEND(client->viewlist, view, link); + + dns_view_freeze(view); /* too early? */ + + ISC_LIST_INIT(client->resctxs); + ISC_LIST_INIT(client->reqctxs); + ISC_LIST_INIT(client->updatectxs); + + client->mctx = NULL; + isc_mem_attach(mctx, &client->mctx); + + client->update_timeout = DEF_UPDATE_TIMEOUT; + client->update_udptimeout = DEF_UPDATE_UDPTIMEOUT; + client->update_udpretries = DEF_UPDATE_UDPRETRIES; + client->find_timeout = DEF_FIND_TIMEOUT; + client->find_udpretries = DEF_FIND_UDPRETRIES; + client->attributes = 0; + + client->references = 1; + client->magic = DNS_CLIENT_MAGIC; + + *clientp = client; + + return (ISC_R_SUCCESS); + + cleanup: + if (dispatchv4 != NULL) + dns_dispatch_detach(&dispatchv4); + if (dispatchv6 != NULL) + dns_dispatch_detach(&dispatchv6); + if (dispatchmgr != NULL) + dns_dispatchmgr_destroy(&dispatchmgr); + if (client->task != NULL) + isc_task_detach(&client->task); + isc_mem_put(mctx, client, sizeof(*client)); + + return (result); +} + +static void +destroyclient(dns_client_t **clientp) { + dns_client_t *client = *clientp; + dns_view_t *view; + + 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); + + /* + * If the client has created its own running environments, + * destroy them. + */ + if ((client->attributes & DNS_CLIENTATTR_OWNCTX) != 0) { + isc_taskmgr_destroy(&client->taskmgr); + isc_timermgr_destroy(&client->timermgr); + isc_socketmgr_destroy(&client->socketmgr); + + isc_app_ctxfinish(client->actx); + isc_appctx_destroy(&client->actx); + } + + DESTROYLOCK(&client->lock); + client->magic = 0; + + isc_mem_putanddetach(&client->mctx, client, sizeof(*client)); + + *clientp = NULL; +} + +void +dns_client_destroy(dns_client_t **clientp) { + dns_client_t *client; + bool destroyok = false; + + REQUIRE(clientp != NULL); + client = *clientp; + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + client->references--; + if (client->references == 0 && ISC_LIST_EMPTY(client->resctxs) && + ISC_LIST_EMPTY(client->reqctxs) && + ISC_LIST_EMPTY(client->updatectxs)) { + destroyok = true; + } + UNLOCK(&client->lock); + + if (destroyok) + destroyclient(&client); + + *clientp = NULL; +} + +isc_result_t +dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, + 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, + 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); +} + +isc_result_t +dns_client_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, + const char *dlvname) +{ + isc_result_t result; + isc_buffer_t b; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) + goto cleanup; + + if (dlvname == NULL) + view->dlv = NULL; + else { + dns_name_t *newdlv; + + isc_buffer_constinit(&b, dlvname, strlen(dlvname)); + isc_buffer_add(&b, strlen(dlvname)); + newdlv = dns_fixedname_name(&view->dlv_fixed); + result = dns_name_fromtext(newdlv, &b, dns_rootname, + DNS_NAME_DOWNCASE, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + + view->dlv = dns_fixedname_name(&view->dlv_fixed); + } + + cleanup: + if (view != NULL) + 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)); + if (rdataset == NULL) + return (ISC_R_NOMEMORY); + + 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; + REQUIRE(rdataset != NULL); + + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + + isc_mem_put(mctx, rdataset, sizeof(*rdataset)); + + *rdatasetp = NULL; +} + +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 inline 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, fopts, + 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, + 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. + */ + ansname = isc_mem_get(mctx, sizeof(*ansname)); + if (ansname == NULL) + tresult = ISC_R_NOMEMORY; + else { + dns_name_t *aname; + + aname = dns_fixedname_name(&rctx->name); + dns_name_init(ansname, NULL); + tresult = dns_name_dup(aname, mctx, ansname); + if (tresult != ISC_R_SUCCESS) + isc_mem_put(mctx, ansname, + sizeof(*ansname)); + } + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + + 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; + tresult = dns_name_copy(&cname.cname, name, NULL); + dns_rdata_freestruct(&cname); + if (tresult == ISC_R_SUCCESS) + want_restart = true; + else + result = tresult; + 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, + &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 (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); + DESTROYLOCK(&resarg->lock); + isc_mem_put(resarg->client->mctx, resarg, sizeof(*resarg)); + } +} + +isc_result_t +dns_client_resolve(dns_client_t *client, dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, dns_namelist_t *namelist) +{ + isc_result_t result; + isc_appctx_t *actx; + resarg_t *resarg; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist)); + + if ((client->attributes & DNS_CLIENTATTR_OWNCTX) == 0 && + (options & DNS_CLIENTRESOPT_ALLOWRUN) == 0) { + /* + * If the client is run under application's control, we need + * to create a new running (sub)environment for this + * particular resolution. + */ + return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */ + } else + actx = client->actx; + + resarg = isc_mem_get(client->mctx, sizeof(*resarg)); + if (resarg == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&resarg->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + return (result); + } + + resarg->actx = actx; + resarg->client = client; + resarg->result = DNS_R_SERVFAIL; + resarg->namelist = namelist; + resarg->trans = NULL; + resarg->canceled = false; + result = dns_client_startresolve(client, name, rdclass, type, options, + client->task, resolve_done, resarg, + &resarg->trans); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&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(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); + + DESTROYLOCK(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + } + + return (result); +} + +isc_result_t +dns_client_startresolve(dns_client_t *client, 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); + want_validation = !(options & DNS_CLIENTRESOPT_NOVALIDATE); + want_cdflag = !(options & DNS_CLIENTRESOPT_NOCDFLAG); + want_tcp = (options & DNS_CLIENTRESOPT_TCP); + + /* + * 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)); + if (event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + event->result = DNS_R_SERVFAIL; + ISC_LIST_INIT(event->answerlist); + + rctx = isc_mem_get(mctx, sizeof(*rctx)); + if (rctx == NULL) + result = ISC_R_NOMEMORY; + else { + result = isc_mutex_init(&rctx->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, rctx, sizeof(*rctx)); + rctx = NULL; + } + } + if (result != ISC_R_SUCCESS) + goto cleanup; + + 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); + result = dns_name_copy(name, dns_fixedname_name(&rctx->name), NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + + 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; + + 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); + if (rctx != NULL) { + DESTROYLOCK(&rctx->lock); + isc_mem_put(mctx, rctx, sizeof(*rctx)); + } + if (event != NULL) + 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; + bool need_destroyclient = false; + + REQUIRE(transp != NULL); + rctx = (resctx_t *)*transp; + 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); + + if (client->references == 0 && ISC_LIST_EMPTY(client->resctxs) && + ISC_LIST_EMPTY(client->reqctxs) && + ISC_LIST_EMPTY(client->updatectxs)) + need_destroyclient = true; + + UNLOCK(&client->lock); + + INSIST(ISC_LIST_EMPTY(rctx->namelist)); + + DESTROYLOCK(&rctx->lock); + rctx->magic = 0; + + isc_mem_put(mctx, rctx, sizeof(*rctx)); + + if (need_destroyclient) + destroyclient(&client); + + *transp = NULL; +} + +isc_result_t +dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *keyname, isc_buffer_t *keydatabuf) +{ + isc_result_t result; + dns_view_t *view = NULL; + dst_key_t *dstkey = NULL; + dns_keytable_t *secroots = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_view_getsecroots(view, &secroots); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dst_key_fromdns(keyname, rdclass, keydatabuf, client->mctx, + &dstkey); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_keytable_add(secroots, false, &dstkey); + + cleanup: + if (dstkey != NULL) + dst_key_free(&dstkey); + if (view != NULL) + dns_view_detach(&view); + if (secroots != NULL) + dns_keytable_detach(&secroots); + return (result); +} + +/*% + * Simple request routines + */ +static void +request_done(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = NULL; + dns_request_t *request; + isc_result_t result, eresult; + reqctx_t *ctx; + + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + result = eresult = reqev->result; + ctx = reqev->ev_arg; + REQUIRE(REQCTX_VALID(ctx)); + + isc_event_free(&event); + + LOCK(&ctx->lock); + + if (eresult == ISC_R_SUCCESS) { + result = dns_request_getresponse(request, ctx->event->rmessage, + ctx->parseoptions); + } + + if (ctx->tsigkey != NULL) + dns_tsigkey_detach(&ctx->tsigkey); + + if (ctx->canceled) + ctx->event->result = ISC_R_CANCELED; + else + ctx->event->result = result; + task = ctx->event->ev_sender; + ctx->event->ev_sender = ctx; + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&ctx->event)); + + UNLOCK(&ctx->lock); +} + +static void +localrequest_done(isc_task_t *task, isc_event_t *event) { + reqarg_t *reqarg = event->ev_arg; + dns_clientreqevent_t *rev =(dns_clientreqevent_t *)event; + + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_CLIENTREQDONE); + + LOCK(&reqarg->lock); + + reqarg->result = rev->result; + dns_client_destroyreqtrans(&reqarg->trans); + isc_event_free(&event); + + if (!reqarg->canceled) { + UNLOCK(&reqarg->lock); + + /* Exit from the internal event loop */ + isc_app_ctxsuspend(reqarg->actx); + } else { + /* + * We have already exited from the loop (due to some + * unexpected event). Just clean the arg up. + */ + UNLOCK(&reqarg->lock); + DESTROYLOCK(&reqarg->lock); + isc_mem_put(reqarg->client->mctx, reqarg, sizeof(*reqarg)); + } +} + +isc_result_t +dns_client_request(dns_client_t *client, dns_message_t *qmessage, + dns_message_t *rmessage, isc_sockaddr_t *server, + unsigned int options, unsigned int parseoptions, + dns_tsec_t *tsec, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries) +{ + isc_appctx_t *actx; + reqarg_t *reqarg; + isc_result_t result; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(qmessage != NULL); + REQUIRE(rmessage != NULL); + + if ((client->attributes & DNS_CLIENTATTR_OWNCTX) == 0 && + (options & DNS_CLIENTREQOPT_ALLOWRUN) == 0) { + /* + * If the client is run under application's control, we need + * to create a new running (sub)environment for this + * particular resolution. + */ + return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */ + } else + actx = client->actx; + + reqarg = isc_mem_get(client->mctx, sizeof(*reqarg)); + if (reqarg == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&reqarg->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(client->mctx, reqarg, sizeof(*reqarg)); + return (result); + } + + reqarg->actx = actx; + reqarg->client = client; + reqarg->trans = NULL; + reqarg->canceled = false; + + result = dns_client_startrequest(client, qmessage, rmessage, server, + options, parseoptions, tsec, timeout, + udptimeout, udpretries, + client->task, localrequest_done, + reqarg, &reqarg->trans); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&reqarg->lock); + isc_mem_put(client->mctx, reqarg, sizeof(*reqarg)); + return (result); + } + + /* + * Start internal event loop. It blocks until the entire process + * is completed. + */ + result = isc_app_ctxrun(actx); + + LOCK(&reqarg->lock); + if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) + result = reqarg->result; + if (reqarg->trans != NULL) { + /* + * Unusual termination (perhaps due to signal). We need some + * tricky cleanup process. + */ + reqarg->canceled = true; + dns_client_cancelresolve(reqarg->trans); + + UNLOCK(&reqarg->lock); + + /* reqarg will be freed in the event handler. */ + } else { + UNLOCK(&reqarg->lock); + + DESTROYLOCK(&reqarg->lock); + isc_mem_put(client->mctx, reqarg, sizeof(*reqarg)); + } + + return (result); +} + +isc_result_t +dns_client_startrequest(dns_client_t *client, dns_message_t *qmessage, + dns_message_t *rmessage, 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) +{ + isc_result_t result; + dns_view_t *view = NULL; + isc_task_t *tclone = NULL; + dns_clientreqevent_t *event = NULL; + reqctx_t *ctx = NULL; + dns_tsectype_t tsectype = dns_tsectype_none; + unsigned int reqoptions; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(qmessage != NULL); + REQUIRE(rmessage != NULL); + REQUIRE(transp != NULL && *transp == NULL); + + if (tsec != NULL) { + tsectype = dns_tsec_gettype(tsec); + if (tsectype != dns_tsectype_tsig) + return (ISC_R_NOTIMPLEMENTED); /* XXX */ + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + qmessage->rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) + return (result); + + reqoptions = 0; + if ((options & DNS_CLIENTREQOPT_TCP) != 0) + reqoptions |= DNS_REQUESTOPT_TCP; + + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_clientreqevent_t *) + isc_event_allocate(client->mctx, tclone, + DNS_EVENT_CLIENTREQDONE, + action, arg, sizeof(*event)); + if (event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + ctx = isc_mem_get(client->mctx, sizeof(*ctx)); + if (ctx == NULL) + result = ISC_R_NOMEMORY; + else { + result = isc_mutex_init(&ctx->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(client->mctx, ctx, sizeof(*ctx)); + ctx = NULL; + } + } + if (result != ISC_R_SUCCESS) + goto cleanup; + + ctx->client = client; + ISC_LINK_INIT(ctx, link); + ctx->parseoptions = parseoptions; + ctx->canceled = false; + ctx->event = event; + ctx->event->rmessage = rmessage; + ctx->tsigkey = NULL; + if (tsec != NULL) + dns_tsec_getkey(tsec, &ctx->tsigkey); + + ctx->magic = REQCTX_MAGIC; + + LOCK(&client->lock); + ISC_LIST_APPEND(client->reqctxs, ctx, link); + UNLOCK(&client->lock); + + ctx->request = NULL; + result = dns_request_createvia3(view->requestmgr, qmessage, NULL, + server, reqoptions, ctx->tsigkey, + timeout, udptimeout, udpretries, + client->task, request_done, ctx, + &ctx->request); + if (result == ISC_R_SUCCESS) { + dns_view_detach(&view); + *transp = (dns_clientreqtrans_t *)ctx; + return (ISC_R_SUCCESS); + } + + cleanup: + if (ctx != NULL) { + LOCK(&client->lock); + ISC_LIST_UNLINK(client->reqctxs, ctx, link); + UNLOCK(&client->lock); + DESTROYLOCK(&ctx->lock); + isc_mem_put(client->mctx, ctx, sizeof(*ctx)); + } + if (event != NULL) + isc_event_free(ISC_EVENT_PTR(&event)); + isc_task_detach(&tclone); + dns_view_detach(&view); + + return (result); +} + +void +dns_client_cancelrequest(dns_clientreqtrans_t *trans) { + reqctx_t *ctx; + + REQUIRE(trans != NULL); + ctx = (reqctx_t *)trans; + REQUIRE(REQCTX_VALID(ctx)); + + LOCK(&ctx->lock); + + if (!ctx->canceled) { + ctx->canceled = true; + if (ctx->request != NULL) + dns_request_cancel(ctx->request); + } + + UNLOCK(&ctx->lock); +} + +void +dns_client_destroyreqtrans(dns_clientreqtrans_t **transp) { + reqctx_t *ctx; + isc_mem_t *mctx; + dns_client_t *client; + bool need_destroyclient = false; + + REQUIRE(transp != NULL); + ctx = (reqctx_t *)*transp; + REQUIRE(REQCTX_VALID(ctx)); + client = ctx->client; + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(ctx->event == NULL); + REQUIRE(ctx->request != NULL); + + dns_request_destroy(&ctx->request); + mctx = client->mctx; + + LOCK(&client->lock); + + INSIST(ISC_LINK_LINKED(ctx, link)); + ISC_LIST_UNLINK(client->reqctxs, ctx, link); + + if (client->references == 0 && ISC_LIST_EMPTY(client->resctxs) && + ISC_LIST_EMPTY(client->reqctxs) && + ISC_LIST_EMPTY(client->updatectxs)) { + need_destroyclient = true; + } + + UNLOCK(&client->lock); + + DESTROYLOCK(&ctx->lock); + ctx->magic = 0; + + isc_mem_put(mctx, ctx, sizeof(*ctx)); + + if (need_destroyclient) + destroyclient(&client); + + *transp = NULL; +} + +/*% + * Dynamic update routines + */ +static isc_result_t +rcode2result(dns_rcode_t rcode) { + /* XXX: isn't there a similar function? */ + switch (rcode) { + case dns_rcode_formerr: + return (DNS_R_FORMERR); + case dns_rcode_servfail: + return (DNS_R_SERVFAIL); + case dns_rcode_nxdomain: + return (DNS_R_NXDOMAIN); + case dns_rcode_notimp: + return (DNS_R_NOTIMP); + case dns_rcode_refused: + return (DNS_R_REFUSED); + case dns_rcode_yxdomain: + return (DNS_R_YXDOMAIN); + case dns_rcode_yxrrset: + return (DNS_R_YXRRSET); + case dns_rcode_nxrrset: + return (DNS_R_NXRRSET); + case dns_rcode_notauth: + return (DNS_R_NOTAUTH); + case dns_rcode_notzone: + return (DNS_R_NOTZONE); + case dns_rcode_badvers: + return (DNS_R_BADVERS); + } + + return (ISC_R_FAILURE); +} + +static void +update_sendevent(updatectx_t *uctx, isc_result_t result) { + isc_task_t *task; + + dns_message_destroy(&uctx->updatemsg); + if (uctx->tsigkey != NULL) + dns_tsigkey_detach(&uctx->tsigkey); + if (uctx->sig0key != NULL) + dst_key_free(&uctx->sig0key); + + if (uctx->canceled) + uctx->event->result = ISC_R_CANCELED; + else + uctx->event->result = result; + uctx->event->state = uctx->state; + task = uctx->event->ev_sender; + uctx->event->ev_sender = uctx; + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&uctx->event)); +} + +static void +update_done(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_requestevent_t *reqev = NULL; + dns_request_t *request; + dns_message_t *answer = NULL; + updatectx_t *uctx = event->ev_arg; + dns_client_t *client; + unsigned int timeout, reqoptions; + + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + REQUIRE(UCTX_VALID(uctx)); + client = uctx->client; + REQUIRE(DNS_CLIENT_VALID(client)); + + result = reqev->result; + if (result != ISC_R_SUCCESS) + goto out; + + result = dns_message_create(client->mctx, DNS_MESSAGE_INTENTPARSE, + &answer); + if (result != ISC_R_SUCCESS) + goto out; + uctx->state = dns_clientupdatestate_done; + result = dns_request_getresponse(request, answer, + DNS_MESSAGEPARSE_PRESERVEORDER); + if (result == ISC_R_SUCCESS && answer->rcode != dns_rcode_noerror) + result = rcode2result(answer->rcode); + + out: + if (answer != NULL) + dns_message_destroy(&answer); + isc_event_free(&event); + + LOCK(&uctx->lock); + uctx->currentserver = ISC_LIST_NEXT(uctx->currentserver, link); + dns_request_destroy(&uctx->updatereq); + /* + * Moving on to the next server shouldn't change the result + * for NXDOMAIN, YXDOMAIN, NXRRSET and YXRRSET as they + * indicate a prerequisite failure. REFUSED should also + * be consistent across all servers but often isn't as that + * is policy rather that zone content driven (slaves that + * aren't willing to forward should return NOTIMPL). NOTZONE + * indicates that we stuffed up the request construction so + * don't retry. + */ + if (result != ISC_R_SUCCESS && result != DNS_R_NXDOMAIN && + result != DNS_R_YXDOMAIN && result != DNS_R_YXRRSET && + result != DNS_R_NXRRSET && result != DNS_R_NOTZONE && + !uctx->canceled && uctx->currentserver != NULL) + { + dns_message_renderreset(uctx->updatemsg); + dns_message_settsigkey(uctx->updatemsg, NULL); + + timeout = client->update_timeout / uctx->nservers; + if (timeout < MIN_UPDATE_TIMEOUT) + timeout = MIN_UPDATE_TIMEOUT; + reqoptions = 0; + if (uctx->want_tcp) + reqoptions |= DNS_REQUESTOPT_TCP; + result = dns_request_createvia3(uctx->view->requestmgr, + uctx->updatemsg, + NULL, + uctx->currentserver, + reqoptions, + uctx->tsigkey, + timeout, + client->update_udptimeout, + client->update_udpretries, + client->task, + update_done, uctx, + &uctx->updatereq); + UNLOCK(&uctx->lock); + + if (result == ISC_R_SUCCESS) { + /* XXX: should we keep the 'done' state here? */ + uctx->state = dns_clientupdatestate_sent; + return; + } + } else + UNLOCK(&uctx->lock); + + update_sendevent(uctx, result); +} + +static isc_result_t +send_update(updatectx_t *uctx) { + isc_result_t result; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + dns_client_t *client = uctx->client; + unsigned int timeout, reqoptions; + + REQUIRE(uctx->zonename != NULL && uctx->currentserver != NULL); + + result = dns_message_gettempname(uctx->updatemsg, &name); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_init(name, NULL); + dns_name_clone(uctx->zonename, name); + result = dns_message_gettemprdataset(uctx->updatemsg, &rdataset); + if (result != ISC_R_SUCCESS) { + dns_message_puttempname(uctx->updatemsg, &name); + return (result); + } + dns_rdataset_makequestion(rdataset, uctx->rdclass, dns_rdatatype_soa); + ISC_LIST_INIT(name->list); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(uctx->updatemsg, name, DNS_SECTION_ZONE); + if (uctx->tsigkey == NULL && uctx->sig0key != NULL) { + result = dns_message_setsig0key(uctx->updatemsg, + uctx->sig0key); + if (result != ISC_R_SUCCESS) + return (result); + } + timeout = client->update_timeout / uctx->nservers; + if (timeout < MIN_UPDATE_TIMEOUT) + timeout = MIN_UPDATE_TIMEOUT; + reqoptions = 0; + if (uctx->want_tcp) + reqoptions |= DNS_REQUESTOPT_TCP; + result = dns_request_createvia3(uctx->view->requestmgr, + uctx->updatemsg, + NULL, uctx->currentserver, + reqoptions, uctx->tsigkey, timeout, + client->update_udptimeout, + client->update_udpretries, + client->task, update_done, uctx, + &uctx->updatereq); + if (result == ISC_R_SUCCESS && + uctx->state == dns_clientupdatestate_prepare) { + uctx->state = dns_clientupdatestate_sent; + } + + return (result); +} + +static void +resolveaddr_done(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + int family; + dns_rdatatype_t qtype; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_name_t *name; + dns_rdataset_t *rdataset; + updatectx_t *uctx; + bool completed = false; + + UNUSED(task); + + REQUIRE(event->ev_arg != NULL); + uctx = *(updatectx_t **)event->ev_arg; + REQUIRE(UCTX_VALID(uctx)); + + if (event->ev_arg == &uctx->bp4) { + family = AF_INET; + qtype = dns_rdatatype_a; + LOCK(&uctx->lock); + dns_client_destroyrestrans(&uctx->restrans); + UNLOCK(&uctx->lock); + } else { + INSIST(event->ev_arg == &uctx->bp6); + family = AF_INET6; + qtype = dns_rdatatype_aaaa; + LOCK(&uctx->lock); + dns_client_destroyrestrans(&uctx->restrans2); + UNLOCK(&uctx->lock); + } + + result = rev->result; + if (result != ISC_R_SUCCESS) + goto done; + + for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + if (!dns_rdataset_isassociated(rdataset)) + continue; + if (rdataset->type != qtype) + continue; + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_rdata_t rdata; + dns_rdata_in_a_t rdata_a; + dns_rdata_in_aaaa_t rdata_aaaa; + isc_sockaddr_t *sa; + + sa = isc_mem_get(uctx->client->mctx, + sizeof(*sa)); + if (sa == NULL) { + /* + * If we fail to get a sockaddr, + we simply move forward with the + * addresses we've got so far. + */ + goto done; + } + + dns_rdata_init(&rdata); + switch (family) { + case AF_INET: + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rdata_a, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin(sa, + &rdata_a.in_addr, + 53); + dns_rdata_freestruct(&rdata_a); + break; + case AF_INET6: + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rdata_aaaa, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_sockaddr_fromin6(sa, + &rdata_aaaa.in6_addr, + 53); + dns_rdata_freestruct(&rdata_aaaa); + break; + } + + ISC_LINK_INIT(sa, link); + ISC_LIST_APPEND(uctx->servers, sa, link); + uctx->nservers++; + } + } + } + + done: + dns_client_freeresanswer(uctx->client, &rev->answerlist); + isc_event_free(&event); + + LOCK(&uctx->lock); + if (uctx->restrans == NULL && uctx->restrans2 == NULL) + completed = true; + UNLOCK(&uctx->lock); + + if (completed) { + INSIST(uctx->currentserver == NULL); + uctx->currentserver = ISC_LIST_HEAD(uctx->servers); + if (uctx->currentserver != NULL && !uctx->canceled) + send_update(uctx); + else { + if (result == ISC_R_SUCCESS) + result = ISC_R_NOTFOUND; + update_sendevent(uctx, result); + } + } +} + +static isc_result_t +process_soa(updatectx_t *uctx, dns_rdataset_t *soaset, dns_name_t *soaname) { + isc_result_t result; + dns_rdata_t soarr = DNS_RDATA_INIT; + dns_rdata_soa_t soa; + dns_name_t primary; + unsigned int resoptions; + + result = dns_rdataset_first(soaset); + if (result != ISC_R_SUCCESS) + return (result); + dns_rdata_init(&soarr); + dns_rdataset_current(soaset, &soarr); + result = dns_rdata_tostruct(&soarr, &soa, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + dns_name_init(&primary, NULL); + dns_name_clone(&soa.origin, &primary); + + if (uctx->zonename == NULL) { + uctx->zonename = dns_fixedname_name(&uctx->zonefname); + result = dns_name_copy(soaname, uctx->zonename, NULL); + if (result != ISC_R_SUCCESS) + goto out; + } + + if (uctx->currentserver != NULL) + result = send_update(uctx); + else { + /* + * Get addresses of the primary server. We don't use the ADB + * feature so that we could avoid caching data. + */ + LOCK(&uctx->lock); + uctx->bp4 = uctx; + resoptions = 0; + if (uctx->want_tcp) + resoptions |= DNS_CLIENTRESOPT_TCP; + result = dns_client_startresolve(uctx->client, &primary, + uctx->rdclass, + dns_rdatatype_a, + resoptions, + uctx->client->task, + resolveaddr_done, &uctx->bp4, + &uctx->restrans); + if (result == ISC_R_SUCCESS) { + uctx->bp6 = uctx; + result = dns_client_startresolve(uctx->client, + &primary, + uctx->rdclass, + dns_rdatatype_aaaa, + resoptions, + uctx->client->task, + resolveaddr_done, + &uctx->bp6, + &uctx->restrans2); + } + UNLOCK(&uctx->lock); + } + + out: + dns_rdata_freestruct(&soa); + + return (result); +} + +static void +receive_soa(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = NULL; + updatectx_t *uctx; + dns_client_t *client; + isc_result_t result, eresult; + dns_request_t *request; + dns_message_t *rcvmsg = NULL; + dns_section_t section; + dns_rdataset_t *soaset = NULL; + int pass = 0; + dns_name_t *name; + dns_message_t *soaquery = NULL; + isc_sockaddr_t *addr; + bool seencname = false; + bool droplabel = false; + dns_name_t tname; + unsigned int nlabels, reqoptions; + + UNUSED(task); + + REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE); + reqev = (dns_requestevent_t *)event; + request = reqev->request; + result = eresult = reqev->result; + POST(result); + uctx = reqev->ev_arg; + client = uctx->client; + soaquery = uctx->soaquery; + addr = uctx->currentserver; + INSIST(addr != NULL); + + isc_event_free(&event); + + if (eresult != ISC_R_SUCCESS) { + result = eresult; + goto out; + } + + result = dns_message_create(uctx->client->mctx, + DNS_MESSAGE_INTENTPARSE, &rcvmsg); + if (result != ISC_R_SUCCESS) + goto out; + result = dns_request_getresponse(request, rcvmsg, + DNS_MESSAGEPARSE_PRESERVEORDER); + + if (result == DNS_R_TSIGERRORSET) { + dns_request_t *newrequest = NULL; + + /* Retry SOA request without TSIG */ + dns_message_destroy(&rcvmsg); + dns_message_renderreset(uctx->soaquery); + reqoptions = 0; + if (uctx->want_tcp) + reqoptions |= DNS_REQUESTOPT_TCP; + result = dns_request_createvia3(uctx->view->requestmgr, + uctx->soaquery, NULL, addr, + reqoptions, NULL, + client->find_timeout * 20, + client->find_timeout, 3, + uctx->client->task, + receive_soa, uctx, + &newrequest); + if (result == ISC_R_SUCCESS) { + LOCK(&uctx->lock); + dns_request_destroy(&uctx->soareq); + uctx->soareq = newrequest; + UNLOCK(&uctx->lock); + + return; + } + goto out; + } + + section = DNS_SECTION_ANSWER; + POST(section); + + if (rcvmsg->rcode != dns_rcode_noerror && + rcvmsg->rcode != dns_rcode_nxdomain) { + result = rcode2result(rcvmsg->rcode); + goto out; + } + + lookforsoa: + if (pass == 0) + section = DNS_SECTION_ANSWER; + else if (pass == 1) + section = DNS_SECTION_AUTHORITY; + else { + droplabel = true; + goto out; + } + + result = dns_message_firstname(rcvmsg, section); + if (result != ISC_R_SUCCESS) { + pass++; + goto lookforsoa; + } + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(rcvmsg, section, &name); + soaset = NULL; + result = dns_message_findtype(name, dns_rdatatype_soa, 0, + &soaset); + if (result == ISC_R_SUCCESS) + break; + if (section == DNS_SECTION_ANSWER) { + dns_rdataset_t *tset = NULL; + if (dns_message_findtype(name, dns_rdatatype_cname, 0, + &tset) == ISC_R_SUCCESS + || + dns_message_findtype(name, dns_rdatatype_dname, 0, + &tset) == ISC_R_SUCCESS + ) + { + seencname = true; + break; + } + } + + result = dns_message_nextname(rcvmsg, section); + } + + if (soaset == NULL && !seencname) { + pass++; + goto lookforsoa; + } + + if (seencname) { + droplabel = true; + goto out; + } + + result = process_soa(uctx, soaset, name); + + out: + if (droplabel) { + result = dns_message_firstname(soaquery, DNS_SECTION_QUESTION); + INSIST(result == ISC_R_SUCCESS); + name = NULL; + dns_message_currentname(soaquery, DNS_SECTION_QUESTION, &name); + nlabels = dns_name_countlabels(name); + if (nlabels == 1) + result = DNS_R_SERVFAIL; /* is there a better error? */ + else { + dns_name_init(&tname, NULL); + dns_name_getlabelsequence(name, 1, nlabels - 1, + &tname); + dns_name_clone(&tname, name); + dns_request_destroy(&request); + LOCK(&uctx->lock); + uctx->soareq = NULL; + UNLOCK(&uctx->lock); + dns_message_renderreset(soaquery); + dns_message_settsigkey(soaquery, NULL); + reqoptions = 0; + if (uctx->want_tcp) + reqoptions |= DNS_REQUESTOPT_TCP; + result = dns_request_createvia3(uctx->view->requestmgr, + soaquery, NULL, + uctx->currentserver, + reqoptions, + uctx->tsigkey, + client->find_timeout * + 20, + client->find_timeout, + 3, client->task, + receive_soa, uctx, + &uctx->soareq); + } + } + + if (!droplabel || result != ISC_R_SUCCESS) { + dns_message_destroy(&uctx->soaquery); + LOCK(&uctx->lock); + dns_request_destroy(&uctx->soareq); + UNLOCK(&uctx->lock); + } + + if (rcvmsg != NULL) + dns_message_destroy(&rcvmsg); + + if (result != ISC_R_SUCCESS) + update_sendevent(uctx, result); +} + +static isc_result_t +request_soa(updatectx_t *uctx) { + isc_result_t result; + dns_message_t *soaquery = uctx->soaquery; + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + unsigned int reqoptions; + + if (soaquery == NULL) { + result = dns_message_create(uctx->client->mctx, + DNS_MESSAGE_INTENTRENDER, + &soaquery); + if (result != ISC_R_SUCCESS) + return (result); + } + soaquery->flags |= DNS_MESSAGEFLAG_RD; + result = dns_message_gettempname(soaquery, &name); + if (result != ISC_R_SUCCESS) + goto fail; + result = dns_message_gettemprdataset(soaquery, &rdataset); + if (result != ISC_R_SUCCESS) + goto fail; + dns_rdataset_makequestion(rdataset, uctx->rdclass, dns_rdatatype_soa); + dns_name_clone(uctx->firstname, name); + ISC_LIST_APPEND(name->list, rdataset, link); + dns_message_addname(soaquery, name, DNS_SECTION_QUESTION); + rdataset = NULL; + name = NULL; + reqoptions = 0; + if (uctx->want_tcp) + reqoptions |= DNS_REQUESTOPT_TCP; + + result = dns_request_createvia3(uctx->view->requestmgr, + soaquery, NULL, uctx->currentserver, + reqoptions, uctx->tsigkey, + uctx->client->find_timeout * 20, + uctx->client->find_timeout, 3, + uctx->client->task, receive_soa, uctx, + &uctx->soareq); + if (result == ISC_R_SUCCESS) { + uctx->soaquery = soaquery; + return (ISC_R_SUCCESS); + } + + fail: + if (rdataset != NULL) { + ISC_LIST_UNLINK(name->list, rdataset, link); /* for safety */ + dns_message_puttemprdataset(soaquery, &rdataset); + } + if (name != NULL) + dns_message_puttempname(soaquery, &name); + dns_message_destroy(&soaquery); + + return (result); +} + +static void +resolvesoa_done(isc_task_t *task, isc_event_t *event) { + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + updatectx_t *uctx; + dns_name_t *name, tname; + dns_rdataset_t *rdataset = NULL; + isc_result_t result = rev->result; + unsigned int nlabels, resoptions; + + UNUSED(task); + + uctx = event->ev_arg; + REQUIRE(UCTX_VALID(uctx)); + + LOCK(&uctx->lock); + dns_client_destroyrestrans(&uctx->restrans); + UNLOCK(&uctx->lock); + + uctx = event->ev_arg; + if (result != ISC_R_SUCCESS && + result != DNS_R_NCACHENXDOMAIN && + result != DNS_R_NCACHENXRRSET) { + /* XXX: what about DNSSEC failure? */ + goto out; + } + + for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + if (dns_rdataset_isassociated(rdataset) && + rdataset->type == dns_rdatatype_soa) + break; + } + } + + if (rdataset == NULL) { + /* Drop one label and retry resolution. */ + nlabels = dns_name_countlabels(&uctx->soaqname); + if (nlabels == 1) { + result = DNS_R_SERVFAIL; /* is there a better error? */ + goto out; + } + dns_name_init(&tname, NULL); + dns_name_getlabelsequence(&uctx->soaqname, 1, nlabels - 1, + &tname); + dns_name_clone(&tname, &uctx->soaqname); + resoptions = 0; + if (uctx->want_tcp) + resoptions |= DNS_CLIENTRESOPT_TCP; + + result = dns_client_startresolve(uctx->client, &uctx->soaqname, + uctx->rdclass, + dns_rdatatype_soa, + resoptions, + uctx->client->task, + resolvesoa_done, uctx, + &uctx->restrans); + } else + result = process_soa(uctx, rdataset, &uctx->soaqname); + + out: + dns_client_freeresanswer(uctx->client, &rev->answerlist); + isc_event_free(&event); + + if (result != ISC_R_SUCCESS) + update_sendevent(uctx, result); +} + +static isc_result_t +copy_name(isc_mem_t *mctx, dns_message_t *msg, dns_name_t *name, + dns_name_t **newnamep) +{ + isc_result_t result; + dns_name_t *newname = NULL; + isc_region_t r; + isc_buffer_t *namebuf = NULL, *rdatabuf = NULL; + dns_rdatalist_t *rdatalist; + dns_rdataset_t *rdataset, *newrdataset; + dns_rdata_t rdata = DNS_RDATA_INIT, *newrdata; + + result = dns_message_gettempname(msg, &newname); + if (result != ISC_R_SUCCESS) + return (result); + result = isc_buffer_allocate(mctx, &namebuf, DNS_NAME_MAXWIRE); + if (result != ISC_R_SUCCESS) + goto fail; + dns_name_init(newname, NULL); + dns_name_setbuffer(newname, namebuf); + dns_message_takebuffer(msg, &namebuf); + result = dns_name_copy(name, newname, NULL); + if (result != ISC_R_SUCCESS) + goto fail; + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + rdatalist = NULL; + result = dns_message_gettemprdatalist(msg, &rdatalist); + if (result != ISC_R_SUCCESS) + goto fail; + dns_rdatalist_init(rdatalist); + rdatalist->type = rdataset->type; + rdatalist->rdclass = rdataset->rdclass; + rdatalist->covers = rdataset->covers; + rdatalist->ttl = rdataset->ttl; + + result = dns_rdataset_first(rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(rdataset, &rdata); + + newrdata = NULL; + result = dns_message_gettemprdata(msg, &newrdata); + if (result != ISC_R_SUCCESS) + goto fail; + dns_rdata_toregion(&rdata, &r); + rdatabuf = NULL; + result = isc_buffer_allocate(mctx, &rdatabuf, + r.length); + if (result != ISC_R_SUCCESS) + goto fail; + isc_buffer_putmem(rdatabuf, r.base, r.length); + isc_buffer_usedregion(rdatabuf, &r); + dns_rdata_init(newrdata); + dns_rdata_fromregion(newrdata, rdata.rdclass, + rdata.type, &r); + newrdata->flags = rdata.flags; + + ISC_LIST_APPEND(rdatalist->rdata, newrdata, link); + dns_message_takebuffer(msg, &rdatabuf); + + result = dns_rdataset_next(rdataset); + } + + newrdataset = NULL; + result = dns_message_gettemprdataset(msg, &newrdataset); + if (result != ISC_R_SUCCESS) + goto fail; + dns_rdatalist_tordataset(rdatalist, newrdataset); + + ISC_LIST_APPEND(newname->list, newrdataset, link); + } + + *newnamep = newname; + + return (ISC_R_SUCCESS); + + fail: + dns_message_puttempname(msg, &newname); + + return (result); + +} + +static void +internal_update_callback(isc_task_t *task, isc_event_t *event) { + updatearg_t *uarg = event->ev_arg; + dns_clientupdateevent_t *uev = (dns_clientupdateevent_t *)event; + + UNUSED(task); + + LOCK(&uarg->lock); + + uarg->result = uev->result; + + dns_client_destroyupdatetrans(&uarg->trans); + isc_event_free(&event); + + if (!uarg->canceled) { + UNLOCK(&uarg->lock); + + /* Exit from the internal event loop */ + isc_app_ctxsuspend(uarg->actx); + } else { + /* + * We have already exited from the loop (due to some + * unexpected event). Just clean the arg up. + */ + UNLOCK(&uarg->lock); + DESTROYLOCK(&uarg->lock); + isc_mem_put(uarg->client->mctx, uarg, sizeof(*uarg)); + } +} + +isc_result_t +dns_client_update(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *zonename, dns_namelist_t *prerequisites, + dns_namelist_t *updates, isc_sockaddrlist_t *servers, + dns_tsec_t *tsec, unsigned int options) +{ + isc_result_t result; + isc_appctx_t *actx; + updatearg_t *uarg; + + REQUIRE(DNS_CLIENT_VALID(client)); + + if ((client->attributes & DNS_CLIENTATTR_OWNCTX) == 0 && + (options & DNS_CLIENTUPDOPT_ALLOWRUN) == 0) { + /* + * If the client is run under application's control, we need + * to create a new running (sub)environment for this + * particular update. + */ + return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */ + } else + actx = client->actx; + + uarg = isc_mem_get(client->mctx, sizeof(*uarg)); + if (uarg == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&uarg->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(client->mctx, uarg, sizeof(*uarg)); + return (result); + } + + uarg->actx = actx; + uarg->client = client; + uarg->result = ISC_R_FAILURE; + uarg->trans = NULL; + uarg->canceled = false; + + result = dns_client_startupdate(client, rdclass, zonename, + prerequisites, updates, servers, + tsec, options, client->task, + internal_update_callback, uarg, + &uarg->trans); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&uarg->lock); + isc_mem_put(client->mctx, uarg, sizeof(*uarg)); + return (result); + } + + /* + * Start internal event loop. It blocks until the entire process + * is completed. + */ + result = isc_app_ctxrun(actx); + + LOCK(&uarg->lock); + if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) + result = uarg->result; + + if (uarg->trans != NULL) { + /* + * Unusual termination (perhaps due to signal). We need some + * tricky cleanup process. + */ + uarg->canceled = true; + dns_client_cancelupdate(uarg->trans); + + UNLOCK(&uarg->lock); + + /* uarg will be freed in the event handler. */ + } else { + UNLOCK(&uarg->lock); + + DESTROYLOCK(&uarg->lock); + isc_mem_put(client->mctx, uarg, sizeof(*uarg)); + } + + return (result); +} + +static void +startupdate(isc_task_t *task, isc_event_t *event) { + updatectx_t *uctx; + isc_result_t result; + unsigned int resoptions; + + REQUIRE(event != NULL); + + UNUSED(task); + + uctx = event->ev_arg; + + if (uctx->zonename != NULL && uctx->currentserver != NULL) { + result = send_update(uctx); + if (result != ISC_R_SUCCESS) + goto fail; + } else if (uctx->currentserver != NULL) { + result = request_soa(uctx); + if (result != ISC_R_SUCCESS) + goto fail; + } else { + resoptions = 0; + if (uctx->want_tcp) + resoptions |= DNS_CLIENTRESOPT_TCP; + dns_name_clone(uctx->firstname, &uctx->soaqname); + result = dns_client_startresolve(uctx->client, &uctx->soaqname, + uctx->rdclass, + dns_rdatatype_soa, resoptions, + uctx->client->task, + resolvesoa_done, uctx, + &uctx->restrans); + if (result != ISC_R_SUCCESS) + goto fail; + } + + isc_event_free(&event); + +fail: + if (result != ISC_R_SUCCESS) + update_sendevent(uctx, result); +} + +isc_result_t +dns_client_startupdate(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *zonename, dns_namelist_t *prerequisites, + dns_namelist_t *updates, isc_sockaddrlist_t *servers, + dns_tsec_t *tsec, unsigned int options, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_clientupdatetrans_t **transp) +{ + dns_view_t *view = NULL; + isc_result_t result; + dns_name_t *name, *newname; + updatectx_t *uctx; + isc_task_t *tclone = NULL; + dns_section_t section = DNS_SECTION_UPDATE; + isc_sockaddr_t *server, *sa = NULL; + dns_tsectype_t tsectype = dns_tsectype_none; + bool want_tcp; + + UNUSED(options); + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(transp != NULL && *transp == NULL); + REQUIRE(updates != NULL); + REQUIRE(task != NULL); + + if (tsec != NULL) { + tsectype = dns_tsec_gettype(tsec); + if (tsectype != dns_tsectype_tsig) + return (ISC_R_NOTIMPLEMENTED); /* XXX */ + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) + return (result); + + want_tcp = (options & DNS_CLIENTUPDOPT_TCP); + + /* + * Create a context and prepare some resources. + */ + + uctx = isc_mem_get(client->mctx, sizeof(*uctx)); + if (uctx == NULL) { + dns_view_detach(&view); + return (ISC_R_NOMEMORY); + } + + result = isc_mutex_init(&uctx->lock); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + isc_mem_put(client->mctx, uctx, sizeof(*uctx)); + return (ISC_R_NOMEMORY); + } + + tclone = NULL; + isc_task_attach(task, &tclone); + uctx->client = client; + ISC_LINK_INIT(uctx, link); + uctx->state = dns_clientupdatestate_prepare; + uctx->view = view; + uctx->rdclass = rdclass; + uctx->canceled = false; + uctx->updatemsg = NULL; + uctx->soaquery = NULL; + uctx->updatereq = NULL; + uctx->restrans = NULL; + uctx->restrans2 = NULL; + uctx->bp4 = NULL; + uctx->bp6 = NULL; + uctx->soareq = NULL; + uctx->event = NULL; + uctx->tsigkey = NULL; + uctx->sig0key = NULL; + uctx->zonename = NULL; + uctx->want_tcp = want_tcp; + dns_name_init(&uctx->soaqname, NULL); + ISC_LIST_INIT(uctx->servers); + uctx->nservers = 0; + uctx->currentserver = NULL; + dns_fixedname_init(&uctx->zonefname); + if (tsec != NULL) + dns_tsec_getkey(tsec, &uctx->tsigkey); + uctx->event = (dns_clientupdateevent_t *) + isc_event_allocate(client->mctx, tclone, DNS_EVENT_UPDATEDONE, + action, arg, sizeof(*uctx->event)); + if (uctx->event == NULL) + goto fail; + if (zonename != NULL) { + uctx->zonename = dns_fixedname_name(&uctx->zonefname); + result = dns_name_copy(zonename, uctx->zonename, NULL); + } + if (servers != NULL) { + for (server = ISC_LIST_HEAD(*servers); + server != NULL; + server = ISC_LIST_NEXT(server, link)) { + sa = isc_mem_get(client->mctx, sizeof(*sa)); + if (sa == NULL) + goto fail; + sa->type = server->type; + sa->length = server->length; + ISC_LINK_INIT(sa, link); + ISC_LIST_APPEND(uctx->servers, sa, link); + if (uctx->currentserver == NULL) + uctx->currentserver = sa; + uctx->nservers++; + } + } + + /* Make update message */ + result = dns_message_create(client->mctx, DNS_MESSAGE_INTENTRENDER, + &uctx->updatemsg); + if (result != ISC_R_SUCCESS) + goto fail; + uctx->updatemsg->opcode = dns_opcode_update; + + if (prerequisites != NULL) { + for (name = ISC_LIST_HEAD(*prerequisites); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + newname = NULL; + result = copy_name(client->mctx, uctx->updatemsg, + name, &newname); + if (result != ISC_R_SUCCESS) + goto fail; + dns_message_addname(uctx->updatemsg, newname, + DNS_SECTION_PREREQUISITE); + } + } + + for (name = ISC_LIST_HEAD(*updates); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + newname = NULL; + result = copy_name(client->mctx, uctx->updatemsg, name, + &newname); + if (result != ISC_R_SUCCESS) + goto fail; + dns_message_addname(uctx->updatemsg, newname, + DNS_SECTION_UPDATE); + } + + uctx->firstname = NULL; + result = dns_message_firstname(uctx->updatemsg, section); + if (result == ISC_R_NOMORE) { + section = DNS_SECTION_PREREQUISITE; + result = dns_message_firstname(uctx->updatemsg, section); + } + if (result != ISC_R_SUCCESS) + goto fail; + dns_message_currentname(uctx->updatemsg, section, &uctx->firstname); + + uctx->magic = UCTX_MAGIC; + + LOCK(&client->lock); + ISC_LIST_APPEND(client->updatectxs, uctx, link); + UNLOCK(&client->lock); + + *transp = (dns_clientupdatetrans_t *)uctx; + result = isc_app_ctxonrun(client->actx, client->mctx, client->task, + startupdate, uctx); + if (result == ISC_R_ALREADYRUNNING) { + isc_event_t *event; + event = isc_event_allocate(client->mctx, dns_client_startupdate, + DNS_EVENT_STARTUPDATE, startupdate, + uctx, sizeof(*event)); + if (event != NULL) { + result = ISC_R_SUCCESS; + isc_task_send(task, &event); + } else + result = ISC_R_NOMEMORY; + } + if (result == ISC_R_SUCCESS) + return (result); + *transp = NULL; + + fail: + if (ISC_LINK_LINKED(uctx, link)) { + LOCK(&client->lock); + ISC_LIST_UNLINK(client->updatectxs, uctx, link); + UNLOCK(&client->lock); + } + if (uctx->updatemsg != NULL) + dns_message_destroy(&uctx->updatemsg); + while ((sa = ISC_LIST_HEAD(uctx->servers)) != NULL) { + ISC_LIST_UNLINK(uctx->servers, sa, link); + isc_mem_put(client->mctx, sa, sizeof(*sa)); + } + if (uctx->event != NULL) + isc_event_free(ISC_EVENT_PTR(&uctx->event)); + if (uctx->tsigkey != NULL) + dns_tsigkey_detach(&uctx->tsigkey); + isc_task_detach(&tclone); + DESTROYLOCK(&uctx->lock); + uctx->magic = 0; + isc_mem_put(client->mctx, uctx, sizeof(*uctx)); + dns_view_detach(&view); + + return (result); +} + +void +dns_client_cancelupdate(dns_clientupdatetrans_t *trans) { + updatectx_t *uctx; + + REQUIRE(trans != NULL); + uctx = (updatectx_t *)trans; + REQUIRE(UCTX_VALID(uctx)); + + LOCK(&uctx->lock); + + if (!uctx->canceled) { + uctx->canceled = true; + if (uctx->updatereq != NULL) + dns_request_cancel(uctx->updatereq); + if (uctx->soareq != NULL) + dns_request_cancel(uctx->soareq); + if (uctx->restrans != NULL) + dns_client_cancelresolve(uctx->restrans); + if (uctx->restrans2 != NULL) + dns_client_cancelresolve(uctx->restrans2); + } + + UNLOCK(&uctx->lock); +} + +void +dns_client_destroyupdatetrans(dns_clientupdatetrans_t **transp) { + updatectx_t *uctx; + isc_mem_t *mctx; + dns_client_t *client; + bool need_destroyclient = false; + isc_sockaddr_t *sa; + + REQUIRE(transp != NULL); + uctx = (updatectx_t *)*transp; + REQUIRE(UCTX_VALID(uctx)); + client = uctx->client; + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(uctx->updatereq == NULL && uctx->updatemsg == NULL && + uctx->soareq == NULL && uctx->soaquery == NULL && + uctx->event == NULL && uctx->tsigkey == NULL && + uctx->sig0key == NULL); + + mctx = client->mctx; + dns_view_detach(&uctx->view); + while ((sa = ISC_LIST_HEAD(uctx->servers)) != NULL) { + ISC_LIST_UNLINK(uctx->servers, sa, link); + isc_mem_put(mctx, sa, sizeof(*sa)); + } + + LOCK(&client->lock); + + INSIST(ISC_LINK_LINKED(uctx, link)); + ISC_LIST_UNLINK(client->updatectxs, uctx, link); + + if (client->references == 0 && ISC_LIST_EMPTY(client->resctxs) && + ISC_LIST_EMPTY(client->reqctxs) && + ISC_LIST_EMPTY(client->updatectxs)) + need_destroyclient = true; + + UNLOCK(&client->lock); + + DESTROYLOCK(&uctx->lock); + uctx->magic = 0; + + isc_mem_put(mctx, uctx, sizeof(*uctx)); + + if (need_destroyclient) + destroyclient(&client); + + *transp = NULL; +} + +isc_mem_t * +dns_client_mctx(dns_client_t *client) { + + REQUIRE(DNS_CLIENT_VALID(client)); + return (client->mctx); +} + +typedef struct { + isc_buffer_t buffer; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + dns_rdata_t rdata; + size_t size; + isc_mem_t * mctx; + unsigned char data[FLEXIBLE_ARRAY_MEMBER]; +} dns_client_updaterec_t; + +isc_result_t +dns_client_updaterec(dns_client_updateop_t op, dns_name_t *owner, + dns_rdatatype_t type, dns_rdata_t *source, + dns_ttl_t ttl, dns_name_t *target, + dns_rdataset_t *rdataset, dns_rdatalist_t *rdatalist, + dns_rdata_t *rdata, isc_mem_t *mctx) +{ + dns_client_updaterec_t *updaterec = NULL; + size_t size = offsetof(dns_client_updaterec_t, data); + + REQUIRE(op < updateop_max); + REQUIRE(owner != NULL); + REQUIRE((rdataset != NULL && rdatalist != NULL && rdata != NULL) || + (rdataset == NULL && rdatalist == NULL && rdata == NULL && + mctx != NULL)); + if (op == updateop_add) + REQUIRE(source != NULL); + if (source != NULL) { + REQUIRE(source->type == type); + REQUIRE(op == updateop_add || op == updateop_delete || + op == updateop_exist); + } + + size += owner->length; + if (source != NULL) + size += source->length; + + if (rdataset == NULL) { + updaterec = isc_mem_get(mctx, size); + if (updaterec == NULL) + return (ISC_R_NOMEMORY); + rdataset = &updaterec->rdataset; + rdatalist = &updaterec->rdatalist; + rdata = &updaterec->rdata; + dns_rdataset_init(rdataset); + dns_rdatalist_init(&updaterec->rdatalist); + dns_rdata_init(&updaterec->rdata); + isc_buffer_init(&updaterec->buffer, updaterec->data, + (unsigned int) + (size - + offsetof(dns_client_updaterec_t, data))); + dns_name_copy(owner, target, &updaterec->buffer); + if (source != NULL) { + isc_region_t r; + dns_rdata_clone(source, rdata); + dns_rdata_toregion(rdata, &r); + rdata->data = isc_buffer_used(&updaterec->buffer); + isc_buffer_copyregion(&updaterec->buffer, &r); + } + updaterec->mctx = NULL; + isc_mem_attach(mctx, &updaterec->mctx); + } else if (source != NULL) + dns_rdata_clone(source, rdata); + + switch (op) { + case updateop_add: + break; + case updateop_delete: + if (source != NULL) { + ttl = 0; + dns_rdata_makedelete(rdata); + } else + dns_rdata_deleterrset(rdata, type); + break; + case updateop_notexist: + dns_rdata_notexist(rdata, type); + break; + case updateop_exist: + if (source == NULL) { + ttl = 0; + dns_rdata_exists(rdata, type); + } + case updateop_none: + break; + default: + INSIST(0); + } + + rdatalist->type = rdata->type; + rdatalist->rdclass = rdata->rdclass; + if (source != NULL) { + rdatalist->covers = dns_rdata_covers(rdata); + rdatalist->ttl = ttl; + } + ISC_LIST_APPEND(rdatalist->rdata, rdata, link); + dns_rdatalist_tordataset(rdatalist, rdataset); + ISC_LIST_APPEND(target->list, rdataset, link); + if (updaterec != NULL) { + target->attributes |= DNS_NAMEATTR_HASUPDATEREC; + dns_name_setbuffer(target, &updaterec->buffer); + } + if (op == updateop_add || op == updateop_delete) + target->attributes |= DNS_NAMEATTR_UPDATE; + else + target->attributes |= DNS_NAMEATTR_PREREQUISITE; + return (ISC_R_SUCCESS); +} + +void +dns_client_freeupdate(dns_name_t **namep) { + dns_client_updaterec_t *updaterec; + dns_rdatalist_t *rdatalist; + dns_rdataset_t *rdataset; + dns_rdata_t *rdata; + dns_name_t *name; + + REQUIRE(namep != NULL && *namep != NULL); + + name = *namep; + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_HEAD(name->list)) { + ISC_LIST_UNLINK(name->list, rdataset, link); + rdatalist = NULL; + dns_rdatalist_fromrdataset(rdataset, &rdatalist); + if (rdatalist == NULL) { + dns_rdataset_disassociate(rdataset); + continue; + } + for (rdata = ISC_LIST_HEAD(rdatalist->rdata); + rdata != NULL; + rdata = ISC_LIST_HEAD(rdatalist->rdata)) + ISC_LIST_UNLINK(rdatalist->rdata, rdata, link); + dns_rdataset_disassociate(rdataset); + } + + if ((name->attributes & DNS_NAMEATTR_HASUPDATEREC) != 0) { + updaterec = (dns_client_updaterec_t *)name->buffer; + INSIST(updaterec != NULL); + isc_mem_putanddetach(&updaterec->mctx, updaterec, + updaterec->size); + *namep = NULL; + } +} diff --git a/lib/dns/clientinfo.c b/lib/dns/clientinfo.c new file mode 100644 index 0000000..6d025d9 --- /dev/null +++ b/lib/dns/clientinfo.c @@ -0,0 +1,32 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include "config.h" + +#include + +void +dns_clientinfomethods_init(dns_clientinfomethods_t *methods, + dns_clientinfo_sourceip_t sourceip) +{ + methods->version = DNS_CLIENTINFOMETHODS_VERSION; + methods->age = DNS_CLIENTINFOMETHODS_AGE; + methods->sourceip = sourceip; +} + +void +dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp) { + ci->version = DNS_CLIENTINFO_VERSION; + ci->data = data; + ci->dbversion = versionp; +} diff --git a/lib/dns/compress.c b/lib/dns/compress.c new file mode 100644 index 0000000..ec97235 --- /dev/null +++ b/lib/dns/compress.c @@ -0,0 +1,400 @@ +/* + * 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 http://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 +#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) + +#define TABLE_READY \ + do { \ + unsigned int i; \ + \ + if ((cctx->allowed & DNS_COMPRESS_READY) == 0) { \ + cctx->allowed |= DNS_COMPRESS_READY; \ + for (i = 0; i < DNS_COMPRESS_TABLESIZE; i++) \ + cctx->table[i] = NULL; \ + } \ + } while (0) + +/*** + *** 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->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)); + + if ((cctx->allowed & DNS_COMPRESS_READY) != 0) { + 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); +} + +#define NODENAME(node, name) \ +do { \ + (name)->length = (node)->r.length; \ + (name)->labels = (node)->labels; \ + (name)->ndata = (node)->r.base; \ + (name)->attributes = DNS_NAMEATTR_ABSOLUTE; \ +} while (0) + +/* + * 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, nname; + dns_compressnode_t *node = NULL; + unsigned int labels, hash, n; + + REQUIRE(VALID_CCTX(cctx)); + REQUIRE(dns_name_isabsolute(name) == true); + REQUIRE(offset != NULL); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) + return (false); + + TABLE_READY; + + if (cctx->count == 0) + return (false); + + labels = dns_name_countlabels(name); + INSIST(labels > 0); + + dns_name_init(&tname, NULL); + dns_name_init(&nname, NULL); + + for (n = 0; n < labels - 1; n++) { + dns_name_getlabelsequence(name, n, labels - n, &tname); + hash = dns_name_hash(&tname, false) % + DNS_COMPRESS_TABLESIZE; + for (node = cctx->table[hash]; node != NULL; node = node->next) + { + NODENAME(node, &nname); + if ((cctx->allowed & DNS_COMPRESS_CASESENSITIVE) != 0) { + if (dns_name_caseequal(&nname, &tname)) + break; + } else { + if (dns_name_equal(&nname, &tname)) + break; + } + } + if (node != NULL) + break; + } + + /* + * 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 inline 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 hash; + dns_compressnode_t *node; + unsigned int length; + unsigned int tlength; + uint16_t toffset; + unsigned char *tmp; + isc_region_t r; + + REQUIRE(VALID_CCTX(cctx)); + REQUIRE(dns_name_isabsolute(name)); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) + return; + + TABLE_READY; + + 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; + tmp = isc_mem_get(cctx->mctx, length); + if (tmp == NULL) + return; + /* + * Copy name data to 'tmp' and make 'r' use 'tmp'. + */ + memmove(tmp, r.base, r.length); + r.base = tmp; + dns_name_fromregion(&xname, &r); + + while (count > 0) { + dns_name_getlabelsequence(&xname, start, n, &tname); + hash = dns_name_hash(&tname, false) % + DNS_COMPRESS_TABLESIZE; + 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)); + if (node == NULL) + break; + } + 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) + toffset |= 0x8000; + node->offset = toffset; + dns_name_toregion(&tname, &node->r); + node->labels = (uint8_t)dns_name_countlabels(&tname); + node->next = cctx->table[hash]; + cctx->table[hash] = node; + start++; + n--; + count--; + } + + if (start == 0) + isc_mem_put(cctx->mctx, tmp, length); +} + +void +dns_compress_rollback(dns_compress_t *cctx, uint16_t offset) { + unsigned int i; + dns_compressnode_t *node; + + REQUIRE(VALID_CCTX(cctx)); + + if ((cctx->allowed & DNS_COMPRESS_ENABLED) == 0) + return; + + if ((cctx->allowed & DNS_COMPRESS_READY) == 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..ee3e00d --- /dev/null +++ b/lib/dns/db.c @@ -0,0 +1,1132 @@ +/* + * 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 http://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 +#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" +#include "rbtdb64.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 dns_dbimplementation_t rbt64imp; + +static void +initialize(void) { + RUNTIME_CHECK(isc_rwlock_init(&implock, 0, 0) == ISC_R_SUCCESS); + + rbtimp.name = "rbt"; + rbtimp.create = dns_rbtdb_create; + rbtimp.mctx = NULL; + rbtimp.driverarg = NULL; + ISC_LINK_INIT(&rbtimp, link); + + rbt64imp.name = "rbt64"; + rbt64imp.create = dns_rbtdb64_create; + rbt64imp.mctx = NULL; + rbt64imp.driverarg = NULL; + ISC_LINK_INIT(&rbt64imp, link); + + ISC_LIST_INIT(implementations); + ISC_LIST_APPEND(implementations, &rbtimp, link); + ISC_LIST_APPEND(implementations, &rbt64imp, link); +} + +static inline 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, 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); +} + +isc_result_t +dns_db_ondestroy(dns_db_t *db, isc_task_t *task, isc_event_t **eventp) +{ + REQUIRE(DNS_DB_VALID(db)); + + return (isc_ondestroy_register(&db->ondest, task, eventp)); +} + + +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) { + return (dns_db_load3(db, filename, dns_masterformat_text, 0)); +} + +isc_result_t +dns_db_load2(dns_db_t *db, const char *filename, dns_masterformat_t format) { + return (dns_db_load3(db, filename, format, 0)); +} + +isc_result_t +dns_db_load3(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_loadfile2(filename, &db->origin, &db->origin, + db->rdclass, options, + &callbacks, db->mctx, format); + 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)); +} + +isc_result_t +dns_db_dump2(dns_db_t *db, dns_dbversion_t *version, const char *filename, + dns_masterformat_t masterformat) { + /* + * Dump 'db' into master file 'filename' in the 'masterformat' format. + * XXXJT: is it okay to modify the interface to the existing "dump" + * method? + */ + + REQUIRE(DNS_DB_VALID(db)); + + return ((db->methods->dump)(db, version, filename, masterformat)); +} + +/*** + *** 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 == true) { + 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, 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, 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, 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, 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, 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, dns_name_t *name, + 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 deepest known zonecut which encloses 'name' in 'db'. + */ + + 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, 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, + 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, 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)); +} + +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)); + if (imp == NULL) { + RWUNLOCK(&implock, isc_rwlocktype_write); + return (ISC_R_NOMEMORY); + } + 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; + isc_mem_t *mctx; + + 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); + mctx = imp->mctx; + isc_mem_put(mctx, imp, sizeof(dns_dbimplementation_t)); + isc_mem_detach(&mctx); + 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) == true); + 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) == true); + + 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) == true); + + 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, dns_rpz_zones_t *rpzs, dns_rpz_num_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); + + listener = isc_mem_get(db->mctx, sizeof(dns_dbonupdatelistener_t)); + if (listener == NULL) + return (ISC_R_NOMEMORY); + + 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)); +} diff --git a/lib/dns/dbiterator.c b/lib/dns/dbiterator.c new file mode 100644 index 0000000..548f611 --- /dev/null +++ b/lib/dns/dbiterator.c @@ -0,0 +1,138 @@ +/* + * 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 http://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 + +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, 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..3cc7cb6 --- /dev/null +++ b/lib/dns/dbtable.c @@ -0,0 +1,279 @@ +/* + * 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 http://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 + +struct dns_dbtable { + /* Unlocked. */ + unsigned int magic; + isc_mem_t * mctx; + dns_rdataclass_t rdclass; + isc_mutex_t lock; + isc_rwlock_t tree_lock; + /* Locked by lock. */ + unsigned int 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 = (dns_dbtable_t *)isc_mem_get(mctx, sizeof(*dbtable)); + if (dbtable == NULL) + return (ISC_R_NOMEMORY); + + dbtable->rbt = NULL; + result = dns_rbt_create(mctx, dbdetach, NULL, &dbtable->rbt); + if (result != ISC_R_SUCCESS) + goto clean1; + + result = isc_mutex_init(&dbtable->lock); + if (result != ISC_R_SUCCESS) + goto clean2; + + result = isc_rwlock_init(&dbtable->tree_lock, 0, 0); + if (result != ISC_R_SUCCESS) + goto clean3; + + dbtable->default_db = NULL; + dbtable->mctx = NULL; + isc_mem_attach(mctx, &dbtable->mctx); + dbtable->rdclass = rdclass; + dbtable->magic = DBTABLE_MAGIC; + dbtable->references = 1; + + *dbtablep = dbtable; + + return (ISC_R_SUCCESS); + + clean3: + DESTROYLOCK(&dbtable->lock); + + clean2: + dns_rbt_destroy(&dbtable->rbt); + + clean1: + isc_mem_putanddetach(&mctx, dbtable, sizeof(*dbtable)); + + return (result); +} + +static inline 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); + + LOCK(&source->lock); + + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); + + UNLOCK(&source->lock); + + *targetp = source; +} + +void +dns_dbtable_detach(dns_dbtable_t **dbtablep) { + dns_dbtable_t *dbtable; + bool free_dbtable = false; + + REQUIRE(dbtablep != NULL); + dbtable = *dbtablep; + REQUIRE(VALID_DBTABLE(dbtable)); + + LOCK(&dbtable->lock); + + INSIST(dbtable->references > 0); + dbtable->references--; + if (dbtable->references == 0) + free_dbtable = true; + + UNLOCK(&dbtable->lock); + + if (free_dbtable) + dbtable_free(dbtable); + + *dbtablep = NULL; +} + +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, 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..d846f04 --- /dev/null +++ b/lib/dns/diff.c @@ -0,0 +1,673 @@ +/* + * 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 http://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 + +#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, 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); + if (t == NULL) + return (ISC_R_NOMEMORY); + 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; + 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); + *tp = NULL; +} + +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; + } + + ENSURE(*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, 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: + INSIST(0); + } + + 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_equal(&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_equal(&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 *)); + if (v == NULL) + return (ISC_R_NOMEMORY); + 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); + if (mem == NULL) + return (ISC_R_NOMEMORY); + + 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); + if (mem == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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..d1e4c16 --- /dev/null +++ b/lib/dns/dispatch.c @@ -0,0 +1,3906 @@ +/* + * 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 http://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 + +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; + isc_entropy_t *entropy; /*%< entropy source */ + + /* Locked by "lock". */ + isc_mutex_t lock; + unsigned int state; + ISC_LIST(dns_dispatch_t) list; + + /* Locked by rng_lock. */ + isc_mutex_t rng_lock; + isc_rng_t *rngctx; /*%< RNG context for QID */ + + /* 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 */ + + /* Locked internally. */ + isc_mutex_t depool_lock; + isc_mempool_t *depool; /*%< pool for dispatch events */ + isc_mutex_t rpool_lock; + isc_mempool_t *rpool; /*%< pool for replies */ + isc_mutex_t dpool_lock; + isc_mempool_t *dpool; /*%< dispatch allocations */ + isc_mutex_t bpool_lock; + isc_mempool_t *bpool; /*%< pool for buffers */ + isc_mutex_t spool_lock; + isc_mempool_t *spool; /*%< pool for dispsocks */ + + /*% + * 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 + +/*% + * 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 + +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; + unsigned int refs; + ISC_LINK(struct dispportentry) link; +}; + +#ifndef DNS_DISPATCH_PORTTABLESIZE +#define DNS_DISPATCH_PORTTABLESIZE 1024 +#endif + +#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_mutex_t sepool_lock; + isc_mempool_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; + isc_rng_t *rngctx; /*%< for QID/UDP port num */ + dispportlist_t *port_table; /*%< hold ports 'owned' by us */ + isc_mempool_t *portpool; /*%< port table entries */ +}; + +#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 +#define DISP_RNGCTX(disp) ((disp)->socktype == isc_sockettype_udp) ? \ + ((disp)->rngctx) : ((disp)->mgr->rngctx) + +/*% + * 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 *, 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 *, 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 inline void free_devent(dns_dispatch_t *disp, dns_dispatchevent_t *ev); +static inline 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, + isc_sockaddr_t *localaddr, + isc_socket_t **sockp, + isc_socket_t *dup_socket); +static isc_result_t dispatch_createudp(dns_dispatchmgr_t *mgr, + isc_socketmgr_t *sockmgr, + isc_taskmgr_t *taskmgr, + 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, isc_sockaddr_t *local, + unsigned int options, isc_socket_t **sockp, + isc_socket_t *dup_socket); +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 inline void +inc_stats(dns_dispatchmgr_t *mgr, isc_statscounter_t counter) { + if (mgr->stats != NULL) + isc_stats_increment(mgr->stats, counter); +} + +static inline 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, 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_mempool_destroy(&disp->sepool); + (void)isc_mutex_destroy(&disp->sepool_lock); + } + + 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_mempool_get(disp->portpool); + if (portentry == NULL) + return (portentry); + + portentry->port = port; + 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 not hold the qid->lock. + */ +static void +deref_portentry(dns_dispatch_t *disp, dispportentry_t **portentryp) { + dispportentry_t *portentry = *portentryp; + dns_qid_t *qid; + + REQUIRE(disp->port_table != NULL); + REQUIRE(portentry != NULL && portentry->refs > 0); + + qid = DNS_QID(disp); + LOCK(&qid->lock); + portentry->refs--; + + if (portentry->refs == 0) { + ISC_LIST_UNLINK(disp->port_table[portentry->port % + DNS_DISPATCH_PORTTABLESIZE], + portentry, link); + isc_mempool_put(disp->portpool, portentry); + } + + /* + * Set '*portentryp' to NULL inside the lock so that + * dispsock->portentry does not change in socket_search. + */ + *portentryp = NULL; + + UNLOCK(&qid->lock); +} + +/*% + * 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, 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, isc_sockaddr_t *dest, + isc_socketmgr_t *sockmgr, dispsocket_t **dispsockp, + in_port_t *portp) +{ + int i; + uint32_t r; + 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; + unsigned int 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_mempool_get(mgr->spool); + if (dispsock == NULL) + return (ISC_R_NOMEMORY); + + disp->nsockets++; + dispsock->socket = NULL; + dispsock->disp = disp; + dispsock->resp = NULL; + dispsock->portentry = NULL; + isc_random_get(&r); + dispsock->task = NULL; + isc_task_attach(disp->task[r % 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_rng_uniformrandom(DISP_RNGCTX(disp), 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); + if (result == ISC_R_SUCCESS) { + if (portentry == NULL) { + portentry = new_portentry(disp, port); + if (portentry == NULL) { + result = ISC_R_NOMEMORY; + break; + } + } else { + LOCK(&qid->lock); + portentry->refs++; + UNLOCK(&qid->lock); + } + 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->portentry = portentry; + dispsock->bucket = bucket; + LOCK(&qid->lock); + 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; + + /* + * The dispatch must be locked. + */ + + REQUIRE(dispsockp != NULL && *dispsockp != NULL); + dispsock = *dispsockp; + REQUIRE(!ISC_LINK_LINKED(dispsock, link)); + + disp->nsockets--; + dispsock->magic = 0; + if (dispsock->portentry != NULL) + deref_portentry(disp, &dispsock->portentry); + if (dispsock->socket != NULL) + isc_socket_detach(&dispsock->socket); + if (ISC_LINK_LINKED(dispsock, blink)) { + qid = DNS_QID(disp); + 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_mempool_put(disp->mgr->spool, dispsock); + + *dispsockp = NULL; +} + +/*% + * 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; + + /* + * 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); + deref_portentry(disp, &dispsock->portentry); + + if (disp->nsockets > DNS_DISPATCH_POOLSOCKS) + destroy_dispsocket(disp, &dispsock); + else { + result = isc_socket_close(dispsock->socket); + + qid = DNS_QID(disp); + 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, 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) { + isc_mempool_t *bpool; + 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--; + bpool = disp->mgr->bpool; + UNLOCK(&disp->mgr->buffer_lock); + isc_mempool_put(bpool, buf); + break; + default: + INSIST(0); + break; + } +} + +static void * +allocate_udp_buffer(dns_dispatch_t *disp) { + isc_mempool_t *bpool; + void *temp; + + LOCK(&disp->mgr->buffer_lock); + bpool = disp->mgr->bpool; + disp->mgr->buffers++; + UNLOCK(&disp->mgr->buffer_lock); + + temp = isc_mempool_get(bpool); + + if (temp == NULL) { + LOCK(&disp->mgr->buffer_lock); + disp->mgr->buffers--; + UNLOCK(&disp->mgr->buffer_lock); + } + + return (temp); +} + +static inline void +free_sevent(isc_event_t *ev) { + isc_mempool_t *pool = ev->ev_destroy_arg; + isc_socketevent_t *sev = (isc_socketevent_t *) ev; + isc_mempool_put(pool, sev); +} + +static inline 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_mempool_get(disp->sepool); + if (ev == NULL) + return (NULL); + 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); + ISC_LIST_INIT(ev->bufferlist); + ev->region.base = NULL; + ev->n = 0; + ev->offset = 0; + ev->attributes = 0; + + return (ev); +} + + +static inline 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_mempool_put(disp->mgr->depool, ev); +} + +static inline dns_dispatchevent_t * +allocate_devent(dns_dispatch_t *disp) { + dns_dispatchevent_t *ev; + + ev = isc_mempool_get(disp->mgr->depool); + if (ev == NULL) + return (NULL); + 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; + + dispatch_log(disp, LVL(90), + "got packet: requests %d, buffers %d, recvs %d", + disp->requests, disp->mgr->buffers, disp->recv_pending); + + 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) ? '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")); + + if (resp == NULL) { + inc_stats(mgr, dns_resstatscounter_mismatch); + free_buffer(disp, ev->region.base, ev->region.length); + goto unlock; + } + } 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; + } + + /* + * 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; + + dispatch_log(disp, LVL(90), + "got TCP packet: requests %d, buffers %d, recvs %d", + disp->requests, disp->tcpbuffers, disp->recv_pending); + + LOCK(&disp->lock); + + INSIST(disp->recv_pending != 0); + disp->recv_pending = 0; + + if (disp->refcount == 0) { + /* + * This dispatcher is shutting down. Force cancelation. + */ + 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) ? '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->mgr->buffers >= disp->mgr->maxbuffers) + return (ISC_R_NOMEMORY); + + 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: + INSIST(0); + break; + } + + 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, " + "depool=%d, rpool=%d, dpool=%d", + MGR_IS_SHUTTINGDOWN(mgr), !ISC_LIST_EMPTY(mgr->list), + isc_mempool_getallocated(mgr->depool), + isc_mempool_getallocated(mgr->rpool), + isc_mempool_getallocated(mgr->dpool)); + if (!MGR_IS_SHUTTINGDOWN(mgr)) + return (false); + if (!ISC_LIST_EMPTY(mgr->list)) + return (false); + if (isc_mempool_getallocated(mgr->depool) != 0) + return (false); + if (isc_mempool_getallocated(mgr->rpool) != 0) + return (false); + if (isc_mempool_getallocated(mgr->dpool) != 0) + return (false); + + return (true); +} + +/* + * Mgr must be unlocked when calling this function. + */ +static void +destroy_mgr(dns_dispatchmgr_t **mgrp) { + isc_mem_t *mctx; + dns_dispatchmgr_t *mgr; + + mgr = *mgrp; + *mgrp = NULL; + + mctx = mgr->mctx; + + mgr->magic = 0; + mgr->mctx = NULL; + DESTROYLOCK(&mgr->lock); + mgr->state = 0; + + if (mgr->rngctx != NULL) + isc_rng_detach(&mgr->rngctx); + DESTROYLOCK(&mgr->rng_lock); + + isc_mempool_destroy(&mgr->depool); + isc_mempool_destroy(&mgr->rpool); + isc_mempool_destroy(&mgr->dpool); + if (mgr->bpool != NULL) + isc_mempool_destroy(&mgr->bpool); + if (mgr->spool != NULL) + isc_mempool_destroy(&mgr->spool); + + DESTROYLOCK(&mgr->spool_lock); + DESTROYLOCK(&mgr->bpool_lock); + DESTROYLOCK(&mgr->dpool_lock); + DESTROYLOCK(&mgr->rpool_lock); + DESTROYLOCK(&mgr->depool_lock); + + if (mgr->entropy != NULL) + isc_entropy_detach(&mgr->entropy); + if (mgr->qid != NULL) + qid_destroy(mctx, &mgr->qid); + + DESTROYLOCK(&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(mctx, mgr->v4ports, + mgr->nv4ports * sizeof(in_port_t)); + } + if (mgr->v6ports != NULL) { + isc_mem_put(mctx, mgr->v6ports, + mgr->nv6ports * sizeof(in_port_t)); + } + isc_mem_put(mctx, mgr, sizeof(dns_dispatchmgr_t)); + isc_mem_detach(&mctx); +} + +static isc_result_t +open_socket(isc_socketmgr_t *mgr, isc_sockaddr_t *local, + unsigned int options, isc_socket_t **sockp, + isc_socket_t *dup_socket) +{ + 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) { + 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 + 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, isc_entropy_t *entropy, + 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)); + if (mgr == NULL) + return (ISC_R_NOMEMORY); + + mgr->mctx = NULL; + isc_mem_attach(mctx, &mgr->mctx); + + mgr->blackhole = NULL; + mgr->stats = NULL; + mgr->rngctx = NULL; + + result = isc_mutex_init(&mgr->lock); + if (result != ISC_R_SUCCESS) + goto deallocate; + + result = isc_mutex_init(&mgr->rng_lock); + if (result != ISC_R_SUCCESS) + goto kill_lock; + + result = isc_mutex_init(&mgr->buffer_lock); + if (result != ISC_R_SUCCESS) + goto kill_rng_lock; + + result = isc_mutex_init(&mgr->depool_lock); + if (result != ISC_R_SUCCESS) + goto kill_buffer_lock; + + result = isc_mutex_init(&mgr->rpool_lock); + if (result != ISC_R_SUCCESS) + goto kill_depool_lock; + + result = isc_mutex_init(&mgr->dpool_lock); + if (result != ISC_R_SUCCESS) + goto kill_rpool_lock; + + result = isc_mutex_init(&mgr->bpool_lock); + if (result != ISC_R_SUCCESS) + goto kill_dpool_lock; + + result = isc_mutex_init(&mgr->spool_lock); + if (result != ISC_R_SUCCESS) + goto kill_bpool_lock; + + mgr->depool = NULL; + if (isc_mempool_create(mgr->mctx, sizeof(dns_dispatchevent_t), + &mgr->depool) != ISC_R_SUCCESS) { + result = ISC_R_NOMEMORY; + goto kill_spool_lock; + } + + mgr->rpool = NULL; + if (isc_mempool_create(mgr->mctx, sizeof(dns_dispentry_t), + &mgr->rpool) != ISC_R_SUCCESS) { + result = ISC_R_NOMEMORY; + goto kill_depool; + } + + mgr->dpool = NULL; + if (isc_mempool_create(mgr->mctx, sizeof(dns_dispatch_t), + &mgr->dpool) != ISC_R_SUCCESS) { + result = ISC_R_NOMEMORY; + goto kill_rpool; + } + + isc_mempool_setname(mgr->depool, "dispmgr_depool"); + isc_mempool_setmaxalloc(mgr->depool, 32768); + isc_mempool_setfreemax(mgr->depool, 32768); + isc_mempool_associatelock(mgr->depool, &mgr->depool_lock); + isc_mempool_setfillcount(mgr->depool, 32); + + isc_mempool_setname(mgr->rpool, "dispmgr_rpool"); + isc_mempool_setmaxalloc(mgr->rpool, 32768); + isc_mempool_setfreemax(mgr->rpool, 32768); + isc_mempool_associatelock(mgr->rpool, &mgr->rpool_lock); + isc_mempool_setfillcount(mgr->rpool, 32); + + isc_mempool_setname(mgr->dpool, "dispmgr_dpool"); + isc_mempool_setmaxalloc(mgr->dpool, 32768); + isc_mempool_setfreemax(mgr->dpool, 32768); + isc_mempool_associatelock(mgr->dpool, &mgr->dpool_lock); + isc_mempool_setfillcount(mgr->dpool, 32); + + mgr->buffers = 0; + mgr->buffersize = 0; + mgr->maxbuffers = 0; + mgr->bpool = NULL; + mgr->spool = NULL; + mgr->entropy = NULL; + mgr->qid = NULL; + mgr->state = 0; + ISC_LIST_INIT(mgr->list); + mgr->v4ports = NULL; + mgr->v6ports = NULL; + mgr->nv4ports = 0; + mgr->nv6ports = 0; + 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; + + if (entropy != NULL) + isc_entropy_attach(entropy, &mgr->entropy); + + result = isc_rng_create(mctx, mgr->entropy, &mgr->rngctx); + if (result != ISC_R_SUCCESS) + goto kill_dpool; + + *mgrp = mgr; + return (ISC_R_SUCCESS); + + kill_dpool: + isc_mempool_destroy(&mgr->dpool); + kill_rpool: + isc_mempool_destroy(&mgr->rpool); + kill_depool: + isc_mempool_destroy(&mgr->depool); + kill_spool_lock: + DESTROYLOCK(&mgr->spool_lock); + kill_bpool_lock: + DESTROYLOCK(&mgr->bpool_lock); + kill_dpool_lock: + DESTROYLOCK(&mgr->dpool_lock); + kill_rpool_lock: + DESTROYLOCK(&mgr->rpool_lock); + kill_depool_lock: + DESTROYLOCK(&mgr->depool_lock); + kill_buffer_lock: + DESTROYLOCK(&mgr->buffer_lock); + kill_rng_lock: + DESTROYLOCK(&mgr->rng_lock); + kill_lock: + DESTROYLOCK(&mgr->lock); + deallocate: + isc_mem_put(mctx, mgr, sizeof(dns_dispatchmgr_t)); + isc_mem_detach(&mctx); + + 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); + if (v4ports == NULL) + return (ISC_R_NOMEMORY); + } + v6ports = NULL; + if (nv6ports != 0) { + v6ports = isc_mem_get(mgr->mctx, sizeof(in_port_t) * nv6ports); + if (v6ports == NULL) { + if (v4ports != NULL) { + isc_mem_put(mgr->mctx, v4ports, + sizeof(in_port_t) * + isc_portset_nports(v4portset)); + } + return (ISC_R_NOMEMORY); + } + } + + 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); + + /* + * 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); + + /* Create or adjust buffer pool */ + if (mgr->bpool != NULL) { + /* + * We only increase the maxbuffers to avoid accidental buffer + * shortage. Ideally we'd separate the manager-wide maximum + * from per-dispatch limits and respect the latter within the + * global limit. But at this moment that's deemed to be + * overkilling and isn't worth additional implementation + * complexity. + */ + if (maxbuffers > mgr->maxbuffers) { + isc_mempool_setmaxalloc(mgr->bpool, maxbuffers); + isc_mempool_setfreemax(mgr->bpool, maxbuffers); + mgr->maxbuffers = maxbuffers; + } + } else { + result = isc_mempool_create(mgr->mctx, buffersize, &mgr->bpool); + if (result != ISC_R_SUCCESS) { + UNLOCK(&mgr->buffer_lock); + return (result); + } + isc_mempool_setname(mgr->bpool, "dispmgr_bpool"); + isc_mempool_setmaxalloc(mgr->bpool, maxbuffers); + isc_mempool_setfreemax(mgr->bpool, maxbuffers); + isc_mempool_associatelock(mgr->bpool, &mgr->bpool_lock); + isc_mempool_setfillcount(mgr->bpool, 32); + } + + /* Create or adjust socket pool */ + if (mgr->spool != NULL) { + if (maxrequests < DNS_DISPATCH_POOLSOCKS * 2) { + isc_mempool_setmaxalloc(mgr->spool, + DNS_DISPATCH_POOLSOCKS * 2); + isc_mempool_setfreemax(mgr->spool, + DNS_DISPATCH_POOLSOCKS * 2); + } + UNLOCK(&mgr->buffer_lock); + return (ISC_R_SUCCESS); + } + result = isc_mempool_create(mgr->mctx, sizeof(dispsocket_t), + &mgr->spool); + if (result != ISC_R_SUCCESS) + goto cleanup; + + isc_mempool_setname(mgr->spool, "dispmgr_spool"); + isc_mempool_setmaxalloc(mgr->spool, maxrequests); + isc_mempool_setfreemax(mgr->spool, maxrequests); + isc_mempool_associatelock(mgr->spool, &mgr->spool_lock); + isc_mempool_setfillcount(mgr->spool, 32); + + 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: + isc_mempool_destroy(&mgr->bpool); + if (mgr->spool != NULL) + isc_mempool_destroy(&mgr->spool); + 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, 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, 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; + isc_result_t result; + + 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)); + if (qid == NULL) + return (ISC_R_NOMEMORY); + + qid->qid_table = isc_mem_get(mgr->mctx, + buckets * sizeof(dns_displist_t)); + if (qid->qid_table == NULL) { + isc_mem_put(mgr->mctx, qid, sizeof(*qid)); + return (ISC_R_NOMEMORY); + } + + qid->sock_table = NULL; + if (needsocktable) { + qid->sock_table = isc_mem_get(mgr->mctx, buckets * + sizeof(dispsocketlist_t)); + if (qid->sock_table == NULL) { + isc_mem_put(mgr->mctx, qid->qid_table, + buckets * sizeof(dns_displist_t)); + isc_mem_put(mgr->mctx, qid, sizeof(*qid)); + return (ISC_R_NOMEMORY); + } + } + + result = isc_mutex_init(&qid->lock); + if (result != ISC_R_SUCCESS) { + if (qid->sock_table != NULL) { + isc_mem_put(mgr->mctx, qid->sock_table, + buckets * sizeof(dispsocketlist_t)); + } + isc_mem_put(mgr->mctx, qid->qid_table, + buckets * sizeof(dns_displist_t)); + isc_mem_put(mgr->mctx, qid, sizeof(*qid)); + return (result); + } + + 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; + + REQUIRE(VALID_QID(qid)); + + *qidp = NULL; + 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)); + } + DESTROYLOCK(&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_mempool_get(mgr->dpool); + if (disp == NULL) + return (ISC_R_NOMEMORY); + + 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->rngctx = NULL; + isc_rng_attach(mgr->rngctx, &disp->rngctx); + disp->port_table = NULL; + disp->portpool = NULL; + disp->dscp = -1; + + result = isc_mutex_init(&disp->lock); + if (result != ISC_R_SUCCESS) + goto deallocate; + + 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: + DESTROYLOCK(&disp->lock); + deallocate: + if (disp->rngctx != NULL) + isc_rng_detach(&disp->rngctx); + isc_mempool_put(mgr->dpool, 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; + int i; + + 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_mempool_put(mgr->depool, disp->failsafe_ev); + disp->failsafe_ev = NULL; + + if (disp->qid != NULL) + qid_destroy(mgr->mctx, &disp->qid); + + if (disp->port_table != NULL) { + for (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); + } + + if (disp->portpool != NULL) + isc_mempool_destroy(&disp->portpool); + + if (disp->rngctx != NULL) + isc_rng_detach(&disp->rngctx); + + disp->mgr = NULL; + DESTROYLOCK(&disp->lock); + disp->magic = 0; + isc_mempool_put(mgr->dpool, disp); +} + +isc_result_t +dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_taskmgr_t *taskmgr, unsigned int buffersize, + unsigned int maxbuffers, unsigned int maxrequests, + unsigned int buckets, unsigned int increment, + unsigned int attributes, dns_dispatch_t **dispp) +{ + + attributes |= DNS_DISPATCHATTR_PRIVATE; /* XXXMLG */ + + return (dns_dispatch_createtcp2(mgr, sock, taskmgr, NULL, NULL, + buffersize, maxbuffers, maxrequests, + buckets, increment, attributes, + dispp)); +} + +isc_result_t +dns_dispatch_createtcp2(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_taskmgr_t *taskmgr, isc_sockaddr_t *localaddr, + 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, 0, &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)); + if (disp->ctlevent == NULL) { + result = ISC_R_NOMEMORY; + goto kill_task; + } + + 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); + + /* + * Error returns. + */ + kill_task: + isc_task_detach(&disp->task[0]); + 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, isc_sockaddr_t *destaddr, + isc_sockaddr_t *localaddr, 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); + + 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; + } + } + 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_gettcp2(dns_dispatchmgr_t *mgr, isc_sockaddr_t *destaddr, + 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); + REQUIRE(connected != NULL); + + /* First pass (same as dns_dispatch_gettcp()) */ + 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; + *connected = true; + } + } + UNLOCK(&disp->lock); + disp = ISC_LIST_NEXT(disp, link); + } + if (match) { + UNLOCK(&mgr->lock); + return (ISC_R_SUCCESS); + } + + /* Second pass */ + 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, 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, 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 + +static isc_result_t +get_udpsocket(dns_dispatchmgr_t *mgr, dns_dispatch_t *disp, + isc_socketmgr_t *sockmgr, isc_sockaddr_t *localaddr, + isc_socket_t **sockp, isc_socket_t *dup_socket) +{ + 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_rng_uniformrandom(DISP_RNGCTX(disp), + nports)]; + isc_sockaddr_setport(&localaddr_bound, prt); + result = open_socket(sockmgr, &localaddr_bound, + 0, &sock, NULL); + /* + * Continue if the port choosen 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); + + 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); + 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, + 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; + + /* + * 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); + 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); + 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); + if (disp->port_table == NULL) + goto deallocate_dispatch; + for (i = 0; i < DNS_DISPATCH_PORTTABLESIZE; i++) + ISC_LIST_INIT(disp->port_table[i]); + + result = isc_mempool_create(mgr->mctx, sizeof(dispportentry_t), + &disp->portpool); + if (result != ISC_R_SUCCESS) + goto deallocate_dispatch; + isc_mempool_setname(disp->portpool, "disp_portpool"); + isc_mempool_setfreemax(disp->portpool, 128); + } + 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)); + if (disp->ctlevent == NULL) { + result = ISC_R_NOMEMORY; + goto kill_task; + } + + disp->sepool = NULL; + if (isc_mempool_create(mgr->mctx, sizeof(isc_socketevent_t), + &disp->sepool) != ISC_R_SUCCESS) + { + result = ISC_R_NOMEMORY; + goto kill_ctlevent; + } + + result = isc_mutex_init(&disp->sepool_lock); + if (result != ISC_R_SUCCESS) + goto kill_sepool; + + isc_mempool_setname(disp->sepool, "disp_sepool"); + isc_mempool_setmaxalloc(disp->sepool, 32768); + isc_mempool_setfreemax(disp->sepool, 32768); + isc_mempool_associatelock(disp->sepool, &disp->sepool_lock); + isc_mempool_setfillcount(disp->sepool, 16); + + 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_sepool: + isc_mempool_destroy(&disp->sepool); + kill_ctlevent: + isc_event_free(&disp->ctlevent); + kill_task: + for (i = 0; i < disp->ntasks; i++) + isc_task_detach(&disp->task[i]); + 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_addresponse2(dns_dispatch_t *disp, 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) +{ + return (dns_dispatch_addresponse3(disp, 0, dest, task, action, arg, + idp, resp, sockmgr)); +} + +isc_result_t +dns_dispatch_addresponse3(dns_dispatch_t *disp, unsigned int options, + 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_rng_random(DISP_RNGCTX(disp)); + 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_mempool_get(disp->mgr->rpool); + if (res == NULL) { + if (dispsocket != NULL) + destroy_dispsocket(disp, &dispsocket); + UNLOCK(&disp->lock); + return (ISC_R_NOMEMORY); + } + + 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_mempool_put(disp->mgr->rpool, 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); +} + +isc_result_t +dns_dispatch_addresponse(dns_dispatch_t *disp, isc_sockaddr_t *dest, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_messageid_t *idp, dns_dispentry_t **resp) +{ + REQUIRE(VALID_DISPATCH(disp)); + REQUIRE((disp->attributes & DNS_DISPATCHATTR_EXCLUSIVE) == 0); + + return (dns_dispatch_addresponse3(disp, 0, dest, task, action, arg, + idp, resp, NULL)); +} + +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)); + + REQUIRE(resp->item_out == true); + resp->item_out = false; + + ev = *sockevent; + *sockevent = NULL; + + LOCK(&disp->lock); + 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 == true); + 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_mempool_put(disp->mgr->rpool, 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); +} + +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 hander 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)); + if (newsevent == NULL) + return; + + 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)); + if (dset == NULL) + return (ISC_R_NOMEMORY); + memset(dset, 0, sizeof(*dset)); + + result = isc_mutex_init(&dset->lock); + if (result != ISC_R_SUCCESS) + goto fail_alloc; + + dset->dispatches = isc_mem_get(mctx, sizeof(dns_dispatch_t *) * n); + if (dset->dispatches == NULL) { + result = ISC_R_NOMEMORY; + goto fail_lock; + } + + 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); + + fail_lock: + DESTROYLOCK(&dset->lock); + + fail_alloc: + 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; + 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); + DESTROYLOCK(&dset->lock); + isc_mem_putanddetach(&dset->mctx, dset, sizeof(dns_dispatchset_t)); + + *dsetp = NULL; +} + +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 diff --git a/lib/dns/dlz.c b/lib/dns/dlz.c new file mode 100644 index 0000000..6c93d97 --- /dev/null +++ b/lib/dns/dlz.c @@ -0,0 +1,549 @@ +/* + * Portions 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 http://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 +#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) { + RUNTIME_CHECK(isc_rwlock_init(&dlz_implock, 0, 0) == ISC_R_SUCCESS); + ISC_LIST_INIT(dlz_implementations); +} + +/*% + * Searches the dlz_implementations list for a driver matching name. + */ +static inline 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, dns_name_t *name, + 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); + + /* + * if ISC_R_NOPERM, we found the right database but + * the zone may not transfer. + */ + if (result == ISC_R_SUCCESS || result == ISC_R_NOPERM) + return (result); + } + + 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)); + if (db == NULL) { + RWUNLOCK(&dlz_implock, isc_rwlocktype_read); + return (ISC_R_NOMEMORY); + } + + /* 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_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)); + if (dlz_imp == NULL) { + RWUNLOCK(&dlz_implock, isc_rwlocktype_write); + return (ISC_R_NOMEMORY); + } + + /* 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; + isc_mem_t *mctx; + + /* 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); + mctx = dlz_imp->mctx; + + /* + * Return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_put(mctx, dlz_imp, sizeof(dns_dlzimplementation_t)); + isc_mem_detach(&mctx); + + /* 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, dns_name_t *signer, + dns_name_t *name, 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..490445c --- /dev/null +++ b/lib/dns/dns64.c @@ -0,0 +1,297 @@ +/* + * 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 http://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, isc_netaddr_t *prefix, + unsigned int prefixlen, 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)); + if (dns64 == NULL) + return (ISC_R_NOMEMORY); + 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/dnssec.c b/lib/dns/dnssec.c new file mode 100644 index 0000000..7d45e3a --- /dev/null +++ b/lib/dns/dnssec.c @@ -0,0 +1,2241 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for DNS_TSIG_FUDGE */ + +#include + +LIBDNS_EXTERNAL_DATA isc_stats_t *dns_dnssec_stats; + +#define is_response(msg) (msg->flags & DNS_MESSAGEFLAG_QR) + +#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 inline 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)); + if (data == NULL) + return (ISC_R_NOMEMORY); + + 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(dns_name_t *name, 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(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) + 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); + if (sig.signature == NULL) + return (ISC_R_NOMEMORY); + + ret = isc_buffer_allocate(mctx, &databuf, sigsize + 256 + 18); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + + 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_create3(key, mctx, + DNS_LOGCATEGORY_DNSSEC, true, &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); +cleanup_signature: + isc_mem_put(mctx, sig.signature, sig.siglen); + + return (ret); +} + +isc_result_t +dns_dnssec_verify2(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, isc_mem_t *mctx, + dns_rdata_t *sigrdata, dns_name_t *wild) +{ + return (dns_dnssec_verify3(name, set, key, ignoretime, 0, mctx, + sigrdata, wild)); +} + +isc_result_t +dns_dnssec_verify3(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) { + 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_create4(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); +} + +isc_result_t +dns_dnssec_verify(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, isc_mem_t *mctx, + dns_rdata_t *sigrdata) +{ + isc_result_t result; + + result = dns_dnssec_verify2(name, set, key, ignoretime, mctx, + sigrdata, NULL); + if (result == DNS_R_FROMWILDCARD) + result = ISC_R_SUCCESS; + return (result); +} + +bool +dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now) { + isc_result_t result; + isc_stdtime_t publish, active, revoke, inactive, deltime; + bool pubset = false, actset = false; + bool revset = false, inactset = false; + bool delset = false; + 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; prior to that, all + * keys are assumed active + */ + if (major == 1 && minor <= 2) + return (true); + + result = dst_key_gettime(key, DST_TIME_PUBLISH, &publish); + if (result == ISC_R_SUCCESS) + pubset = true; + + result = dst_key_gettime(key, DST_TIME_ACTIVATE, &active); + if (result == ISC_R_SUCCESS) + actset = true; + + result = dst_key_gettime(key, DST_TIME_REVOKE, &revoke); + if (result == ISC_R_SUCCESS) + revset = true; + + result = dst_key_gettime(key, DST_TIME_INACTIVE, &inactive); + if (result == ISC_R_SUCCESS) + inactset = true; + + result = dst_key_gettime(key, DST_TIME_DELETE, &deltime); + if (result == ISC_R_SUCCESS) + delset = true; + + if ((inactset && inactive <= now) || (delset && deltime <= now)) + return (false); + + if (revset && revoke <= now && pubset && publish <= now) + return (true); + + if (actset && active <= now) + return (true); + + return (false); +} + +/*%< + * Indicate whether a key is scheduled to to have CDS/CDNSKEY records + * published now. + * + * Returns true iff. + * - 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; + 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); + + result = dst_key_gettime(key, DST_TIME_SYNCPUBLISH, &when); + if (result != ISC_R_SUCCESS) + return (false); + + result = dst_key_gettime(key, DST_TIME_SYNCDELETE, &when); + if (result != ISC_R_SUCCESS) + return (true); + if (when <= now) + return (false); + return (true); +} + +/*%< + * Indicate whether a key is scheduled to to have CDS/CDNSKEY records + * deleted now. + * + * Returns true iff. 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; + 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); + + 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_findzonekeys3(dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, 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, + 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, + 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, ISC_DIR_NAMEMAX); + result2 = dst_key_getfilename(dst_key_name(pubkey), + dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | + DST_TYPE_PRIVATE), + 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_findzonekeys2(dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, dns_name_t *name, + const char *directory, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys) +{ + isc_stdtime_t now; + + isc_stdtime_get(&now); + return (dns_dnssec_findzonekeys3(db, ver, node, name, directory, now, + mctx, maxkeys, keys, nkeys)); +} + +isc_result_t +dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, dns_name_t *name, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys) +{ + isc_stdtime_t now; + + isc_stdtime_get(&now); + return (dns_dnssec_findzonekeys3(db, ver, node, name, NULL, now, + mctx, maxkeys, keys, nkeys)); +} + +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; + bool signeedsfree = true; + + 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_create3(key, mctx, + DNS_LOGCATEGORY_DNSSEC, true, &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 = (unsigned char *) isc_mem_get(mctx, sig.siglen); + if (sig.signature == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + + 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)); + RETERR(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); + signeedsfree = false; + + 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 (signeedsfree) + 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_create3(key, mctx, + DNS_LOGCATEGORY_DNSSEC, false, &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, 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, 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_verify2(name, rdataset, dstkey, + ignoretime, 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)); + if (dk == NULL) + return (ISC_R_NOMEMORY); + + dk->key = *dstkey; + *dstkey = NULL; + dk->force_publish = false; + dk->force_sign = false; + dk->hint_publish = false; + dk->hint_sign = false; + dk->hint_remove = false; + dk->first_sign = false; + dk->is_active = false; + dk->prepublish = 0; + dk->source = dns_keysource_unknown; + dk->index = 0; + + /* KSK or ZSK? */ + dk->ksk = (dst_key_flags(dk->key) & DNS_KEYFLAG_KSK); + + /* 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; + if (dk->key != NULL) + dst_key_free(&dk->key); + isc_mem_put(mctx, dk, sizeof(dns_dnsseckey_t)); + *dkp = NULL; +} + +static void +get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { + isc_result_t result; + isc_stdtime_t publish, active, revoke, inactive, deltime; + bool pubset = false, actset = false; + bool revset = false, inactset = false; + bool delset = false; + + REQUIRE(key != NULL && key->key != NULL); + + result = dst_key_gettime(key->key, DST_TIME_PUBLISH, &publish); + if (result == ISC_R_SUCCESS) + pubset = true; + + result = dst_key_gettime(key->key, DST_TIME_ACTIVATE, &active); + if (result == ISC_R_SUCCESS) + actset = true; + + result = dst_key_gettime(key->key, DST_TIME_REVOKE, &revoke); + if (result == ISC_R_SUCCESS) + revset = true; + + result = dst_key_gettime(key->key, DST_TIME_INACTIVE, &inactive); + if (result == ISC_R_SUCCESS) + inactset = true; + + result = dst_key_gettime(key->key, DST_TIME_DELETE, &deltime); + if (result == ISC_R_SUCCESS) + delset = true; + + /* Metadata says publish (but possibly not activate) */ + if (pubset && publish <= now) + key->hint_publish = true; + + /* Metadata says activate (so we must also publish) */ + if (actset && active <= now) { + key->hint_sign = true; + + /* Only publish if publish time has already passed. */ + if (pubset && publish <= now) + key->hint_publish = true; + } + + /* + * Activation date is set (maybe in the future), but + * publication date isn't. Most likely the user wants to + * publish now and activate later. + */ + if (actset && !pubset) + key->hint_publish = true; + + /* + * If activation date is in the future, make note of how far off + */ + if (key->hint_publish && actset && active > now) { + key->prepublish = active - now; + } + + /* + * Key has been marked inactive: we can continue publishing, + * but don't sign. + */ + if (key->hint_publish && inactset && inactive <= now) { + key->hint_sign = false; + } + + /* + * 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 && (revset && revoke <= now)) { + 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. + */ + if (delset && deltime <= now) { + key->hint_publish = false; + key->hint_sign = false; + key->hint_remove = true; + } +} + +/*% + * Get a list of DNSSEC keys from the key repository + */ +isc_result_t +dns_dnssec_findmatchingkeys2(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 (dir.entry.name[i] < '0' || dir.entry.name[i] > '9') + 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 (dir.entry.name[i] < '0' || dir.entry.name[i] > '9') + 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, + mctx, &dstkey); + + switch (alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_HMACMD5: +#endif + 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; + 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); +} + +isc_result_t +dns_dnssec_findmatchingkeys(dns_name_t *origin, const char *directory, + isc_mem_t *mctx, dns_dnsseckeylist_t *keylist) +{ + isc_stdtime_t now; + + isc_stdtime_get(&now); + return (dns_dnssec_findmatchingkeys2(origin, directory, now, mctx, + keylist)); +} + +/*% + * 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(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 *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); + RETERR(dns_dnssec_keyfromrdata(origin, &rdata, mctx, &pubkey)); + dst_key_setttl(pubkey, keys.ttl); + + if (!is_zone_key(pubkey) || + (dst_key_flags(pubkey) & DNS_KEYTYPE_NOAUTH) != 0) + goto skip; + + /* Corrupted .key file? */ + if (!dns_name_equal(origin, dst_key_name(pubkey))) + goto skip; + + if (publickey) { + RETERR(addkey(keylist, &pubkey, savekeys, mctx)); + goto skip; + } + + result = dst_key_fromfile(dst_key_name(pubkey), + dst_key_id(pubkey), + dst_key_alg(pubkey), + DST_TYPE_PUBLIC|DST_TYPE_PRIVATE, + 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(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, + directory, + mctx, &privkey); + if (result == ISC_R_SUCCESS && + dst_key_pubcompare(pubkey, privkey, + false)) { + dst_key_setflags(privkey, 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, ISC_DIR_NAMEMAX); + result2 = dst_key_getfilename(dst_key_name(pubkey), + dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | + DST_TYPE_PRIVATE), + 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_keylistfromrdataset: error " + "reading %s: %s", + filename, isc_result_totext(result)); + } + + if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { + RETERR(addkey(keylist, &pubkey, 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(pubkey)); + + RETERR(addkey(keylist, &privkey, savekeys, mctx)); + skip: + 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 (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 +publish(dns_rdata_t *rdata, dns_diff_t *diff, 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, 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, dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx, bool allzsk, + void (*report)(const char *, ...)) +{ + isc_result_t result; + dns_difftuple_t *tuple = NULL; + unsigned char buf[DST_KEY_MAXSIZE]; + dns_rdata_t dnskey = DNS_RDATA_INIT; + char alg[80]; + + dns_rdata_reset(&dnskey); + RETERR(make_dnskey(key->key, buf, sizeof(buf), &dnskey)); + + dns_secalg_format(dst_key_alg(key->key), alg, sizeof(alg)); + report("Fetching %s %d/%s from key %s.", + key->ksk ? (allzsk ? "KSK/ZSK" : "KSK") : "ZSK", + dst_key_id(key->key), alg, + key->source == dns_keysource_user ? "file" : "repository"); + + if (key->prepublish && ttl > key->prepublish) { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t now; + + dst_key_format(key->key, keystr, sizeof(keystr)); + report("Key %s: Delaying activation to match the DNSKEY TTL.\n", + keystr, ttl); + + isc_stdtime_get(&now); + dst_key_settime(key->key, DST_TIME_ACTIVATE, now + ttl); + } + + /* publish key */ + RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_ADD, origin, ttl, + &dnskey, &tuple)); + dns_diff_appendminimal(diff, &tuple); + result = ISC_R_SUCCESS; + + failure: + return (result); +} + +static isc_result_t +remove_key(dns_diff_t *diff, dns_dnsseckey_t *key, dns_name_t *origin, + dns_ttl_t ttl, isc_mem_t *mctx, const char *reason, + void (*report)(const char *, ...)) +{ + isc_result_t result; + dns_difftuple_t *tuple = NULL; + 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)); + RETERR(dns_difftuple_create(mctx, DNS_DIFFOP_DEL, origin, ttl, &dnskey, + &tuple)); + dns_diff_appendminimal(diff, &tuple); + result = ISC_R_SUCCESS; + + 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 cdsrdata1 = DNS_RDATA_INIT; + dns_rdata_t cdsrdata2 = 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)); + + /* + * 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, + &cdsrdata1)); + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA256, dsbuf2, + &cdsrdata2)); + + /* + * Now that the we have created the DS records convert + * the rdata to CDNSKEY and CDS for comparison. + */ + cdnskeyrdata.type = dns_rdatatype_cdnskey; + cdsrdata1.type = dns_rdatatype_cds; + cdsrdata2.type = dns_rdatatype_cds; + + if (syncpublish(key->key, now)) { + if (!dns_rdataset_isassociated(cdnskey) || + !exists(cdnskey, &cdnskeyrdata)) + RETERR(publish(&cdnskeyrdata, diff, origin, + ttl, mctx)); + if (!dns_rdataset_isassociated(cds) || + !exists(cds, &cdsrdata1)) + RETERR(publish(&cdsrdata1, diff, origin, + ttl, mctx)); + if (!dns_rdataset_isassociated(cds) || + !exists(cds, &cdsrdata2)) + RETERR(publish(&cdsrdata2, diff, origin, + ttl, mctx)); + } + + if (dns_rdataset_isassociated(cds) && + syncdelete(key->key, now)) { + if (exists(cds, &cdsrdata1)) + RETERR(delrdata(&cdsrdata1, diff, origin, + cds->ttl, mctx)); + if (exists(cds, &cdsrdata2)) + RETERR(delrdata(&cdsrdata2, diff, origin, + cds->ttl, mctx)); + } + + if (dns_rdataset_isassociated(cdnskey) && + syncdelete(key->key, now)) { + if (exists(cdnskey, &cdnskeyrdata)) + RETERR(delrdata(&cdnskeyrdata, diff, origin, + cdnskey->ttl, mctx)); + } + } + + if (!dns_rdataset_isassociated(cds) && + !dns_rdataset_isassociated(cdnskey)) + return (ISC_R_SUCCESS); + + /* + * Unconditionaly remove CDS/DNSKEY records for removed keys. + */ + for (key = ISC_LIST_HEAD(*rmkeys); + key != NULL; + key = ISC_LIST_NEXT(key, link)) { + dns_rdata_t cdsrdata1 = DNS_RDATA_INIT; + dns_rdata_t cdsrdata2 = 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)); + + if (dns_rdataset_isassociated(cds)) { + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA1, dsbuf1, + &cdsrdata1)); + RETERR(dns_ds_buildrdata(origin, &cdnskeyrdata, + DNS_DSDIGEST_SHA256, dsbuf2, + &cdsrdata2)); + if (exists(cds, &cdsrdata1)) + RETERR(delrdata(&cdsrdata1, diff, origin, + cds->ttl, mctx)); + if (exists(cds, &cdsrdata2)) + RETERR(delrdata(&cdsrdata2, diff, origin, + cds->ttl, mctx)); + } + + if (dns_rdataset_isassociated(cdnskey)) { + if (exists(cdnskey, &cdnskeyrdata)) + RETERR(delrdata(&cdnskeyrdata, 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, dns_name_t *origin, + dns_ttl_t hint_ttl, dns_diff_t *diff, + bool allzsk, 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, allzsk, 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; + + 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; + } + } + + /* 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, allzsk, report)); + if (key1->hint_sign || key1->force_sign) + key1->first_sign = true; + } + + continue; + } + + /* 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); + 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); + else + dns_dnsseckey_destroy(mctx, &key2); + + RETERR(publish_key(diff, key1, origin, ttl, + mctx, allzsk, 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; + 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); +} diff --git a/lib/dns/dnstap.c b/lib/dns/dnstap.c new file mode 100644 index 0000000..ab3bd74 --- /dev/null +++ b/lib/dns/dnstap.c @@ -0,0 +1,1251 @@ +/* + * 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 http://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 */ + +#include + +#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 + +#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_region_t identity; + isc_region_t version; + char *path; + dns_dtmode_t mode; + isc_stats_t *stats; +}; + +#define CHECK(x) do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +static isc_mutex_t dt_mutex; +static bool dt_initialized = false; +static isc_thread_key_t dt_key; +static isc_once_t mutex_once = ISC_ONCE_INIT; +static isc_mem_t *dt_mctx = NULL; + +/* + * Change under task exclusive. + */ +static unsigned int generation; + +static void +mutex_init(void) { + RUNTIME_CHECK(isc_mutex_init(&dt_mutex) == ISC_R_SUCCESS); +} + +static void +dtfree(void *arg) { + free(arg); + isc_thread_key_setspecific(dt_key, NULL); +} + +static isc_result_t +dt_init(void) { + isc_result_t result; + + result = isc_once_do(&mutex_once, mutex_init); + if (result != ISC_R_SUCCESS) + return (result); + + if (dt_initialized) + return (ISC_R_SUCCESS); + + LOCK(&dt_mutex); + if (!dt_initialized) { + int ret; + + if (dt_mctx == NULL) + result = isc_mem_create2(0, 0, &dt_mctx, 0); + if (result != ISC_R_SUCCESS) + goto unlock; + isc_mem_setname(dt_mctx, "dt", NULL); + isc_mem_setdestroycheck(dt_mctx, false); + + ret = isc_thread_key_create(&dt_key, dtfree); + if (ret == 0) + dt_initialized = true; + else + result = ISC_R_FAILURE; + } +unlock: + UNLOCK(&dt_mutex); + + return (result); +} + +isc_result_t +dns_dt_create(isc_mem_t *mctx, dns_dtmode_t mode, const char *path, + struct fstrm_iothr_options **foptp, 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); + + generation++; + + env = isc_mem_get(mctx, sizeof(dns_dtenv_t)); + if (env == NULL) + CHECK(ISC_R_NOMEMORY); + + memset(env, 0, sizeof(dns_dtenv_t)); + + CHECK(isc_refcount_init(&env->refcount, 1)); + CHECK(isc_stats_create(mctx, &env->stats, dns_dnstapcounter_max)); + env->path = isc_mem_strdup(mctx, path); + if (env->path == NULL) + CHECK(ISC_R_NOMEMORY); + + 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->fopt = *foptp; + *foptp = NULL; + + isc_mem_attach(mctx, &env->mctx); + + 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) { + if (env != NULL) { + if (env->mctx != NULL) + isc_mem_detach(&env->mctx); + if (env->path != NULL) + isc_mem_free(mctx, env->path); + if (env->stats != NULL) + isc_stats_detach(&env->stats); + isc_mem_put(mctx, env, sizeof(dns_dtenv_t)); + } + } + + return (result); +} + +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)); + + /* + * Check that we can create a new fw object. + */ + fwopt = fstrm_writer_options_init(); + if (fwopt == NULL) + return (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); + + generation++; + + if (env->iothr != NULL) + fstrm_iothr_destroy(&env->iothr); + + 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 != 0 ? roll : ISC_LOG_ROLLINFINITE; + file.maximum_size = 0; + file.maximum_reached = false; + 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 (ffwopt != NULL) + fstrm_file_options_destroy(&ffwopt); + + if (fw != NULL) + fstrm_writer_destroy(&fw); + + if (fwopt != NULL) + fstrm_writer_options_destroy(&fwopt); + + if (fuwopt != NULL) + fstrm_unix_writer_options_destroy(&fuwopt); + + 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 (p == NULL) + return (ISC_R_NOMEMORY); + } + + 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 struct fstrm_iothr_queue * +dt_queue(dns_dtenv_t *env) { + isc_result_t result; + struct ioq { + unsigned int generation; + struct fstrm_iothr_queue *ioq; + } *ioq; + + REQUIRE(VALID_DTENV(env)); + + if (env->iothr == NULL) + return (NULL); + + result = dt_init(); + if (result != ISC_R_SUCCESS) + return (NULL); + + ioq = (struct ioq *)isc_thread_key_getspecific(dt_key); + if (ioq != NULL && ioq->generation != generation) { + result = isc_thread_key_setspecific(dt_key, NULL); + if (result != ISC_R_SUCCESS) + return (NULL); + free(ioq); + ioq = NULL; + } + if (ioq == NULL) { + ioq = malloc(sizeof(*ioq)); + if (ioq == NULL) + return (NULL); + ioq->generation = generation; + ioq->ioq = fstrm_iothr_get_input_queue(env->iothr); + if (ioq->ioq == NULL) { + free(ioq); + return (NULL); + } + result = isc_thread_key_setspecific(dt_key, ioq); + if (result != ISC_R_SUCCESS) { + free(ioq); + return (NULL); + } + } + + return (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, NULL); + *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; + + generation++; + + 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) { + unsigned int refs; + dns_dtenv_t *env; + + REQUIRE(envp != NULL && VALID_DTENV(*envp)); + + env = *envp; + *envp = NULL; + + isc_refcount_decrement(&env->refcount, &refs); + if (refs == 0) + 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); + default: + INSIST(0); + } +} + +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; +} + +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)); + + 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: + 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; + + cpbuf(buf, &dm.m.response_message, &dm.m.has_response_message); + + /* Types RR and FR get both query and response times */ + if (msgtype == DNS_DTTYPE_CR || msgtype == DNS_DTTYPE_AR) + 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: + 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; + + cpbuf(buf, &dm.m.query_message, &dm.m.has_query_message); + break; + default: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSTAP, + DNS_LOGMODULE_DNSTAP, ISC_LOG_ERROR, + "invalid dnstap message type %d", msgtype); + return; + } + + /* 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); +} + +void +dns_dt_shutdown() { + if (dt_mctx != NULL) + isc_mem_detach(&dt_mctx); +} + +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)); + if (handle == NULL) + CHECK(ISC_R_NOMEMORY); + + 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: + return (ISC_R_NOTIMPLEMENTED); + default: + INSIST(0); + } + + 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__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)); + if (d == NULL) + return (ISC_R_NOMEMORY); + + 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); + + if (d->frame->type != DNSTAP__DNSTAP__TYPE__MESSAGE) + CHECK(DNS_R_BADDNSTAP); + + m = d->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; + 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); + CHECK(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_destroy(&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; + 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; + + if (d->msg != NULL) + dns_message_destroy(&d->msg); + if (d->frame != NULL) + dnstap__dnstap__free_unpacked(d->frame, NULL); + + isc_mem_putanddetach(&d->mctx, d, sizeof(*d)); + + *dp = NULL; +} diff --git a/lib/dns/dnstap.proto b/lib/dns/dnstap.proto new file mode 100644 index 0000000..1ed1bb0 --- /dev/null +++ b/lib/dns/dnstap.proto @@ -0,0 +1,268 @@ +// 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 authorative + // 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; + } + + // 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..d1a507b --- /dev/null +++ b/lib/dns/ds.c @@ -0,0 +1,151 @@ +/* + * 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 http://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 + +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) +#include "dst_gost.h" +#endif + +isc_result_t +dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key, + unsigned int digest_type, unsigned char *buffer, + dns_rdata_t *rdata) +{ + dns_fixedname_t fname; + dns_name_t *name; + unsigned char digest[ISC_SHA384_DIGESTLENGTH]; + isc_region_t r; + isc_buffer_t b; + dns_rdata_ds_t ds; + isc_sha1_t sha1; + isc_sha256_t sha256; + isc_sha384_t sha384; +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) + isc_gost_t gost; +#endif + + REQUIRE(key != NULL); + REQUIRE(key->type == dns_rdatatype_dnskey); + + if (!dst_ds_digest_supported(digest_type)) + return (ISC_R_NOTIMPLEMENTED); + + name = dns_fixedname_initname(&fname); + (void)dns_name_downcase(owner, name, NULL); + + memset(buffer, 0, DNS_DS_BUFFERSIZE); + isc_buffer_init(&b, buffer, DNS_DS_BUFFERSIZE); + + switch (digest_type) { + case DNS_DSDIGEST_SHA1: + isc_sha1_init(&sha1); + dns_name_toregion(name, &r); + isc_sha1_update(&sha1, r.base, r.length); + dns_rdata_toregion(key, &r); + INSIST(r.length >= 4); + isc_sha1_update(&sha1, r.base, r.length); + isc_sha1_final(&sha1, digest); + break; + +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) +#define RETERR(x) do { \ + isc_result_t ret = (x); \ + if (ret != ISC_R_SUCCESS) { \ + isc_gost_invalidate(&gost); \ + return (ret); \ + } \ +} while (0) + + case DNS_DSDIGEST_GOST: + RETERR(isc_gost_init(&gost)); + dns_name_toregion(name, &r); + RETERR(isc_gost_update(&gost, r.base, r.length)); + dns_rdata_toregion(key, &r); + INSIST(r.length >= 4); + RETERR(isc_gost_update(&gost, r.base, r.length)); + RETERR(isc_gost_final(&gost, digest)); + break; +#endif + + case DNS_DSDIGEST_SHA384: + isc_sha384_init(&sha384); + dns_name_toregion(name, &r); + isc_sha384_update(&sha384, r.base, r.length); + dns_rdata_toregion(key, &r); + INSIST(r.length >= 4); + isc_sha384_update(&sha384, r.base, r.length); + isc_sha384_final(digest, &sha384); + break; + + case DNS_DSDIGEST_SHA256: + default: + isc_sha256_init(&sha256); + dns_name_toregion(name, &r); + isc_sha256_update(&sha256, r.base, r.length); + dns_rdata_toregion(key, &r); + INSIST(r.length >= 4); + isc_sha256_update(&sha256, r.base, r.length); + isc_sha256_final(digest, &sha256); + break; + } + + ds.mctx = NULL; + ds.common.rdclass = key->rdclass; + ds.common.rdtype = dns_rdatatype_ds; + ds.algorithm = r.base[3]; + ds.key_tag = dst_region_computeid(&r, ds.algorithm); + ds.digest_type = digest_type; + switch (digest_type) { + case DNS_DSDIGEST_SHA1: + ds.length = ISC_SHA1_DIGESTLENGTH; + break; + +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) + case DNS_DSDIGEST_GOST: + ds.length = ISC_GOST_DIGESTLENGTH; + break; +#endif + + case DNS_DSDIGEST_SHA384: + ds.length = ISC_SHA384_DIGESTLENGTH; + break; + + case DNS_DSDIGEST_SHA256: + default: + ds.length = ISC_SHA256_DIGESTLENGTH; + break; + } + ds.digest = digest; + + return (dns_rdata_fromstruct(rdata, key->rdclass, dns_rdatatype_ds, + &ds, &b)); +} diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c new file mode 100644 index 0000000..97fee68 --- /dev/null +++ b/lib/dns/dst_api.c @@ -0,0 +1,2029 @@ +/* + * Portions 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 http://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. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DST_KEY_INTERNAL + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" + +#define DST_AS_STR(t) ((t).value.as_textregion.base) + +static dst_func_t *dst_t_func[DST_MAX_ALGS]; +static isc_entropy_t *dst_entropy_pool = NULL; +static unsigned int dst_entropy_flags = 0; + +static bool dst_initialized = false; + +void gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); + +LIBDNS_EXTERNAL_DATA isc_mem_t *dst__memory_pool = NULL; + +/* + * Static functions. + */ +static dst_key_t * get_key_struct(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 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(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); \ + +#if defined(OPENSSL) +static void * +default_memalloc(void *arg, size_t size) { + UNUSED(arg); + if (size == 0U) + size = 1; + return (malloc(size)); +} + +static void +default_memfree(void *arg, void *ptr) { + UNUSED(arg); + free(ptr); +} +#endif + +isc_result_t +dst_lib_init(isc_mem_t *mctx, isc_entropy_t *ectx, unsigned int eflags) { + return (dst_lib_init2(mctx, ectx, NULL, eflags)); +} + +isc_result_t +dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, + const char *engine, unsigned int eflags) { + isc_result_t result; + + REQUIRE(mctx != NULL); + REQUIRE(dst_initialized == false); + +#if !defined(OPENSSL) && !defined(PKCS11CRYPTO) + UNUSED(engine); +#endif + + dst__memory_pool = NULL; + +#if defined(OPENSSL) + UNUSED(mctx); + /* + * When using --with-openssl, there seems to be no good way of not + * leaking memory due to the openssl error handling mechanism. + * Avoid assertions by using a local memory context and not checking + * for leaks on exit. Note: as there are leaks we cannot use + * ISC_MEMFLAG_INTERNAL as it will free up memory still being used + * by libcrypto. + */ + result = isc_mem_createx2(0, 0, default_memalloc, default_memfree, + NULL, &dst__memory_pool, 0); + if (result != ISC_R_SUCCESS) + return (result); + isc_mem_setname(dst__memory_pool, "dst", NULL); +#ifndef OPENSSL_LEAKS + isc_mem_setdestroycheck(dst__memory_pool, false); +#endif +#else /* OPENSSL */ + isc_mem_attach(mctx, &dst__memory_pool); +#endif /* OPENSSL */ + if (ectx != NULL) { + isc_entropy_attach(ectx, &dst_entropy_pool); + dst_entropy_flags = eflags; + } + + dst_result_register(); + + memset(dst_t_func, 0, sizeof(dst_t_func)); +#ifndef PK11_MD5_DISABLE + RETERR(dst__hmacmd5_init(&dst_t_func[DST_ALG_HMACMD5])); +#endif + 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])); +#ifdef OPENSSL + RETERR(dst__openssl_init(engine)); +#ifndef PK11_MD5_DISABLE + RETERR(dst__opensslrsa_init(&dst_t_func[DST_ALG_RSAMD5], + DST_ALG_RSAMD5)); +#endif + 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)); +#if defined(HAVE_OPENSSL_DSA) && !defined(PK11_DSA_DISABLE) + RETERR(dst__openssldsa_init(&dst_t_func[DST_ALG_DSA])); + RETERR(dst__openssldsa_init(&dst_t_func[DST_ALG_NSEC3DSA])); +#endif +#ifndef PK11_DH_DISABLE + RETERR(dst__openssldh_init(&dst_t_func[DST_ALG_DH])); +#endif +#ifdef HAVE_OPENSSL_GOST + RETERR(dst__opensslgost_init(&dst_t_func[DST_ALG_ECCGOST])); +#endif +#ifdef HAVE_OPENSSL_ECDSA + RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA256])); + RETERR(dst__opensslecdsa_init(&dst_t_func[DST_ALG_ECDSA384])); +#endif +#ifdef HAVE_OPENSSL_ED25519 + RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED25519])); +#endif +#ifdef HAVE_OPENSSL_ED448 + RETERR(dst__openssleddsa_init(&dst_t_func[DST_ALG_ED448])); +#endif +#elif PKCS11CRYPTO + RETERR(dst__pkcs11_init(mctx, engine)); +#ifndef PK11_MD5_DISABLE + RETERR(dst__pkcs11rsa_init(&dst_t_func[DST_ALG_RSAMD5])); +#endif + 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])); +#ifndef PK11_DSA_DISABLE + RETERR(dst__pkcs11dsa_init(&dst_t_func[DST_ALG_DSA])); + RETERR(dst__pkcs11dsa_init(&dst_t_func[DST_ALG_NSEC3DSA])); +#endif +#ifndef PK11_DH_DISABLE + RETERR(dst__pkcs11dh_init(&dst_t_func[DST_ALG_DH])); +#endif +#ifdef HAVE_PKCS11_ECDSA + RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA256])); + RETERR(dst__pkcs11ecdsa_init(&dst_t_func[DST_ALG_ECDSA384])); +#endif +#ifdef HAVE_PKCS11_ED25519 + RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED25519])); +#endif +#ifdef HAVE_PKCS11_ED448 + RETERR(dst__pkcs11eddsa_init(&dst_t_func[DST_ALG_ED448])); +#endif +#ifdef HAVE_PKCS11_GOST + RETERR(dst__pkcs11gost_init(&dst_t_func[DST_ALG_ECCGOST])); +#endif +#endif /* if OPENSSL, elif PKCS11CRYPTO */ +#ifdef GSSAPI + RETERR(dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI])); +#endif + 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 == true); + 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(); +#ifdef OPENSSL + dst__openssl_destroy(); +#elif PKCS11CRYPTO + (void) dst__pkcs11_destroy(); +#endif /* if OPENSSL, elif PKCS11CRYPTO */ + if (dst__memory_pool != NULL) + isc_mem_detach(&dst__memory_pool); + if (dst_entropy_pool != NULL) + isc_entropy_detach(&dst_entropy_pool); +} + +bool +dst_algorithm_supported(unsigned int alg) { + REQUIRE(dst_initialized == true); + + 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 || +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) + digest_type == DNS_DSDIGEST_GOST || +#endif + digest_type == DNS_DSDIGEST_SHA384); +} + +isc_result_t +dst_context_create(dst_key_t *key, isc_mem_t *mctx, dst_context_t **dctxp) { + return (dst_context_create4(key, mctx, DNS_LOGCATEGORY_GENERAL, + true, 0, dctxp)); +} + +isc_result_t +dst_context_create2(dst_key_t *key, isc_mem_t *mctx, + isc_logcategory_t *category, dst_context_t **dctxp) +{ + return (dst_context_create4(key, mctx, category, true, 0, dctxp)); +} + +isc_result_t +dst_context_create3(dst_key_t *key, isc_mem_t *mctx, + isc_logcategory_t *category, bool useforsigning, + dst_context_t **dctxp) +{ + return (dst_context_create4(key, mctx, category, + useforsigning, 0, dctxp)); +} + +isc_result_t +dst_context_create4(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 == true); + 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)); + if (dctx == NULL) + return (ISC_R_NOMEMORY); + 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; + 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)); + *dctxp = NULL; +} + +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) == false) + 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 == true); + 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) == false) + 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 == true); + REQUIRE(VALID_KEY(key)); + REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 0); + + CHECKALG(key->key_alg); + + if (key->func->tofile == NULL) + return (DST_R_UNSUPPORTEDALG); + + if (type & DST_TYPE_PUBLIC) { + ret = write_public_key(key, type, directory); + if (ret != ISC_R_SUCCESS) + return (ret); + } + + if ((type & DST_TYPE_PRIVATE) && + (key->key_flags & DNS_KEYFLAG_TYPEMASK) != DNS_KEYTYPE_NOKEY) + return (key->func->tofile(key, directory)); + else + return (ISC_R_SUCCESS); +} + +void +dst_key_setexternal(dst_key_t *key, bool value) { + key->external = value; +} + +bool +dst_key_isexternal(dst_key_t *key) { + return (key->external); +} + +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 == true); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) != 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[ISC_DIR_NAMEMAX]; + isc_buffer_t buf; + dst_key_t *key; + + REQUIRE(dst_initialized == true); + 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, ISC_DIR_NAMEMAX); + 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; + int newfilenamelen = 0; + isc_lex_t *lex = NULL; + + REQUIRE(dst_initialized == true); + 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 + + newfilenamelen = strlen(filename) + 5; + if (dirname != NULL) + newfilenamelen += strlen(dirname) + 1; + newfilename = isc_mem_get(mctx, newfilenamelen); + if (newfilename == NULL) + return (ISC_R_NOMEMORY); + result = addsuffix(newfilename, newfilenamelen, + dirname, filename, ".key"); + INSIST(result == ISC_R_SUCCESS); + + result = dst_key_read_public(newfilename, type, mctx, &pubkey); + isc_mem_put(mctx, newfilename, newfilenamelen); + newfilename = NULL; + RETERR(result); + + if ((type & (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC)) == DST_TYPE_PUBLIC || + (pubkey->key_flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { + result = computeid(pubkey); + if (result != ISC_R_SUCCESS) { + dst_key_free(&pubkey); + return (result); + } + + *keyp = pubkey; + return (ISC_R_SUCCESS); + } + + result = algorithm_status(pubkey->key_alg); + if (result != ISC_R_SUCCESS) { + dst_key_free(&pubkey); + return (result); + } + + key = get_key_struct(pubkey->key_name, pubkey->key_alg, + pubkey->key_flags, pubkey->key_proto, 0, + pubkey->key_class, pubkey->key_ttl, mctx); + if (key == NULL) { + dst_key_free(&pubkey); + return (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); + if (newfilename == NULL) + RETERR(ISC_R_NOMEMORY); + 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); + + RETERR(computeid(key)); + + if (pubkey->key_id != key->key_id) + RETERR(DST_R_INVALIDPRIVATEKEY); + dst_key_free(&pubkey); + + *keyp = key; + return (ISC_R_SUCCESS); + + out: + if (pubkey != NULL) + dst_key_free(&pubkey); + if (newfilename != NULL) + isc_mem_put(mctx, newfilename, newfilenamelen); + 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 == true); + 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) { + 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(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, alg); + rid = dst_region_computerid(&r, alg); + + if (flags & DNS_KEYFLAG_EXTENDED) { + 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(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 == true); + 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 == true); + 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); +} + +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(dns_name_t *name, 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. + */ + RETERR(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: + return result; +} + +isc_result_t +dst_key_buildinternal(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 == true); + 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(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 == true); + 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(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) +{ + return (dst_key_generate2(name, alg, bits, param, flags, protocol, + rdclass, mctx, keyp, NULL)); +} + +isc_result_t +dst_key_generate2(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 == true); + 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_getnum(const dst_key_t *key, int type, uint32_t *valuep) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(valuep != NULL); + REQUIRE(type <= DST_MAX_NUMERIC); + if (!key->numset[type]) + return (ISC_R_NOTFOUND); + *valuep = key->nums[type]; + 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); + key->nums[type] = value; + key->numset[type] = true; +} + +void +dst_key_unsetnum(dst_key_t *key, int type) +{ + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_NUMERIC); + key->numset[type] = false; +} + +isc_result_t +dst_key_gettime(const dst_key_t *key, int type, isc_stdtime_t *timep) { + REQUIRE(VALID_KEY(key)); + REQUIRE(timep != NULL); + REQUIRE(type <= DST_MAX_TIMES); + if (!key->timeset[type]) + return (ISC_R_NOTFOUND); + *timep = key->times[type]; + 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); + key->times[type] = when; + key->timeset[type] = true; +} + +void +dst_key_unsettime(dst_key_t *key, int type) { + REQUIRE(VALID_KEY(key)); + REQUIRE(type <= DST_MAX_TIMES); + key->timeset[type] = false; +} + +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 == true); + 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); +#ifndef PK11_MD5_DISABLE + if (key1->key_alg == DST_ALG_RSAMD5) + return (false); +#endif + 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 == true); + 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) == true) + return (true); + else + return (false); +} + +void +dst_key_attach(dst_key_t *source, dst_key_t **target) { + + REQUIRE(dst_initialized == true); + REQUIRE(target != NULL && *target == NULL); + REQUIRE(VALID_KEY(source)); + + isc_refcount_increment(&source->refs, NULL); + *target = source; +} + +void +dst_key_free(dst_key_t **keyp) { + isc_mem_t *mctx; + dst_key_t *key; + unsigned int refs; + + REQUIRE(dst_initialized == true); + REQUIRE(keyp != NULL && VALID_KEY(*keyp)); + + key = *keyp; + mctx = key->mctx; + + isc_refcount_decrement(&key->refs, &refs); + if (refs != 0) + return; + + isc_refcount_destroy(&key->refs); + 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_safe_memwipe(key, sizeof(*key)); + isc_mem_putanddetach(&mctx, key, sizeof(*key)); + *keyp = NULL; +} + +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 == 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 == true); + REQUIRE(VALID_KEY(key)); + REQUIRE(n != NULL); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + *n = (key->key_size + 7) / 8; + break; +#ifndef PK11_DSA_DISABLE + case DST_ALG_DSA: + case DST_ALG_NSEC3DSA: + *n = DNS_SIG_DSASIGSIZE; + break; +#endif + case DST_ALG_ECCGOST: + *n = DNS_SIG_GOSTSIGSIZE; + 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; +#ifndef PK11_MD5_DISABLE + case DST_ALG_HMACMD5: + *n = 16; + break; +#endif + case DST_ALG_HMACSHA1: + *n = ISC_SHA1_DIGESTLENGTH; + break; + case DST_ALG_HMACSHA224: + *n = ISC_SHA224_DIGESTLENGTH; + break; + case DST_ALG_HMACSHA256: + *n = ISC_SHA256_DIGESTLENGTH; + break; + case DST_ALG_HMACSHA384: + *n = ISC_SHA384_DIGESTLENGTH; + break; + case DST_ALG_HMACSHA512: + *n = ISC_SHA512_DIGESTLENGTH; + break; + case DST_ALG_GSSAPI: + *n = 128; /*%< XXX */ + break; +#ifndef PK11_DH_DISABLE + case DST_ALG_DH: +#endif + 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 == true); + REQUIRE(VALID_KEY(key)); + REQUIRE(n != NULL); + +#ifndef PK11_DH_DISABLE + if (key->key_alg == DST_ALG_DH) + *n = (key->key_size + 7) / 8; + else +#endif + return (DST_R_UNSUPPORTEDALG); +#ifndef PK11_DH_DISABLE + return (ISC_R_SUCCESS); +#endif +} + +/*% + * 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 == true); + 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(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; + isc_result_t result; + int i; + + key = (dst_key_t *) isc_mem_get(mctx, sizeof(dst_key_t)); + if (key == NULL) + return (NULL); + + memset(key, 0, sizeof(dst_key_t)); + + key->key_name = isc_mem_get(mctx, sizeof(dns_name_t)); + if (key->key_name == NULL) { + isc_mem_put(mctx, key, sizeof(dst_key_t)); + return (NULL); + } + + dns_name_init(key->key_name, NULL); + result = dns_name_dup(name, mctx, key->key_name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, key->key_name, sizeof(dns_name_t)); + isc_mem_put(mctx, key, sizeof(dst_key_t)); + return (NULL); + } + + result = isc_refcount_init(&key->refs, 1); + if (result != ISC_R_SUCCESS) { + dns_name_free(key->key_name, mctx); + isc_mem_put(mctx, key->key_name, sizeof(dns_name_t)); + isc_mem_put(mctx, key, sizeof(dst_key_t)); + return (NULL); + } + 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; + } + 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; + +#define NEXTTOKEN(lex, opt, token) { \ + ret = isc_lex_gettoken(lex, opt, token); \ + if (ret != ISC_R_SUCCESS) \ + goto cleanup; \ + } + +#define BADTOKEN() { \ + ret = ISC_R_UNEXPECTEDTOKEN; \ + 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 bool +issymmetric(const dst_key_t *key) { + REQUIRE(dst_initialized == true); + REQUIRE(VALID_KEY(key)); + + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: +#ifndef PK11_DSA_DISABLE + case DST_ALG_DSA: + case DST_ALG_NSEC3DSA: +#endif +#ifndef PK11_DH_DISABLE + case DST_ALG_DH: +#endif + case DST_ALG_ECCGOST: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + return (false); +#ifndef PK11_MD5_DISABLE + case DST_ALG_HMACMD5: +#endif + 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 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; +#ifdef ISC_PLATFORM_USETHREADS + char output[26]; /* Minimum buffer as per ctime_r() specification. */ +#else + const char *output; +#endif + isc_stdtime_t when; + time_t t; + 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; + + /* time_t and isc_stdtime_t might be different sizes */ + t = when; +#ifdef ISC_PLATFORM_USETHREADS +#ifdef WIN32 + if (ctime_s(output, sizeof(output), &t) != 0) + goto error; +#else + if (ctime_r(&t, output) == NULL) + goto error; +#endif +#else + output = ctime(&t); +#endif + + 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, + (int)strlen(output) - 1, output); + return; + + error: + fprintf(stream, "%s: (set, unable to display)\n", tag); +} + +/*% + * 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[ISC_DIR_NAMEMAX]; + 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 = ""; + unsigned int len; + isc_result_t result; + + REQUIRE(out != NULL); + if ((type & DST_TYPE_PRIVATE) != 0) + suffix = ".private"; + else if (type == DST_TYPE_PUBLIC) + suffix = ".key"; + 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); + len = 1 + 3 + 1 + 5 + strlen(suffix) + 1; + if (isc_buffer_availablelength(out) < len) + return (ISC_R_NOSPACE); + snprintf((char *) isc_buffer_used(out), + (int)isc_buffer_availablelength(out), + "+%03d+%05d%s", alg, id, suffix); + isc_buffer_add(out, len); + + return (ISC_R_SUCCESS); +} + +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_alg); + key->key_rid = dst_region_computerid(&r, key->key_alg); + return (ISC_R_SUCCESS); +} + +static isc_result_t +frombuffer(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 == true); + + if (dst_algorithm_supported(alg)) + return (ISC_R_SUCCESS); +#if !defined(OPENSSL) && !defined(PKCS11CRYPTO) + if (alg == DST_ALG_RSAMD5 || alg == DST_ALG_RSASHA1 || + alg == DST_ALG_DSA || alg == DST_ALG_DH || + alg == DST_ALG_HMACMD5 || alg == DST_ALG_NSEC3DSA || + alg == DST_ALG_NSEC3RSASHA1 || + alg == DST_ALG_RSASHA256 || alg == DST_ALG_RSASHA512 || + alg == DST_ALG_ECCGOST || + alg == DST_ALG_ECDSA256 || alg == DST_ALG_ECDSA384 || + alg == DST_ALG_ED25519 || alg == DST_ALG_ED448) + return (DST_R_NOCRYPTO); +#endif + 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_result_t +dst__entropy_getdata(void *buf, unsigned int len, bool pseudo) { + unsigned int flags = dst_entropy_flags; + + if (dst_entropy_pool == NULL) + return (ISC_R_FAILURE); + + if (len == 0) + return (ISC_R_SUCCESS); + +#ifdef PKCS11CRYPTO + UNUSED(pseudo); + UNUSED(flags); + return (pk11_rand_bytes(buf, len)); +#else /* PKCS11CRYPTO */ + if (pseudo) + flags &= ~ISC_ENTROPY_GOODONLY; + else + flags |= ISC_ENTROPY_BLOCKING; + return (isc_entropy_getdata(dst_entropy_pool, buf, len, NULL, flags)); +#endif /* PKCS11CRYPTO */ +} + +unsigned int +dst__entropy_status(void) { +#ifndef PKCS11CRYPTO +#ifdef GSSAPI + unsigned int flags = dst_entropy_flags; + isc_result_t ret; + unsigned char buf[32]; + static bool first = true; + + if (dst_entropy_pool == NULL) + return (0); + + if (first) { + /* Someone believes RAND_status() initializes the PRNG */ + flags &= ~ISC_ENTROPY_GOODONLY; + ret = isc_entropy_getdata(dst_entropy_pool, buf, + sizeof(buf), NULL, flags); + INSIST(ret == ISC_R_SUCCESS); + isc_entropy_putdata(dst_entropy_pool, buf, + sizeof(buf), 2 * sizeof(buf)); + first = false; + } +#endif + return (isc_entropy_status(dst_entropy_pool)); +#else + return (0); +#endif +} + +isc_buffer_t * +dst_key_tkeytoken(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->key_tkeytoken); +} diff --git a/lib/dns/dst_gost.h b/lib/dns/dst_gost.h new file mode 100644 index 0000000..e42c6a1 --- /dev/null +++ b/lib/dns/dst_gost.h @@ -0,0 +1,58 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef DST_GOST_H +#define DST_GOST_H 1 + +#include +#include +#include + +#define ISC_GOST_DIGESTLENGTH 32U + +#ifdef HAVE_OPENSSL_GOST +#include + +typedef struct { + EVP_MD_CTX *ctx; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX _ctx; +#endif +} isc_gost_t; + +#endif +#ifdef HAVE_PKCS11_GOST +#include + +typedef pk11_context_t isc_gost_t; +#endif + +ISC_LANG_BEGINDECLS + +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) + +isc_result_t +isc_gost_init(isc_gost_t *ctx); + +void +isc_gost_invalidate(isc_gost_t *ctx); + +isc_result_t +isc_gost_update(isc_gost_t *ctx, const unsigned char *data, unsigned int len); + +isc_result_t +isc_gost_final(isc_gost_t *ctx, unsigned char *digest); + +ISC_LANG_ENDDECLS + +#endif /* HAVE_OPENSSL_GOST || HAVE_PKCS11_GOST */ + +#endif /* DST_GOST_H */ diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h new file mode 100644 index 0000000..11e5fc6 --- /dev/null +++ b/lib/dns/dst_internal.h @@ -0,0 +1,306 @@ +/* + * Portions 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 http://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. + */ + + +#ifndef DST_DST_INTERNAL_H +#define DST_DST_INTERNAL_H 1 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#ifdef OPENSSL +#ifndef PK11_DH_DISABLE +#include +#endif +#ifndef PK11_DSA_DISABLE +#include +#endif +#include +#include +#include +#include +#endif + +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) + +LIBDNS_EXTERNAL_DATA extern isc_mem_t *dst__memory_pool; + +/*** + *** Types + ***/ + +typedef struct dst_func dst_func_t; + +#ifndef PK11_MD5_DISABLE +typedef struct dst_hmacmd5_key dst_hmacmd5_key_t; +#endif +typedef struct dst_hmacsha1_key dst_hmacsha1_key_t; +typedef struct dst_hmacsha224_key dst_hmacsha224_key_t; +typedef struct dst_hmacsha256_key dst_hmacsha256_key_t; +typedef struct dst_hmacsha384_key dst_hmacsha384_key_t; +typedef struct dst_hmacsha512_key dst_hmacsha512_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; + 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; + gss_ctx_id_t gssctx; +#ifdef OPENSSL +#if !defined(USE_EVP) || !USE_EVP + RSA *rsa; +#endif +#ifndef PK11_DSA_DISABLE + DSA *dsa; +#endif +#ifndef PK11_DH_DISABLE + DH *dh; +#endif + EVP_PKEY *pkey; +#elif PKCS11CRYPTO + pk11_object_t *pkey; +#endif +#ifndef PK11_MD5_DISABLE + dst_hmacmd5_key_t *hmacmd5; +#endif + dst_hmacsha1_key_t *hmacsha1; + dst_hmacsha224_key_t *hmacsha224; + dst_hmacsha256_key_t *hmacsha256; + dst_hmacsha384_key_t *hmacsha384; + dst_hmacsha512_key_t *hmacsha512; + + } 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? */ + isc_stdtime_t nums[DST_MAX_NUMERIC + 1]; /*%< numeric metadata */ + bool numset[DST_MAX_NUMERIC + 1]; /*%< data set? */ + bool inactive; /*%< private key not present as it is + inactive */ + bool external; /*%< external key */ + + 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; +#ifndef PK11_MD5_DISABLE + isc_md5_t *md5ctx; +#endif + isc_sha1_t *sha1ctx; + isc_sha256_t *sha256ctx; + isc_sha512_t *sha512ctx; +#ifndef PK11_MD5_DISABLE + isc_hmacmd5_t *hmacmd5ctx; +#endif + isc_hmacsha1_t *hmacsha1ctx; + isc_hmacsha224_t *hmacsha224ctx; + isc_hmacsha256_t *hmacsha256ctx; + isc_hmacsha384_t *hmacsha384ctx; + isc_hmacsha512_t *hmacsha512ctx; +#ifdef OPENSSL + EVP_MD_CTX *evp_md_ctx; +#elif PKCS11CRYPTO + pk11_context_t *pk11_ctx; +#endif + } 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 + +#ifndef PK11_MD5_DISABLE +isc_result_t dst__hmacmd5_init(struct dst_func **funcp); +#endif +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__opensslrsa_init(struct dst_func **funcp, + unsigned char algorithm); +isc_result_t dst__pkcs11rsa_init(struct dst_func **funcp); +#ifndef PK11_DSA_DISABLE +isc_result_t dst__openssldsa_init(struct dst_func **funcp); +isc_result_t dst__pkcs11dsa_init(struct dst_func **funcp); +#endif +#ifndef PK11_DH_DISABLE +isc_result_t dst__openssldh_init(struct dst_func **funcp); +isc_result_t dst__pkcs11dh_init(struct dst_func **funcp); +#endif +isc_result_t dst__gssapi_init(struct dst_func **funcp); +#ifdef HAVE_OPENSSL_ECDSA +isc_result_t dst__opensslecdsa_init(struct dst_func **funcp); +#endif +#if defined(HAVE_OPENSSL_ED25519) || defined(HAVE_OPENSSL_ED448) +isc_result_t dst__openssleddsa_init(struct dst_func **funcp); +#endif +#ifdef HAVE_PKCS11_ECDSA +isc_result_t dst__pkcs11ecdsa_init(struct dst_func **funcp); +#endif +#if defined(HAVE_PKCS11_ED25519) || defined(HAVE_PKCS11_ED448) +isc_result_t dst__pkcs11eddsa_init(struct dst_func **funcp); +#endif +#ifdef HAVE_OPENSSL_GOST +isc_result_t dst__opensslgost_init(struct dst_func **funcp); +#endif +#ifdef HAVE_PKCS11_GOST +isc_result_t dst__pkcs11gost_init(struct dst_func **funcp); +#endif + +/*% + * 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); + +/*% + * Entropy retriever using the DST entropy pool. + */ +isc_result_t dst__entropy_getdata(void *buf, unsigned int len, + bool pseudo); + +/* + * Entropy status hook. + */ +unsigned int dst__entropy_status(void); + +ISC_LANG_ENDDECLS + +#endif /* DST_DST_INTERNAL_H */ +/*! \file */ diff --git a/lib/dns/dst_lib.c b/lib/dns/dst_lib.c new file mode 100644 index 0000000..4edf728 --- /dev/null +++ b/lib/dns/dst_lib.c @@ -0,0 +1,56 @@ +/* + * 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 http://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 + +/*** + *** Globals + ***/ + +LIBDNS_EXTERNAL_DATA isc_msgcat_t * dst_msgcat = NULL; + + +/*** + *** Private + ***/ + +static isc_once_t msgcat_once = ISC_ONCE_INIT; + + +/*** + *** Functions + ***/ + +static void +open_msgcat(void) { + isc_msgcat_open("libdst.cat", &dst_msgcat); +} + +void +dst_lib_initmsgcat(void) { + + /* + * Initialize the DST library's message catalog, dst_msgcat, if it + * has not already been initialized. + */ + + RUNTIME_CHECK(isc_once_do(&msgcat_once, open_msgcat) == ISC_R_SUCCESS); +} diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h new file mode 100644 index 0000000..e085f11 --- /dev/null +++ b/lib/dns/dst_openssl.h @@ -0,0 +1,71 @@ +/* + * 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 http://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 OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +/* + * 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 OPENSSL_VERSION_NUMBER < 0x10100000L + * _cb; + * #endif + */ +#define BN_GENCB_free(x) ((void)0) +#define BN_GENCB_new() (&_cb) +#define BN_GENCB_get_arg(x) ((x)->arg) +#endif + +#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 + +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 +#define dst__openssl_getengine(x) NULL +#endif + +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..f31c33d --- /dev/null +++ b/lib/dns/dst_parse.c @@ -0,0 +1,856 @@ +/* + * Portions 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 http://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. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst/result.h" + +#define DST_AS_STR(t) ((t).value.as_textregion.base) + +#define PRIVATE_KEY_STR "Private-key-format:" +#define ALGORITHM_STR "Algorithm:" + +#define TIMING_NTAGS (DST_MAX_TIMES + 1) +static const char *timetags[TIMING_NTAGS] = { + "Created:", + "Publish:", + "Activate:", + "Revoke:", + "Inactive:", + "Delete:", + "DSPublish:", + "SyncPublish:", + "SyncDelete:" +}; + +#define NUMERIC_NTAGS (DST_MAX_NUMERIC + 1) +static const char *numerictags[NUMERIC_NTAGS] = { + "Predecessor:", + "Successor:", + "MaxTTL:", + "RollPeriod:" +}; + +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:" }, + +#ifndef PK11_DH_DISABLE + {TAG_DH_PRIME, "Prime(p):"}, + {TAG_DH_GENERATOR, "Generator(g):"}, + {TAG_DH_PRIVATE, "Private_value(x):"}, + {TAG_DH_PUBLIC, "Public_value(y):"}, +#endif + +#ifndef PK11_DSA_DISABLE + {TAG_DSA_PRIME, "Prime(p):"}, + {TAG_DSA_SUBPRIME, "Subprime(q):"}, + {TAG_DSA_BASE, "Base(g):"}, + {TAG_DSA_PRIVATE, "Private_value(x):"}, + {TAG_DSA_PUBLIC, "Public_value(y):"}, +#endif + + {TAG_GOST_PRIVASN1, "GostAsn1:"}, + {TAG_GOST_PRIVRAW, "PrivateKey:"}, + + {TAG_ECDSA_PRIVATEKEY, "PrivateKey:"}, + {TAG_ECDSA_ENGINE, "Engine:" }, + {TAG_ECDSA_LABEL, "Label:" }, + + {TAG_EDDSA_PRIVATEKEY, "PrivateKey:"}, + {TAG_EDDSA_ENGINE, "Engine:" }, + {TAG_EDDSA_LABEL, "Label:" }, + +#ifndef PK11_MD5_DISABLE + {TAG_HMACMD5_KEY, "Key:"}, + {TAG_HMACMD5_BITS, "Bits:"}, +#endif + + {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_RSAMD5, 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 ); +} + +#ifndef PK11_DH_DISABLE +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); +} +#endif + +#ifndef PK11_DSA_DISABLE +static int +check_dsa(const dst_private_t *priv, bool external) { + int i, j; + + if (external) + return ((priv->nelements == 0)? 0 : -1); + + if (priv->nelements != DSA_NTAGS) + return (-1); + + for (i = 0; i < DSA_NTAGS; i++) { + for (j = 0; j < priv->nelements; j++) + if (priv->elements[j].tag == TAG(DST_ALG_DSA, i)) + break; + if (j == priv->nelements) + return (-1); + } + return (0); +} +#endif + +static int +check_gost(const dst_private_t *priv, bool external) { + + if (external) + return ((priv->nelements == 0)? 0 : -1); + + if (priv->nelements != GOST_NTAGS) + return (-1); + if ((priv->elements[0].tag != TAG(DST_ALG_ECCGOST, 0)) && + (priv->elements[0].tag != TAG(DST_ALG_ECCGOST, 1))) + 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 ); +} + +#ifndef PK11_MD5_DISABLE +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); +} +#endif + +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) +{ +#ifdef PK11_MD5_DISABLE + UNUSED(old); +#endif + /* XXXVIX this switch statement is too sparse to gen a jump table. */ + switch (alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + return (check_rsa(priv, external)); +#ifndef PK11_DH_DISABLE + case DST_ALG_DH: + return (check_dh(priv)); +#endif +#ifndef PK11_DSA_DISABLE + case DST_ALG_DSA: + case DST_ALG_NSEC3DSA: + return (check_dsa(priv, external)); +#endif + case DST_ALG_ECCGOST: + return (check_gost(priv, external)); + 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)); +#ifndef PK11_MD5_DISABLE + case DST_ALG_HMACMD5: + return (check_hmac_md5(priv, old)); +#endif + 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 = (unsigned char *) isc_mem_get(mctx, MAXFIELDSIZE); + if (data == NULL) + goto fail; + + 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; + } + +#ifdef PK11_MD5_DISABLE + check = check_data(priv, alg == DST_ALG_RSA ? DST_ALG_RSASHA1 : alg, + true, external); +#else + check = check_data(priv, alg, true, external); +#endif + 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[ISC_DIR_NAMEMAX]; + 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 + level = ISC_LOG_WARNING; +#endif + 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_RSAMD5: + fprintf(fp, "(RSA)\n"); + break; + case DST_ALG_DH: + fprintf(fp, "(DH)\n"); + break; + case DST_ALG_DSA: + fprintf(fp, "(DSA)\n"); + break; + case DST_ALG_RSASHA1: + fprintf(fp, "(RSASHA1)\n"); + break; + case DST_ALG_NSEC3RSASHA1: + fprintf(fp, "(NSEC3RSASHA1)\n"); + break; + case DST_ALG_NSEC3DSA: + fprintf(fp, "(NSEC3DSA)\n"); + break; + case DST_ALG_RSASHA256: + fprintf(fp, "(RSASHA256)\n"); + break; + case DST_ALG_RSASHA512: + fprintf(fp, "(RSASHA512)\n"); + break; + case DST_ALG_ECCGOST: + fprintf(fp, "(ECC-GOST)\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; + 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); + + 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..a2600e1 --- /dev/null +++ b/lib/dns/dst_parse.h @@ -0,0 +1,142 @@ +/* + * Portions 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 http://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. + */ + + +/*! \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 both RSA-MD5 and RSA-SHA1 */ +#define RSA_NTAGS 11 +#define TAG_RSA_MODULUS ((DST_ALG_RSAMD5 << TAG_SHIFT) + 0) +#define TAG_RSA_PUBLICEXPONENT ((DST_ALG_RSAMD5 << TAG_SHIFT) + 1) +#define TAG_RSA_PRIVATEEXPONENT ((DST_ALG_RSAMD5 << TAG_SHIFT) + 2) +#define TAG_RSA_PRIME1 ((DST_ALG_RSAMD5 << TAG_SHIFT) + 3) +#define TAG_RSA_PRIME2 ((DST_ALG_RSAMD5 << TAG_SHIFT) + 4) +#define TAG_RSA_EXPONENT1 ((DST_ALG_RSAMD5 << TAG_SHIFT) + 5) +#define TAG_RSA_EXPONENT2 ((DST_ALG_RSAMD5 << TAG_SHIFT) + 6) +#define TAG_RSA_COEFFICIENT ((DST_ALG_RSAMD5 << TAG_SHIFT) + 7) +#define TAG_RSA_ENGINE ((DST_ALG_RSAMD5 << TAG_SHIFT) + 8) +#define TAG_RSA_LABEL ((DST_ALG_RSAMD5 << 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 DSA_NTAGS 5 +#define TAG_DSA_PRIME ((DST_ALG_DSA << TAG_SHIFT) + 0) +#define TAG_DSA_SUBPRIME ((DST_ALG_DSA << TAG_SHIFT) + 1) +#define TAG_DSA_BASE ((DST_ALG_DSA << TAG_SHIFT) + 2) +#define TAG_DSA_PRIVATE ((DST_ALG_DSA << TAG_SHIFT) + 3) +#define TAG_DSA_PUBLIC ((DST_ALG_DSA << TAG_SHIFT) + 4) + +#define GOST_NTAGS 1 +#define TAG_GOST_PRIVASN1 ((DST_ALG_ECCGOST << TAG_SHIFT) + 0) +#define TAG_GOST_PRIVRAW ((DST_ALG_ECCGOST << TAG_SHIFT) + 1) + +#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..b8011f2 --- /dev/null +++ b/lib/dns/dst_pkcs11.h @@ -0,0 +1,38 @@ +/* + * 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 http://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..caee5f0 --- /dev/null +++ b/lib/dns/dst_result.c @@ -0,0 +1,110 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#include +#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_msgcat, 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_msgcat, DST_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_registerids() failed: %u", result); +} + +static void +initialize(void) { + dst_lib_initmsgcat(); + 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..93ad795 --- /dev/null +++ b/lib/dns/dyndb.c @@ -0,0 +1,467 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#include + +#if HAVE_DLFCN_H +#include +#elif _WIN32 +#include +#endif + +#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) { + RUNTIME_CHECK(isc_mutex_init(&dyndb_lock) == ISC_R_SUCCESS); + 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, flags; + + 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); + + flags = RTLD_NOW|RTLD_LOCAL; +#ifdef RTLD_DEEPBIND + flags |= RTLD_DEEPBIND; +#endif + + handle = dlopen(filename, flags); + 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)); + if (imp == NULL) + CHECK(ISC_R_NOMEMORY); + + 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); + if (imp->name == NULL) + CHECK(ISC_R_NOMEMORY); + + 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; + + isc_mem_free(imp->mctx, imp->name); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); + + *impp = NULL; +} +#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)); + if (imp == NULL) + CHECK(ISC_R_NOMEMORY); + + 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); + if (imp->name == NULL) + CHECK(ISC_R_NOMEMORY); + + 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; + + isc_mem_free(imp->mctx, imp->name); + isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); + + *impp = NULL; +} +#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 == true) + 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)); + if (dctx == NULL) + return (ISC_R_NOMEMORY); + + 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->refvar = &isc_bind9; + + 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..cf24d73 --- /dev/null +++ b/lib/dns/ecdb.c @@ -0,0 +1,835 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include "config.h" + +#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; + + /* Locked */ + unsigned int references; + 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; + unsigned int 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, 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 */ + NULL, /* getadditional */ + NULL, /* setadditional */ + NULL, /* putadditional */ + rdataset_settrust, /* settrust */ + NULL, /* expire */ + NULL, /* clearprefetch */ + NULL, /* setownercase */ + NULL /* getownercase */ +}; + +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); + + LOCK(&ecdb->lock); + ecdb->references++; + UNLOCK(&ecdb->lock); + + *targetp = source; +} + +static void +destroy_ecdb(dns_ecdb_t **ecdbp) { + dns_ecdb_t *ecdb = *ecdbp; + isc_mem_t *mctx = ecdb->common.mctx; + + if (dns_name_dynamic(&ecdb->common.origin)) + dns_name_free(&ecdb->common.origin, mctx); + + DESTROYLOCK(&ecdb->lock); + + ecdb->common.impmagic = 0; + ecdb->common.magic = 0; + + isc_mem_putanddetach(&mctx, ecdb, sizeof(*ecdb)); + + *ecdbp = NULL; +} + +static void +detach(dns_db_t **dbp) { + dns_ecdb_t *ecdb; + bool need_destroy = false; + + REQUIRE(dbp != NULL); + ecdb = (dns_ecdb_t *)*dbp; + REQUIRE(VALID_ECDB(ecdb)); + + LOCK(&ecdb->lock); + ecdb->references--; + if (ecdb->references == 0 && ISC_LIST_EMPTY(ecdb->nodes)) + need_destroy = true; + UNLOCK(&ecdb->lock); + + if (need_destroy) + destroy_ecdb(&ecdb); + + *dbp = NULL; +} + +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); + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references++; + INSIST(node->references != 0); /* Catch overflow. */ + UNLOCK(&node->lock); + + *targetp = node; +} + +static void +destroynode(dns_ecdbnode_t *node) { + isc_mem_t *mctx; + dns_ecdb_t *ecdb = node->ecdb; + bool need_destroydb = false; + rdatasetheader_t *header; + + mctx = ecdb->common.mctx; + + LOCK(&ecdb->lock); + ISC_LIST_UNLINK(ecdb->nodes, node, link); + if (ecdb->references == 0 && ISC_LIST_EMPTY(ecdb->nodes)) + need_destroydb = true; + 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); + } + + DESTROYLOCK(&node->lock); + + node->magic = 0; + isc_mem_put(mctx, node, sizeof(*node)); + + if (need_destroydb) + 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; + bool need_destroy = false; + + REQUIRE(VALID_ECDB(ecdb)); + REQUIRE(nodep != NULL); + node = (dns_ecdbnode_t *)*nodep; + REQUIRE(VALID_ECDBNODE(node)); + + UNUSED(ecdb); /* in case REQUIRE() is empty */ + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references--; + if (node->references == 0) + need_destroy = true; + UNLOCK(&node->lock); + + if (need_destroy) + destroynode(node); + + *nodep = NULL; +} + +static isc_result_t +find(dns_db_t *db, 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, dns_name_t *name, + 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(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + return (ISC_R_NOTFOUND); +} + +static isc_result_t +findnode(dns_db_t *db, 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; + isc_result_t result; + + 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)); + if (node == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&node->lock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + isc_mem_put(mctx, node, sizeof(*node)); + return (ISC_R_UNEXPECTED); + } + + dns_name_init(&node->name, NULL); + result = dns_name_dup(name, mctx, &node->name); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&node->lock); + isc_mem_put(mctx, node, sizeof(*node)); + return (result); + } + node->ecdb= ecdb; + node->references = 1; + ISC_LIST_INIT(node->rdatasets); + + ISC_LINK_INIT(node, link); + + 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; + + INSIST(node->references > 0); + 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, + 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)); + if (iterator == NULL) + return (ISC_R_NOMEMORY); + + 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.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 */ +}; + +static isc_result_t +dns_ecdb_create(isc_mem_t *mctx, 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)); + if (ecdb == NULL) + return (ISC_R_NOMEMORY); + + 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); + } + + result = isc_mutex_init(&ecdb->lock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + if (dns_name_dynamic(&ecdb->common.origin)) + dns_name_free(&ecdb->common.origin, mctx); + isc_mem_put(mctx, ecdb, sizeof(*ecdb)); + return (ISC_R_UNEXPECTED); + } + + 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 + raw += 2; +#endif + /* + * 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 + raw += length + 2; +#endif + 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 + raw += 2; +#endif + 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); + *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; + + 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)); + + *iteratorp = NULL; +} + +static isc_result_t +rdatasetiter_first(dns_rdatasetiter_t *iterator) { + ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator; + dns_ecdbnode_t *ecdbnode = (dns_ecdbnode_t *)iterator->node; + + REQUIRE(DNS_RDATASETITER_VALID(iterator)); + + 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) { + ecdb_rdatasetiter_t *ecdbiterator = (ecdb_rdatasetiter_t *)iterator; + + REQUIRE(DNS_RDATASETITER_VALID(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/fixedname.c b/lib/dns/fixedname.c new file mode 100644 index 0000000..3d5de3b --- /dev/null +++ b/lib/dns/fixedname.c @@ -0,0 +1,39 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#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..cf1865f --- /dev/null +++ b/lib/dns/forward.c @@ -0,0 +1,257 @@ +/* + * 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 http://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 + +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(dns_fwdtable_t)); + if (fwdtable == NULL) + return (ISC_R_NOMEMORY); + + fwdtable->table = NULL; + result = dns_rbt_create(mctx, auto_detach, fwdtable, &fwdtable->table); + if (result != ISC_R_SUCCESS) + goto cleanup_fwdtable; + + result = isc_rwlock_init(&fwdtable->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_rbt; + + fwdtable->mctx = NULL; + isc_mem_attach(mctx, &fwdtable->mctx); + fwdtable->magic = FWDTABLEMAGIC; + *fwdtablep = fwdtable; + + return (ISC_R_SUCCESS); + + cleanup_rbt: + dns_rbt_destroy(&fwdtable->table); + + cleanup_fwdtable: + isc_mem_put(mctx, fwdtable, sizeof(dns_fwdtable_t)); + + return (result); +} + +isc_result_t +dns_fwdtable_addfwd(dns_fwdtable_t *fwdtable, 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(dns_forwarders_t)); + if (forwarders == NULL) + return (ISC_R_NOMEMORY); + + 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(dns_forwarder_t)); + if (nfwd == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + *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(isc_sockaddr_t)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(dns_forwarders_t)); + return (result); +} + +isc_result_t +dns_fwdtable_add(dns_fwdtable_t *fwdtable, 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(dns_forwarders_t)); + if (forwarders == NULL) + return (ISC_R_NOMEMORY); + + 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(dns_forwarder_t)); + if (fwd == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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(dns_forwarder_t)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(dns_forwarders_t)); + return (result); +} + +isc_result_t +dns_fwdtable_delete(dns_fwdtable_t *fwdtable, 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, dns_name_t *name, + dns_forwarders_t **forwardersp) +{ + return (dns_fwdtable_find2(fwdtable, name, NULL, forwardersp)); +} + +isc_result_t +dns_fwdtable_find2(dns_fwdtable_t *fwdtable, 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; + isc_mem_t *mctx; + + REQUIRE(fwdtablep != NULL && VALID_FWDTABLE(*fwdtablep)); + + fwdtable = *fwdtablep; + + dns_rbt_destroy(&fwdtable->table); + isc_rwlock_destroy(&fwdtable->rwlock); + fwdtable->magic = 0; + mctx = fwdtable->mctx; + isc_mem_put(mctx, fwdtable, sizeof(dns_fwdtable_t)); + isc_mem_detach(&mctx); + + *fwdtablep = NULL; +} + +/*** + *** 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(dns_forwarder_t)); + } + isc_mem_put(fwdtable->mctx, forwarders, sizeof(dns_forwarders_t)); +} diff --git a/lib/dns/gen-unix.h b/lib/dns/gen-unix.h new file mode 100644 index 0000000..84c3e7f --- /dev/null +++ b/lib/dns/gen-unix.h @@ -0,0 +1,90 @@ +/* + * 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 http://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 /* Required on some systems for dirent.h. */ + +#include +#include +#include /* XXXDCL Required for ?. */ + +#include + +#ifdef NEED_OPTARG +extern char *optarg; +#endif + +#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) { + dirent = readdir(dir->handle); + if (dirent != NULL) + dir->filename = dirent->d_name; + } + + 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..4791032 --- /dev/null +++ b/lib/dns/gen-win32.h @@ -0,0 +1,273 @@ +/* + * 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 http://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 + +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); +} + +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..2a3b94b --- /dev/null +++ b/lib/dns/gen.c @@ -0,0 +1,923 @@ +/* + * 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 http://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 +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include "gen-win32.h" +#else +#include "gen-unix.h" +#endif + +#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 http://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 +#define DIRNAMESIZE 256 + +static struct cc { + struct cc *next; + int rdclass; + char classname[TYPECLASSBUF]; +} *classes; + +static struct tt { + struct tt *next; + int rdclass; + int type; + char classname[TYPECLASSBUF]; + char typename[TYPECLASSBUF]; + char dirname[DIRNAMESIZE]; /* XXX Should be max path length */ +} *types; + +static struct ttnam { + char typename[TYPECLASSBUF]; + char macroname[TYPECLASSBUF]; + char attr[ATTRIBUTESIZE]; + unsigned int sorted; + int 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->typename, buf1), args); + else + fprintf(stdout, + "\t\tcase %d:%s %s_%s_%s(%s); break;", + tt->rdclass, result, function, + funname(tt->classname, buf1), + funname(tt->typename, 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].typename[0] != 0 && + typenames[i].type == type) + return (&typenames[i]); + } + return (NULL); +} + +static void +insert_into_typenames(int type, const char *typename, const char *attr) { + struct ttnam *ttn = NULL; + size_t c; + int i, n; + char tmp[256]; + + INSIST(strlen(typename) < TYPECLASSBUF); + for (i = 0; i < TYPENAMES; i++) { + if (typenames[i].typename[0] != 0 && + typenames[i].type == type && + strcmp(typename, typenames[i].typename) != 0) { + fprintf(stderr, + "Error: type %d has two names: %s, %s\n", + type, typenames[i].typename, typename); + exit(1); + } + if (typenames[i].typename[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(typename) > sizeof(ttn->typename) - 1) { + fprintf(stderr, "Error: type name %s is too long\n", + typename); + exit(1); + } + + strncpy(ttn->typename, typename, sizeof(ttn->typename)); + ttn->typename[sizeof(ttn->typename) - 1] = '\0'; + + strncpy(ttn->macroname, ttn->typename, 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, typename); + 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 *classname, int type, const char *typename, + const char *dirname) +{ + struct tt *newtt = (struct tt *)malloc(sizeof(*newtt)); + struct tt *tt, *oldtt; + struct cc *newcc; + struct cc *cc, *oldcc; + + INSIST(strlen(typename) < TYPECLASSBUF); + INSIST(strlen(classname) < TYPECLASSBUF); + INSIST(strlen(dirname) < DIRNAMESIZE); + + insert_into_typenames(type, typename, NULL); + + if (newtt == NULL) { + fprintf(stderr, "malloc() failed\n"); + exit(1); + } + + newtt->next = NULL; + newtt->rdclass = rdclass; + newtt->type = type; + + strncpy(newtt->classname, classname, sizeof(newtt->classname)); + newtt->classname[sizeof(newtt->classname) - 1] = '\0'; + + strncpy(newtt->typename, typename, sizeof(newtt->typename)); + newtt->typename[sizeof(newtt->typename) - 1] = '\0'; + + if (strncmp(dirname, "./", 2) == 0) + dirname += 2; + strncpy(newtt->dirname, dirname, sizeof(newtt->dirname)); + newtt->dirname[sizeof(newtt->dirname) - 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->typename, typename) != 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->classname, classname, sizeof(newcc->classname)); + newcc->classname[sizeof(newcc->classname) - 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 *classname, const char *dirname, char filetype) { + char buf[TYPECLASSLEN + sizeof("_65535.h")]; + char typename[TYPECLASSBUF]; + int type, n; + isc_dir_t dir; + + if (!start_directory(dirname, &dir)) + return; + + while (next_file(&dir)) { + if (sscanf(dir.filename, TYPECLASSFMT, typename, &type) != 2) + continue; + if ((type > 65535) || (type < 0)) + continue; + + n = snprintf(buf, sizeof(buf), "%s_%d.%c", typename, + type, filetype); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf, dir.filename) != 0) + continue; + add(rdclass, classname, type, typename, dirname); + } + + 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[DIRNAMESIZE]; /* XXX Should be max path length */ + char srcdir[DIRNAMESIZE]; /* XXX Should be max path length */ + int rdclass; + char classname[TYPECLASSBUF]; + struct tt *tt; + struct cc *cc; + struct ttnam *ttn, *ttn2; + unsigned int hash; + struct tm *tm; + 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; + 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) > + DIRNAMESIZE - 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, classname, + &rdclass) != 2) + continue; + if ((rdclass > 65535) || (rdclass < 0)) + continue; + + n = snprintf(buf, sizeof(buf), "%srdata/%s_%d", + srcdir, classname, rdclass); + INSIST(n > 0 && (unsigned)n < sizeof(buf)); + if (strcmp(buf + 6 + strlen(srcdir), dir.filename) != 0) + continue; + sd(rdclass, classname, buf, filetype); + } + end_directory(&dir); + n = snprintf(buf, sizeof(buf), "%srdata/generic", srcdir); + INSIST(n > 0 && (unsigned)n < sizeof(srcdir)); + sd(0, "", buf, filetype); + + if (time(&now) != -1) { + if ((tm = localtime(&now)) != 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\n", stdout); + fputs("#include \n\n", stdout); + + for (tt = types; tt != NULL; tt = tt->next) + fprintf(stdout, "#include \"%s/%s_%d.c\"\n", + tt->dirname, tt->typename, 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 RESERVED "DNS_RDATATYPEATTR_RESERVED" + + /* + * Add in reserved/special types. This will let us + * sort them without special cases. + */ + insert_into_typenames(0, "reserved0", RESERVED); + insert_into_typenames(31, "eid", RESERVED); + insert_into_typenames(32, "nimloc", RESERVED); + insert_into_typenames(34, "atma", RESERVED); + insert_into_typenames(100, "uinfo", RESERVED); + insert_into_typenames(101, "uid", RESERVED); + insert_into_typenames(102, "gid", RESERVED); + 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->typename); + 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->typename)) { + fprintf(stdout, "\t\t\tRDATATYPE_COMPARE" + "(\"%s\", %d, " + "_typename, _length, _typep); \\\n", + ttn2->typename, 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->typename)); + } + 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->typename, 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->typename, 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->classname, 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->dirname, tt->typename, 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->dirname, tt->typename, tt->type); + } + + if (ferror(stdout) != 0) + exit(1); + + return (0); +} diff --git a/lib/dns/geoip.c b/lib/dns/geoip.c new file mode 100644 index 0000000..ec20baa --- /dev/null +++ b/lib/dns/geoip.c @@ -0,0 +1,881 @@ +/* + * 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 http://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 +#ifndef WIN32 +#include +#else +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ +#endif +#include +#endif /* WIN32 */ +#include + +#ifdef HAVE_GEOIP +#include +#include + +/* + * This structure preserves state from the previous GeoIP lookup, + * so that successive lookups for the same data from the same IP + * address will not require repeated calls into the GeoIP library + * to look up data in the database. This should improve performance + * somewhat. + * + * For lookups in the City and Region databases, we preserve pointers + * to the GeoIPRecord and GeoIPregion structures; these will need to be + * freed by GeoIPRecord_delete() and GeoIPRegion_delete(). + * + * for lookups in ISP, AS, Org and Domain we prserve a pointer to + * the returned name; these must be freed by free(). + * + * For lookups in Country we preserve a pointer to the text of + * the country code, name, etc (we use a different pointer for this + * than for the names returned by Org, ISP, etc, because those need + * to be freed but country lookups do not). + * + * For lookups in Netspeed we preserve the returned ID. + * + * XXX: Currently this mechanism is only used for IPv4 lookups; the + * family and addr6 fields are to be used IPv6 is added. + */ +typedef struct geoip_state { + uint16_t subtype; + unsigned int family; + uint32_t ipnum; + geoipv6_t ipnum6; + uint8_t scope; + GeoIPRecord *record; + GeoIPRegion *region; + const char *text; + char *name; + int id; + isc_mem_t *mctx; +} geoip_state_t; + +#ifdef ISC_PLATFORM_USETHREADS +static isc_mutex_t key_mutex; +static bool state_key_initialized = false; +static isc_thread_key_t state_key; +static isc_once_t mutex_once = ISC_ONCE_INIT; +static isc_mem_t *state_mctx = NULL; + +static void +key_mutex_init(void) { + RUNTIME_CHECK(isc_mutex_init(&key_mutex) == ISC_R_SUCCESS); +} + +static void +free_state(void *arg) { + geoip_state_t *state = arg; + if (state != NULL && state->record != NULL) + GeoIPRecord_delete(state->record); + if (state != NULL) + isc_mem_putanddetach(&state->mctx, + state, sizeof(geoip_state_t)); + isc_thread_key_setspecific(state_key, NULL); +} + +static isc_result_t +state_key_init(void) { + isc_result_t result; + + result = isc_once_do(&mutex_once, key_mutex_init); + if (result != ISC_R_SUCCESS) + return (result); + + if (!state_key_initialized) { + LOCK(&key_mutex); + if (!state_key_initialized) { + int ret; + + if (state_mctx == NULL) + result = isc_mem_create2(0, 0, &state_mctx, 0); + if (result != ISC_R_SUCCESS) + goto unlock; + isc_mem_setname(state_mctx, "geoip_state", NULL); + isc_mem_setdestroycheck(state_mctx, false); + + ret = isc_thread_key_create(&state_key, free_state); + if (ret == 0) + state_key_initialized = true; + else + result = ISC_R_FAILURE; + } + unlock: + UNLOCK(&key_mutex); + } + + return (result); +} +#else +static geoip_state_t saved_state; +#endif + +static void +clean_state(geoip_state_t *state) { + if (state == NULL) + return; + + if (state->record != NULL) { + GeoIPRecord_delete(state->record); + state->record = NULL; + } + if (state->region != NULL) { + GeoIPRegion_delete(state->region); + state->region = NULL; + } + if (state->name != NULL) { + free (state->name); + state->name = NULL; + } + state->ipnum = 0; + state->text = NULL; + state->id = 0; +} + +static isc_result_t +set_state(unsigned int family, uint32_t ipnum, const geoipv6_t *ipnum6, + uint8_t scope, dns_geoip_subtype_t subtype, GeoIPRecord *record, + GeoIPRegion *region, char *name, const char *text, int id) +{ + geoip_state_t *state = NULL; +#ifdef ISC_PLATFORM_USETHREADS + isc_result_t result; + + result = state_key_init(); + if (result != ISC_R_SUCCESS) + return (result); + + state = (geoip_state_t *) isc_thread_key_getspecific(state_key); + if (state == NULL) { + state = (geoip_state_t *) isc_mem_get(state_mctx, + sizeof(geoip_state_t)); + if (state == NULL) + return (ISC_R_NOMEMORY); + memset(state, 0, sizeof(*state)); + + result = isc_thread_key_setspecific(state_key, state); + if (result != ISC_R_SUCCESS) { + isc_mem_put(state_mctx, state, sizeof(geoip_state_t)); + return (result); + } + + isc_mem_attach(state_mctx, &state->mctx); + } else + clean_state(state); +#else + state = &saved_state; + clean_state(state); +#endif + + if (family == AF_INET) { + state->ipnum = ipnum; + } else { + INSIST(ipnum6 != NULL); + state->ipnum6 = *ipnum6; + } + + state->family = family; + state->subtype = subtype; + state->scope = scope; + state->record = record; + state->region = region; + state->name = name; + state->text = text; + state->id = id; + + return (ISC_R_SUCCESS); +} + +static geoip_state_t * +get_state_for(unsigned int family, uint32_t ipnum, + const geoipv6_t *ipnum6) +{ + geoip_state_t *state; + +#ifdef ISC_PLATFORM_USETHREADS + isc_result_t result; + + result = state_key_init(); + if (result != ISC_R_SUCCESS) + return (NULL); + + state = (geoip_state_t *) isc_thread_key_getspecific(state_key); + if (state == NULL) + return (NULL); +#else + state = &saved_state; +#endif + + if (state->family == family && + ((state->family == AF_INET && state->ipnum == ipnum) || + (state->family == AF_INET6 && ipnum6 != NULL && + memcmp(state->ipnum6.s6_addr, ipnum6->s6_addr, 16) == 0))) + return (state); + + return (NULL); +} + +/* + * Country lookups are performed if the previous lookup was from a + * different IP address than the current, or was for a search of a + * different subtype. + */ +static const char * +country_lookup(GeoIP *db, dns_geoip_subtype_t subtype, + unsigned int family, + uint32_t ipnum, const geoipv6_t *ipnum6, + uint8_t *scope) +{ + geoip_state_t *prev_state = NULL; + const char *text = NULL; + GeoIPLookup gl; + + REQUIRE(db != NULL); + +#ifndef HAVE_GEOIP_V6 + /* no IPv6 support? give up now */ + if (family == AF_INET6) + return (NULL); +#endif + + prev_state = get_state_for(family, ipnum, ipnum6); + if (prev_state != NULL && prev_state->subtype == subtype) { + text = prev_state->text; + if (scope != NULL) + *scope = prev_state->scope; + } + + if (text == NULL) { + switch (subtype) { + case dns_geoip_country_code: + if (family == AF_INET) + text = GeoIP_country_code_by_ipnum_gl(db, + ipnum, &gl); +#ifdef HAVE_GEOIP_V6 + else + text = GeoIP_country_code_by_ipnum_v6_gl(db, + *ipnum6, &gl); +#endif + break; + case dns_geoip_country_code3: + if (family == AF_INET) + text = GeoIP_country_code3_by_ipnum_gl(db, + ipnum, &gl); +#ifdef HAVE_GEOIP_V6 + else + text = GeoIP_country_code3_by_ipnum_v6_gl(db, + *ipnum6, &gl); +#endif + break; + case dns_geoip_country_name: + if (family == AF_INET) + text = GeoIP_country_name_by_ipnum_gl(db, + ipnum, &gl); +#ifdef HAVE_GEOIP_V6 + else + text = GeoIP_country_name_by_ipnum_v6_gl(db, + *ipnum6, &gl); +#endif + break; + default: + INSIST(0); + } + + if (text == NULL) + return (NULL); + + if (scope != NULL) + *scope = gl.netmask; + + set_state(family, ipnum, ipnum6, gl.netmask, subtype, + NULL, NULL, NULL, text, 0); + } + + return (text); +} + +static char * +city_string(GeoIPRecord *record, dns_geoip_subtype_t subtype, int *maxlen) { + const char *s; + char *deconst; + + REQUIRE(record != NULL); + REQUIRE(maxlen != NULL); + + /* Set '*maxlen' to the maximum length of this subtype, if any */ + switch (subtype) { + case dns_geoip_city_countrycode: + case dns_geoip_city_region: + case dns_geoip_city_continentcode: + *maxlen = 2; + break; + + case dns_geoip_city_countrycode3: + *maxlen = 3; + break; + + default: + /* No fixed length; just use strcasecmp() for comparison */ + *maxlen = 255; + } + + switch (subtype) { + case dns_geoip_city_countrycode: + return (record->country_code); + case dns_geoip_city_countrycode3: + return (record->country_code3); + case dns_geoip_city_countryname: + return (record->country_name); + case dns_geoip_city_region: + return (record->region); + case dns_geoip_city_regionname: + s = GeoIP_region_name_by_code(record->country_code, + record->region); + DE_CONST(s, deconst); + return (deconst); + case dns_geoip_city_name: + return (record->city); + case dns_geoip_city_postalcode: + return (record->postal_code); + case dns_geoip_city_continentcode: + return (record->continent_code); + case dns_geoip_city_timezonecode: + s = GeoIP_time_zone_by_country_and_region(record->country_code, + record->region); + DE_CONST(s, deconst); + return (deconst); + default: + INSIST(0); + } +} + +static bool +is_city(dns_geoip_subtype_t subtype) { + switch (subtype) { + case dns_geoip_city_countrycode: + case dns_geoip_city_countrycode3: + case dns_geoip_city_countryname: + case dns_geoip_city_region: + case dns_geoip_city_regionname: + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_continentcode: + case dns_geoip_city_timezonecode: + case dns_geoip_city_metrocode: + case dns_geoip_city_areacode: + return (true); + default: + return (false); + } +} + +/* + * GeoIPRecord lookups are performed if the previous lookup was + * from a different IP address than the current, or was for a search + * outside the City database. + */ +static GeoIPRecord * +city_lookup(GeoIP *db, dns_geoip_subtype_t subtype, + unsigned int family, uint32_t ipnum, + const geoipv6_t *ipnum6, + uint8_t *scope) +{ + GeoIPRecord *record = NULL; + geoip_state_t *prev_state = NULL; + + REQUIRE(db != NULL); + +#ifndef HAVE_GEOIP_V6 + /* no IPv6 support? give up now */ + if (family == AF_INET6) + return (NULL); +#endif + + prev_state = get_state_for(family, ipnum, ipnum6); + if (prev_state != NULL && is_city(prev_state->subtype)) { + record = prev_state->record; + if (scope != NULL) + *scope = record->netmask; + } + + if (record == NULL) { + if (family == AF_INET) + record = GeoIP_record_by_ipnum(db, ipnum); +#ifdef HAVE_GEOIP_V6 + else + record = GeoIP_record_by_ipnum_v6(db, *ipnum6); +#endif + if (record == NULL) + return (NULL); + + if (scope != NULL) + *scope = record->netmask; + + set_state(family, ipnum, ipnum6, record->netmask, subtype, + record, NULL, NULL, NULL, 0); + } + + return (record); +} + +static char * region_string(GeoIPRegion *region, dns_geoip_subtype_t subtype, int *maxlen) { + const char *s; + char *deconst; + + REQUIRE(region != NULL); + REQUIRE(maxlen != NULL); + + switch (subtype) { + case dns_geoip_region_countrycode: + *maxlen = 2; + return (region->country_code); + case dns_geoip_region_code: + *maxlen = 2; + return (region->region); + case dns_geoip_region_name: + *maxlen = 255; + s = GeoIP_region_name_by_code(region->country_code, + region->region); + DE_CONST(s, deconst); + return (deconst); + default: + INSIST(0); + } +} + +static bool +is_region(dns_geoip_subtype_t subtype) { + switch (subtype) { + case dns_geoip_region_countrycode: + case dns_geoip_region_code: + return (true); + default: + return (false); + } +} + +/* + * GeoIPRegion lookups are performed if the previous lookup was + * from a different IP address than the current, or was for a search + * outside the Region database. + */ +static GeoIPRegion * +region_lookup(GeoIP *db, dns_geoip_subtype_t subtype, + uint32_t ipnum, uint8_t *scope) +{ + GeoIPRegion *region = NULL; + geoip_state_t *prev_state = NULL; + GeoIPLookup gl; + + REQUIRE(db != NULL); + + prev_state = get_state_for(AF_INET, ipnum, NULL); + if (prev_state != NULL && is_region(prev_state->subtype)) { + region = prev_state->region; + if (scope != NULL) + *scope = prev_state->scope; + } + + if (region == NULL) { + region = GeoIP_region_by_ipnum_gl(db, ipnum, &gl); + if (region == NULL) + return (NULL); + + if (scope != NULL) + *scope = gl.netmask; + + set_state(AF_INET, ipnum, NULL, gl.netmask, + subtype, NULL, region, NULL, NULL, 0); + } + + return (region); +} + +/* + * ISP, Organization, AS Number and Domain lookups are performed if + * the previous lookup was from a different IP address than the current, + * or was for a search of a different subtype. + */ +static char * +name_lookup(GeoIP *db, dns_geoip_subtype_t subtype, + uint32_t ipnum, uint8_t *scope) +{ + char *name = NULL; + geoip_state_t *prev_state = NULL; + GeoIPLookup gl; + + REQUIRE(db != NULL); + + prev_state = get_state_for(AF_INET, ipnum, NULL); + if (prev_state != NULL && prev_state->subtype == subtype) { + name = prev_state->name; + if (scope != NULL) + *scope = prev_state->scope; + } + + if (name == NULL) { + name = GeoIP_name_by_ipnum_gl(db, ipnum, &gl); + if (name == NULL) + return (NULL); + + if (scope != NULL) + *scope = gl.netmask; + + set_state(AF_INET, ipnum, NULL, gl.netmask, + subtype, NULL, NULL, name, NULL, 0); + } + + return (name); +} + +/* + * Netspeed lookups are performed if the previous lookup was from a + * different IP address than the current, or was for a search of a + * different subtype. + */ +static int +netspeed_lookup(GeoIP *db, dns_geoip_subtype_t subtype, + uint32_t ipnum, uint8_t *scope) +{ + geoip_state_t *prev_state = NULL; + bool found = false; + GeoIPLookup gl; + int id = -1; + + REQUIRE(db != NULL); + + prev_state = get_state_for(AF_INET, ipnum, NULL); + if (prev_state != NULL && prev_state->subtype == subtype) { + id = prev_state->id; + if (scope != NULL) + *scope = prev_state->scope; + found = true; + } + + if (!found) { + id = GeoIP_id_by_ipnum_gl(db, ipnum, &gl); + if (id == 0) + return (0); + + if (scope != NULL) + *scope = gl.netmask; + + set_state(AF_INET, ipnum, NULL, gl.netmask, + subtype, NULL, NULL, NULL, NULL, id); + } + + return (id); +} +#endif /* HAVE_GEOIP */ + +#define DB46(addr, geoip, name) \ + ((addr->family == AF_INET) ? (geoip->name##_v4) : (geoip->name##_v6)) + +#ifdef HAVE_GEOIP +/* + * Find the best database to answer a generic subtype + */ +static dns_geoip_subtype_t +fix_subtype(const isc_netaddr_t *reqaddr, const dns_geoip_databases_t *geoip, + dns_geoip_subtype_t subtype) +{ + dns_geoip_subtype_t ret = subtype; + + switch (subtype) { + case dns_geoip_countrycode: + if (DB46(reqaddr, geoip, city) != NULL) + ret = dns_geoip_city_countrycode; + else if (reqaddr->family == AF_INET && geoip->region != NULL) + ret = dns_geoip_region_countrycode; + else if (DB46(reqaddr, geoip, country) != NULL) + ret = dns_geoip_country_code; + break; + case dns_geoip_countrycode3: + if (DB46(reqaddr, geoip, city) != NULL) + ret = dns_geoip_city_countrycode3; + else if (DB46(reqaddr, geoip, country) != NULL) + ret = dns_geoip_country_code3; + break; + case dns_geoip_countryname: + if (DB46(reqaddr, geoip, city) != NULL) + ret = dns_geoip_city_countryname; + else if (DB46(reqaddr, geoip, country) != NULL) + ret = dns_geoip_country_name; + break; + case dns_geoip_region: + if (DB46(reqaddr, geoip, city) != NULL) + ret = dns_geoip_city_region; + else if (reqaddr->family == AF_INET && geoip->region != NULL) + ret = dns_geoip_region_code; + break; + case dns_geoip_regionname: + if (DB46(reqaddr, geoip, city) != NULL) + ret = dns_geoip_city_regionname; + else if (reqaddr->family == AF_INET && geoip->region != NULL) + ret = dns_geoip_region_name; + break; + default: + break; + } + + return (ret); +} +#endif /* HAVE_GEOIP */ + +bool +dns_geoip_match(const isc_netaddr_t *reqaddr, uint8_t *scope, + const dns_geoip_databases_t *geoip, + const dns_geoip_elem_t *elt) +{ +#ifndef HAVE_GEOIP + UNUSED(reqaddr); + UNUSED(geoip); + UNUSED(elt); + + return (false); +#else + GeoIP *db; + GeoIPRecord *record; + GeoIPRegion *region; + dns_geoip_subtype_t subtype; + uint32_t ipnum = 0; + int maxlen = 0, id, family; + const char *cs; + char *s; +#ifdef HAVE_GEOIP_V6 + const geoipv6_t *ipnum6 = NULL; +#else + const void *ipnum6 = NULL; +#endif + + INSIST(geoip != NULL); + + family = reqaddr->family; + switch (family) { + case AF_INET: + ipnum = ntohl(reqaddr->type.in.s_addr); + break; + case AF_INET6: +#ifdef HAVE_GEOIP_V6 + ipnum6 = &reqaddr->type.in6; + break; +#else + return (false); +#endif + default: + return (false); + } + + subtype = fix_subtype(reqaddr, geoip, elt->subtype); + + switch (subtype) { + case dns_geoip_country_code: + maxlen = 2; + goto getcountry; + + case dns_geoip_country_code3: + maxlen = 3; + goto getcountry; + + case dns_geoip_country_name: + maxlen = 255; + getcountry: + db = DB46(reqaddr, geoip, country); + if (db == NULL) + return (false); + + INSIST(elt->as_string != NULL); + + cs = country_lookup(db, subtype, family, ipnum, ipnum6, scope); + if (cs != NULL && strncasecmp(elt->as_string, cs, maxlen) == 0) + return (true); + break; + + case dns_geoip_city_countrycode: + case dns_geoip_city_countrycode3: + case dns_geoip_city_countryname: + case dns_geoip_city_region: + case dns_geoip_city_regionname: + case dns_geoip_city_name: + case dns_geoip_city_postalcode: + case dns_geoip_city_continentcode: + case dns_geoip_city_timezonecode: + INSIST(elt->as_string != NULL); + + db = DB46(reqaddr, geoip, city); + if (db == NULL) + return (false); + + record = city_lookup(db, subtype, family, + ipnum, ipnum6, scope); + if (record == NULL) + break; + + s = city_string(record, subtype, &maxlen); + INSIST(maxlen != 0); + if (s != NULL && strncasecmp(elt->as_string, s, maxlen) == 0) + return (true); + break; + + case dns_geoip_city_metrocode: + db = DB46(reqaddr, geoip, city); + if (db == NULL) + return (false); + + record = city_lookup(db, subtype, family, + ipnum, ipnum6, scope); + if (record == NULL) + break; + + if (elt->as_int == record->metro_code) + return (true); + break; + + case dns_geoip_city_areacode: + db = DB46(reqaddr, geoip, city); + if (db == NULL) + return (false); + + record = city_lookup(db, subtype, family, + ipnum, ipnum6, scope); + if (record == NULL) + break; + + if (elt->as_int == record->area_code) + return (true); + break; + + case dns_geoip_region_countrycode: + case dns_geoip_region_code: + case dns_geoip_region_name: + case dns_geoip_region: + if (geoip->region == NULL) + return (false); + + INSIST(elt->as_string != NULL); + + /* Region DB is not supported for IPv6 */ + if (family == AF_INET6) + return (false); + + region = region_lookup(geoip->region, subtype, ipnum, scope); + if (region == NULL) + break; + + s = region_string(region, subtype, &maxlen); + INSIST(maxlen != 0); + if (s != NULL && strncasecmp(elt->as_string, s, maxlen) == 0) + return (true); + break; + + case dns_geoip_isp_name: + db = geoip->isp; + goto getname; + + case dns_geoip_org_name: + db = geoip->org; + goto getname; + + case dns_geoip_as_asnum: + db = geoip->as; + goto getname; + + case dns_geoip_domain_name: + db = geoip->domain; + + getname: + if (db == NULL) + return (false); + + INSIST(elt->as_string != NULL); + /* ISP, Org, AS, and Domain are not supported for IPv6 */ + if (family == AF_INET6) + return (false); + + s = name_lookup(db, subtype, ipnum, scope); + if (s != NULL) { + size_t l; + if (strcasecmp(elt->as_string, s) == 0) + return (true); + if (subtype != dns_geoip_as_asnum) + break; + /* + * Just check if the ASNNNN value matches. + */ + l = strlen(elt->as_string); + if (l > 0U && strchr(elt->as_string, ' ') == NULL && + strncasecmp(elt->as_string, s, l) == 0 && + s[l] == ' ') + return (true); + } + break; + + case dns_geoip_netspeed_id: + INSIST(geoip->netspeed != NULL); + + /* Netspeed DB is not supported for IPv6 */ + if (family == AF_INET6) + return (false); + + id = netspeed_lookup(geoip->netspeed, subtype, ipnum, scope); + if (id == elt->as_int) + return (true); + break; + + case dns_geoip_countrycode: + case dns_geoip_countrycode3: + case dns_geoip_countryname: + case dns_geoip_regionname: + /* + * If these were not remapped by fix_subtype(), + * the database was unavailable. Always return false. + */ + break; + + default: + INSIST(0); + } + + return (false); +#endif +} + +void +dns_geoip_shutdown(void) { +#ifdef HAVE_GEOIP + GeoIP_cleanup(); +#ifdef ISC_PLATFORM_USETHREADS + if (state_mctx != NULL) + isc_mem_detach(&state_mctx); +#endif +#else + return; +#endif +} diff --git a/lib/dns/gssapi_link.c b/lib/dns/gssapi_link.c new file mode 100644 index 0000000..82eac95 --- /dev/null +++ b/lib/dns/gssapi_link.c @@ -0,0 +1,388 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#ifdef GSSAPI + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" + +#include + +#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; + isc_result_t result; + + UNUSED(key); + + ctx = isc_mem_get(dctx->mctx, sizeof(dst_gssapi_signverifyctx_t)); + if (ctx == NULL) + return (ISC_R_NOMEMORY); + ctx->buffer = NULL; + result = isc_buffer_allocate(dctx->mctx, &ctx->buffer, + INITIAL_BUFFER_SIZE); + if (result != ISC_R_SUCCESS) { + isc_mem_put(dctx->mctx, ctx, sizeof(dst_gssapi_signverifyctx_t)); + return (result); + } + + 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; + + result = isc_buffer_allocate(dctx->mctx, &newbuffer, length); + if (result != ISC_R_SUCCESS) + return (result); + + 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, r; + gss_buffer_desc gmessage, gsig; + OM_uint32 minor, gret; + gss_ctx_id_t gssctx = dctx->key->keydata.gssctx; + unsigned char *buf; + 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); + + /* + * XXXMLG + * It seem that gss_verify_mic() modifies the signature buffer, + * at least on Heimdal's implementation. Copy it here to an allocated + * buffer. + */ + buf = isc_mem_allocate(dst__memory_pool, sig->length); + if (buf == NULL) + return (ISC_R_FAILURE); + memmove(buf, sig->base, sig->length); + r.base = buf; + r.length = sig->length; + REGION_TO_GBUFFER(r, gsig); + + /* + * Verify the data. + */ + gret = gss_verify_mic(&minor, gssctx, &gmessage, &gsig, NULL); + + isc_mem_free(dst__memory_pool, buf); + + /* + * 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; + + result = isc_buffer_allocate(key->mctx, &b, len); + if (result != ISC_R_SUCCESS) + return (result); + + 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, + &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, &key->keydata.gssctx, + &gssbuffer); + if (major != GSS_S_COMPLETE) { + fprintf(stderr, "gss_export_sec_context -> %d, %d\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); + if (buf == NULL) { + gss_release_buffer(&minor, &gssbuffer); + return (ISC_R_NOMEMORY); + } + 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 +int gssapi_link_unneeded = 1; +#endif + +/*! \file */ diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c new file mode 100644 index 0000000..8bd99af --- /dev/null +++ b/lib/dns/gssapictx.c @@ -0,0 +1,894 @@ +/* + * 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 http://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 "dst_internal.h" + +/* + * If we're using our own SPNEGO implementation (see configure.in), + * pull it in now. Otherwise, we just use whatever GSSAPI supplies. + */ +#if defined(GSSAPI) && defined(USE_ISC_SPNEGO) +#include "spnego.h" +#define gss_accept_sec_context gss_accept_sec_context_spnego +#define gss_init_sec_context gss_init_sec_context_spnego +#endif + +/* + * 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 +#include ISC_PLATFORM_KRB5HEADER +#endif + +static unsigned char krb5_mech_oid_bytes[] = { + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 +}; + +#ifndef USE_ISC_SPNEGO +static unsigned char spnego_mech_oid_bytes[] = { + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 +}; +#endif + +static gss_OID_desc mech_oid_set_array[] = { + { sizeof(krb5_mech_oid_bytes), krb5_mech_oid_bytes }, +#ifndef USE_ISC_SPNEGO + { sizeof(spnego_mech_oid_bytes), spnego_mech_oid_bytes }, +#endif +}; + +static gss_OID_set_desc mech_oid_set = { + sizeof(mech_oid_set_array) / sizeof(*mech_oid_set_array), + mech_oid_set_array +}; + +#endif + +#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) + +#ifdef GSSAPI +static inline void +name_to_gbuffer(dns_name_t *name, isc_buffer_t *buffer, + gss_buffer_desc *gbuffer) +{ + dns_name_t tname, *namep; + isc_region_t r; + isc_result_t result; + + if (!dns_name_isabsolute(name)) + namep = name; + else + { + unsigned int labels; + dns_name_init(&tname, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 0, labels - 1, &tname); + namep = &tname; + } + + result = dns_name_toprincipal(namep, buffer); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_buffer_putuint8(buffer, 0); + isc_buffer_usedregion(buffer, &r); + REGION_TO_GBUFFER(r, *gbuffer); +} + +static void +log_cred(const gss_cred_id_t cred) { + OM_uint32 gret, minor, lifetime; + gss_name_t gname; + gss_buffer_desc gbuffer; + gss_cred_usage_t usage; + const char *usage_text; + char buf[1024]; + + gret = gss_inquire_cred(&minor, cred, &gname, &lifetime, &usage, NULL); + if (gret != GSS_S_COMPLETE) { + gss_log(3, "failed gss_inquire_cred: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + return; + } + + gret = gss_display_name(&minor, gname, &gbuffer, NULL); + if (gret != GSS_S_COMPLETE) + gss_log(3, "failed gss_display_name: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + else { + switch (usage) { + case GSS_C_BOTH: + usage_text = "GSS_C_BOTH"; + break; + case GSS_C_INITIATE: + usage_text = "GSS_C_INITIATE"; + break; + case GSS_C_ACCEPT: + usage_text = "GSS_C_ACCEPT"; + break; + default: + usage_text = "???"; + } + gss_log(3, "gss cred: \"%s\", %s, %lu", (char *)gbuffer.value, + usage_text, (unsigned long)lifetime); + } + + if (gret == GSS_S_COMPLETE) { + if (gbuffer.length != 0U) { + gret = gss_release_buffer(&minor, &gbuffer); + if (gret != GSS_S_COMPLETE) + gss_log(3, "failed gss_release_buffer: %s", + gss_error_tostring(gret, minor, buf, + sizeof(buf))); + } + } + + gret = gss_release_name(&minor, &gname); + if (gret != GSS_S_COMPLETE) + gss_log(3, "failed gss_release_name: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); +} +#endif + +#ifdef GSSAPI +/* + * 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); +} +#endif + +isc_result_t +dst_gssapi_acquirecred(dns_name_t *name, bool initiate, + gss_cred_id_t *cred) +{ +#ifdef GSSAPI + 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]; + + 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 = gss_acquire_cred(&minor, gname, GSS_C_INDEFINITE, + &mech_oid_set, usage, 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: + 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); +#else + REQUIRE(cred != NULL && *cred == NULL); + + UNUSED(name); + UNUSED(initiate); + UNUSED(cred); + + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +bool +dst_gssapi_identitymatchesrealmkrb5(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, + bool subdomain) +{ +#ifdef GSSAPI + 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); +#else + UNUSED(signer); + UNUSED(name); + UNUSED(realm); + UNUSED(subdomain); + return (false); +#endif +} + +bool +dst_gssapi_identitymatchesrealmms(const dns_name_t *signer, + const dns_name_t *name, + const dns_name_t *realm, + bool subdomain) +{ +#ifdef GSSAPI + 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'; + sname = sbuf; + + 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); +#else + UNUSED(signer); + UNUSED(name); + UNUSED(realm); + UNUSED(subdomain); + return (false); +#endif +} + +isc_result_t +dst_gssapi_releasecred(gss_cred_id_t *cred) { +#ifdef GSSAPI + OM_uint32 gret, minor; + char buf[1024]; + + REQUIRE(cred != NULL && *cred != NULL); + + gret = gss_release_cred(&minor, 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); +#else + UNUSED(cred); + + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +#ifdef GSSAPI +/* + * 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); +} +#endif + +isc_result_t +dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken, + isc_buffer_t *outtoken, gss_ctx_id_t *gssctx, + isc_mem_t *mctx, char **err_message) +{ +#ifdef GSSAPI + 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, 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); +#else + UNUSED(name); + UNUSED(intoken); + UNUSED(outtoken); + UNUSED(gssctx); + UNUSED(mctx); + UNUSED(err_message); + + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +isc_result_t +dst_gssapi_acceptctx(gss_cred_id_t cred, + const char *gssapi_keytab, + isc_region_t *intoken, isc_buffer_t **outtoken, + gss_ctx_id_t *ctxout, dns_name_t *principal, + isc_mem_t *mctx) +{ +#ifdef GSSAPI + 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 + /* + * Minimize memory leakage by only setting KRB5_KTNAME + * if it needs to change. + */ + const char *old = getenv("KRB5_KTNAME"); + if (old == NULL || strcmp(old, gssapi_keytab) != 0) { + size_t size; + char *kt; + + size = strlen(gssapi_keytab) + 13; + kt = malloc(size); + if (kt == NULL) + return (ISC_R_NOMEMORY); + snprintf(kt, size, "KRB5_KTNAME=%s", gssapi_keytab); + if (putenv(kt) != 0) + return (ISC_R_NOMEMORY); + } +#endif + } + + log_cred(cred); + + gret = gss_accept_sec_context(&minor, &context, cred, &gintoken, + GSS_C_NO_CHANNEL_BINDINGS, &gname, + NULL, &gouttoken, NULL, NULL, NULL); + + result = ISC_R_FAILURE; + + switch (gret) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + break; + case GSS_S_DEFECTIVE_TOKEN: + case GSS_S_DEFECTIVE_CREDENTIAL: + case GSS_S_BAD_SIG: + case GSS_S_DUPLICATE_TOKEN: + case GSS_S_OLD_TOKEN: + case GSS_S_NO_CRED: + case GSS_S_CREDENTIALS_EXPIRED: + case GSS_S_BAD_BINDINGS: + case GSS_S_NO_CONTEXT: + case GSS_S_BAD_MECH: + case GSS_S_FAILURE: + result = DNS_R_INVALIDTKEY; + /* fall through */ + default: + gss_log(3, "failed gss_accept_sec_context: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); + return (result); + } + + if (gouttoken.length > 0U) { + RETERR(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); +#else + UNUSED(cred); + UNUSED(gssapi_keytab); + UNUSED(intoken); + UNUSED(outtoken); + UNUSED(ctxout); + UNUSED(principal); + UNUSED(mctx); + + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +isc_result_t +dst_gssapi_deletectx(isc_mem_t *mctx, gss_ctx_id_t *gssctx) +{ +#ifdef GSSAPI + 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, 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); +#else + UNUSED(mctx); + UNUSED(gssctx); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +char * +gss_error_tostring(uint32_t major, uint32_t minor, + char *buf, size_t buflen) { +#ifdef GSSAPI + 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 + snprintf(buf, buflen, "GSSAPI error: Major = %u, Minor = %u.", + major, minor); + + return (buf); +#endif +} + +void +gss_log(int level, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_TKEY, ISC_LOG_DEBUG(level), fmt, ap); + va_end(ap); +} + +/*! \file */ diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c new file mode 100644 index 0000000..94e73b1 --- /dev/null +++ b/lib/dns/hmac_link.c @@ -0,0 +1,1811 @@ +/* + * Portions 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 http://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. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#ifdef HAVE_FIPS_MODE +#include "dst_openssl.h" /* FIPS_mode() prototype */ +#endif +#include "dst_parse.h" + +#ifndef PK11_MD5_DISABLE +static isc_result_t hmacmd5_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacmd5_key { + unsigned char key[ISC_MD5_BLOCK_LENGTH]; +}; +#endif + +static isc_result_t +getkeybits(dst_key_t *key, struct dst_private_element *element) { + + if (element->length != 2) + return (DST_R_INVALIDPRIVATEKEY); + + key->key_bits = (element->data[0] << 8) + element->data[1]; + + return (ISC_R_SUCCESS); +} + +#ifndef PK11_MD5_DISABLE +static isc_result_t +hmacmd5_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacmd5_t *hmacmd5ctx; + dst_hmacmd5_key_t *hkey = key->keydata.hmacmd5; + + hmacmd5ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacmd5_t)); + if (hmacmd5ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacmd5_init(hmacmd5ctx, hkey->key, ISC_MD5_BLOCK_LENGTH); + dctx->ctxdata.hmacmd5ctx = hmacmd5ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacmd5_destroyctx(dst_context_t *dctx) { + isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; + + if (hmacmd5ctx != NULL) { + isc_hmacmd5_invalidate(hmacmd5ctx); + isc_mem_put(dctx->mctx, hmacmd5ctx, sizeof(isc_hmacmd5_t)); + dctx->ctxdata.hmacmd5ctx = NULL; + } +} + +static isc_result_t +hmacmd5_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; + + isc_hmacmd5_update(hmacmd5ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacmd5_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_MD5_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacmd5_sign(hmacmd5ctx, digest); + isc_buffer_add(sig, ISC_MD5_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacmd5_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacmd5_t *hmacmd5ctx = dctx->ctxdata.hmacmd5ctx; + + if (sig->length > ISC_MD5_DIGESTLENGTH) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacmd5_verify2(hmacmd5ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacmd5_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacmd5_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacmd5; + hkey2 = key2->keydata.hmacmd5; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_MD5_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacmd5_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_MD5_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_MD5_BLOCK_LENGTH) { + bytes = ISC_MD5_BLOCK_LENGTH; + key->key_size = ISC_MD5_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_MD5_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacmd5_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacmd5_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacmd5_destroy(dst_key_t *key) { + dst_hmacmd5_key_t *hkey = key->keydata.hmacmd5; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacmd5 = NULL; +} + +static isc_result_t +hmacmd5_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacmd5_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacmd5 != NULL); + + hkey = key->keydata.hmacmd5; + + 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 +hmacmd5_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacmd5_key_t *hkey; + int keylen; + isc_region_t r; + isc_md5_t md5ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacmd5_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_MD5_BLOCK_LENGTH) { + isc_md5_init(&md5ctx); + isc_md5_update(&md5ctx, r.base, r.length); + isc_md5_final(&md5ctx, hkey->key); + keylen = ISC_MD5_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacmd5 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacmd5_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacmd5_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacmd5 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacmd5; + + priv.elements[cnt].tag = TAG_HMACMD5_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACMD5_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacmd5_parse(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, DST_ALG_HMACMD5, 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: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacmd5_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + case TAG_HMACMD5_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); +} + +static dst_func_t hmacmd5_functions = { + hmacmd5_createctx, + NULL, /*%< createctx2 */ + hmacmd5_destroyctx, + hmacmd5_adddata, + hmacmd5_sign, + hmacmd5_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + hmacmd5_compare, + NULL, /*%< paramcompare */ + hmacmd5_generate, + hmacmd5_isprivate, + hmacmd5_destroy, + hmacmd5_todns, + hmacmd5_fromdns, + hmacmd5_tofile, + hmacmd5_parse, + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__hmacmd5_init(dst_func_t **funcp) { +#ifdef HAVE_FIPS_MODE + /* + * Problems from OpenSSL are likely from FIPS mode + */ + int fips_mode = FIPS_mode(); + + if (fips_mode != 0) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "FIPS mode is %d: MD5 is only supported " + "if the value is 0.\n" + "Please disable either FIPS mode or MD5.", + fips_mode); + } +#endif + + /* + * Prevent use of incorrect crypto + */ + + RUNTIME_CHECK(isc_md5_check(false)); + RUNTIME_CHECK(isc_hmacmd5_check(0)); + + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacmd5_functions; + return (ISC_R_SUCCESS); +} +#endif + +static isc_result_t hmacsha1_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacsha1_key { + unsigned char key[ISC_SHA1_BLOCK_LENGTH]; +}; + +static isc_result_t +hmacsha1_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacsha1_t *hmacsha1ctx; + dst_hmacsha1_key_t *hkey = key->keydata.hmacsha1; + + hmacsha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha1_t)); + if (hmacsha1ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacsha1_init(hmacsha1ctx, hkey->key, ISC_SHA1_BLOCK_LENGTH); + dctx->ctxdata.hmacsha1ctx = hmacsha1ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacsha1_destroyctx(dst_context_t *dctx) { + isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; + + if (hmacsha1ctx != NULL) { + isc_hmacsha1_invalidate(hmacsha1ctx); + isc_mem_put(dctx->mctx, hmacsha1ctx, sizeof(isc_hmacsha1_t)); + dctx->ctxdata.hmacsha1ctx = NULL; + } +} + +static isc_result_t +hmacsha1_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; + + isc_hmacsha1_update(hmacsha1ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha1_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_SHA1_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacsha1_sign(hmacsha1ctx, digest, ISC_SHA1_DIGESTLENGTH); + isc_buffer_add(sig, ISC_SHA1_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha1_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacsha1_t *hmacsha1ctx = dctx->ctxdata.hmacsha1ctx; + + if (sig->length > ISC_SHA1_DIGESTLENGTH || sig->length == 0) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacsha1_verify(hmacsha1ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacsha1_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacsha1_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacsha1; + hkey2 = key2->keydata.hmacsha1; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_SHA1_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacsha1_generate(dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_SHA1_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_SHA1_BLOCK_LENGTH) { + bytes = ISC_SHA1_BLOCK_LENGTH; + key->key_size = ISC_SHA1_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_SHA1_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacsha1_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacsha1_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacsha1_destroy(dst_key_t *key) { + dst_hmacsha1_key_t *hkey = key->keydata.hmacsha1; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacsha1 = NULL; +} + +static isc_result_t +hmacsha1_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha1_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacsha1 != NULL); + + hkey = key->keydata.hmacsha1; + + 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 +hmacsha1_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha1_key_t *hkey; + int keylen; + isc_region_t r; + isc_sha1_t sha1ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha1_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_SHA1_BLOCK_LENGTH) { + isc_sha1_init(&sha1ctx); + isc_sha1_update(&sha1ctx, r.base, r.length); + isc_sha1_final(&sha1ctx, hkey->key); + keylen = ISC_SHA1_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacsha1 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha1_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacsha1_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacsha1 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacsha1; + + priv.elements[cnt].tag = TAG_HMACSHA1_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACSHA1_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacsha1_parse(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, DST_ALG_HMACSHA1, 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_HMACSHA1_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacsha1_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + case TAG_HMACSHA1_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); +} + +static dst_func_t hmacsha1_functions = { + hmacsha1_createctx, + NULL, /*%< createctx2 */ + hmacsha1_destroyctx, + hmacsha1_adddata, + hmacsha1_sign, + hmacsha1_verify, + NULL, /* verify2 */ + NULL, /* computesecret */ + hmacsha1_compare, + NULL, /* paramcompare */ + hmacsha1_generate, + hmacsha1_isprivate, + hmacsha1_destroy, + hmacsha1_todns, + hmacsha1_fromdns, + hmacsha1_tofile, + hmacsha1_parse, + NULL, /* cleanup */ + NULL, /* fromlabel */ + NULL, /* dump */ + NULL, /* restore */ +}; + +isc_result_t +dst__hmacsha1_init(dst_func_t **funcp) { + /* + * Prevent use of incorrect crypto + */ + RUNTIME_CHECK(isc_sha1_check(false)); + RUNTIME_CHECK(isc_hmacsha1_check(0)); + + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacsha1_functions; + return (ISC_R_SUCCESS); +} + +static isc_result_t hmacsha224_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacsha224_key { + unsigned char key[ISC_SHA224_BLOCK_LENGTH]; +}; + +static isc_result_t +hmacsha224_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacsha224_t *hmacsha224ctx; + dst_hmacsha224_key_t *hkey = key->keydata.hmacsha224; + + hmacsha224ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha224_t)); + if (hmacsha224ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacsha224_init(hmacsha224ctx, hkey->key, ISC_SHA224_BLOCK_LENGTH); + dctx->ctxdata.hmacsha224ctx = hmacsha224ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacsha224_destroyctx(dst_context_t *dctx) { + isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; + + if (hmacsha224ctx != NULL) { + isc_hmacsha224_invalidate(hmacsha224ctx); + isc_mem_put(dctx->mctx, hmacsha224ctx, sizeof(isc_hmacsha224_t)); + dctx->ctxdata.hmacsha224ctx = NULL; + } +} + +static isc_result_t +hmacsha224_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; + + isc_hmacsha224_update(hmacsha224ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha224_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_SHA224_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacsha224_sign(hmacsha224ctx, digest, ISC_SHA224_DIGESTLENGTH); + isc_buffer_add(sig, ISC_SHA224_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha224_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacsha224_t *hmacsha224ctx = dctx->ctxdata.hmacsha224ctx; + + if (sig->length > ISC_SHA224_DIGESTLENGTH || sig->length == 0) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacsha224_verify(hmacsha224ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacsha224_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacsha224_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacsha224; + hkey2 = key2->keydata.hmacsha224; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_SHA224_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacsha224_generate(dst_key_t *key, int pseudorandom_ok, + void (*callback)(int)) +{ + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_SHA224_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_SHA224_BLOCK_LENGTH) { + bytes = ISC_SHA224_BLOCK_LENGTH; + key->key_size = ISC_SHA224_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_SHA224_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacsha224_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacsha224_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacsha224_destroy(dst_key_t *key) { + dst_hmacsha224_key_t *hkey = key->keydata.hmacsha224; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacsha224 = NULL; +} + +static isc_result_t +hmacsha224_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha224_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacsha224 != NULL); + + hkey = key->keydata.hmacsha224; + + 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 +hmacsha224_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha224_key_t *hkey; + int keylen; + isc_region_t r; + isc_sha224_t sha224ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha224_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_SHA224_BLOCK_LENGTH) { + isc_sha224_init(&sha224ctx); + isc_sha224_update(&sha224ctx, r.base, r.length); + isc_sha224_final(hkey->key, &sha224ctx); + keylen = ISC_SHA224_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacsha224 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha224_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacsha224_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacsha224 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacsha224; + + priv.elements[cnt].tag = TAG_HMACSHA224_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACSHA224_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacsha224_parse(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, DST_ALG_HMACSHA224, 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_HMACSHA224_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacsha224_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + case TAG_HMACSHA224_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); +} + +static dst_func_t hmacsha224_functions = { + hmacsha224_createctx, + NULL, /*%< createctx2 */ + hmacsha224_destroyctx, + hmacsha224_adddata, + hmacsha224_sign, + hmacsha224_verify, + NULL, /* verify2 */ + NULL, /* computesecret */ + hmacsha224_compare, + NULL, /* paramcompare */ + hmacsha224_generate, + hmacsha224_isprivate, + hmacsha224_destroy, + hmacsha224_todns, + hmacsha224_fromdns, + hmacsha224_tofile, + hmacsha224_parse, + NULL, /* cleanup */ + NULL, /* fromlabel */ + NULL, /* dump */ + NULL, /* restore */ +}; + +isc_result_t +dst__hmacsha224_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacsha224_functions; + return (ISC_R_SUCCESS); +} + +static isc_result_t hmacsha256_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacsha256_key { + unsigned char key[ISC_SHA256_BLOCK_LENGTH]; +}; + +static isc_result_t +hmacsha256_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacsha256_t *hmacsha256ctx; + dst_hmacsha256_key_t *hkey = key->keydata.hmacsha256; + + hmacsha256ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha256_t)); + if (hmacsha256ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacsha256_init(hmacsha256ctx, hkey->key, ISC_SHA256_BLOCK_LENGTH); + dctx->ctxdata.hmacsha256ctx = hmacsha256ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacsha256_destroyctx(dst_context_t *dctx) { + isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; + + if (hmacsha256ctx != NULL) { + isc_hmacsha256_invalidate(hmacsha256ctx); + isc_mem_put(dctx->mctx, hmacsha256ctx, sizeof(isc_hmacsha256_t)); + dctx->ctxdata.hmacsha256ctx = NULL; + } +} + +static isc_result_t +hmacsha256_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; + + isc_hmacsha256_update(hmacsha256ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha256_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_SHA256_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacsha256_sign(hmacsha256ctx, digest, ISC_SHA256_DIGESTLENGTH); + isc_buffer_add(sig, ISC_SHA256_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha256_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacsha256_t *hmacsha256ctx = dctx->ctxdata.hmacsha256ctx; + + if (sig->length > ISC_SHA256_DIGESTLENGTH || sig->length == 0) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacsha256_verify(hmacsha256ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacsha256_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacsha256_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacsha256; + hkey2 = key2->keydata.hmacsha256; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_SHA256_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacsha256_generate(dst_key_t *key, int pseudorandom_ok, + void (*callback)(int)) +{ + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_SHA256_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_SHA256_BLOCK_LENGTH) { + bytes = ISC_SHA256_BLOCK_LENGTH; + key->key_size = ISC_SHA256_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_SHA256_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacsha256_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacsha256_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacsha256_destroy(dst_key_t *key) { + dst_hmacsha256_key_t *hkey = key->keydata.hmacsha256; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacsha256 = NULL; +} + +static isc_result_t +hmacsha256_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha256_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacsha256 != NULL); + + hkey = key->keydata.hmacsha256; + + 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 +hmacsha256_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha256_key_t *hkey; + int keylen; + isc_region_t r; + isc_sha256_t sha256ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha256_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_SHA256_BLOCK_LENGTH) { + isc_sha256_init(&sha256ctx); + isc_sha256_update(&sha256ctx, r.base, r.length); + isc_sha256_final(hkey->key, &sha256ctx); + keylen = ISC_SHA256_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacsha256 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha256_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacsha256_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacsha256 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacsha256; + + priv.elements[cnt].tag = TAG_HMACSHA256_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACSHA256_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacsha256_parse(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, DST_ALG_HMACSHA256, 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_HMACSHA256_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacsha256_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + case TAG_HMACSHA256_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); +} + +static dst_func_t hmacsha256_functions = { + hmacsha256_createctx, + NULL, /*%< createctx2 */ + hmacsha256_destroyctx, + hmacsha256_adddata, + hmacsha256_sign, + hmacsha256_verify, + NULL, /* verify2 */ + NULL, /* computesecret */ + hmacsha256_compare, + NULL, /* paramcompare */ + hmacsha256_generate, + hmacsha256_isprivate, + hmacsha256_destroy, + hmacsha256_todns, + hmacsha256_fromdns, + hmacsha256_tofile, + hmacsha256_parse, + NULL, /* cleanup */ + NULL, /* fromlabel */ + NULL, /* dump */ + NULL, /* restore */ +}; + +isc_result_t +dst__hmacsha256_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacsha256_functions; + return (ISC_R_SUCCESS); +} + +static isc_result_t hmacsha384_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacsha384_key { + unsigned char key[ISC_SHA384_BLOCK_LENGTH]; +}; + +static isc_result_t +hmacsha384_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacsha384_t *hmacsha384ctx; + dst_hmacsha384_key_t *hkey = key->keydata.hmacsha384; + + hmacsha384ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha384_t)); + if (hmacsha384ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacsha384_init(hmacsha384ctx, hkey->key, ISC_SHA384_BLOCK_LENGTH); + dctx->ctxdata.hmacsha384ctx = hmacsha384ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacsha384_destroyctx(dst_context_t *dctx) { + isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; + + if (hmacsha384ctx != NULL) { + isc_hmacsha384_invalidate(hmacsha384ctx); + isc_mem_put(dctx->mctx, hmacsha384ctx, sizeof(isc_hmacsha384_t)); + dctx->ctxdata.hmacsha384ctx = NULL; + } +} + +static isc_result_t +hmacsha384_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; + + isc_hmacsha384_update(hmacsha384ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha384_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_SHA384_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacsha384_sign(hmacsha384ctx, digest, ISC_SHA384_DIGESTLENGTH); + isc_buffer_add(sig, ISC_SHA384_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha384_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacsha384_t *hmacsha384ctx = dctx->ctxdata.hmacsha384ctx; + + if (sig->length > ISC_SHA384_DIGESTLENGTH || sig->length == 0) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacsha384_verify(hmacsha384ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacsha384_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacsha384_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacsha384; + hkey2 = key2->keydata.hmacsha384; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_SHA384_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacsha384_generate(dst_key_t *key, int pseudorandom_ok, + void (*callback)(int)) +{ + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_SHA384_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_SHA384_BLOCK_LENGTH) { + bytes = ISC_SHA384_BLOCK_LENGTH; + key->key_size = ISC_SHA384_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_SHA384_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacsha384_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacsha384_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacsha384_destroy(dst_key_t *key) { + dst_hmacsha384_key_t *hkey = key->keydata.hmacsha384; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacsha384 = NULL; +} + +static isc_result_t +hmacsha384_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha384_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacsha384 != NULL); + + hkey = key->keydata.hmacsha384; + + 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 +hmacsha384_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha384_key_t *hkey; + int keylen; + isc_region_t r; + isc_sha384_t sha384ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha384_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_SHA384_BLOCK_LENGTH) { + isc_sha384_init(&sha384ctx); + isc_sha384_update(&sha384ctx, r.base, r.length); + isc_sha384_final(hkey->key, &sha384ctx); + keylen = ISC_SHA384_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacsha384 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha384_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacsha384_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacsha384 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacsha384; + + priv.elements[cnt].tag = TAG_HMACSHA384_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACSHA384_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacsha384_parse(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, DST_ALG_HMACSHA384, 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_HMACSHA384_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacsha384_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + case TAG_HMACSHA384_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); +} + +static dst_func_t hmacsha384_functions = { + hmacsha384_createctx, + NULL, /*%< createctx2 */ + hmacsha384_destroyctx, + hmacsha384_adddata, + hmacsha384_sign, + hmacsha384_verify, + NULL, /* verify2 */ + NULL, /* computesecret */ + hmacsha384_compare, + NULL, /* paramcompare */ + hmacsha384_generate, + hmacsha384_isprivate, + hmacsha384_destroy, + hmacsha384_todns, + hmacsha384_fromdns, + hmacsha384_tofile, + hmacsha384_parse, + NULL, /* cleanup */ + NULL, /* fromlabel */ + NULL, /* dump */ + NULL, /* restore */ +}; + +isc_result_t +dst__hmacsha384_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacsha384_functions; + return (ISC_R_SUCCESS); +} + +static isc_result_t hmacsha512_fromdns(dst_key_t *key, isc_buffer_t *data); + +struct dst_hmacsha512_key { + unsigned char key[ISC_SHA512_BLOCK_LENGTH]; +}; + +static isc_result_t +hmacsha512_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_hmacsha512_t *hmacsha512ctx; + dst_hmacsha512_key_t *hkey = key->keydata.hmacsha512; + + hmacsha512ctx = isc_mem_get(dctx->mctx, sizeof(isc_hmacsha512_t)); + if (hmacsha512ctx == NULL) + return (ISC_R_NOMEMORY); + isc_hmacsha512_init(hmacsha512ctx, hkey->key, ISC_SHA512_BLOCK_LENGTH); + dctx->ctxdata.hmacsha512ctx = hmacsha512ctx; + return (ISC_R_SUCCESS); +} + +static void +hmacsha512_destroyctx(dst_context_t *dctx) { + isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; + + if (hmacsha512ctx != NULL) { + isc_hmacsha512_invalidate(hmacsha512ctx); + isc_mem_put(dctx->mctx, hmacsha512ctx, sizeof(isc_hmacsha512_t)); + dctx->ctxdata.hmacsha512ctx = NULL; + } +} + +static isc_result_t +hmacsha512_adddata(dst_context_t *dctx, const isc_region_t *data) { + isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; + + isc_hmacsha512_update(hmacsha512ctx, data->base, data->length); + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha512_sign(dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; + unsigned char *digest; + + if (isc_buffer_availablelength(sig) < ISC_SHA512_DIGESTLENGTH) + return (ISC_R_NOSPACE); + digest = isc_buffer_used(sig); + isc_hmacsha512_sign(hmacsha512ctx, digest, ISC_SHA512_DIGESTLENGTH); + isc_buffer_add(sig, ISC_SHA512_DIGESTLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha512_verify(dst_context_t *dctx, const isc_region_t *sig) { + isc_hmacsha512_t *hmacsha512ctx = dctx->ctxdata.hmacsha512ctx; + + if (sig->length > ISC_SHA512_DIGESTLENGTH || sig->length == 0) + return (DST_R_VERIFYFAILURE); + + if (isc_hmacsha512_verify(hmacsha512ctx, sig->base, sig->length)) + return (ISC_R_SUCCESS); + else + return (DST_R_VERIFYFAILURE); +} + +static bool +hmacsha512_compare(const dst_key_t *key1, const dst_key_t *key2) { + dst_hmacsha512_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmacsha512; + hkey2 = key2->keydata.hmacsha512; + + if (hkey1 == NULL && hkey2 == NULL) + return (true); + else if (hkey1 == NULL || hkey2 == NULL) + return (false); + + if (isc_safe_memequal(hkey1->key, hkey2->key, ISC_SHA512_BLOCK_LENGTH)) + return (true); + else + return (false); +} + +static isc_result_t +hmacsha512_generate(dst_key_t *key, int pseudorandom_ok, + void (*callback)(int)) +{ + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes; + unsigned char data[ISC_SHA512_BLOCK_LENGTH]; + + UNUSED(callback); + + bytes = (key->key_size + 7) / 8; + if (bytes > ISC_SHA512_BLOCK_LENGTH) { + bytes = ISC_SHA512_BLOCK_LENGTH; + key->key_size = ISC_SHA512_BLOCK_LENGTH * 8; + } + + memset(data, 0, ISC_SHA512_BLOCK_LENGTH); + ret = dst__entropy_getdata(data, bytes, pseudorandom_ok); + + if (ret != ISC_R_SUCCESS) + return (ret); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + ret = hmacsha512_fromdns(key, &b); + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmacsha512_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmacsha512_destroy(dst_key_t *key) { + dst_hmacsha512_key_t *hkey = key->keydata.hmacsha512; + + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmacsha512 = NULL; +} + +static isc_result_t +hmacsha512_todns(const dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha512_key_t *hkey; + unsigned int bytes; + + REQUIRE(key->keydata.hmacsha512 != NULL); + + hkey = key->keydata.hmacsha512; + + 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 +hmacsha512_fromdns(dst_key_t *key, isc_buffer_t *data) { + dst_hmacsha512_key_t *hkey; + int keylen; + isc_region_t r; + isc_sha512_t sha512ctx; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmacsha512_key_t)); + if (hkey == NULL) + return (ISC_R_NOMEMORY); + + memset(hkey->key, 0, sizeof(hkey->key)); + + if (r.length > ISC_SHA512_BLOCK_LENGTH) { + isc_sha512_init(&sha512ctx); + isc_sha512_update(&sha512ctx, r.base, r.length); + isc_sha512_final(hkey->key, &sha512ctx); + keylen = ISC_SHA512_DIGESTLENGTH; + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmacsha512 = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmacsha512_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + dst_hmacsha512_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + unsigned char buf[2]; + + if (key->keydata.hmacsha512 == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + hkey = key->keydata.hmacsha512; + + priv.elements[cnt].tag = TAG_HMACSHA512_KEY; + priv.elements[cnt].length = bytes; + priv.elements[cnt++].data = hkey->key; + + buf[0] = (key->key_bits >> 8) & 0xffU; + buf[1] = key->key_bits & 0xffU; + priv.elements[cnt].tag = TAG_HMACSHA512_BITS; + priv.elements[cnt].data = buf; + priv.elements[cnt++].length = 2; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +hmacsha512_parse(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, DST_ALG_HMACSHA512, 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_HMACSHA512_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmacsha512_fromdns(key, &b); + if (tresult != ISC_R_SUCCESS) + result = tresult; + break; + 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); +} + +static dst_func_t hmacsha512_functions = { + hmacsha512_createctx, + NULL, /*%< createctx2 */ + hmacsha512_destroyctx, + hmacsha512_adddata, + hmacsha512_sign, + hmacsha512_verify, + NULL, /* verify2 */ + NULL, /* computesecret */ + hmacsha512_compare, + NULL, /* paramcompare */ + hmacsha512_generate, + hmacsha512_isprivate, + hmacsha512_destroy, + hmacsha512_todns, + hmacsha512_fromdns, + hmacsha512_tofile, + hmacsha512_parse, + NULL, /* cleanup */ + NULL, /* fromlabel */ + NULL, /* dump */ + NULL, /* restore */ +}; + +isc_result_t +dst__hmacsha512_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &hmacsha512_functions; + return (ISC_R_SUCCESS); +} + +/*! \file */ diff --git a/lib/dns/include/Makefile.in b/lib/dns/include/Makefile.in new file mode 100644 index 0000000..5467ea1 --- /dev/null +++ b/lib/dns/include/Makefile.in @@ -0,0 +1,17 @@ +# 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 http://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..59897fe --- /dev/null +++ b/lib/dns/include/dns/Makefile.in @@ -0,0 +1,60 @@ +# 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 http://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 = acache.h 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 dnssec.h ds.h dsdigest.h \ + dnstap.h dyndb.h \ + edns.h ecdb.h events.h fixedname.h forward.h geoip.h \ + ipkeylist.h iptable.h \ + journal.h keydata.h keyflags.h keytable.h keyvalues.h \ + lib.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 zt.h + +GENHEADERS = @DNSTAP_PB_C_H@ 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/acache.h b/lib/dns/include/dns/acache.h new file mode 100644 index 0000000..7b1dcd7 --- /dev/null +++ b/lib/dns/include/dns/acache.h @@ -0,0 +1,442 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: acache.h,v 1.8 2007/06/19 23:47:16 tbox Exp $ */ + +#ifndef DNS_ACACHE_H +#define DNS_ACACHE_H 1 + +/***** + ***** Module Info + *****/ + +/* + * Acache + * + * The Additional Cache Object + * + * This module manages internal caching entries that correspond to + * the additional section data of a DNS DB node (an RRset header, more + * accurately). An additional cache entry is expected to be (somehow) + * attached to a particular RR in a particular DB node, and contains a set + * of information of an additional data for the DB node. + * + * An additional cache object is intended to be created as a per-view + * object, and manages all cache entries within the view. + * + * The intended usage of the additional caching is to provide a short cut + * to additional glue RRs of an NS RR. For each NS RR, it is often + * necessary to look for glue RRs to make a proper response. Once the + * glue RRs are known, the additional caching allows the client to + * associate the information to the original NS RR so that further + * expensive lookups can be avoided for the NS RR. + * + * Each additional cache entry contains information to identify a + * particular DB node and (optionally) an associated RRset. The + * information consists of its zone, database, the version of the + * database, database node, and RRset. + * + * A "negative" information can also be cached. For example, if a glue + * RR does not exist as an authoritative data in the same zone as that + * of the NS RR, this fact can be cached by specifying a NULL pointer + * for the database, version, and node. (See the description for + * dns_acache_getentry() below for more details.) + * + * Since each member stored in an additional cache entry holds a reference + * to a corresponding object, a stale cache entry may cause unnecessary + * memory consumption. For instance, when a zone is reloaded, additional + * cache entries that have a reference to the zone (and its DB and/or + * DB nodes) can delay the cleanup of the referred objects. In order to + * minimize such a bad effect, this module provides several cleanup + * mechanisms. + * + * The first one is a shutdown procedure called when the associated view + * is shut down. In this case, dns_acache_shutdown() will be called and + * all cache entries will be purged. This mechanism will help the + * situation when the configuration is reloaded or the main server is + * stopped. + * + * Per-DB cleanup mechanism is also provided. Each additional cache entry + * is associated with related DB, which is expected to have been + * registered when the DB was created by dns_acache_setdb(). If a + * particular DB is going to be destroyed, the primary holder of the DB, + * a typical example of which is a zone, will call dns_acache_putdb(). + * Then this module will clean-up all cache entries associated with the + * DB. This mechanism is effective when a secondary zone DB is going to + * be stale after a zone transfer. + * + * Finally, this module supports for periodic clean-up of stale entries. + * Each cache entry has a timestamp field, which is updated every time + * the entry is referred. A periodically invoked cleaner checks the + * timestamp of each entry, and purge entries that have not been referred + * for a certain period. The cleaner interval can be specified by + * dns_acache_setcleaninginterval(). If the periodic clean-up is not + * enough, it is also possible to specify the upper limit of entries + * in terms of the memory consumption. If the maximum value is + * specified, the cleaner is invoked when the memory consumption reaches + * the high watermark inferred from the maximum value. In this case, + * the cleaner will use more aggressive algorithm to decide the "victim" + * entries. The maximum value can be specified by + * dns_acache_setcachesize(). + * + * When a cache entry is going to be purged within this module, the + * callback function specified at the creation time will be called. + * The callback function is expected to release all internal resources + * related to the entry, which will typically be specific to DB + * implementation, and to call dns_acache_detachentry(). The callback + * mechanism is very important, since the holder of an additional cache + * entry may not be able to initiate the clean-up of the entry, due to + * the reference ordering. For example, as long as an additional cache + * entry has a reference to a DB object, the DB cannot be freed, in which + * a DB node may have a reference to the cache entry. + * + * Credits: + * The basic idea of this kind of short-cut for frequently used + * information is similar to the "pre-compiled answer" approach adopted + * in nsd by NLnet LABS with RIPE NCC. Our work here is an independent + * effort, but the success of nsd encouraged us to pursue this path. + * + * The design and implementation of the periodic memory management and + * the upper limitation of memory consumption was derived from the cache + * DB implementation of BIND9. + * + * MP: + * There are two main locks in this module. One is for each entry, and + * the other is for the additional cache object. + * + * Reliability: + * The callback function for a cache entry is called with holding the + * entry lock. Thus, it implicitly assumes the callback function does not + * call a function that can require the lock. Typically, the only + * function that can be called from the callback function safely is + * dns_acache_detachentry(). The breakage of this implicit assumption + * may cause a deadlock. + * + * Resources: + * In a 32-bit architecture (such as i386), the following additional + * memory is required comparing to the case that disables this module. + * - 76 bytes for each additional cache entry + * - if the entry has a DNS name and associated RRset, + * * 44 bytes + size of the name (1-255 bytes) + * * 52 bytes x number_of_RRs + * - 28 bytes for each DB related to this module + * + * Using the additional cache also requires extra memory consumption in + * the DB implementation. In the current implementation for rbtdb, we + * need: + * - two additional pointers for each DB node (8 bytes for a 32-bit + * architecture + * - for each RR associated to an RR in a DB node, we also need + * a pointer and management objects to support the additional cache + * function. These are allocated on-demand. The total size is + * 32 bytes for a 32-bit architecture. + * + * Security: + * Since this module does not handle any low-level data directly, + * no security issue specific to this module is anticipated. + * + * Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include +#include +#include + +#include + +/*** + *** Functions + ***/ +ISC_LANG_BEGINDECLS + +isc_result_t +dns_acache_create(dns_acache_t **acachep, isc_mem_t *mctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr); +/* + * Create a new DNS additional cache object. + * + * Requires: + * + * 'mctx' is a valid memory context + * + * 'taskmgr' is a valid task manager + * + * 'timermgr' is a valid timer or NULL. If NULL, no periodic cleaning of + * the cache will take place. + * + * 'acachep' is a valid pointer, and *acachep == NULL + * + * Ensures: + * + * '*acachep' is attached to the newly created cache + * + * Returns: + * + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_UNEXPECTED + */ + +void +dns_acache_attach(dns_acache_t *source, dns_acache_t **targetp); +/* + * Attach *targetp to cache. + * + * Requires: + * + * 'acache' is a valid additional cache. + * + * 'targetp' points to a NULL dns_acache_t *. + * + * Ensures: + * + * *targetp is attached to the 'source' additional cache. + */ + +void +dns_acache_detach(dns_acache_t **acachep); +/* + * Detach *acachep from its cache. + * + * Requires: + * + * '*acachep' points to a valid additional cache. + * + * Ensures: + * + * *acachep is NULL. + * + * If '*acachep' is the last reference to the cache and the additional + * cache does not have an outstanding task, all resources used by the + * cache will be freed. + */ + +void +dns_acache_setcleaninginterval(dns_acache_t *acache, unsigned int t); +/* + * Set the periodic cleaning interval of an additional cache to 'interval' + * seconds. + */ + +void +dns_acache_setcachesize(dns_acache_t *acache, size_t size); +/* + * Set the maximum additional cache size. 0 means unlimited. + */ + +isc_result_t +dns_acache_setdb(dns_acache_t *acache, dns_db_t *db); +/* + * Set 'db' in 'acache' when the db can be referred from acache, in order + * to provide a hint for resolving the back reference. + * + * Requires: + * 'acache' is a valid acache pointer. + * 'db' is a valid DNS DB pointer. + * + * Ensures: + * 'acache' will have a reference to 'db'. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_EXISTS (which means the specified 'db' is already set) + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_putdb(dns_acache_t *acache, dns_db_t *db); +/* + * Release 'db' from 'acache' if it has been set by dns_acache_setdb(). + * + * Requires: + * 'acache' is a valid acache pointer. + * 'db' is a valid DNS DB pointer. + * + * Ensures: + * 'acache' will release the reference to 'db'. Additionally, the content + * of each cache entry that is related to the 'db' will be released via + * the callback function. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND (which means the specified 'db' is not set in 'acache') + * ISC_R_NOMEMORY + */ + +void +dns_acache_shutdown(dns_acache_t *acache); +/* + * Shutdown 'acache'. + * + * Requires: + * + * '*acache' is a valid additional cache. + */ + +isc_result_t +dns_acache_createentry(dns_acache_t *acache, dns_db_t *origdb, + void (*callback)(dns_acacheentry_t *, void **), + void *cbarg, dns_acacheentry_t **entryp); +/* + * Create an additional cache entry. A new entry is created and attached to + * the given additional cache object. A callback function is also associated + * with the created entry, which will be called when the cache entry is purged + * for some reason. + * + * Requires: + * + * 'acache' is a valid additional cache. + * 'entryp' is a valid pointer, and *entryp == NULL + * 'origdb' is a valid DNS DB pointer. + * 'callback' and 'cbarg' can be NULL. In this case, however, the entry + * is meaningless (and will be cleaned-up in the next periodical + * cleaning). + * + * Ensures: + * '*entryp' will point to a new additional cache entry. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_getentry(dns_acacheentry_t *entry, dns_zone_t **zonep, + dns_db_t **dbp, dns_dbversion_t **versionp, + dns_dbnode_t **nodep, dns_name_t *fname, + dns_message_t *msg, isc_stdtime_t now); +/* + * Get content from a particular additional cache entry. + * + * Requires: + * + * 'entry' is a valid additional cache entry. + * 'zonep' is a NULL pointer or '*zonep' == NULL (this is the only + * optional parameter.) + * 'dbp' is a valid pointer, and '*dbp' == NULL + * 'versionp' is a valid pointer, and '*versionp' == NULL + * 'nodep' is a valid pointer, and '*nodep' == NULL + * 'fname' is a valid DNS name. + * 'msg' is a valid DNS message. + * + * Ensures: + * Several possible cases can happen according to the content. + * 1. For a positive cache entry, + * '*zonep' will point to the corresponding zone (if zonep is a valid + * pointer), + * '*dbp' will point to a DB for the zone, + * '*versionp' will point to its version, and + * '*nodep' will point to the corresponding DB node. + * 'fname' will have the DNS name of the DB node and contain a list of + * rdataset for the node (which can be an empty list). + * + * 2. For a negative cache entry that means no corresponding zone exists, + * '*zonep' == NULL (if zonep is a valid pointer) + * '*dbp', '*versionp', and '*nodep' will be NULL. + * + * 3. For a negative cache entry that means no corresponding DB node + * exists, '*zonep' will point to the corresponding zone (if zonep is a + * valid pointer), + * '*dbp' will point to a corresponding DB for zone, + * '*versionp' will point to its version. + * '*nodep' will be kept as NULL. + * 'fname' will not change. + * + * On failure, no new references will be created. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +isc_result_t +dns_acache_setentry(dns_acache_t *acache, dns_acacheentry_t *entry, + dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, + dns_dbnode_t *node, dns_name_t *fname); +/* + * Set content to a particular additional cache entry. + * + * Requires: + * 'acache' is a valid additional cache. + * 'entry' is a valid additional cache entry. + * All the others pointers are NULL or a valid pointer of the + * corresponding type. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_NOTFOUND + */ + +bool +dns_acache_cancelentry(dns_acacheentry_t *entry); +/* + * Cancel the use of the cache entry 'entry'. This function is supposed to + * be called when the node that holds the entry finds the content is not + * correct any more. This function will try to release as much dependency as + * possible, and will be ready to be cleaned-up. The registered callback + * function will be canceled and will never called. + * + * Requires: + * 'entry' is a valid additional cache entry. + * + * Returns: + * true if the entry was active when canceled + */ + +void +dns_acache_attachentry(dns_acacheentry_t *source, dns_acacheentry_t **targetp); +/* + * Attach *targetp to the cache entry 'source'. + * + * Requires: + * + * 'source' is a valid additional cache entry. + * + * 'targetp' points to a NULL dns_acacheentry_t *. + * + * Ensures: + * + * *targetp is attached to 'source'. + */ + +void +dns_acache_detachentry(dns_acacheentry_t **entryp); +/* + * Detach *entryp from its cache. + * + * Requires: + * + * '*entryp' points to a valid additional cache entry. + * + * Ensures: + * + * *entryp is NULL. + * + * If '*entryp' is the last reference to the entry, + * cache does not have an outstanding task, all resources used by the + * entry (including the entry object itself) will be freed. + */ + +void +dns_acache_countquerymiss(dns_acache_t *acache); +/* + * Count up a missed acache query. XXXMLG need more docs. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_ACACHE_H */ diff --git a/lib/dns/include/dns/acl.h b/lib/dns/include/dns/acl.h new file mode 100644 index 0000000..c8a7833 --- /dev/null +++ b/lib/dns/include/dns/acl.h @@ -0,0 +1,282 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: acl.h,v 1.35 2011/06/17 23:47:49 tbox Exp $ */ + +#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 + +#ifdef HAVE_GEOIP +#include +#endif +#include +#include +#include + +#ifdef HAVE_GEOIP +#include +#endif + +/*** + *** Types + ***/ + +typedef enum { + dns_aclelementtype_ipprefix, + dns_aclelementtype_keyname, + dns_aclelementtype_nestedacl, + dns_aclelementtype_localhost, + dns_aclelementtype_localnets, +#ifdef HAVE_GEOIP + dns_aclelementtype_geoip, +#endif /* HAVE_GEOIP */ + 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; +#ifdef HAVE_GEOIP + dns_geoip_elem_t geoip_elem; +#endif /* HAVE_GEOIP */ + dns_acl_t *nestedacl; + int node_num; +}; + +struct dns_acl { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t refcount; + dns_iptable_t *iptable; +#define node_count iptable->radix->num_added_node + 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; +#ifdef HAVE_GEOIP + dns_geoip_databases_t *geoip; + bool geoip_use_ecs; +#endif +}; + +#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. + */ + +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); + +isc_result_t +dns_acl_match2(const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, + const isc_netaddr_t *ecs, + uint8_t ecslen, + uint8_t *scope, + 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', + * and optionally the client prefix 'ecs' of length 'ecslen' + * (reported via EDNS client subnet option) against 'acl'. + * + * 'reqsigner' and 'ecs' may be NULL. If an ACL matches against 'ecs' + * and 'ecslen', then 'scope' will be set to indicate the netmask that + * matched. + * + * 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); + +bool +dns_aclelement_match2(const isc_netaddr_t *reqaddr, + const dns_name_t *reqsigner, + const isc_netaddr_t *ecs, + uint8_t ecslen, + uint8_t *scope, + 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..ca35bac --- /dev/null +++ b/lib/dns/include/dns/adb.h @@ -0,0 +1,841 @@ +/* + * 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 http://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 + +/*% + * 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, dns_name_t *name, dns_name_t *qname, + dns_rdatatype_t qtype, unsigned int options, + isc_stdtime_t now, dns_name_t *target, + in_port_t port, dns_adbfind_t **find); +isc_result_t +dns_adb_createfind2(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action, + void *arg, dns_name_t *name, 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, 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); +unsigned int +dns_adb_probesize2(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, 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, 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, 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 addresss. 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); +/* + * Retieve 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..276e3cb --- /dev/null +++ b/lib/dns/include/dns/badcache.h @@ -0,0 +1,151 @@ +/* + * 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 http://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, 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, 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, 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, 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..9ebb57a --- /dev/null +++ b/lib/dns/include/dns/bit.h @@ -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 http://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..9fe3009 --- /dev/null +++ b/lib/dns/include/dns/byaddr.h @@ -0,0 +1,166 @@ +/* + * 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 http://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 + +#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; + +/* + * This option is deprecated since we now only consider nibbles. +#define DNS_BYADDROPT_IPV6NIBBLE 0x0001 + */ +/*% Note DNS_BYADDROPT_IPV6NIBBLE is now deprecated. */ +#define DNS_BYADDROPT_IPV6INT 0x0002 + +isc_result_t +dns_byaddr_create(isc_mem_t *mctx, 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 + * + *\li #DNS_BYADDROPT_IPV6INT can be used to get nibble lookups under ip6.int. + * + * 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(isc_netaddr_t *address, bool nibble, + dns_name_t *name); + +isc_result_t +dns_byaddr_createptrname2(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..62797db --- /dev/null +++ b/lib/dns/include/dns/cache.h @@ -0,0 +1,342 @@ +/* + * 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 http://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 + +#include + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_cache_create(isc_mem_t *cmctx, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_rdataclass_t rdclass, + const char *db_type, unsigned int db_argc, char **db_argv, + dns_cache_t **cachep); +isc_result_t +dns_cache_create2(isc_mem_t *cmctx, 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 +dns_cache_create3(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. + */ + +void +dns_cache_setcleaninginterval(dns_cache_t *cache, unsigned int interval); +/*%< + * Set the periodic cache cleaning interval to 'interval' seconds. + */ + +unsigned int +dns_cache_getcleaninginterval(dns_cache_t *cache); +/*%< + * Get the periodic cache cleaning interval to 'interval' seconds. + */ + +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. + */ + +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, 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, 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, xmlTextWriterPtr writer); +/* + * Render cache statistics and status in XML for 'writer'. + */ +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON +isc_result_t +dns_cache_renderjson(dns_cache_t *cache, json_object *cstats); +/* + * Render cache statistics and status in JSON + */ +#endif /* HAVE_JSON */ + +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..9c723f3 --- /dev/null +++ b/lib/dns/include/dns/callbacks.h @@ -0,0 +1,102 @@ +/* + * 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 http://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..61426f7 --- /dev/null +++ b/lib/dns/include/dns/catz.h @@ -0,0 +1,469 @@ +/* + * 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 http://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 + +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 overriden 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 not NULL + * \li entryp is not NULL, *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 not NULL + * \li entryp is not NULL, *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) + */ + +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 not NULL + * \li eb is not NULL + * + * 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 not NULL + * \li zonep is not NULL, *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, *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 not NULL + * \li zonep is not NULL, *zonep is NULL + * \li name is not NULL + * + */ + +dns_name_t * +dns_catz_zone_getname(dns_catz_zone_t *zone); +/*%< + * Get catalog zone name + * + * Requires: + * \li zone is not NULL + */ + +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 not NULL + */ + +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 not NULL + */ + +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 not NULL + * \li newzone is not NULL, *newzone is not NULL + * + */ + +isc_result_t +dns_catz_update_process(dns_catz_zones_t *catzs, dns_catz_zone_t *zone, + 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 not NULL + * \li zone is not NULL + * \li src_name is not NULL + * \li rdataset is valid + */ + +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 not NULL + * \li entry is not NULL + * \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 not NULL + * \li entry is not NULL + * \li buf is not NULL + * \li *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, *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 not NULL + * \li name is not NULL + * \li zonep is not NULL, *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 not NULL + * \li name is not NULL + */ + +void +dns_catz_catzs_attach(dns_catz_zones_t *catzs, dns_catz_zones_t **catzsp); +/*%< + * Attach 'catzs' to 'catzsp' + * + * Requires: + * \li catzs is not NULL + * \li catzsp is not NULL, *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, *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 not NULL + * \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 not NULL + * + */ + +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 not NULL + * + */ + +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 not NULL + */ + +isc_result_t +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. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li Any other value -- failure + */ + +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..188d283 --- /dev/null +++ b/lib/dns/include/dns/cert.h @@ -0,0 +1,62 @@ +/* + * 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 http://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..d3c5790 --- /dev/null +++ b/lib/dns/include/dns/client.h @@ -0,0 +1,674 @@ +/* + * 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 http://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_ALLOWRUN 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_ALLOWRUN 0x01 +/*%< Use TCP transport. */ +#define DNS_CLIENTREQOPT_TCP 0x02 + +/*% + * Optional flags for dns_client_(start)update. + */ +/*%< Allow running external context. */ +#define DNS_CLIENTUPDOPT_ALLOWRUN 0x01 +/*%< Use TCP transport. */ +#define DNS_CLIENTUPDOPT_TCP 0x02 + +/*% + * 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? */ + +/*% + * Status of a dynamic update procedure. + */ +typedef enum { + dns_clientupdatestate_prepare, /*%< no updates have been sent */ + dns_clientupdatestate_sent, /*%< updates were sent, no response */ + dns_clientupdatestate_done /*%< update was sent and succeeded */ +} dns_clientupdatestate_t; + +/*% + * 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? */ + +/*% + * A dns_clientupdateevent_t is sent when dynamic update performed by a client + * completes. 'result' stores the result code of the entire update procedure. + * 'state' specifies the status of the update procedure when this event is + * sent. This can be used as a hint by the receiver to determine whether + * the update attempt was ever made. In particular, if the state is + * dns_clientupdatestate_prepare, the receiver can be sure that the requested + * update was not applied. + */ +typedef struct dns_clientupdateevent { + ISC_EVENT_COMMON(struct dns_clientupdateevent); + isc_result_t result; + dns_clientupdatestate_t state; +} dns_clientupdateevent_t; /* too long? */ + +isc_result_t +dns_client_create(dns_client_t **clientp, unsigned int options); + +isc_result_t +dns_client_createx(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); + +isc_result_t +dns_client_createx2(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, + isc_sockaddr_t *localaddr4, isc_sockaddr_t *localaddr6); +/*%< + * Create a DNS client. These functions create a new client object with + * minimal internal resources such as the default 'view' for the IN class and + * IPv4/IPv6 dispatches for the view. + * + * dns_client_createx() takes 'manager' arguments so that the caller can + * control the behavior of the client through the underlying event framework. + * On the other hand, dns_client_create() simplifies the interface and creates + * the managers internally. A DNS client object created via + * dns_client_create() is expected to be used by an application that only needs + * simple synchronous services or by a thread-based application. + * + * dns_client_createx2 takes two additional parameters, 'localaddr4' and + * 'localaddr6', to specify the local address to use for each 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 other protocol family will not be used. + * + * If the DNS_CLIENTCREATEOPT_USECACHE flag is set in 'options', + * dns_client_create(x) will create a cache database with the view. + * + * 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, + 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, + 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_setdlv(dns_client_t *client, dns_rdataclass_t rdclass, + const char *dlvname); +/*%< + * Specify a name to use for DNSSEC lookaside validation. + * If a trusted key has been added for that name, then DLV will be + * used during validation. If 'dlvname' is NULL, then DLV will no + * longer be used for this client. + * + * Requires: + * + *\li 'client' is a valid client. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ + +isc_result_t +dns_client_resolve(dns_client_t *client, 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, 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 typically expected that the client object passed to + * dns_client_resolve() was created via dns_client_create() and has its own + * managers and contexts. However, if the DNS_CLIENTRESOPT_ALLOWRUN flag is + * set in 'options', this function performs the synchronous service even if + * it does not have its own manager and context structures. + * + * 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_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. 'keyname' is the DNS name of the key, + * and 'keydatabuf' stores the resource data of the key. + * + * 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, 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, 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 containig 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 usually expected that the client object passed to + * dns_client_request() was created via dns_client_create() and has its own + * managers and contexts. However, if the DNS_CLIENTREQOPT_ALLOWRUN flag is + * set in 'options', this function performs the synchronous service even if + * it does not have its own manager and context structures. + * + * 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_result_t +dns_client_update(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *zonename, dns_namelist_t *prerequisites, + dns_namelist_t *updates, isc_sockaddrlist_t *servers, + dns_tsec_t *tsec, unsigned int options); + +isc_result_t +dns_client_startupdate(dns_client_t *client, dns_rdataclass_t rdclass, + dns_name_t *zonename, dns_namelist_t *prerequisites, + dns_namelist_t *updates, isc_sockaddrlist_t *servers, + dns_tsec_t *tsec, unsigned int options, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_clientupdatetrans_t **transp); +/*%< + * Perform DNS dynamic update for 'updates' of the 'rdclass' class with + * optional 'prerequisites'. + * + * 'updates' are a list of names with associated RRsets to be updated. + * + * 'prerequisites' are a list of names with associated RRsets corresponding to + * the prerequisites of the updates. This is optional and can be NULL, in + * which case the prerequisite section of the update message will be empty. + * + * Both 'updates' and 'prerequisites' must be constructed as specified in + * RFC2136. + * + * 'zonename' is the name of the zone in which the updated names exist. + * This is optional and can be NULL. In this case, these functions internally + * identify the appropriate zone through some queries for the SOA RR starting + * with the first name in prerequisites or updates. + * + * 'servers' is a list of authoritative servers to which the update message + * should be sent. This is optional and can be NULL. In this case, these + * functions internally identify the appropriate primary server name and its + * addresses through some queries for the SOA RR (like the case of zonename) + * and supplemental A/AAAA queries for the server name. + * Note: The client module generally assumes the given addresses are of the + * primary server of the corresponding zone. It will work even if a secondary + * server address is specified as long as the server allows update forwarding, + * it is generally discouraged to include secondary server addresses unless + * there's strong reason to do so. + * + * 'tsec' is a transaction security object containing, e.g. a TSIG key for + * authenticating the update transaction (and the supplemental query/response + * transactions if the server is specified). This is optional and can be + * NULL, in which case the library tries the update without any transaction + * authentication. + * + * It is typically expected that the client object passed to + * dns_client_update() was created via dns_client_create() and has its own + * managers and contexts. However, if the DNS_CLIENTUPDOPT_ALLOWRUN flag is + * set in 'options', this function performs the synchronous service even if + * it does not have its own manager and context structures. + * + * dns_client_update() provides a synchronous service. This function blocks + * until the entire update procedure completes, including the additional + * queries when necessary. + * + * dns_client_startupdate() is an asynchronous version of dns_client_update(). + * It immediately returns (typically with *transp being set to a non-NULL + * pointer), and performs the update procedure through a set of internal + * events. All transactions including the additional query exchanges are + * performed as a separate event, so none of these events cause blocking + * operation. When the update procedure completes, the specified function + * 'action' will be called with the argument of a 'dns_clientupdateevent_t' + * structure. On return, '*transp' is set to an opaque transaction ID so that + * the caller can cancel this update process. + * + * DNS_CLIENTUPDOPT_TCP switches to the TCP (vs. UDP) transport. + * + * Requires: + * + *\li 'client' is a valid client. + * + *\li 'updates' != NULL. + * + *\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_cancelupdate(dns_clientupdatetrans_t *trans); +/*%< + * Cancel an ongoing dynamic update procedure started via + * dns_client_startupdate(). + * + * Notes: + * + *\li If the update procedure has not completed, post its UPDATEDONE + * event with a result code of #ISC_R_CANCELED. + * + * Requires: + * + *\li 'trans' is a valid transaction ID. + */ + +void +dns_client_destroyupdatetrans(dns_clientupdatetrans_t **transp); +/*%< + * Destroy dynamic update transaction identified by '*transp'. + * + * Requires: + * + *\li '*transp' is a valid transaction ID. + * + *\li The caller has received the UPDATEDONE event (either because the + * update completed or because dns_client_cancelupdate() was called). + * + * Ensures: + * + *\li *transp == NULL. + */ + +isc_result_t +dns_client_updaterec(dns_client_updateop_t op, dns_name_t *owner, + dns_rdatatype_t type, dns_rdata_t *source, + dns_ttl_t ttl, dns_name_t *target, + dns_rdataset_t *rdataset, dns_rdatalist_t *rdatalist, + dns_rdata_t *rdata, isc_mem_t *mctx); +/*%< + * TBD + */ + +void +dns_client_freeupdate(dns_name_t **namep); +/*%< + * TBD + */ + +isc_mem_t * +dns_client_mctx(dns_client_t *client); + +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..30e29c8 --- /dev/null +++ b/lib/dns/include/dns/clientinfo.h @@ -0,0 +1,82 @@ +/* + * 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 http://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 + +ISC_LANG_BEGINDECLS + +/***** + ***** Types + *****/ + +#define DNS_CLIENTINFO_VERSION 2 +typedef struct dns_clientinfo { + uint16_t version; + void *data; + void *dbversion; +} dns_clientinfo_t; + +typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client, + isc_sockaddr_t **addrp); + +#define DNS_CLIENTINFOMETHODS_VERSION 1 +#define DNS_CLIENTINFOMETHODS_AGE 0 + +typedef struct dns_clientinfomethods { + uint16_t version; + uint16_t age; + dns_clientinfo_sourceip_t sourceip; +} dns_clientinfomethods_t; + +/***** + ***** Methods + *****/ +void +dns_clientinfomethods_init(dns_clientinfomethods_t *methods, + dns_clientinfo_sourceip_t sourceip); + +void +dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp); + +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..0c951d7 --- /dev/null +++ b/lib/dns/include/dns/compress.h @@ -0,0 +1,291 @@ +/* + * 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 http://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 + +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 + +#define DNS_COMPRESS_READY 0x80000000 + +#define DNS_COMPRESS_TABLESIZE 64 +#define DNS_COMPRESS_INITIALNODES 16 + +typedef struct dns_compressnode dns_compressnode_t; + +struct dns_compressnode { + isc_region_t r; + uint16_t offset; + uint16_t count; + uint8_t labels; + dns_compressnode_t *next; +}; + +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 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..ae6ae36 --- /dev/null +++ b/lib/dns/include/dns/db.h @@ -0,0 +1,1690 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id$ */ + +#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 +#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, dns_name_t *name, + bool create, + dns_dbnode_t **nodep); + isc_result_t (*find)(dns_db_t *db, 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, dns_name_t *name, + unsigned int options, isc_stdtime_t now, + dns_dbnode_t **nodep, + dns_name_t *foundname, + 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, + 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, 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, dns_rpz_zones_t *rpzs, + dns_rpz_num_t rpz_num); + isc_result_t (*rpz_ready)(dns_db_t *db); + isc_result_t (*findnodeext)(dns_db_t *db, 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, 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); +} dns_dbmethods_t; + +typedef isc_result_t +(*dns_dbcreatefunc_t)(isc_mem_t *mctx, 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_ondestroy_t ondest; + 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 +/*@}*/ + +/*@{*/ +/*% + * 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 +/*@}*/ + +/***** + ***** Methods + *****/ + +/*** + *** Basic DB Methods + ***/ + +isc_result_t +dns_db_create(isc_mem_t *mctx, const char *db_type, 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 + */ + +isc_result_t +dns_db_ondestroy(dns_db_t *db, isc_task_t *task, isc_event_t **eventp); +/*%< + * Causes 'eventp' to be sent to be sent to 'task' when the database is + * destroyed. + * + * Note; ownership of the eventp is taken from the caller (and *eventp is + * set to NULL). The sender field of the event is set to 'db' before it is + * sent to the task. + */ + +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); + +isc_result_t +dns_db_load2(dns_db_t *db, const char *filename, dns_masterformat_t format); + +isc_result_t +dns_db_load3(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); + +isc_result_t +dns_db_dump2(dns_db_t *db, dns_dbversion_t *version, const char *filename, + dns_masterformat_t masterformat); +/*%< + * 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, dns_name_t *name, bool create, + dns_dbnode_t **nodep); + +isc_result_t +dns_db_findnodeext(dns_db_t *db, 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 + * retreive 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, 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, 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 retreive + * 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, dns_name_t *name, + 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 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 '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 + * + * \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 SIG. + * + * \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, + 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 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. + */ + +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 *interations, + 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 *bytes); +/*%< + * Get the number of records in the given version of the database as well + * as the number bytes used to store those records. + * + * 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 'bytes' 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, 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, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num); +/*%< + * 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); +/*%< + * 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. + * + * Requires: + * + * \li 'db' is a valid database + * \li 'db' does not have an update callback registered + * \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_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..8b50c8c --- /dev/null +++ b/lib/dns/include/dns/dbiterator.h @@ -0,0 +1,294 @@ +/* + * 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 http://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, 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, 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..8f139b9 --- /dev/null +++ b/lib/dns/include/dns/dbtable.h @@ -0,0 +1,158 @@ +/* + * 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 http://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, 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..50f6e9d --- /dev/null +++ b/lib/dns/include/dns/diff.h @@ -0,0 +1,279 @@ +/* + * 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 http://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; + +#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; + ISC_LIST(dns_difftuple_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, 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..476863a --- /dev/null +++ b/lib/dns/include/dns/dispatch.h @@ -0,0 +1,614 @@ +/* + * 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 http://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 + +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_DISPATCHOPT_FIXEDID 0x00000001U + +isc_result_t +dns_dispatchmgr_create(isc_mem_t *mctx, isc_entropy_t *entropy, + dns_dispatchmgr_t **mgrp); +/*%< + * Creates a new dispatchmgr object. + * + * Requires: + *\li "mctx" be a valid memory context. + * + *\li mgrp != NULL && *mgrp == NULL + * + *\li "entropy" may be NULL, in which case an insecure random generator + * will be used. If it is non-NULL, it must be a valid entropy + * source. + * + * 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, 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, 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, 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 +dns_dispatch_createtcp2(dns_dispatchmgr_t *mgr, isc_socket_t *sock, + isc_taskmgr_t *taskmgr, isc_sockaddr_t *localaddr, + 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, isc_sockaddr_t *destaddr, + isc_sockaddr_t *localaddr, dns_dispatch_t **dispp); +isc_result_t +dns_dispatch_gettcp2(dns_dispatchmgr_t *mgr, isc_sockaddr_t *destaddr, + isc_sockaddr_t *localaddr, bool *connected, + dns_dispatch_t **dispp); +/* + * Attempt to connect to a existing TCP connection (connection completed + * for dns_dispatch_gettcp()). + */ + + +isc_result_t +dns_dispatch_addresponse3(dns_dispatch_t *disp, unsigned int options, + 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); + +isc_result_t +dns_dispatch_addresponse2(dns_dispatch_t *disp, 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); + +isc_result_t +dns_dispatch_addresponse(dns_dispatch_t *disp, isc_sockaddr_t *dest, + isc_task_t *task, isc_taskaction_t action, void *arg, + uint16_t *idp, dns_dispentry_t **resp); +/*%< + * Add a response entry for this dispatch. + * + * "*idp" is filled in with the assigned message ID, and *resp is filled in + * 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..cc43f7c --- /dev/null +++ b/lib/dns/include/dns/dlz.h @@ -0,0 +1,339 @@ +/* + * Portions 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 http://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, dns_name_t *name, + 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. Otherwise it will 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 it should return a + * result code indicating 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, 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)(dns_name_t *signer, + dns_name_t *name, + 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, dns_name_t *name, + 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, + dns_name_t *signer, dns_name_t *name, 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..3719aa7 --- /dev/null +++ b/lib/dns/include/dns/dlz_dlopen.h @@ -0,0 +1,172 @@ +/* + * 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 http://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 diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h new file mode 100644 index 0000000..34c2ad5 --- /dev/null +++ b/lib/dns/include/dns/dns64.h @@ -0,0 +1,171 @@ +/* + * 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 http://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, isc_netaddr_t *prefix, + unsigned int prefixlen, 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 syntesis. 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-exluded 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/dnssec.h b/lib/dns/include/dns/dnssec.h new file mode 100644 index 0000000..0b6369c --- /dev/null +++ b/lib/dns/include/dns/dnssec.h @@ -0,0 +1,383 @@ +/* + * 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 http://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_remove; /*% metadata says *don't* publish */ + bool is_active; /*% key is already active */ + bool first_sign; /*% key is newly becoming active */ + 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 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(dns_name_t *name, 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(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(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, isc_mem_t *mctx, + dns_rdata_t *sigrdata); + +isc_result_t +dns_dnssec_verify2(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, isc_mem_t *mctx, + dns_rdata_t *sigrdata, dns_name_t *wild); + +isc_result_t +dns_dnssec_verify3(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, + dns_name_t *name, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys); + +isc_result_t +dns_dnssec_findzonekeys2(dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, dns_name_t *name, + const char *directory, isc_mem_t *mctx, + unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys); + +isc_result_t +dns_dnssec_findzonekeys3(dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, 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, 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, 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. + */ + +isc_result_t +dns_dnssec_findmatchingkeys(dns_name_t *origin, const char *directory, + isc_mem_t *mctx, dns_dnsseckeylist_t *keylist); + +isc_result_t +dns_dnssec_findmatchingkeys2(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'. + * + * 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(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, dns_name_t *origin, + dns_ttl_t hint_ttl, dns_diff_t *diff, bool allzsk, + 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. + * + * If 'allzsk' is true, we are allowing KSK-flagged keys to be used as + * ZSKs. + * + * '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. + */ + +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..279f7fd --- /dev/null +++ b/lib/dns/include/dns/dnstap.h @@ -0,0 +1,377 @@ +/* + * 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 http://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 + +#ifdef HAVE_DNSTAP +#include +#include +#include +#else +struct fstrm_iothr_options; +#endif /* HAVE_DNSTAP */ + +#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_QUERY \ + (DNS_DTTYPE_SQ|DNS_DTTYPE_CQ|DNS_DTTYPE_AQ|\ + DNS_DTTYPE_RQ|DNS_DTTYPE_FQ|DNS_DTTYPE_TQ) +#define DNS_DTTYPE_RESPONSE \ + (DNS_DTTYPE_SR|DNS_DTTYPE_CR|DNS_DTTYPE_AR|\ + DNS_DTTYPE_RR|DNS_DTTYPE_FR|DNS_DTTYPE_TR) +#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; + + Dnstap__Dnstap *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, 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. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'path' is a valid C string. + * + *\li 'fopt' 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_reopen(dns_dtenv_t *env, int roll); +/*%< + * Reopens files established by dns_dt_create(). + * + * 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() must be called in task exclusive mode. + * + * 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_shutdown(void); +/*%< + * Shuts down dnstap and frees global resources. This function must only + * be called immediately before server shutdown. + */ + +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..4ea5a0d --- /dev/null +++ b/lib/dns/include/dns/ds.h @@ -0,0 +1,52 @@ +/* + * 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 http://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 + +#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_buildrdata(dns_name_t *owner, dns_rdata_t *key, + unsigned int digest_type, unsigned char *buffer, + dns_rdata_t *rdata); +/*%< + * Build the rdata of a DS record. + * + * Requires: + *\li key Points to a valid DNS KEY record. + *\li buffer Points to a temporary 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..b029ecd --- /dev/null +++ b/lib/dns/include/dns/dsdigest.h @@ -0,0 +1,71 @@ +/* + * 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 http://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/DLV 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/DLV 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..d5fedb5 --- /dev/null +++ b/lib/dns/include/dns/dyndb.h @@ -0,0 +1,161 @@ +/* + * 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 http://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 intializing 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; + bool *refvar; +}; + +#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 + +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' must be set to the driver instance handle if the functino + * 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..6d6c880 --- /dev/null +++ b/lib/dns/include/dns/ecdb.h @@ -0,0 +1,48 @@ +/* + * 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 http://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/edns.h b/lib/dns/include/dns/edns.h new file mode 100644 index 0000000..018c89d --- /dev/null +++ b/lib/dns/include/dns/edns.h @@ -0,0 +1,27 @@ +/* + * 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 http://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 + +#endif diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h new file mode 100644 index 0000000..bd051c1 --- /dev/null +++ b/lib/dns/include/dns/events.h @@ -0,0 +1,85 @@ +/* + * 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 http://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_STARTUPDATE (ISC_EVENTCLASS_DNS + 58) + +#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..6a9b555 --- /dev/null +++ b/lib/dns/include/dns/fixedname.h @@ -0,0 +1,84 @@ +/* + * 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 http://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..eb0e045 --- /dev/null +++ b/lib/dns/include/dns/forward.h @@ -0,0 +1,139 @@ +/* + * 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 http://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, dns_name_t *name, + dns_forwarderlist_t *fwdrs, dns_fwdpolicy_t policy); +isc_result_t +dns_fwdtable_add(dns_fwdtable_t *fwdtable, 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, 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, dns_name_t *name, + 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 + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + */ + +isc_result_t +dns_fwdtable_find2(dns_fwdtable_t *fwdtable, 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..d9028e5 --- /dev/null +++ b/lib/dns/include/dns/geoip.h @@ -0,0 +1,115 @@ +/* + * 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 http://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/acl.h + * \brief + * Address match list handling. + */ + +/*** + *** Imports + ***/ + +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_GEOIP +#include +#else +typedef void GeoIP; +#endif + +/*** + *** Types + ***/ + +typedef enum { + dns_geoip_countrycode, + dns_geoip_countrycode3, + dns_geoip_countryname, + dns_geoip_region, + dns_geoip_regionname, + dns_geoip_country_code, + dns_geoip_country_code3, + dns_geoip_country_name, + 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_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; + GeoIP *db; + union { + char as_string[256]; + int as_int; + }; +} dns_geoip_elem_t; + +typedef struct dns_geoip_databases { + GeoIP *country_v4; /* DB 1 */ + GeoIP *city_v4; /* DB 2 or 6 */ + GeoIP *region; /* DB 3 or 7 */ + GeoIP *isp; /* DB 4 */ + GeoIP *org; /* DB 5 */ + GeoIP *as; /* DB 9 */ + GeoIP *netspeed; /* DB 10 */ + GeoIP *domain; /* DB 11 */ + GeoIP *country_v6; /* DB 12 */ + GeoIP *city_v6; /* DB 30 or 31 */ +} dns_geoip_databases_t; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +bool +dns_geoip_match(const isc_netaddr_t *reqaddr, uint8_t *scope, + const dns_geoip_databases_t *geoip, + const dns_geoip_elem_t *elt); + +void +dns_geoip_shutdown(void); + +ISC_LANG_ENDDECLS +#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..5b012b4 --- /dev/null +++ b/lib/dns/include/dns/ipkeylist.h @@ -0,0 +1,87 @@ +/* + * 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 http://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 successs + * \li #ISC_R_NOMEMORY if there's no memory, ipkeylist is left untoched + */ + +#endif diff --git a/lib/dns/include/dns/iptable.h b/lib/dns/include/dns/iptable.h new file mode 100644 index 0000000..26a6ad5 --- /dev/null +++ b/lib/dns/include/dns/iptable.h @@ -0,0 +1,72 @@ +/* + * 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 http://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 + +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, isc_netaddr_t *addr, + uint16_t bitlen, bool pos); +isc_result_t +dns_iptable_addprefix2(dns_iptable_t *tab, isc_netaddr_t *addr, + uint16_t bitlen, bool pos, + bool is_ecs); +/* + * 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..8bcb551 --- /dev/null +++ b/lib/dns/include/dns/journal.h @@ -0,0 +1,298 @@ +/* + * 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 http://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 + +/*** + *** 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. + */ + +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); +/*%< + * Prepare to iterate over the transactions that will bring the database + * from SOA serial number 'begin_serial' to 'end_serial'. + * + * 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(isc_mem_t *mctx, dns_db_t *db, unsigned int options, + const char *filename); +/*%< + * 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 'mctx' is a valid memory context. + *\li 'db' is a valid database which does not have a version + * open for writing. + *\li 'filename' is the name of the journal file belonging to 'db'. + * + * Returns: + *\li DNS_R_NOJOURNAL when journal does not exist. + *\li ISC_R_NOTFOUND when current serial in not in journal. + *\li ISC_R_RANGE when current serial in not in journals range. + *\li ISC_R_SUCCESS journal has been applied successfully to database. + * others + */ + +isc_result_t +dns_journal_print(isc_mem_t *mctx, 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 target_size); +/*%< + * Attempt to compact the journal if it is greater that 'target_size'. + * Changes from 'serial' onwards will be preserved. If the journal + * exists and is non-empty 'serial' must exist in the journal. + */ + +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/keydata.h b/lib/dns/include/dns/keydata.h new file mode 100644 index 0000000..f12932a --- /dev/null +++ b/lib/dns/include/dns/keydata.h @@ -0,0 +1,51 @@ +/* + * 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 http://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..6d41a1c --- /dev/null +++ b/lib/dns/include/dns/keyflags.h @@ -0,0 +1,47 @@ +/* + * 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 http://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/keytable.h b/lib/dns/include/dns/keytable.h new file mode 100644 index 0000000..a94fa3e --- /dev/null +++ b/lib/dns/include/dns/keytable.h @@ -0,0 +1,439 @@ +/* + * 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 http://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 + +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, + dst_key_t **keyp); +/*%< + * Add '*keyp' to 'keytable' (using the name in '*keyp'). + * The value of keynode->managed is set to 'managed' + * + * Notes: + * + *\li Ownership of *keyp is transferred to the keytable. + *\li If the key already exists in the table, ISC_R_EXISTS is + * returned and the new key is freed. + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + * + *\li keyp != NULL && *keyp is a valid dst_key_t *. + * + * Ensures: + * + *\li On success, *keyp == NULL + * + * 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, 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, dns_name_t *keyname); +/*%< + * Delete node(s) 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_deletekeynode(dns_keytable_t *keytable, dst_key_t *dstkey); +/*%< + * Delete node(s) from 'keytable' containing copies of the key pointed + * to by 'dstkey' + * + * Requires: + * + *\li 'keytable' points to a valid keytable. + *\li 'dstkey' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_keytable_find(dns_keytable_t *keytable, dns_name_t *keyname, + dns_keynode_t **keynodep); +/*%< + * Search for the first instance of a key named 'name' in 'keytable', + * without regard to keyid and algorithm. Use dns_keytable_nextkeynode() + * to find subsequent instances. + * + * 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_nextkeynode(dns_keytable_t *keytable, dns_keynode_t *keynode, + dns_keynode_t **nextnodep); +/*%< + * Return for the next key after 'keynode' in 'keytable', without regard to + * keyid and algorithm. + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'keynode' is a valid keynode. + * + *\li nextnodep != NULL && *nextnodep == NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + * + *\li Any other result indicates an error. + */ + +isc_result_t +dns_keytable_findkeynode(dns_keytable_t *keytable, dns_name_t *name, + dns_secalg_t algorithm, dns_keytag_t tag, + dns_keynode_t **keynodep); +/*%< + * Search for a key named 'name', matching 'algorithm' and 'tag' in + * 'keytable'. This finds the first instance which matches. Use + * dns_keytable_findnextkeynode() to find other instances. + * + * 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 DNS_R_PARTIALMATCH the name existed in the keytable. + *\li ISC_R_NOTFOUND + * + *\li Any other result indicates an error. + */ + +isc_result_t +dns_keytable_findnextkeynode(dns_keytable_t *keytable, dns_keynode_t *keynode, + dns_keynode_t **nextnodep); +/*%< + * Search for the next key with the same properties as 'keynode' in + * 'keytable' as found by dns_keytable_findkeynode(). + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'keynode' is a valid keynode. + * + *\li nextnodep != NULL && *nextnodep == 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, 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_attachkeynode(dns_keytable_t *keytable, dns_keynode_t *source, + dns_keynode_t **target); +/*%< + * Attach a keynode and and increment the active_nodes counter in a + * corresponding keytable. + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li 'source' is a valid keynode. + * + *\li 'target' is not null and '*target' is null. + */ + +void +dns_keytable_detachkeynode(dns_keytable_t *keytable, + dns_keynode_t **keynodep); +/*%< + * Give back a keynode found via dns_keytable_findkeynode(). + * + * Requires: + * + *\li 'keytable' is a valid keytable. + * + *\li *keynodep is a valid keynode returned by a call to + * dns_keytable_findkeynode(). + * + * Ensures: + * + *\li *keynodep == NULL + */ + +isc_result_t +dns_keytable_issecuredomain(dns_keytable_t *keytable, 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' + */ + +dst_key_t * +dns_keynode_key(dns_keynode_t *keynode); +/*%< + * Get the DST key associated with keynode. + */ + +bool +dns_keynode_managed(dns_keynode_t *keynode); +/*%< + * Is this flagged as a managed key? + */ + +isc_result_t +dns_keynode_create(isc_mem_t *mctx, dns_keynode_t **target); +/*%< + * Allocate space for a keynode + */ + +void +dns_keynode_attach(dns_keynode_t *source, dns_keynode_t **target); +/*%< + * Attach keynode 'source' to '*target' + */ + +void +dns_keynode_detach(isc_mem_t *mctx, dns_keynode_t **target); +/*%< + * Detach a single keynode, without touching any keynodes that + * may be pointed to by its 'next' pointer + */ + +void +dns_keynode_detachall(isc_mem_t *mctx, dns_keynode_t **target); +/*%< + * Detach a keynode and all its succesors. + */ + +isc_result_t +dns_keytable_forall(dns_keytable_t *keytable, + void (*func)(dns_keytable_t *, dns_keynode_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..ad17261 --- /dev/null +++ b/lib/dns/include/dns/keyvalues.h @@ -0,0 +1,113 @@ +/* + * 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 http://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 DNS_KEYALG_RSAMD5 +#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 */ + +/* 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_DSASIGSIZE 41 +#define DNS_SIG_DSAMINBITS 512 +#define DNS_SIG_DSAMAXBITS 1024 +#define DNS_SIG_DSAMINBYTES 213 +#define DNS_SIG_DSAMAXBYTES 405 + +#define DNS_SIG_GOSTSIGSIZE 64 + +#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..d63efdd --- /dev/null +++ b/lib/dns/include/dns/lib.h @@ -0,0 +1,52 @@ +/* + * 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 http://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; +LIBDNS_EXTERNAL_DATA extern isc_msgcat_t *dns_msgcat; + +void +dns_lib_initmsgcat(void); +/*%< + * Initialize the DNS library's message catalog, dns_msgcat, if it + * has not already been initialized. + */ + +isc_result_t +dns_lib_init(void); +/*%< + * A set of initialization procedure 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/log.h b/lib/dns/include/dns/log.h new file mode 100644 index 0000000..278ada0 --- /dev/null +++ b/lib/dns/include/dns/log.h @@ -0,0 +1,111 @@ +/* + * 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 http://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]) + +/* 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_RBTDB64 (&dns_modules[2]) +#define DNS_LOGMODULE_RBT (&dns_modules[3]) +#define DNS_LOGMODULE_RDATA (&dns_modules[4]) +#define DNS_LOGMODULE_MASTER (&dns_modules[5]) +#define DNS_LOGMODULE_MESSAGE (&dns_modules[6]) +#define DNS_LOGMODULE_CACHE (&dns_modules[7]) +#define DNS_LOGMODULE_CONFIG (&dns_modules[8]) +#define DNS_LOGMODULE_RESOLVER (&dns_modules[9]) +#define DNS_LOGMODULE_ZONE (&dns_modules[10]) +#define DNS_LOGMODULE_JOURNAL (&dns_modules[11]) +#define DNS_LOGMODULE_ADB (&dns_modules[12]) +#define DNS_LOGMODULE_XFER_IN (&dns_modules[13]) +#define DNS_LOGMODULE_XFER_OUT (&dns_modules[14]) +#define DNS_LOGMODULE_ACL (&dns_modules[15]) +#define DNS_LOGMODULE_VALIDATOR (&dns_modules[16]) +#define DNS_LOGMODULE_DISPATCH (&dns_modules[17]) +#define DNS_LOGMODULE_REQUEST (&dns_modules[18]) +#define DNS_LOGMODULE_MASTERDUMP (&dns_modules[19]) +#define DNS_LOGMODULE_TSIG (&dns_modules[20]) +#define DNS_LOGMODULE_TKEY (&dns_modules[21]) +#define DNS_LOGMODULE_SDB (&dns_modules[22]) +#define DNS_LOGMODULE_DIFF (&dns_modules[23]) +#define DNS_LOGMODULE_HINTS (&dns_modules[24]) +#define DNS_LOGMODULE_ACACHE (&dns_modules[25]) +#define DNS_LOGMODULE_DLZ (&dns_modules[26]) +#define DNS_LOGMODULE_DNSSEC (&dns_modules[27]) +#define DNS_LOGMODULE_CRYPTO (&dns_modules[28]) +#define DNS_LOGMODULE_PACKETS (&dns_modules[29]) +#define DNS_LOGMODULE_NTA (&dns_modules[30]) +#define DNS_LOGMODULE_DYNDB (&dns_modules[31]) +#define DNS_LOGMODULE_DNSTAP (&dns_modules[32]) +#define DNS_LOGMODULE_SSU (&dns_modules[33]) + +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..1af58fe --- /dev/null +++ b/lib/dns/include/dns/lookup.h @@ -0,0 +1,130 @@ +/* + * 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 http://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, 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..61c93b0 --- /dev/null +++ b/lib/dns/include/dns/master.h @@ -0,0 +1,382 @@ +/* + * 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 http://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 + +/* + * 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, + dns_rdatacallbacks_t *callbacks, + isc_mem_t *mctx); + +isc_result_t +dns_master_loadfile2(const char *master_file, + dns_name_t *top, + dns_name_t *origin, + dns_rdataclass_t zclass, + unsigned int options, + dns_rdatacallbacks_t *callbacks, + isc_mem_t *mctx, + dns_masterformat_t format); + +isc_result_t +dns_master_loadfile3(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_mem_t *mctx, + dns_masterformat_t format); + +isc_result_t +dns_master_loadfile4(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); + +isc_result_t +dns_master_loadfile5(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, + 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_loadfileinc2(const char *master_file, + 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, + dns_masterformat_t format); + +isc_result_t +dns_master_loadfileinc3(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, isc_mem_t *mctx, + dns_masterformat_t format); + +isc_result_t +dns_master_loadfileinc4(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); + +isc_result_t +dns_master_loadfileinc5(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..b01ca08 --- /dev/null +++ b/lib/dns/include/dns/masterdump.h @@ -0,0 +1,428 @@ +/* + * 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 http://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 + +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 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 + * dns_master_indentstr (defaults to 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; + +/*% + * The default indent string to prepend lines with when using + * styleflag DNS_STYLEFLAG_INDENT or DNS_STYLEFLAG_YAML. + * This is set to "\t" by default. The indent is repeated + * 'dns_master_indent' times. This precedes everything else + * on the line, including comment characters (;). + * + * XXX: Changing this value at runtime is not thread-safe. + */ +LIBDNS_EXTERNAL_DATA extern const char *dns_master_indentstr; + +/*% + * The number of copies of the indent string to put at the beginning + * of the line when using DNS_STYLEFLAG_INDENT or DNS_STYLEFLAG_YAML. + * This is set to 1 by default. It is increased and decreased + * to adjust indentation levels when producing YAML output. + * + * XXX: This is not thread-safe. + */ +LIBDNS_EXTERNAL_DATA extern unsigned int dns_master_indent; + +/*** + *** 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_dumptostreaminc(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, FILE *f); + +isc_result_t +dns_master_dumptostream2(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, + const dns_master_style_t *style, + dns_masterformat_t format, FILE *f); + +isc_result_t +dns_master_dumptostream3(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). + * + * dns_master_dumptostream() is an old form of dns_master_dumptostream3(), + * which always specifies the dns_masterformat_text format. + * dns_master_dumptostream2() is an old form which always specifies + * a NULL header. + * + * 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_CONTINUE dns_master_dumptostreaminc() only. + *\li ISC_R_NOMEMORY + *\li Any database or rrset iterator error. + *\li Any dns_rdata_totext() error code. + */ +/*@}*/ + +/*@{*/ +isc_result_t +dns_master_dumpinc(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); + +isc_result_t +dns_master_dumpinc2(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); + +isc_result_t +dns_master_dumpinc3(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); + +isc_result_t +dns_master_dump2(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); + +isc_result_t +dns_master_dump3(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). + * + * dns_master_dumpinc() and dns_master_dump() are old forms of _dumpinc3() + * and _dump3(), respectively, which always specify the dns_masterformat_text + * format. dns_master_dumpinc2() and dns_master_dump2() are old forms which + * always specify a NULL header. + * + * 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_CONTINUE dns_master_dumpinc() only. + *\li ISC_R_NOMEMORY + *\li Any database or rrset iterator error. + *\li Any dns_rdata_totext() error code. + */ +/*@}*/ + +isc_result_t +dns_master_rdatasettotext(dns_name_t *owner_name, + dns_rdataset_t *rdataset, + const dns_master_style_t *style, + 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(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, 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, 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, unsigned int 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, + isc_mem_t *mctx); + +isc_result_t +dns_master_stylecreate2(dns_master_style_t **style, unsigned int 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..334123b --- /dev/null +++ b/lib/dns/include/dns/message.h @@ -0,0 +1,1436 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifndef DNS_MESSAGE_H +#define DNS_MESSAGE_H 1 + +/*** + *** Imports + ***/ + +#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; + * name = dns_message_gettempname(message, &name); + * dns_name_init(name, NULL); + * 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_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_PAD 12 /*%< PAD opt code */ +#define DNS_OPT_KEY_TAG 14 /*%< Key tag opt code */ + +/*%< Experimental options [65001...65534] as per RFC6891 */ + +/*%< The number of EDNS options we know about. */ +#define DNS_EDNSOPTIONS 5 + +#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. */ +#ifdef ALLOW_FILTER_AAAA +#define DNS_MESSAGERENDER_FILTER_AAAA 0x0020 /*%< filter AAAA records */ +#endif + +typedef struct dns_msgblock dns_msgblock_t; + +struct dns_message { + /* public from here down */ + unsigned int magic; + + 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) */ + + 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; + const void * order_arg; +}; + +struct dns_ednsopt { + uint16_t code; + uint16_t length; + unsigned char *value; +}; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +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_destroy() followed by dns_message_allocate(), + * 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_destroy(dns_message_t **msgp); +/*%< + * Destroy all state in the message. + * + * Requires: + * + *\li 'msgp' be valid. + * + * Ensures: + *\li '*msgp' == NULL + */ + +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_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, + 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(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(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. + * + * It is the caller's responsibility to initialize this name. + * + * 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_gettempoffsets(dns_message_t *msg, dns_offsets_t **item); +/*%< + * Return an offsets array that can be used for any temporary purpose, + * such as attaching to a temporary name. The offsets 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_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, 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, 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, + const void *order_arg); +/*%< + * 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 'arg' as arguments. If 'order' and + * 'order_arg' are NULL, a default order is used. + * + * Requires: + *\li msg be a valid message. + *\li order_arg is NULL if and only if order is 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, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx); +void +dns_message_logpacket2(dns_message_t *message, + const char *description, 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, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx); +void +dns_message_logfmtpacket2(dns_message_t *message, + const char *description, 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. + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_MESSAGE_H */ diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h new file mode 100644 index 0000000..bbb663b --- /dev/null +++ b/lib/dns/include/dns/name.h @@ -0,0 +1,1423 @@ +/* + * 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 http://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 dns_name_t *dns_rootname; +LIBDNS_EXTERNAL_DATA extern 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} \ +} + +/*% + * 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, + bool absolute); + +/*** + *** 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(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(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 + */ + +unsigned int +dns_name_hashbylabel(dns_name_t *name, bool case_sensitive); +/*%< + * Provide a hash value for 'name', where the hash value is the sum + * of the hash values of each label. This function should only be used + * when incremental hashing is necessary, for example, during RBT + * traversal. It is not currently used in BIND. Generally, + * dns_name_fullhash() is the correct function to use for name + * hashing. + * + * 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 comparision. + * + * \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 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(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); +/*%< + * 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(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(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(dns_name_t *prefix, 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(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). + */ + +isc_result_t +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(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(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(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(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(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); +/*%< + * Makes 'dest' refer to a copy of the name in 'source'. The data are + * either copied to 'target' or the dedicated buffer in 'dest'. + * + * Requires: + * \li 'source' is a valid name. + * + * \li 'dest' is an initialized name with a dedicated buffer. + * + * \li 'target' is NULL or an initialized buffer. + * + * \li Either dest has a dedicated buffer or target != NULL. + * + * Ensures: + * + *\li On success, the used space in target is updated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE + */ + +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. + */ + +void +dns_name_destroy(void); +/*%< + * Cleanup dns_name_settotextfilter() / dns_name_totext() state. + * + * This should be called as part of the final cleanup process. + * + * Note: dns_name_settotextfilter(NULL); should be called for all + * threads which have called dns_name_settotextfilter() with a + * non-NULL argument prior to calling dns_name_destroy(); + */ + +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..2942c26 --- /dev/null +++ b/lib/dns/include/dns/ncache.h @@ -0,0 +1,186 @@ +/* + * 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 http://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 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 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..18cec17 --- /dev/null +++ b/lib/dns/include/dns/nsec.h @@ -0,0 +1,111 @@ +/* + * 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 http://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, 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, + 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, dns_name_t *name, + 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 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..07d2bf0 --- /dev/null +++ b/lib/dns/include/dns/nsec3.h @@ -0,0 +1,271 @@ +/* + * 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 http://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 + +#define DNS_NSEC3_SALTSIZE 255 + +/* + * 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_hashname(dns_fixedname_t *result, + unsigned char rethash[NSEC3_MAX_HASH_LENGTH], + size_t *hash_length, dns_name_t *name, 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, + 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, + 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, + 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, 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, dns_name_t *name, + dns_diff_t *diff); + +isc_result_t +dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version, 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. + */ + +isc_result_t +dns_nsec3_maxiterations(dns_db_t *db, dns_dbversion_t *version, + isc_mem_t *mctx, unsigned int *iterationsp); +/*%< + * Find the maximum permissible number of iterations allowed based on + * the key strength. + * + * Requires: + * 'db' to be valid. + * 'version' to be valid or NULL. + * 'mctx' to be valid. + * 'iterationsp' to be non NULL. + */ + +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, dns_name_t* name, + 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..edbaa77 --- /dev/null +++ b/lib/dns/include/dns/nta.h @@ -0,0 +1,204 @@ +/* + * 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 http://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 + +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; + /* Locked by rwlock. */ + uint32_t references; + dns_rbt_t *table; +}; + +#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, 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 false, + * then the name will be checked periodically to see if it's bogus; + * if not, then the NTA will be allowed to expire early. + * + * 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, 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, + dns_name_t *name, 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, isc_buffer_t **buf); +/*%< + * Dump the NTA table to buffer at 'buf' + * + * 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. + */ +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..ff9e82e --- /dev/null +++ b/lib/dns/include/dns/opcode.h @@ -0,0 +1,44 @@ +/* + * 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 http://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..e7c0516 --- /dev/null +++ b/lib/dns/include/dns/order.h @@ -0,0 +1,92 @@ +/* + * 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 http://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, 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, 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..58a1354 --- /dev/null +++ b/lib/dns/include/dns/peer.h @@ -0,0 +1,263 @@ +/* + * 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 http://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 + +#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; + uint32_t refs; + + isc_mem_t *mem; + + ISC_LIST(dns_peer_t) elements; +}; + +struct dns_peer { + unsigned int magic; + uint32_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; + 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 */ + 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, 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, isc_netaddr_t *ipaddr, dns_peer_t **peer); + +isc_result_t +dns_peer_newprefix(isc_mem_t *mem, 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_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_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..471db97 --- /dev/null +++ b/lib/dns/include/dns/portlist.h @@ -0,0 +1,100 @@ +/* + * 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 http://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..8604746 --- /dev/null +++ b/lib/dns/include/dns/private.h @@ -0,0 +1,67 @@ +/* + * 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 http://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 diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h new file mode 100644 index 0000000..67ac3e4 --- /dev/null +++ b/lib/dns/include/dns/rbt.h @@ -0,0 +1,1144 @@ +/* + * 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 http://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 + +#define DNS_RBT_USEHASH 1 + +/*@{*/ +/*% + * 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 +/*@}*/ + +#ifndef DNS_RBT_USEISCREFCOUNT +#ifdef ISC_REFCOUNT_HAVEATOMIC +#define DNS_RBT_USEISCREFCOUNT 1 +#endif +#endif + +#define DNS_RBT_USEMAGIC 1 + +/* + * These should add up to 30. + */ +#define DNS_RBT_LOCKLENGTH 10 +#define DNS_RBT_REFLENGTH 20 + +#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 +#define DNS_RBTNODE_VALID(n) true +#endif + +/*% + * 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 + /*@{*/ + /*! + * 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; + + /* node needs to be cleaned from rpz */ + unsigned int rpz : 1; + unsigned int :0; /* end of bitfields c/o tree lock */ + +#ifdef DNS_RBT_USEHASH + unsigned int hashval; + dns_rbtnode_t *uppernode; + dns_rbtnode_t *hashnext; +#endif + 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; + unsigned int :0; /* start of bitfields c/o node lock */ + unsigned int dirty:1; + unsigned int wild:1; + unsigned int locknum:DNS_RBT_LOCKLENGTH; +#ifndef DNS_RBT_USEISCREFCOUNT + unsigned int references:DNS_RBT_REFLENGTH; +#endif + unsigned int :0; /* end of bitfields c/o node lock */ +#ifdef DNS_RBT_USEISCREFCOUNT + isc_refcount_t references; /* note that this is not in the bitfield */ +#endif + /*@}*/ +}; + +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; + isc_mem_t * mctx; + /*% + * 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, 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, 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, dns_name_t *name, bool recurse); +/*%< + * Delete 'name' from the tree of trees. + * + * Notes: + *\li When 'name' is removed, if recurse is true then all of its + * subnames are removed too. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li dns_name_isabsolute(name) == TRUE + * + * Ensures: + *\li 'name' is not altered in any way. + * + *\li Does NOT ensure that any external references to nodes in the tree + * are unaffected by node joins. + * + *\li If result is ISC_R_SUCCESS: + * 'name' does not appear in the tree with data; however, + * the node for the name might still exist which can be + * found with dns_rbt_findnode (but not dns_rbt_findname). + * + *\li If result is ISC_R_NOTFOUND: + * 'name' does not appear in the tree with data, because + * it did not appear in the tree before the function was called. + * + *\li If result is something else: + * See result codes for dns_rbt_findnode (if it fails, the + * node is not deleted) or dns_rbt_deletenode (if it fails, + * the node is deleted, but the tree is not optimized when + * it could have been). + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOTFOUND No match + *\li something_else Any return code from dns_rbt_findnode except + * DNS_R_PARTIALMATCH (which causes ISC_R_NOTFOUND + * to be returned instead), and any code from + * dns_rbt_deletenode. + */ + +isc_result_t +dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, bool recurse); +/*%< + * Delete 'node' from the tree of trees. + * + * Notes: + *\li When 'node' is removed, if recurse is true then all nodes + * in levels down from it are removed too. + * + * Requires: + *\li rbt is a valid rbt manager. + *\li node != NULL. + * + * Ensures: + *\li Does NOT ensure that any external references to nodes in the tree + * are unaffected by node joins. + * + *\li If result is ISC_R_SUCCESS: + * 'node' does not appear in the tree with data; however, + * the node might still exist if it serves as a pointer to + * a lower tree level as long as 'recurse' was false, hence + * the node could can be found with dns_rbt_findnode when + * that function's empty_data_ok parameter is true. + * + *\li If result is ISC_R_NOMEMORY or ISC_R_NOSPACE: + * The node was deleted, but the tree structure was not + * optimized. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource Limit: Out of Memory when joining nodes. + *\li #ISC_R_NOSPACE dns_name_concatenate failed when joining nodes. + */ + +void +dns_rbt_namefromnode(dns_rbtnode_t *node, dns_name_t *name); +/*%< + * Convert the sequence of labels stored at 'node' into a 'name'. + * + * Notes: + *\li This function does not return the full name, from the root, but + * just the labels at the indicated node. + * + *\li The name data pointed to by 'name' is the information stored + * in the node, not a copy. Altering the data at this pointer + * will likely cause grief. + * + * Requires: + * \li name->offsets == NULL + * + * Ensures: + * \li 'name' is DNS_NAMEATTR_READONLY. + * + * \li 'name' will point directly to the labels stored after the + * dns_rbtnode_t struct. + * + * \li 'name' will have offsets that also point to the information stored + * as part of the node. + */ + +isc_result_t +dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name); +/*%< + * Like dns_rbt_namefromnode, but returns the full name from the root. + * + * Notes: + * \li Unlike dns_rbt_namefromnode, the name will not point directly + * to node data. Rather, dns_name_concatenate will be used to copy + * the name data from each node into the 'name' argument. + * + * Requires: + * \li name != NULL + * \li name has a dedicated buffer. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOSPACE (possible via dns_name_concatenate) + * \li DNS_R_NAMETOOLONG (possible via dns_name_concatenate) + */ + +char * +dns_rbt_formatnodename(dns_rbtnode_t *node, char *printname, + unsigned int size); +/*%< + * Format the full name of a node for printing, using dns_name_format(). + * + * Notes: + * \li 'size' is the length of the printname buffer. This should be + * DNS_NAME_FORMATSIZE or larger. + * + * Requires: + * \li node and printname are not NULL. + * + * Returns: + * \li The 'printname' pointer. + */ + +unsigned int +dns_rbt_nodecount(dns_rbt_t *rbt); +/*%< + * Obtain the number of nodes in the tree of trees. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +size_t +dns_rbt_hashsize(dns_rbt_t *rbt); +/*%< + * Obtain the current number of buckets in the 'rbt' hash table. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +void +dns_rbt_destroy(dns_rbt_t **rbtp); +isc_result_t +dns_rbt_destroy2(dns_rbt_t **rbtp, unsigned int quantum); +/*%< + * Stop working with a red-black tree of trees. + * If 'quantum' is zero then the entire tree will be destroyed. + * If 'quantum' is non zero then up to 'quantum' nodes will be destroyed + * allowing the rbt to be incrementally destroyed by repeated calls to + * dns_rbt_destroy2(). Once dns_rbt_destroy2() has been called no other + * operations than dns_rbt_destroy()/dns_rbt_destroy2() should be + * performed on the tree of trees. + * + * Requires: + * \li *rbt is a valid rbt manager. + * + * Ensures on ISC_R_SUCCESS: + * \li All space allocated by the RBT library has been returned. + * + * \li *rbt is invalidated as an rbt manager. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_QUOTA if 'quantum' nodes have been destroyed. + */ + +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, isc_mem_t *mctx); +/*%< + * Initialize 'chain'. + * + * Requires: + *\li 'chain' is a valid pointer. + * + *\li 'mctx' is a valid memory context. + * + * 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. + */ + +/* + * Wrapper macros for manipulating the rbtnode reference counter: + * Since we selectively use isc_refcount_t for the reference counter of + * a rbtnode, operations on the counter depend on the actual type of it. + * The following macros provide a common interface to these operations, + * hiding the back-end. The usage is the same as that of isc_refcount_xxx(). + */ +#ifdef DNS_RBT_USEISCREFCOUNT +#define dns_rbtnode_refinit(node, n) \ + do { \ + isc_refcount_init(&(node)->references, (n)); \ + } while (0) +#define dns_rbtnode_refdestroy(node) \ + do { \ + isc_refcount_destroy(&(node)->references); \ + } while (0) +#define dns_rbtnode_refcurrent(node) \ + isc_refcount_current(&(node)->references) +#define dns_rbtnode_refincrement0(node, refs) \ + do { \ + isc_refcount_increment0(&(node)->references, (refs)); \ + } while (0) +#define dns_rbtnode_refincrement(node, refs) \ + do { \ + isc_refcount_increment(&(node)->references, (refs)); \ + } while (0) +#define dns_rbtnode_refdecrement(node, refs) \ + do { \ + isc_refcount_decrement(&(node)->references, (refs)); \ + } while (0) +#else /* DNS_RBT_USEISCREFCOUNT */ +#define dns_rbtnode_refinit(node, n) ((node)->references = (n)) +#define dns_rbtnode_refdestroy(node) ISC_REQUIRE((node)->references == 0) +#define dns_rbtnode_refcurrent(node) ((node)->references) + +#if (__STDC_VERSION__ + 0) >= 199901L || defined __GNUC__ +static inline void +dns_rbtnode_refincrement0(dns_rbtnode_t *node, unsigned int *refs) { + node->references++; + if (refs != NULL) + *refs = node->references; +} + +static inline void +dns_rbtnode_refincrement(dns_rbtnode_t *node, unsigned int *refs) { + ISC_REQUIRE(node->references > 0); + node->references++; + if (refs != NULL) + *refs = node->references; +} + +static inline void +dns_rbtnode_refdecrement(dns_rbtnode_t *node, unsigned int *refs) { + ISC_REQUIRE(node->references > 0); + node->references--; + if (refs != NULL) + *refs = node->references; +} +#else +#define dns_rbtnode_refincrement0(node, refs) \ + do { \ + unsigned int *_tmp = (unsigned int *)(refs); \ + (node)->references++; \ + if ((_tmp) != NULL) \ + (*_tmp) = (node)->references; \ + } while (0) +#define dns_rbtnode_refincrement(node, refs) \ + do { \ + ISC_REQUIRE((node)->references > 0); \ + (node)->references++; \ + if ((refs) != NULL) \ + (*refs) = (node)->references; \ + } while (0) +#define dns_rbtnode_refdecrement(node, refs) \ + do { \ + ISC_REQUIRE((node)->references > 0); \ + (node)->references--; \ + if ((refs) != NULL) \ + (*refs) = (node)->references; \ + } while (0) +#endif +#endif /* DNS_RBT_USEISCREFCOUNT */ + +void +dns_rbtnode_nodename(dns_rbtnode_t *node, dns_name_t *name); + +dns_rbtnode_t * +dns_rbt_root(dns_rbt_t *rbt); + +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..59146af --- /dev/null +++ b/lib/dns/include/dns/rcode.h @@ -0,0 +1,106 @@ +/* + * 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 http://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..25220ce --- /dev/null +++ b/lib/dns/include/dns/rdata.h @@ -0,0 +1,774 @@ +/* + * 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 http://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 ISC_LIST_CHECKINIT +#define DNS_RDATA_INITIALIZED(rdata) \ + (!ISC_LINK_LINKED((rdata), link)) +#else +#define DNS_RDATA_INITIALIZED(rdata) true +#endif +#endif + +#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 + +#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, 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, 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, dns_name_t *origin, unsigned int 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 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. + * + */ + +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 + +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(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, 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..67fe1c7 --- /dev/null +++ b/lib/dns/include/dns/rdataclass.h @@ -0,0 +1,94 @@ +/* + * 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 http://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..63015e1 --- /dev/null +++ b/lib/dns/include/dns/rdatalist.h @@ -0,0 +1,123 @@ +/* + * 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 http://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..5295d8e --- /dev/null +++ b/lib/dns/include/dns/rdataset.h @@ -0,0 +1,710 @@ +/* + * 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 http://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, + 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, + dns_name_t *name); + isc_result_t (*getclosest)(dns_rdataset_t *rdataset, + dns_name_t *name, + dns_rdataset_t *neg, + dns_rdataset_t *negsig); + isc_result_t (*getadditional)(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); + isc_result_t (*setadditional)(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); + isc_result_t (*putadditional)(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); + 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); +} 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; /* XXX ? */ + 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; + void * private6; + 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. + */ + +#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 +#define DNS_RDATASETATTR_RANDOMIZE 0x00000800 +#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 + +/*% + * _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, + 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, + 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, 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. + */ + +isc_result_t +dns_rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); +/*%< + * Get cached additional information from the DB node for a particular + * 'rdataset.' 'type' is one of dns_rdatasetadditional_fromauth, + * dns_rdatasetadditional_fromcache, and dns_rdatasetadditional_fromglue, + * which specifies the origin of the information. 'qtype' is intended to + * be used for specifying a particular rdata type in the cached information. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + * \li 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * \li For the other pointers, see dns_acache_getentry(). + * + * Ensures: + * \li See dns_acache_getentry(). + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_FAILURE - additional information caching is not supported. + * \li #ISC_R_NOTFOUND - the corresponding DB node has not cached additional + * information for 'rdataset.' + * \li Any error that dns_acache_getentry() can return. + */ + +isc_result_t +dns_rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); +/*%< + * Set cached additional information to the DB node for a particular + * 'rdataset.' See dns_rdataset_getadditional for the semantics of 'type' + * and 'qtype'. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + * \li 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * \li For the other pointers, see dns_acache_setentry(). + * + * Ensures: + * \li See dns_acache_setentry(). + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_FAILURE - additional information caching is not supported. + * \li #ISC_R_NOMEMORY + * \li Any error that dns_acache_setentry() can return. + */ + +isc_result_t +dns_rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); +/*%< + * Discard cached additional information stored in the DB node for a particular + * 'rdataset.' See dns_rdataset_getadditional for the semantics of 'type' + * and 'qtype'. + * + * Requires: + * \li 'rdataset' is a valid rdataset. + * \li 'acache' can be NULL, in which case this function will simply return + * ISC_R_FAILURE. + * + * Ensures: + * \li See dns_acache_cancelentry(). + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_FAILURE - additional information caching is not supported. + * \li #ISC_R_NOTFOUND - the corresponding DB node has not cached additional + * information for 'rdataset.' + */ + +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. + */ + +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..54b3e90 --- /dev/null +++ b/lib/dns/include/dns/rdatasetiter.h @@ -0,0 +1,163 @@ +/* + * 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 http://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; +}; + +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..2e0d0e1 --- /dev/null +++ b/lib/dns/include/dns/rdataslab.h @@ -0,0 +1,178 @@ +/* + * 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 http://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 + */ + +void +dns_rdataslab_tordataset(unsigned char *slab, unsigned int reservelen, + dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, + dns_rdatatype_t covers, dns_ttl_t ttl, + dns_rdataset_t *rdataset); +/*%< + * Construct an rdataset from a slab. + * + * Requires: + *\li 'slab' points to a slab. + *\li 'rdataset' is disassociated. + * + * Ensures: + *\li 'rdataset' is associated and points to a valid rdataest. + */ + +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_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..0e79232 --- /dev/null +++ b/lib/dns/include/dns/rdatatype.h @@ -0,0 +1,97 @@ +/* + * 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 http://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..672d3e9 --- /dev/null +++ b/lib/dns/include/dns/request.h @@ -0,0 +1,403 @@ +/* + * 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 http://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, + 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 + */ + +/*% See dns_request_createvia4() */ +isc_result_t +dns_request_createvia(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, dns_tsigkey_t *key, + unsigned int timeout, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +/*% See dns_request_createvia4() */ +isc_result_t +dns_request_createvia2(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, dns_tsigkey_t *key, + unsigned int timeout, unsigned int udptimeout, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +/*% See dns_request_createvia4() */ +isc_result_t +dns_request_createvia3(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, dns_tsigkey_t *key, + unsigned int timeout, unsigned int udptimeout, + unsigned int udpretries, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +isc_result_t +dns_request_createvia4(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, 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 + */ + +/*% See dns_request_createraw4() */ +isc_result_t +dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +/*% See dns_request_createraw4() */ +isc_result_t +dns_request_createraw2(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + unsigned int udptimeout, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +/*% See dns_request_createraw4() */ +isc_result_t +dns_request_createraw3(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp); + +isc_result_t +dns_request_createraw4(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, 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. + */ + +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..c09107f --- /dev/null +++ b/lib/dns/include/dns/resolver.h @@ -0,0 +1,714 @@ +/* + * 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 http://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; + 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 0x0001 /*%< Use TCP. */ +#define DNS_FETCHOPT_UNSHARED 0x0002 /*%< See below. */ +#define DNS_FETCHOPT_RECURSIVE 0x0004 /*%< Set RD? */ +#define DNS_FETCHOPT_NOEDNS0 0x0008 /*%< Do not use EDNS. */ +#define DNS_FETCHOPT_FORWARDONLY 0x0010 /*%< Only use forwarders. */ +#define DNS_FETCHOPT_NOVALIDATE 0x0020 /*%< Disable validation. */ +#define DNS_FETCHOPT_EDNS512 0x0040 /*%< Advertise a 512 byte + UDP buffer. */ +#define DNS_FETCHOPT_WANTNSID 0x0080 /*%< Request NSID */ +#define DNS_FETCHOPT_PREFETCH 0x0100 /*%< Do prefetch */ +#define DNS_FETCHOPT_NOCDFLAG 0x0200 /*%< Don't set CD flag. */ +#define DNS_FETCHOPT_NONTA 0x0400 /*%< Ignore NTA table. */ +/* RESERVED ECS 0x0000 */ +/* RESERVED ECS 0x1000 */ +/* RESERVED ECS 0x2000 */ +/* RESERVED TCPCLIENT 0x4000 */ +#define DNS_FETCHOPT_NOCACHED 0x8000 /*%< Force cache update. */ + +/* Reserved in use by adb.c 0x00400000 */ +#define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000 +#define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000 +#define DNS_FETCHOPT_EDNSVERSIONSHIFT 24 + +/* + * 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 + +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, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp); + +isc_result_t +dns_resolver_createfetch2(dns_resolver_t *res, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + isc_sockaddr_t *client, uint16_t id, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp); +isc_result_t +dns_resolver_createfetch3(dns_resolver_t *res, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + isc_sockaddr_t *client, uint16_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. + */ + +unsigned int +dns_resolver_nrunning(dns_resolver_t *resolver); +/*%< + * Return the number of currently running resolutions in this + * resolver. This is may be less than the number of outstanding + * fetches due to multiple identical fetches, or more than the + * number of of outstanding fetches due to the fact that resolution + * can continue even though a fetch has been canceled. + */ + +isc_result_t +dns_resolver_addalternate(dns_resolver_t *resolver, isc_sockaddr_t *alt, + 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/DLV digest types. + */ + +isc_result_t +dns_resolver_disable_algorithm(dns_resolver_t *resolver, 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, dns_name_t *name, + unsigned int digest_type); +/*%< + * Mark the given DS/DLV 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, 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, 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, dns_name_t *name, + bool value); + +bool +dns_resolver_getmustbesecure(dns_resolver_t *resolver, dns_name_t *name); + + +void +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds); +/*%< + * Set the length of time the resolver will work on a query, in 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 seconds. + * + * 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_getoptions(dns_resolver_t *resolver); + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, 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, 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, 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, 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 reuslt 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 + +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..92fa5e1 --- /dev/null +++ b/lib/dns/include/dns/result.h @@ -0,0 +1,201 @@ +/* + * 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 http://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_NRESULTS 118 /*%< 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..f6a1819 --- /dev/null +++ b/lib/dns/include/dns/rootns.h @@ -0,0 +1,38 @@ +/* + * 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 http://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..78f3443 --- /dev/null +++ b/lib/dns/include/dns/rpz.h @@ -0,0 +1,380 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id$ */ + + +#ifndef DNS_RPZ_H +#define DNS_RPZ_H 1 + +#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_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 32 +#if DNS_RPZ_MAX_ZONES > 32 +# if DNS_RPZ_MAX_ZONES > 64 +# error "rpz zone bit masks must fit in a word" +# endif +typedef uint64_t dns_rpz_zbits_t; +#else +typedef uint32_t dns_rpz_zbits_t; +#endif + +#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 : (1<<((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; +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 */ +}; + +/* + * 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; + 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. + */ +typedef struct dns_rpz_zones dns_rpz_zones_t; +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 (initially 0, 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_refcount_t refs; + /* + * 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; +}; + + +/* + * 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; + + /* + * 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 + +/* + * 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, isc_mem_t *mctx); + +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_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, + dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num); + +isc_result_t +dns_rpz_add(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, dns_name_t *name); + +void +dns_rpz_delete(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num, 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..8c0c076 --- /dev/null +++ b/lib/dns/include/dns/rriterator.h @@ -0,0 +1,184 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: rriterator.h,v 1.4 2011/11/01 23:47:00 tbox Exp $ */ + +#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 +#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..0301ed8 --- /dev/null +++ b/lib/dns/include/dns/rrl.h @@ -0,0 +1,277 @@ +/* + * 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 http://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_MAX_TS +#error "DNS_RRL_MAX_WINDOW is too large" +#endif +#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 (1<= DNS_RRL_FOREVER +#error DNS_RRL_LOG_BITS is too big +#endif +#define DNS_RRL_MAX_LOG_SECS 1800 +#if DNS_RRL_MAX_LOG_SECS >= (1<= (1< + +#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..5b6bc3e --- /dev/null +++ b/lib/dns/include/dns/sdlz.h @@ -0,0 +1,374 @@ +/* + * Portions 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 http://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, + 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..e27d6b4 --- /dev/null +++ b/lib/dns/include/dns/secalg.h @@ -0,0 +1,71 @@ +/* + * 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 http://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..7ee8958 --- /dev/null +++ b/lib/dns/include/dns/secproto.h @@ -0,0 +1,64 @@ +/* + * 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 http://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..1e97e7a --- /dev/null +++ b/lib/dns/include/dns/soa.h @@ -0,0 +1,98 @@ +/* + * 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 http://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(dns_name_t *origin, 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..402583c --- /dev/null +++ b/lib/dns/include/dns/ssu.h @@ -0,0 +1,259 @@ +/* + * 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 http://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; + +#define DNS_SSUMATCHTYPE_NAME dns_ssumatchtype_name +#define DNS_SSUMATCHTYPE_SUBDOMAIN dns_ssumatchtype_subdomain +#define DNS_SSUMATCHTYPE_WILDCARD dns_ssumatchtype_wildcard +#define DNS_SSUMATCHTYPE_SELF dns_ssumatchtype_self +#define DNS_SSUMATCHTYPE_SELFSUB dns_ssumatchtype_selfsub +#define DNS_SSUMATCHTYPE_SELFWILD dns_ssumatchtype_selfwild +#define DNS_SSUMATCHTYPE_SELFKRB5 dns_ssumatchtype_selfkrb5 +#define DNS_SSUMATCHTYPE_SELFMS dns_ssumatchtype_selfms +#define DNS_SSUMATCHTYPE_SUBDOMAINMS dns_ssumatchtype_subdomainms +#define DNS_SSUMATCHTYPE_SUBDOMAINKRB5 dns_ssumatchtype_subdomainkrb5 +#define DNS_SSUMATCHTYPE_TCPSELF dns_ssumatchtype_tcpself +#define DNS_SSUMATCHTYPE_6TO4SELF dns_ssumatchtype_6to4self +#define DNS_SSUMATCHTYPE_EXTERNAL dns_ssumatchtype_external +#define DNS_SSUMATCHTYPE_LOCAL dns_ssumatchtype_local +#define DNS_SSUMATCHTYPE_MAX dns_ssumatchtype_max /* max value */ + +#define DNS_SSUMATCHTYPE_DLZ dns_ssumatchtype_dlz /* intentionally higher than _MAX */ + +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, + dns_name_t *identity, unsigned int matchtype, + 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, dns_name_t *signer, + dns_name_t *name, isc_netaddr_t *addr, + dns_rdatatype_t type, const dst_key_t *key); +bool +dns_ssutable_checkrules2(dns_ssutable_t *table, dns_name_t *signer, + dns_name_t *name, 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(dns_name_t *identity, dns_name_t *signer, + dns_name_t *name, 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..9ef47af --- /dev/null +++ b/lib/dns/include/dns/stats.h @@ -0,0 +1,466 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id$ */ + +#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_max = 44, + + /* + * 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 +}; + +#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 + +/*%< + * (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 marked for + * removal. + * + * Note: incrementing _STALE will decrement the corresponding non-stale + * counter. + */ +#define DNS_RDATASTATSTYPE_ATTR_OTHERTYPE 0x0001 +#define DNS_RDATASTATSTYPE_ATTR_NXRRSET 0x0002 +#define DNS_RDATASTATSTYPE_ATTR_NXDOMAIN 0x0004 +#define DNS_RDATASTATSTYPE_ATTR_STALE 0x0008 + +/*%< + * 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 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_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 + */ + +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_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_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..1e8f29b --- /dev/null +++ b/lib/dns/include/dns/tcpmsg.h @@ -0,0 +1,142 @@ +/* + * 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 http://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..012b733 --- /dev/null +++ b/lib/dns/include/dns/time.h @@ -0,0 +1,73 @@ +/* + * 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 http://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..845cf6c --- /dev/null +++ b/lib/dns/include/dns/timer.h @@ -0,0 +1,47 @@ +/* + * 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 http://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..f2500fa --- /dev/null +++ b/lib/dns/include/dns/tkey.h @@ -0,0 +1,248 @@ +/* + * 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 http://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; + gss_cred_id_t gsscred; + isc_mem_t *mctx; + isc_entropy_t *ectx; + char *gssapi_keytab; +}; + +isc_result_t +dns_tkeyctx_create(isc_mem_t *mctx, isc_entropy_t *ectx, + 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, dns_name_t *name, + 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, dns_name_t *name, dns_name_t *gname, + isc_buffer_t *intoken, uint32_t lifetime, + 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, + dns_name_t *gname, 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, + dns_name_t *server, 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..95e16c8 --- /dev/null +++ b/lib/dns/include/dns/tsec.h @@ -0,0 +1,131 @@ +/* + * 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 http://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..a7b7bc2 --- /dev/null +++ b/lib/dns/include/dns/tsig.h @@ -0,0 +1,293 @@ +/* + * 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 http://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. + */ +#ifndef PK11_MD5_DISABLE +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_hmacmd5_name; +#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name +#endif +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_gssapi_name; +#define DNS_TSIG_GSSAPI_NAME dns_tsig_gssapi_name +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_gssapims_name; +#define DNS_TSIG_GSSAPIMS_NAME dns_tsig_gssapims_name +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_hmacsha1_name; +#define DNS_TSIG_HMACSHA1_NAME dns_tsig_hmacsha1_name +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_hmacsha224_name; +#define DNS_TSIG_HMACSHA224_NAME dns_tsig_hmacsha224_name +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_hmacsha256_name; +#define DNS_TSIG_HMACSHA256_NAME dns_tsig_hmacsha256_name +LIBDNS_EXTERNAL_DATA extern dns_name_t *dns_tsig_hmacsha384_name; +#define DNS_TSIG_HMACSHA384_NAME dns_tsig_hmacsha384_name +LIBDNS_EXTERNAL_DATA extern 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; + unsigned int 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 */ + 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; +}; + +#define dns_tsigkey_identity(tsigkey) \ + ((tsigkey) == NULL ? NULL : \ + (tsigkey)->generated ? ((tsigkey)->creator) : \ + (&((tsigkey)->name))) + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_tsigkey_create(dns_name_t *name, dns_name_t *algorithm, + unsigned char *secret, int length, bool generated, + 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(dns_name_t *name, dns_name_t *algorithm, + dst_key_t *dstkey, bool generated, + 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, dns_name_t *name, + 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, 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..9241f10 --- /dev/null +++ b/lib/dns/include/dns/ttl.h @@ -0,0 +1,82 @@ +/* + * 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 http://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 + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +dns_ttl_totext(uint32_t src, bool verbose, + isc_buffer_t *target); +isc_result_t +dns_ttl_totext2(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..6754d16 --- /dev/null +++ b/lib/dns/include/dns/types.h @@ -0,0 +1,437 @@ +/* + * 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 http://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_acache dns_acache_t; +typedef struct dns_acacheentry dns_acacheentry_t; +typedef struct dns_acachestats dns_acachestats_t; +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_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_iptable dns_iptable_t; +typedef uint32_t dns_iterations_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_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 +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; + +typedef enum { + dns_aaaa_ok = 0, + dns_aaaa_filter = 1, + dns_aaaa_break_dnssec = 2 +} dns_aaaa_t; + +/* + * These are generated by gen.c. + */ +#include /* Provides dns_rdatatype_t. */ +#include /* Provides dns_rdataclass_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; + +/* + * 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 *, dns_name_t *, dns_rdataset_t *); + +typedef isc_result_t +(*dns_additionaldatafunc_t)(void *, 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 *, dns_name_t *, dns_name_t *); + +typedef bool +(*dns_checksrvfunc_t)(dns_zone_t *, dns_name_t *, dns_name_t *); + +typedef bool +(*dns_checknsfunc_t)(dns_zone_t *, dns_name_t *, dns_name_t *, + dns_rdataset_t *, dns_rdataset_t *); + +typedef bool +(*dns_isselffunc_t)(dns_view_t *, dns_tsigkey_t *, isc_sockaddr_t *, + 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..6700c8f --- /dev/null +++ b/lib/dns/include/dns/update.h @@ -0,0 +1,66 @@ +/* + * 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 http://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); +/*%< + * 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_unixtime sets the serial number to the current + * time (seconds since UNIX epoch) if possible, or increments by one + * if not. + */ + +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..81e46d8 --- /dev/null +++ b/lib/dns/include/dns/validator.h @@ -0,0 +1,259 @@ +/* + * 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 http://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. + * + * It can also optionally implement ISC's DNSSEC look-aside validation. + * + * 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 +#include /* for dns_rdata_rrsig_t */ + +#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; + dns_keynode_t * keynode; + 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; + bool seensig; + dns_rdataset_t * keyset; + dns_rdataset_t * dsset; + dns_rdataset_t * soaset; + dns_rdataset_t * nsecset; + dns_rdataset_t * nsec3set; + dns_name_t * soaname; + dns_rdataset_t frdataset; + dns_rdataset_t fsigrdataset; + dns_fixedname_t fname; + dns_fixedname_t wild; + dns_fixedname_t nearest; + dns_fixedname_t closest; + ISC_LINK(dns_validator_t) link; + dns_rdataset_t dlv; + dns_fixedname_t dlvsep; + bool havedlvsep; + bool mustbesecure; + unsigned int dlvlabels; + unsigned int depth; + unsigned int authcount; + unsigned int authfail; + isc_stdtime_t start; +}; + +/*% + * dns_validator_create() options. + */ +#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. + * + * options: + * If DNS_VALIDATOR_DLV is set the caller knows there is not a + * trusted key and the validator should immediately attempt to validate + * the answer by looking for an appropriate DLV RRset. + */ + +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..0552e81 --- /dev/null +++ b/lib/dns/include/dns/version.h @@ -0,0 +1,27 @@ +/* + * 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 http://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[]; + +LIBDNS_EXTERNAL_DATA extern const unsigned int dns_libinterface; +LIBDNS_EXTERNAL_DATA extern const unsigned int dns_librevision; +LIBDNS_EXTERNAL_DATA extern const unsigned int dns_libage; + +#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..8e21298 --- /dev/null +++ b/lib/dns/include/dns/view.h @@ -0,0 +1,1353 @@ +/* + * 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 http://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_acache_t * acache; + 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 auth_nxdomain; + bool additionalfromcache; + bool additionalfromauth; + bool minimal_any; + dns_minimaltype_t minimalresponses; + bool enablednssec; + bool enablevalidation; + bool acceptexpired; + bool requireservercookie; + 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; + 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; + dns_name_t * dlv; + dns_fixedname_t dlv_fixed; + uint16_t maxudp; + uint16_t nocookieudp; + unsigned int maxbits; + dns_aaaa_t v4_aaaa; + dns_aaaa_t v6_aaaa; + dns_acl_t * aaaa_acl; + 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; + + /* Locked by lock. */ + unsigned int weakrefs; + unsigned int 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_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 */ +}; + +#define DNS_VIEW_MAGIC ISC_MAGIC('V','i','e','w') +#define DNS_VIEW_VALID(view) ISC_MAGIC_VALID(view, DNS_VIEW_MAGIC) + +#define DNS_VIEWATTR_RESSHUTDOWN 0x01 +#define DNS_VIEWATTR_ADBSHUTDOWN 0x02 +#define DNS_VIEWATTR_REQSHUTDOWN 0x04 + +#ifdef HAVE_LMDB +#include +/* + * MDB_NOTLS is used to prevent problems after configuration is reloaded, due + * to the way LMDB's use of thread-local storage (TLS) interacts with the BIND9 + * thread model. + */ +#define DNS_LMDB_COMMON_FLAGS (MDB_CREATE | MDB_NOSUBDIR | MDB_NOTLS) +#ifndef __OpenBSD__ +#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS) +#else /* __OpenBSD__ */ +/* + * OpenBSD does not have a unified buffer cache, which requires both reads and + * writes to be performed using mmap(). + */ +#define DNS_LMDB_FLAGS (DNS_LMDB_COMMON_FLAGS | MDB_WRITEMAP) +#endif /* __OpenBSD__ */ +#endif /* HAVE_LMDB */ + +isc_result_t +dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, + const char *name, dns_view_t **viewp); +/*%< + * Create a view. + * + * Notes: + * + *\li The newly created view has no cache, no resolver, and an empty + * zone table. The view is not frozen. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'rdclass' is a valid class. + * + *\li 'name' is a valid C string. + * + *\li viewp != NULL && *viewp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +void +dns_view_attach(dns_view_t *source, dns_view_t **targetp); +/*%< + * Attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen view. + * + *\li 'targetp' points to a NULL dns_view_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + *\li While *targetp is attached, the view will not shut down. + */ + +void +dns_view_detach(dns_view_t **viewp); +/*%< + * Detach '*viewp' from its view. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t * + * + * Ensures: + * + *\li *viewp is NULL. + */ + +void +dns_view_flushanddetach(dns_view_t **viewp); +/*%< + * Detach '*viewp' from its view. If this was the last reference + * uncommitted changed in zones will be flushed to disk. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t * + * + * Ensures: + * + *\li *viewp is NULL. + */ + +void +dns_view_weakattach(dns_view_t *source, dns_view_t **targetp); +/*%< + * Weakly attach '*targetp' to 'source'. + * + * Requires: + * + *\li 'source' is a valid, frozen view. + * + *\li 'targetp' points to a NULL dns_view_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + * + * \li While *targetp is attached, the view will not be freed. + */ + +void +dns_view_weakdetach(dns_view_t **targetp); +/*%< + * Detach '*viewp' from its view. + * + * Requires: + * + *\li 'viewp' points to a valid dns_view_t *. + * + * Ensures: + * + *\li *viewp is NULL. + */ + +isc_result_t +dns_view_createzonetable(dns_view_t *view); +/*%< + * Create a zonetable for the view. + * + * Requires: + * + *\li 'view' is a valid, unfrozen view. + * + *\li 'view' does not have a zonetable already. + * + * Returns: + * + *\li #ISC_R_SUCCESS + * + *\li Any error that dns_zt_create() can return. + */ + +isc_result_t +dns_view_createresolver(dns_view_t *view, + isc_taskmgr_t *taskmgr, + unsigned int ntasks, unsigned int ndisp, + isc_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); +void +dns_view_setcache2(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, dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, unsigned int options, bool use_hints, + dns_db_t **dbp, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); +isc_result_t +dns_view_find2(dns_view_t *view, 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, 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. + */ + +/*% See dns_view_findzonecut2() */ +isc_result_t +dns_view_findzonecut(dns_view_t *view, dns_name_t *name, dns_name_t *fname, + isc_stdtime_t now, unsigned int options, + bool use_hints, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset); + +isc_result_t +dns_view_findzonecut2(dns_view_t *view, dns_name_t *name, dns_name_t *fname, + 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'. + * + * 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, 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, 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); + +isc_result_t +dns_view_loadnew(dns_view_t *view, bool stop); + +isc_result_t +dns_view_asyncload(dns_view_t *view, dns_zt_allloaded_t callback, void *arg); + +isc_result_t +dns_view_asyncload2(dns_view_t *view, dns_zt_allloaded_t callback, void *arg, + bool newonly); +/*%< + * Load zones attached to this view. dns_view_load() loads + * all zones whose master file has changed since the last + * load; dns_view_loadnew() loads only zones that have never + * been loaded. + * + * 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. + * + * Requires: + * + *\li 'view' is valid. + */ + +isc_result_t +dns_view_gettsig(dns_view_t *view, 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, 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); +isc_result_t +dns_view_flushcache2(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, 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, 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, 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, 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, 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, dns_name_t *name, + isc_stdtime_t now, bool checknta, + 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. + * + * 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, + dns_name_t *name, 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, dns_name_t *keyname, + dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx); +/*%< + * 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 'mctx' 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_restorekeyring(dns_view_t *view); + +isc_result_t +dns_view_searchdlz(dns_view_t *view, 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. + */ + + +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..123ba56 --- /dev/null +++ b/lib/dns/include/dns/xfrin.h @@ -0,0 +1,112 @@ +/* + * 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 http://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 + +/*% see dns_xfrin_create2() */ +isc_result_t +dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, + isc_sockaddr_t *masteraddr, 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); + +isc_result_t +dns_xfrin_create2(dns_zone_t *zone, dns_rdatatype_t xfrtype, + isc_sockaddr_t *masteraddr, isc_sockaddr_t *sourceaddr, + 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); + +isc_result_t +dns_xfrin_create3(dns_zone_t *zone, dns_rdatatype_t xfrtype, + isc_sockaddr_t *masteraddr, 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..c059ce2 --- /dev/null +++ b/lib/dns/include/dns/zone.h @@ -0,0 +1,2515 @@ +/* + * 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 http://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 + +typedef enum { + dns_zone_none, + dns_zone_master, + dns_zone_slave, + dns_zone_stub, + dns_zone_staticstub, + dns_zone_key, + dns_zone_dlz, + dns_zone_redirect +} dns_zonetype_t; + +typedef enum { + dns_zonestat_none = 0, + dns_zonestat_terse, + dns_zonestat_full +} dns_zonestat_level_t; + +#define DNS_ZONEOPT_SERVERS 0x00000001U /*%< perform server checks */ +#define DNS_ZONEOPT_PARENTS 0x00000002U /*%< perform parent checks */ +#define DNS_ZONEOPT_CHILDREN 0x00000004U /*%< perform child checks */ +#define DNS_ZONEOPT_NOTIFY 0x00000008U /*%< perform NOTIFY */ +#define DNS_ZONEOPT_MANYERRORS 0x00000010U /*%< return many errors on load */ +#define DNS_ZONEOPT_IXFRFROMDIFFS 0x00000020U /*%< calculate differences */ +#define DNS_ZONEOPT_NOMERGE 0x00000040U /*%< don't merge journal */ +#define DNS_ZONEOPT_CHECKNS 0x00000080U /*%< check if NS's are addresses */ +#define DNS_ZONEOPT_FATALNS 0x00000100U /*%< DNS_ZONEOPT_CHECKNS is fatal */ +#define DNS_ZONEOPT_MULTIMASTER 0x00000200U /*%< this zone has multiple masters */ +#define DNS_ZONEOPT_USEALTXFRSRC 0x00000400U /*%< use alternate transfer sources */ +#define DNS_ZONEOPT_CHECKNAMES 0x00000800U /*%< check-names */ +#define DNS_ZONEOPT_CHECKNAMESFAIL 0x00001000U /*%< fatal check-name failures */ +#define DNS_ZONEOPT_CHECKWILDCARD 0x00002000U /*%< check for internal wildcards */ +#define DNS_ZONEOPT_CHECKMX 0x00004000U /*%< check-mx */ +#define DNS_ZONEOPT_CHECKMXFAIL 0x00008000U /*%< fatal check-mx failures */ +#define DNS_ZONEOPT_CHECKINTEGRITY 0x00010000U /*%< perform integrity checks */ +#define DNS_ZONEOPT_CHECKSIBLING 0x00020000U /*%< perform sibling glue checks */ +#define DNS_ZONEOPT_NOCHECKNS 0x00040000U /*%< disable IN NS address checks */ +#define DNS_ZONEOPT_WARNMXCNAME 0x00080000U /*%< warn on MX CNAME check */ +#define DNS_ZONEOPT_IGNOREMXCNAME 0x00100000U /*%< ignore MX CNAME check */ +#define DNS_ZONEOPT_WARNSRVCNAME 0x00200000U /*%< warn on SRV CNAME check */ +#define DNS_ZONEOPT_IGNORESRVCNAME 0x00400000U /*%< ignore SRV CNAME check */ +#define DNS_ZONEOPT_UPDATECHECKKSK 0x00800000U /*%< check dnskey KSK flag */ +#define DNS_ZONEOPT_TRYTCPREFRESH 0x01000000U /*%< try tcp refresh on udp failure */ +#define DNS_ZONEOPT_NOTIFYTOSOA 0x02000000U /*%< Notify the SOA MNAME */ +#define DNS_ZONEOPT_NSEC3TESTZONE 0x04000000U /*%< nsec3-test-zone */ +#define DNS_ZONEOPT_SECURETOINSECURE 0x08000000U /*%< dnssec-secure-to-insecure */ +#define DNS_ZONEOPT_DNSKEYKSKONLY 0x10000000U /*%< dnssec-dnskey-kskonly */ +#define DNS_ZONEOPT_CHECKDUPRR 0x20000000U /*%< check-dup-records */ +#define DNS_ZONEOPT_CHECKDUPRRFAIL 0x40000000U /*%< fatal check-dup-records failures */ +#define DNS_ZONEOPT_CHECKSPF 0x80000000U /*%< check SPF records */ + +/* + * The following zone options are shifted left into the + * higher-order 32 bits of the options. + */ +#define DNS_ZONEOPT2_CHECKTTL 0x00000001U /*%< check max-zone-ttl */ +#define DNS_ZONEOPT2_AUTOEMPTY 0x00000002U /*%< automatic empty zone */ + +#ifndef NOMINUM_PUBLIC +/* + * Nominum specific options build down. + */ +#define DNS_ZONEOPT_NOTIFYFORWARD 0x80000000U /* forward notify to master */ +#endif /* NOMINUM_PUBLIC */ + +/* + * Zone key maintenance options + */ +#define DNS_ZONEKEY_ALLOW 0x00000001U /*%< fetch keys on command */ +#define DNS_ZONEKEY_MAINTAIN 0x00000002U /*%< publish/sign on schedule */ +#define DNS_ZONEKEY_CREATE 0x00000004U /*%< make keys when needed */ +#define DNS_ZONEKEY_FULLSIGN 0x00000008U /*%< roll to new keys immediately */ +#define DNS_ZONEKEY_NORESIGN 0x00000010U /*%< no automatic resigning */ + +#ifndef DNS_ZONE_MINREFRESH +#define DNS_ZONE_MINREFRESH 300 /*%< 5 minutes */ +#endif +#ifndef DNS_ZONE_MAXREFRESH +#define DNS_ZONE_MAXREFRESH 2419200 /*%< 4 weeks */ +#endif +#ifndef DNS_ZONE_DEFAULTREFRESH +#define DNS_ZONE_DEFAULTREFRESH 3600 /*%< 1 hour */ +#endif +#ifndef DNS_ZONE_MINRETRY +#define DNS_ZONE_MINRETRY 300 /*%< 5 minutes */ +#endif +#ifndef DNS_ZONE_MAXRETRY +#define DNS_ZONE_MAXRETRY 1209600 /*%< 2 weeks */ +#endif +#ifndef DNS_ZONE_DEFAULTRETRY +#define DNS_ZONE_DEFAULTRETRY 60 /*%< 1 minute, subject to + exponential backoff */ +#endif + +#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_getserial2(dns_zone_t *zone, uint32_t *serialp); + +uint32_t +dns_zone_getserial(dns_zone_t *zone); +/*%< + * Returns the current serial number of the zone. On success, the SOA + * serial of the zone will be copied into '*serialp'. + * dns_zone_getserial() cannot catch failure cases and is deprecated by + * dns_zone_getserial2(). + * + * 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); + +isc_result_t +dns_zone_setfile2(dns_zone_t *zone, const char *file, + dns_masterformat_t format); +isc_result_t +dns_zone_setfile3(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. + * + * dns_zone_setfile() is a backward-compatible form of + * dns_zone_setfile2(), which always specifies the + * dns_masterformat_text (RFC1035) format. + * + * dns_zone_setfile2() is a backward-compatible form of + * dns_zone_setfile3(), which also specifies the style + * that should be used if a zone using the 'text' + * masterformat is ever dumped. + * + * 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 maximim 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 maximim 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. + */ + +isc_result_t +dns_zone_load(dns_zone_t *zone); + +isc_result_t +dns_zone_loadnew(dns_zone_t *zone); + +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. + * + * dns_zone_loadnew() only loads zones that are not yet loaded. + * dns_zone_load() also loads zones that are already loaded and + * and whose master file has changed since the last load. + * 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, dns_zt_zoneloaded_t done, void *arg); + +isc_result_t +dns_zone_asyncload2(dns_zone_t *zone, dns_zt_zoneloaded_t done, void *arg, + bool newonly); +/*%< + * 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. + */ + +void +dns_zone_setflag(dns_zone_t *zone, unsigned int flags, bool value); +/*%< + * Sets ('value' == 'true') / clears ('value' == 'IS_FALSE') + * zone flags. Valid flag bits are DNS_ZONE_F_*. + * + * Requires + *\li 'zone' to be 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. + */ + +isc_result_t +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 + * + * Returns: + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SUCCESS + */ + +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); + +isc_result_t +dns_zone_dumptostream2(dns_zone_t *zone, FILE *fd, dns_masterformat_t format, + const dns_master_style_t *style); +isc_result_t +dns_zone_dumptostream3(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. + */ + +isc_result_t +dns_zone_fulldumptostream(dns_zone_t *zone, FILE *fd); +/*%< + * The same as dns_zone_dumptostream, but dumps the zone with + * different dump settings (dns_master_style_full). + * + * 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_setmasters(dns_zone_t *zone, const isc_sockaddr_t *masters, + uint32_t count); +isc_result_t +dns_zone_setmasterswithkeys(dns_zone_t *zone, + const isc_sockaddr_t *masters, + 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 'masters' array of isc_sockaddr_t with port set or NULL. + *\li 'count' the number of masters. + *\li 'keynames' array of dns_name_t's for tsig keys or NULL. + * + * \li dns_zone_setmasters() is just a wrapper to setmasterswithkeys(), + * passing NULL in the keynames field. + * + * \li If 'masters' 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. + */ + +void +dns_zone_setoption(dns_zone_t *zone, unsigned int option, + bool value); +void +dns_zone_setoption2(dns_zone_t *zone, unsigned int option, + bool value); +/*%< + * Set the given options on ('value' == true) or off + * ('value' == #false). + * + * dns_zone_setoption2() has been introduced because the number + * of options needed now exceeds the 32 bits in the zone->options + * field; it should be used set options with names beginning + * with DNS_ZONEOPT2_. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +unsigned int +dns_zone_getoptions(dns_zone_t *zone); +unsigned int +dns_zone_getoptions2(dns_zone_t *zone); +/*%< + * Returns the current zone options. + * + * Callers should be aware there is now more than one set of zone + * options. dns_zone_getoptions2() has been introduced because the + * number of options needed now exceeds the 32 bits in the + * zone->options field. It returns the options whose names begin + * with DNS_ZONEOPT2_. + * + * 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_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, + dns_message_t *msg); +isc_result_t +dns_zone_notifyreceive2(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. + */ + +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 RRSIG 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 RRSIG 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 masters. '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_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; + */ + +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_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); +/*%< + * 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); +/*%< + * 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, 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_setacache(dns_zone_t *zone, dns_acache_t *acache); +/*%< + * Associate the zone with an additional cache. + * + * Require: + * 'zone' to be a valid zone. + * 'acache' to be a non NULL pointer. + * + * Ensures: + * 'zone' will have a reference to 'acache' + */ + +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, isc_netaddr_t *srcaddr, + * 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. + */ + +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); +/*% + * 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. + * + * 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_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 + + +#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..01a0369 --- /dev/null +++ b/lib/dns/include/dns/zonekey.h @@ -0,0 +1,37 @@ +/* + * 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 http://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/zt.h b/lib/dns/include/dns/zt.h new file mode 100644 index 0000000..84ca8ea --- /dev/null +++ b/lib/dns/include/dns/zt.h @@ -0,0 +1,234 @@ +/* + * 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 http://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 + +#define DNS_ZTFIND_NOEXACT 0x01 + +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); + +isc_result_t +dns_zt_loadnew(dns_zt_t *zt, bool stop); + +isc_result_t +dns_zt_asyncload(dns_zt_t *zt, dns_zt_allloaded_t alldone, void *arg); + +isc_result_t +dns_zt_asyncload2(dns_zt_t *zt, dns_zt_allloaded_t alldone, void *arg, + bool newonly); +/*%< + * Load all zones in the table. If 'stop' is true, + * stop on the first error and return it. If 'stop' + * is false, ignore errors. + * + * dns_zt_loadnew() only loads zones that are not yet loaded. + * dns_zt_load() also loads zones that are already loaded and + * and whose master file has changed since the last load. + * 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, 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, bool stop, + isc_result_t (*action)(dns_zone_t *, void *), void *uap); + +isc_result_t +dns_zt_apply2(dns_zt_t *zt, 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..04727f7 --- /dev/null +++ b/lib/dns/include/dst/Makefile.in @@ -0,0 +1,34 @@ +# 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 http://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 lib.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..1924e74 --- /dev/null +++ b/lib/dns/include/dst/dst.h @@ -0,0 +1,995 @@ +/* + * 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 http://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; + +/* DST algorithm codes */ +#define DST_ALG_UNKNOWN 0 +#define DST_ALG_RSAMD5 1 +#define DST_ALG_RSA DST_ALG_RSAMD5 /*%< backwards compatibility */ +#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 + +/* 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_MAX_TIMES 8 + +/* 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_MAX_NUMERIC 3 + +/* + * 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, isc_entropy_t *ectx, unsigned int eflags); + +isc_result_t +dst_lib_init2(isc_mem_t *mctx, isc_entropy_t *ectx, + const char *engine, unsigned int eflags); +/*%< + * Initializes the DST subsystem. + * + * Requires: + * \li "mctx" is a valid memory context + * \li "ectx" is a valid entropy 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, dst_context_t **dctxp); + +isc_result_t +dst_context_create2(dst_key_t *key, isc_mem_t *mctx, + isc_logcategory_t *category, dst_context_t **dctxp); + +isc_result_t +dst_context_create3(dst_key_t *key, isc_mem_t *mctx, + isc_logcategory_t *category, bool useforsigning, + dst_context_t **dctxp); + +isc_result_t +dst_context_create4(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, and is specified by name, algorithm, and id. If a private key + * 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 + * \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 + * key, and is specified by filename. If a private key 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 + * \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_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(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(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. + */ + +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(dns_name_t *name, 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(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 + +isc_result_t +dst_key_fromlabel(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(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); + +isc_result_t +dst_key_generate2(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, unsigned int alg); +uint16_t +dst_region_computerid(const isc_region_t *source, unsigned int alg); +/*%< + * Computes the (revoked) key id of the key stored in the provided + * region with the given algorithm. + * + * 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_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 + * "timep" 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_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); + +bool +dst_key_isexternal(dst_key_t *key); + +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..17a9f54 --- /dev/null +++ b/lib/dns/include/dst/gssapi.h @@ -0,0 +1,215 @@ +/* + * 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 http://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 + +#ifdef GSSAPI +#ifdef WIN32 +/* + * MSVC does not like macros in #include lines. + */ +#include +#include +#else +#include ISC_PLATFORM_GSSAPIHEADER +#ifdef ISC_PLATFORM_GSSAPI_KRB5_HEADER +#include ISC_PLATFORM_GSSAPI_KRB5_HEADER +#endif +#endif +#ifndef GSS_SPNEGO_MECHANISM +#define GSS_SPNEGO_MECHANISM ((void*)0) +#endif +#endif + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*** + *** Functions + ***/ + +isc_result_t +dst_gssapi_acquirecred(dns_name_t *name, bool initiate, + 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(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(dns_name_t *name, isc_buffer_t *intoken, + isc_buffer_t *outtoken, 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 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(gss_cred_id_t cred, + const char *gssapi_keytab, + isc_region_t *intoken, isc_buffer_t **outtoken, + 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 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, 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/lib.h b/lib/dns/include/dst/lib.h new file mode 100644 index 0000000..508439f --- /dev/null +++ b/lib/dns/include/dst/lib.h @@ -0,0 +1,34 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef DST_LIB_H +#define DST_LIB_H 1 + +/*! \file dst/lib.h */ + +#include +#include + +ISC_LANG_BEGINDECLS + +LIBDNS_EXTERNAL_DATA extern isc_msgcat_t *dst_msgcat; + +void +dst_lib_initmsgcat(void); +/* + * Initialize the DST library's message catalog, dst_msgcat, if it + * has not already been initialized. + */ + +ISC_LANG_ENDDECLS + +#endif /* DST_LIB_H */ diff --git a/lib/dns/include/dst/result.h b/lib/dns/include/dst/result.h new file mode 100644 index 0000000..0855d80 --- /dev/null +++ b/lib/dns/include/dst/result.h @@ -0,0 +1,67 @@ +/* + * 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 http://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..4f114e5 --- /dev/null +++ b/lib/dns/ipkeylist.c @@ -0,0 +1,248 @@ +/* + * 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 http://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 + +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)); + if (dst->keys[i] == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_keys; + } + dns_name_init(dst->keys[i], NULL); + result = dns_name_dup(src->keys[i], mctx, + dst->keys[i]); + if (result != ISC_R_SUCCESS) + goto cleanup_keys; + } 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)); + if (dst->labels[i] == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_labels; + } + dns_name_init(dst->labels[i], NULL); + result = dns_name_dup(src->labels[i], mctx, + dst->labels[i]); + if (result != ISC_R_SUCCESS) + goto cleanup_labels; + } else { + dst->labels[i] = NULL; + } + } + } + dst->count = src->count; + return (ISC_R_SUCCESS); + + cleanup_labels: + do { + if (dst->labels[i] != NULL) { + if (dns_name_dynamic(dst->labels[i])) + dns_name_free(dst->labels[i], mctx); + isc_mem_put(mctx, dst->labels[i], sizeof(dns_name_t)); + dst->labels[i] = NULL; + } + } while (i-- > 0); + + cleanup_keys: + do { + if (dst->keys[i] != NULL) { + if (dns_name_dynamic(dst->keys[i])) + dns_name_free(dst->keys[i], mctx); + isc_mem_put(mctx, dst->keys[i], sizeof(dns_name_t)); + dst->keys[i] = NULL; + } + } while (i-- > 0); + + return (result); +} + +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)); + if (addrs == NULL) + goto nomemory; + dscps = isc_mem_get(mctx, n * sizeof(isc_dscp_t)); + if (dscps == NULL) + goto nomemory; + keys = isc_mem_get(mctx, n * sizeof(dns_name_t *)); + if (keys == NULL) + goto nomemory; + labels = isc_mem_get(mctx, n * sizeof(dns_name_t *)); + if (labels == NULL) + goto nomemory; + + 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); + +nomemory: + if (addrs != NULL) + isc_mem_put(mctx, addrs, n * sizeof(isc_sockaddr_t)); + if (dscps != NULL) + isc_mem_put(mctx, dscps, n * sizeof(isc_dscp_t)); + if (keys != NULL) + isc_mem_put(mctx, keys, n * sizeof(dns_name_t *)); + if (labels != NULL) + 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..10713a0 --- /dev/null +++ b/lib/dns/iptable.c @@ -0,0 +1,182 @@ +/* + * 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 http://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 + +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)); + if (tab == NULL) + return (ISC_R_NOMEMORY); + 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, isc_netaddr_t *addr, + uint16_t bitlen, bool pos) +{ + return(dns_iptable_addprefix2(tab, addr, bitlen, pos, false)); +} + +isc_result_t +dns_iptable_addprefix2(dns_iptable_t *tab, isc_netaddr_t *addr, + uint16_t bitlen, bool pos, + bool is_ecs) +{ + 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, is_ecs); + + 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, NULL); + *target = source; +} + +void +dns_iptable_detach(dns_iptable_t **tabp) { + dns_iptable_t *tab = *tabp; + unsigned int refs; + REQUIRE(DNS_IPTABLE_VALID(tab)); + isc_refcount_decrement(&tab->refcount, &refs); + if (refs == 0) + destroy_iptable(tab); + *tabp = NULL; +} + +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; + } + + isc_refcount_destroy(&dtab->refcount); + 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..bdbc459 --- /dev/null +++ b/lib/dns/journal.c @@ -0,0 +1,2364 @@ +/* + * 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 http://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. + */ +/*% + * When true, accept IXFR difference sequences where the + * SOA serial number does not change (BIND 8 sends such + * sequences). + */ +static bool bind8_compat = true; /* XXX config */ + +/**************************************************************************/ +/* + * 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 inline uint32_t +decode_uint32(unsigned char *p) { + return ((p[0] << 24) + + (p[1] << 16) + + (p[2] << 8) + + (p[3] << 0)); +} + +static inline void +encode_uint32(uint32_t val, unsigned char *p) { + p[0] = (uint8_t)(val >> 24); + p[1] = (uint8_t)(val >> 16); + p[2] = (uint8_t)(val >> 8); + p[3] = (uint8_t)(val >> 0); +} + +isc_result_t +dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, + dns_diffop_t op, dns_difftuple_t **tp) +{ + isc_result_t result; + dns_dbnode_t *node; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_fixedname_t fixed; + dns_name_t *zonename; + + zonename = dns_fixedname_initname(&fixed); + dns_name_copy(dns_db_origin(db), zonename, NULL); + + 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. */ + +/*% + * 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. + * There is one of these at the beginning of each transaction. + */ +typedef struct { + unsigned char size[4]; /*%< In bytes, excluding header. */ + unsigned char serial0[4]; /*%< SOA serial before update. */ + unsigned char serial1[4]; /*%< SOA serial after update. */ +} journal_rawxhdr_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 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\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 +initial_journal_header = { ";BIND LOG V9\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; + char *filename; /*%< Journal file name */ + FILE * fp; /*%< File handle */ + isc_offset_t offset; /*%< Current file offset */ + 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 */ + 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 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); +} + +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) { + journal_rawxhdr_t raw; + isc_result_t result; + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) + return (result); + xhdr->size = decode_uint32(raw.size); + xhdr->serial0 = decode_uint32(raw.serial0); + xhdr->serial1 = decode_uint32(raw.serial1); + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_write_xhdr(dns_journal_t *j, uint32_t size, + uint32_t serial0, uint32_t serial1) +{ + journal_rawxhdr_t raw; + encode_uint32(size, raw.size); + 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, 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; /* 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); + } + + 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); + if (mem == NULL) { + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + return (ISC_R_NOMEMORY); + } + 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, dns_journal_t **journalp) +{ + FILE *fp = NULL; + isc_result_t result; + journal_rawheader_t rawheader; + dns_journal_t *j; + + INSIST(journalp != NULL && *journalp == NULL); + j = isc_mem_get(mctx, sizeof(*j)); + if (j == NULL) + return (ISC_R_NOMEMORY); + + j->mctx = NULL; + isc_mem_attach(mctx, &j->mctx); + j->state = JOURNAL_STATE_INVALID; + j->fp = NULL; + j->filename = isc_mem_strdup(mctx, filename); + j->index = NULL; + j->rawindex = NULL; + + if (j->filename == NULL) + FAIL(ISC_R_NOMEMORY); + + 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, 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, initial_journal_header.format, + sizeof(initial_journal_header.format)) != 0) { + 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); + if (j->rawindex == NULL) + FAIL(ISC_R_NOMEMORY); + + CHECK(journal_read(j, j->rawindex, rawbytes)); + + j->index = isc_mem_get(mctx, j->header.index_size * + sizeof(journal_pos_t)); + if (j->index == NULL) + FAIL(ISC_R_NOMEMORY); + + 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)); + if (j->filename != NULL) + 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); + writable = (mode & (DNS_JOURNAL_WRITE|DNS_JOURNAL_CREATE)); + + result = journal_open(mctx, filename, writable, create, journalp); + if (result == ISC_R_NOTFOUND) { + namelen = strlen(filename); + if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0) + namelen -= 4; + + result = isc_string_printf(backup, sizeof(backup), "%.*s.jbk", + (int)namelen, filename); + if (result != ISC_R_SUCCESS) + return (result); + result = journal_open(mctx, backup, writable, writable, + 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: + INSIST(0); + } + + 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: + INSIST(0); + } + + 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); +} + +/* + * 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; + 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); + + /* + * Check serial number consistency. + */ + if (xhdr.serial0 != pos->serial) { + 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. + */ + if ((isc_offset_t)(pos->offset + sizeof(journal_rawxhdr_t) + 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 += sizeof(journal_rawxhdr_t) + xhdr.size; + pos->serial = xhdr.serial1; + return (ISC_R_SUCCESS); +} + +/* + * 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; + journal_rawxhdr_t hdr; + + 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. + */ + memset(&hdr, 0, sizeof(hdr)); + CHECK(journal_write(j, &hdr, sizeof(hdr))); + 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; + 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. + */ + size = 0; + 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 >= INT32_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); + if (mem == NULL) + return (ISC_R_NOMEMORY); + + 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); + } + + isc_buffer_usedregion(&buffer, &used); + INSIST(used.length == size); + + j->x.pos[1].offset += used.length; + + /* + * 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) || + (bind8_compat && + j->x.pos[1].serial == j->x.pos[0].serial))) + { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: malformed transaction: serial number " + "would decrease", 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 >= INT32_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)INT32_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 + + /* + * 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) - + 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.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 = *journalp; + REQUIRE(DNS_JOURNAL_VALID(j)); + + 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)); + *journalp = NULL; +} + +/* + * Roll the open journal 'j' into the database 'db'. + * A new database version will be created. + */ + +/* XXX Share code with incoming IXFR? */ + +static isc_result_t +roll_forward(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)); + /* + * XXX do more drastic things, like marking zone stale, + * if this fails? + */ + /* + * XXXRTH The zone code should probably mark the zone as bad and + * scream loudly into the log if this is a dynamic update + * log reply that failed. + */ + + end_serial = dns_journal_last_serial(j); + if (db_serial == end_serial) + CHECK(DNS_R_UPTODATE); + + CHECK(dns_journal_iter_init(j, db_serial, end_serial)); + + for (result = dns_journal_first_rr(j); + result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name; + uint32_t ttl; + dns_rdata_t *rdata; + dns_difftuple_t *tuple = NULL; + + name = NULL; + rdata = NULL; + 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_rollforward(isc_mem_t *mctx, dns_db_t *db, unsigned int options, + const char *filename) +{ + dns_journal_t *j; + isc_result_t result; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(filename != NULL); + + j = 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, but that's OK"); + return (DNS_R_NOJOURNAL); + } + if (result != ISC_R_SUCCESS) + return (result); + if (JOURNAL_EMPTY(&j->header)) + result = DNS_R_UPTODATE; + else + result = roll_forward(j, db, options); + + dns_journal_destroy(&j); + + return (result); +} + +isc_result_t +dns_journal_print(isc_mem_t *mctx, const char *filename, FILE *file) { + dns_journal_t *j; + 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; + + REQUIRE(filename != NULL); + + j = 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); + } + + 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 (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)); + + for (result = dns_journal_first_rr(j); + result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name; + uint32_t ttl; + dns_rdata_t *rdata; + dns_difftuple_t *tuple = NULL; + + name = NULL; + rdata = NULL; + 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 == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: missing " + "initial SOA", j->filename); + FAIL(ISC_R_UNEXPECTED); + } + 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) { + 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. + */ +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) +{ + 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); + + 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; + + 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 (xhdr.serial0 != j->it.current_serial) { + 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); + + /* + * 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, 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; + + CHECK(dns_diff_sort(&diff[0], rdata_order)); + CHECK(dns_diff_sort(&diff[1], rdata_order)); + + 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) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + ISC_LIST_APPEND(r->tuples, 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(r->tuples, p[0], link); + goto next; + } + if (t > 0) { + ISC_LIST_UNLINK(diff[1].tuples, p[1], link); + ISC_LIST_APPEND(r->tuples, 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) { + ISC_LIST_APPEND(r->tuples, p[i], link); + } else { + dns_difftuple_free(&p[i]); + } + } + next: ; + } + 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); +} + +isc_result_t +dns_journal_compact(isc_mem_t *mctx, char *filename, uint32_t serial, + 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 copy_length; + size_t namelen; + char *buf = NULL; + unsigned int size = 0; + isc_result_t result; + unsigned int indexend; + char newname[1024]; + char backup[1024]; + bool is_backup = false; + + REQUIRE(filename != NULL); + + namelen = strlen(filename); + if (namelen > 4U && strcmp(filename + namelen - 4, ".jnl") == 0) + namelen -= 4; + + result = isc_string_printf(newname, sizeof(newname), "%.*s.jnw", + (int)namelen, filename); + if (result != ISC_R_SUCCESS) + return (result); + + result = isc_string_printf(backup, sizeof(backup), "%.*s.jbk", + (int)namelen, filename); + if (result != ISC_R_SUCCESS) + return (result); + + result = journal_open(mctx, filename, false, false, &j1); + if (result == ISC_R_NOTFOUND) { + is_backup = true; + result = journal_open(mctx, backup, false, false, &j1); + } + if (result != ISC_R_SUCCESS) + return (result); + + 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 < indexend * 2) + target_size = target_size/2 + indexend; + + /* + * See if there is any work to do. + */ + if ((uint32_t) j1->header.end.offset < target_size) { + dns_journal_destroy(&j1); + return (ISC_R_SUCCESS); + } + + CHECK(journal_open(mctx, newname, true, true, &j2)); + + /* + * 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)); + + /* + * 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. + */ + copy_length = j1->header.end.offset - best_guess.offset; + + if (copy_length != 0) { + /* + * Copy best_guess to end into space just freed. + */ + size = 64*1024; + if (copy_length < size) + size = copy_length; + buf = isc_mem_get(mctx, size); + if (buf == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + + CHECK(journal_seek(j1, best_guess.offset)); + CHECK(journal_seek(j2, indexend)); + for (i = 0; i < copy_length; i += size) { + unsigned int len = (copy_length - i) > size ? size : + (copy_length - i); + CHECK(journal_read(j1, buf, len)); + CHECK(journal_write(j2, buf, len)); + } + + CHECK(journal_fsync(j2)); + + /* + * Compute new header. + */ + j2->header.begin.serial = best_guess.serial; + j2->header.begin.offset = indexend; + j2->header.end.serial = j1->header.end.serial; + j2->header.end.offset = indexend + copy_length; + j2->header.sourceserial = j1->header.sourceserial; + j2->header.serialset = j1->header.serialset; + + /* + * 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/key.c b/lib/dns/key.c new file mode 100644 index 0000000..16dadf8 --- /dev/null +++ b/lib/dns/key.c @@ -0,0 +1,187 @@ +/* + * 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 http://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 "dst_internal.h" + +uint16_t +dst_region_computeid(const isc_region_t *source, unsigned int alg) { + uint32_t ac; + const unsigned char *p; + int size; + + REQUIRE(source != NULL); + REQUIRE(source->length >= 4); + + p = source->base; + size = source->length; + + if (alg == DST_ALG_RSAMD5) + return ((p[size - 3] << 8) + p[size - 2]); + + 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, unsigned int alg) { + uint32_t ac; + const unsigned char *p; + int size; + + REQUIRE(source != NULL); + REQUIRE(source->length >= 4); + + p = source->base; + size = source->length; + + if (alg == DST_ALG_RSAMD5) + return ((p[size - 3] << 8) + p[size - 2]); + + 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..135d968 --- /dev/null +++ b/lib/dns/keydata.c @@ -0,0 +1,84 @@ +/* + * 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 http://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 + +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); + if (dnskey->data == NULL) + return (ISC_R_NOMEMORY); + 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); + if (keydata->data == NULL) + return (ISC_R_NOMEMORY); + memmove(keydata->data, dnskey->data, keydata->datalen); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c new file mode 100644 index 0000000..57b8608 --- /dev/null +++ b/lib/dns/keytable.c @@ -0,0 +1,768 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include + +#include + +#include +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include + +#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 active_nodes; + isc_refcount_t references; + isc_rwlock_t rwlock; + /* Locked by rwlock. */ + dns_rbt_t *table; +}; + +struct dns_keynode { + unsigned int magic; + isc_refcount_t refcount; + dst_key_t * key; + bool managed; + struct dns_keynode * next; +}; + +static void +free_keynode(void *node, void *arg) { + dns_keynode_t *keynode = node; + isc_mem_t *mctx = arg; + + dns_keynode_detachall(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)); + if (keytable == NULL) { + return (ISC_R_NOMEMORY); + } + + keytable->table = NULL; + result = dns_rbt_create(mctx, free_keynode, mctx, &keytable->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_keytable; + } + + result = isc_rwlock_init(&keytable->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_rbt; + } + + result = isc_refcount_init(&keytable->active_nodes, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_rwlock; + } + + result = isc_refcount_init(&keytable->references, 1); + if (result != ISC_R_SUCCESS) { + goto cleanup_active_nodes; + } + + keytable->mctx = NULL; + isc_mem_attach(mctx, &keytable->mctx); + keytable->magic = KEYTABLE_MAGIC; + *keytablep = keytable; + + return (ISC_R_SUCCESS); + + cleanup_active_nodes: + isc_refcount_destroy(&keytable->active_nodes); + + cleanup_rwlock: + isc_rwlock_destroy(&keytable->rwlock); + + cleanup_rbt: + dns_rbt_destroy(&keytable->table); + + cleanup_keytable: + isc_mem_putanddetach(&mctx, keytable, sizeof(*keytable)); + + return (result); +} + +void +dns_keytable_attach(dns_keytable_t *source, dns_keytable_t **targetp) { + + /* + * Attach *targetp to source. + */ + + REQUIRE(VALID_KEYTABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references, NULL); + + *targetp = source; +} + +void +dns_keytable_detach(dns_keytable_t **keytablep) { + dns_keytable_t *keytable; + unsigned int refs; + + /* + * Detach *keytablep from its keytable. + */ + + REQUIRE(keytablep != NULL && VALID_KEYTABLE(*keytablep)); + + keytable = *keytablep; + *keytablep = NULL; + + isc_refcount_decrement(&keytable->references, &refs); + if (refs == 0) { + INSIST(isc_refcount_current(&keytable->active_nodes) == 0); + isc_refcount_destroy(&keytable->active_nodes); + 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 isc_result_t +insert(dns_keytable_t *keytable, bool managed, + dns_name_t *keyname, dst_key_t **keyp) +{ + isc_result_t result; + dns_keynode_t *knode = NULL; + dns_rbtnode_t *node; + + REQUIRE(keyp == NULL || *keyp != NULL); + REQUIRE(VALID_KEYTABLE(keytable)); + + result = dns_keynode_create(keytable->mctx, &knode); + if (result != ISC_R_SUCCESS) + return (result); + + knode->managed = managed; + + RWLOCK(&keytable->rwlock, isc_rwlocktype_write); + + node = NULL; + result = dns_rbt_addnode(keytable->table, keyname, &node); + + if (keyp != NULL) { + if (result == ISC_R_EXISTS) { + /* Key already in table? */ + dns_keynode_t *k; + for (k = node->data; k != NULL; k = k->next) { + if (k->key == NULL) { + k->key = *keyp; + *keyp = NULL; /* transfer ownership */ + break; + } + if (dst_key_compare(k->key, *keyp) == true) + break; + } + + if (k == NULL) + result = ISC_R_SUCCESS; + else if (*keyp != NULL) + dst_key_free(keyp); + } + + if (result == ISC_R_SUCCESS) { + knode->key = *keyp; + knode->next = node->data; + *keyp = NULL; + } + } + + if (result == ISC_R_SUCCESS) { + node->data = knode; + knode = NULL; + } + + /* Key was already there? That's the same as a success */ + if (result == ISC_R_EXISTS) + result = ISC_R_SUCCESS; + + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write); + + if (knode != NULL) + dns_keynode_detach(keytable->mctx, &knode); + + return (result); +} + +isc_result_t +dns_keytable_add(dns_keytable_t *keytable, bool managed, + dst_key_t **keyp) +{ + REQUIRE(keyp != NULL && *keyp != NULL); + return (insert(keytable, managed, dst_key_name(*keyp), keyp)); +} + +isc_result_t +dns_keytable_marksecure(dns_keytable_t *keytable, dns_name_t *name) { + return (insert(keytable, true, name, NULL)); +} + +isc_result_t +dns_keytable_delete(dns_keytable_t *keytable, 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_deletekeynode(dns_keytable_t *keytable, dst_key_t *dstkey) { + isc_result_t result; + dns_name_t *keyname; + dns_rbtnode_t *node = NULL; + dns_keynode_t *knode = NULL, **kprev = NULL; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(dstkey != NULL); + + keyname = dst_key_name(dstkey); + + 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; + if (knode->next == NULL && knode->key != NULL && + dst_key_compare(knode->key, dstkey) == true) + { + result = dns_rbt_deletenode(keytable->table, node, false); + goto finish; + } + + kprev = (dns_keynode_t **) &node->data; + while (knode != NULL) { + if (knode->key != NULL && + dst_key_compare(knode->key, dstkey) == true) + break; + kprev = &knode->next; + knode = knode->next; + } + + if (knode != NULL) { + if (knode->key != NULL) + dst_key_free(&knode->key); + /* + * This is equivalent to: + * dns_keynode_attach(knode->next, &tmp); + * dns_keynode_detach(kprev); + * dns_keynode_attach(tmp, &kprev); + * dns_keynode_detach(&tmp); + */ + *kprev = knode->next; + knode->next = NULL; + dns_keynode_detach(keytable->mctx, &knode); + } else + result = DNS_R_PARTIALMATCH; + finish: + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_write); + return (result); +} + +isc_result_t +dns_keytable_find(dns_keytable_t *keytable, 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) { + isc_refcount_increment0(&keytable->active_nodes, NULL); + dns_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_nextkeynode(dns_keytable_t *keytable, dns_keynode_t *keynode, + dns_keynode_t **nextnodep) +{ + /* + * Return the next key after 'keynode', regardless of + * properties. + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(VALID_KEYNODE(keynode)); + REQUIRE(nextnodep != NULL && *nextnodep == NULL); + + if (keynode->next == NULL) + return (ISC_R_NOTFOUND); + + dns_keynode_attach(keynode->next, nextnodep); + isc_refcount_increment(&keytable->active_nodes, NULL); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_keytable_findkeynode(dns_keytable_t *keytable, dns_name_t *name, + dns_secalg_t algorithm, dns_keytag_t tag, + dns_keynode_t **keynodep) +{ + isc_result_t result; + dns_keynode_t *knode; + void *data; + + /* + * Search for a key named 'name', matching 'algorithm' and 'tag' in + * 'keytable'. + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(dns_name_isabsolute(name)); + REQUIRE(keynodep != NULL && *keynodep == NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + + /* + * Note we don't want the DNS_R_PARTIALMATCH from dns_rbt_findname() + * as that indicates that 'name' was not found. + * + * DNS_R_PARTIALMATCH indicates that the name was found but we + * didn't get a match on algorithm and key id arguments. + */ + knode = NULL; + data = NULL; + result = dns_rbt_findname(keytable->table, name, 0, NULL, &data); + + if (result == ISC_R_SUCCESS) { + INSIST(data != NULL); + for (knode = data; knode != NULL; knode = knode->next) { + if (knode->key == NULL) { + knode = NULL; + break; + } + if (algorithm == dst_key_alg(knode->key) + && tag == dst_key_id(knode->key)) + break; + } + if (knode != NULL) { + isc_refcount_increment0(&keytable->active_nodes, NULL); + dns_keynode_attach(knode, keynodep); + } else + result = DNS_R_PARTIALMATCH; + } else if (result == DNS_R_PARTIALMATCH) + result = ISC_R_NOTFOUND; + + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + + return (result); +} + +isc_result_t +dns_keytable_findnextkeynode(dns_keytable_t *keytable, dns_keynode_t *keynode, + dns_keynode_t **nextnodep) +{ + isc_result_t result; + dns_keynode_t *knode; + + /* + * Search for the next key with the same properties as 'keynode' in + * 'keytable'. + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(VALID_KEYNODE(keynode)); + REQUIRE(nextnodep != NULL && *nextnodep == NULL); + + for (knode = keynode->next; knode != NULL; knode = knode->next) { + if (knode->key == NULL) { + knode = NULL; + break; + } + if (dst_key_alg(keynode->key) == dst_key_alg(knode->key) && + dst_key_id(keynode->key) == dst_key_id(knode->key)) + break; + } + if (knode != NULL) { + isc_refcount_increment(&keytable->active_nodes, NULL); + result = ISC_R_SUCCESS; + dns_keynode_attach(knode, nextnodep); + } else + result = ISC_R_NOTFOUND; + + return (result); +} + +isc_result_t +dns_keytable_finddeepestmatch(dns_keytable_t *keytable, 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_attachkeynode(dns_keytable_t *keytable, dns_keynode_t *source, + dns_keynode_t **target) +{ + /* + * Give back a keynode found via dns_keytable_findkeynode(). + */ + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(VALID_KEYNODE(source)); + REQUIRE(target != NULL && *target == NULL); + + isc_refcount_increment(&keytable->active_nodes, NULL); + + dns_keynode_attach(source, target); +} + +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)); + + isc_refcount_decrement(&keytable->active_nodes, NULL); + dns_keynode_detach(keytable->mctx, keynodep); +} + +isc_result_t +dns_keytable_issecuredomain(dns_keytable_t *keytable, 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); + + result = isc_buffer_allocate(keytable->mctx, &text, 4096); + if (result != ISC_R_SUCCESS) + return (result); + + 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); +} + +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; + + REQUIRE(VALID_KEYTABLE(keytable)); + REQUIRE(text != NULL && *text != NULL); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, keytable->mctx); + 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 (;;) { + char pbuf[DST_KEY_FORMATSIZE]; + + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + for (knode = node->data; knode != NULL; knode = knode->next) { + char obuf[DNS_NAME_FORMATSIZE + 200]; + if (knode->key == NULL) + continue; + dst_key_format(knode->key, pbuf, sizeof(pbuf)); + snprintf(obuf, sizeof(obuf), "%s ; %s\n", pbuf, + knode->managed ? "managed" : "trusted"); + result = putstr(text, obuf); + if (result != ISC_R_SUCCESS) + break; + } + 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 *, void *), + void *arg) +{ + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + + REQUIRE(VALID_KEYTABLE(keytable)); + + RWLOCK(&keytable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, keytable->mctx); + 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; + } + isc_refcount_increment0(&keytable->active_nodes, NULL); + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) + (*func)(keytable, node->data, 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; + } + } + isc_refcount_decrement(&keytable->active_nodes, NULL); + + cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&keytable->rwlock, isc_rwlocktype_read); + return (result); +} + +dst_key_t * +dns_keynode_key(dns_keynode_t *keynode) { + + /* + * Get the DST key associated with keynode. + */ + + REQUIRE(VALID_KEYNODE(keynode)); + + return (keynode->key); +} + +bool +dns_keynode_managed(dns_keynode_t *keynode) { + /* + * Is this a managed key? + */ + REQUIRE(VALID_KEYNODE(keynode)); + + return (keynode->managed); +} + +isc_result_t +dns_keynode_create(isc_mem_t *mctx, dns_keynode_t **target) { + isc_result_t result; + dns_keynode_t *knode; + + REQUIRE(target != NULL && *target == NULL); + + knode = isc_mem_get(mctx, sizeof(dns_keynode_t)); + if (knode == NULL) + return (ISC_R_NOMEMORY); + + knode->magic = KEYNODE_MAGIC; + knode->managed = false; + knode->key = NULL; + knode->next = NULL; + + result = isc_refcount_init(&knode->refcount, 1); + if (result != ISC_R_SUCCESS) + return (result); + + *target = knode; + return (ISC_R_SUCCESS); +} + +void +dns_keynode_attach(dns_keynode_t *source, dns_keynode_t **target) { + REQUIRE(VALID_KEYNODE(source)); + isc_refcount_increment(&source->refcount, NULL); + *target = source; +} + +void +dns_keynode_detach(isc_mem_t *mctx, dns_keynode_t **keynode) { + unsigned int refs; + dns_keynode_t *node = *keynode; + REQUIRE(VALID_KEYNODE(node)); + isc_refcount_decrement(&node->refcount, &refs); + if (refs == 0) { + if (node->key != NULL) + dst_key_free(&node->key); + isc_refcount_destroy(&node->refcount); + isc_mem_put(mctx, node, sizeof(dns_keynode_t)); + } + *keynode = NULL; +} + +void +dns_keynode_detachall(isc_mem_t *mctx, dns_keynode_t **keynode) { + dns_keynode_t *next = NULL, *node = *keynode; + REQUIRE(VALID_KEYNODE(node)); + while (node != NULL) { + next = node->next; + dns_keynode_detach(mctx, &node); + node = next; + } + *keynode = NULL; +} diff --git a/lib/dns/lib.c b/lib/dns/lib.c new file mode 100644 index 0000000..304814b --- /dev/null +++ b/lib/dns/lib.c @@ -0,0 +1,161 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: lib.c,v 1.19 2009/09/03 00:12:23 each Exp $ */ + +/*! \file */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +/*** + *** Globals + ***/ + +LIBDNS_EXTERNAL_DATA unsigned int dns_pps = 0U; +LIBDNS_EXTERNAL_DATA isc_msgcat_t * dns_msgcat = NULL; + + +/*** + *** Private + ***/ + +static isc_once_t msgcat_once = ISC_ONCE_INIT; + + +/*** + *** Functions + ***/ + +static void +open_msgcat(void) { + isc_msgcat_open("libdns.cat", &dns_msgcat); +} + +void +dns_lib_initmsgcat(void) { + + /* + * Initialize the DNS library's message catalog, dns_msgcat, if it + * has not already been initialized. + */ + + RUNTIME_CHECK(isc_once_do(&msgcat_once, open_msgcat) == ISC_R_SUCCESS); +} + +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_mutex_t reflock; +static unsigned int references = 0; + +static void +initialize(void) { + isc_result_t result; + + REQUIRE(initialize_done == false); + + result = isc_mem_create(0, 0, &dns_g_mctx); + if (result != ISC_R_SUCCESS) + return; + dns_result_register(); + result = dns_ecdb_register(dns_g_mctx, &dbimp); + if (result != ISC_R_SUCCESS) + goto cleanup_mctx; + result = isc_hash_create(dns_g_mctx, NULL, DNS_NAME_MAXWIRE); + if (result != ISC_R_SUCCESS) + goto cleanup_db; + + result = dst_lib_init(dns_g_mctx, NULL, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_hash; + + result = isc_mutex_init(&reflock); + if (result != ISC_R_SUCCESS) + goto cleanup_dst; + + initialize_done = true; + return; + + cleanup_dst: + dst_lib_destroy(); + cleanup_hash: + isc_hash_destroy(); + 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); + + LOCK(&reflock); + references++; + UNLOCK(&reflock); + + return (ISC_R_SUCCESS); +} + +void +dns_lib_shutdown(void) { + bool cleanup_ok = false; + + LOCK(&reflock); + if (--references == 0) + cleanup_ok = true; + UNLOCK(&reflock); + + if (!cleanup_ok) + return; + + dst_lib_destroy(); + + if (isc_hashctx != NULL) + isc_hash_destroy(); + 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..fb1da41 --- /dev/null +++ b/lib/dns/log.c @@ -0,0 +1,100 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#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 }, + { 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/rbtdb64", 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/acache", 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..acd9c34 --- /dev/null +++ b/lib/dns/lookup.c @@ -0,0 +1,491 @@ +/* + * 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 http://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 + +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 inline 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, 0, + 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; + isc_result_t result; + + name = isc_mem_get(lookup->mctx, sizeof(dns_name_t)); + if (name == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_name_init(name, NULL); + result = dns_name_dup(dns_fixedname_name(&lookup->name), + lookup->mctx, name); + if (result != ISC_R_SUCCESS) + goto fail; + + if (dns_rdataset_isassociated(&lookup->rdataset)) { + rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + 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)); + if (sigrdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + 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); + + fail: + if (name != NULL) { + if (dns_name_dynamic(name)) + dns_name_free(name, lookup->mctx); + isc_mem_put(lookup->mctx, name, sizeof(dns_name_t)); + } + if (rdataset != NULL) { + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + isc_mem_put(lookup->mctx, rdataset, sizeof(dns_rdataset_t)); + } + return (result); +} + +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, + &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; + result = dns_name_copy(&cname.cname, name, NULL); + dns_rdata_freestruct(&cname); + if (result == ISC_R_SUCCESS) { + 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, 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) +{ + isc_result_t result; + dns_lookup_t *lookup; + isc_event_t *ievent; + + lookup = isc_mem_get(mctx, sizeof(*lookup)); + if (lookup == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (ievent == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_lookup; + } + 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); + + result = isc_mutex_init(&lookup->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_event; + + dns_fixedname_init(&lookup->name); + + result = dns_name_copy(name, dns_fixedname_name(&lookup->name), NULL); + if (result != ISC_R_SUCCESS) + goto cleanup_lock; + + 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); + + cleanup_lock: + DESTROYLOCK(&lookup->lock); + + cleanup_event: + ievent = (isc_event_t *)lookup->event; + isc_event_free(&ievent); + lookup->event = NULL; + + isc_task_detach(&lookup->task); + + cleanup_lookup: + isc_mem_putanddetach(&mctx, lookup, sizeof(*lookup)); + + return (result); +} + +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; + 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); + + DESTROYLOCK(&lookup->lock); + lookup->magic = 0; + isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup)); + + *lookupp = NULL; +} diff --git a/lib/dns/mapapi b/lib/dns/mapapi new file mode 100644 index 0000000..90bf250 --- /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. Fast files are *never* +# compatible across major releases. +MAPAPI=1.0 diff --git a/lib/dns/master.c b/lib/dns/master.c new file mode 100644 index 0000000..082e63f --- /dev/null +++ b/lib/dns/master.c @@ -0,0 +1,3242 @@ +/* + * 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 http://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 + +/*! + * 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. */ + bool canceled; + isc_mutex_t lock; + isc_result_t result; + /* locked by lock */ + uint32_t references; + 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 inline 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)); + + LOCK(&source->lock); + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); /* Overflow? */ + UNLOCK(&source->lock); + + *target = source; +} + +void +dns_loadctx_detach(dns_loadctx_t **lctxp) { + dns_loadctx_t *lctx; + bool need_destroy = false; + + REQUIRE(lctxp != NULL); + lctx = *lctxp; + REQUIRE(DNS_LCTX_VALID(lctx)); + + LOCK(&lctx->lock); + INSIST(lctx->references > 0); + lctx->references--; + if (lctx->references == 0) + need_destroy = true; + UNLOCK(&lctx->lock); + + if (need_destroy) + loadctx_destroy(lctx); + *lctxp = NULL; +} + +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) { + isc_mem_t *mctx; + isc_result_t result; + + REQUIRE(DNS_LCTX_VALID(lctx)); + + lctx->magic = 0; + if (lctx->inc != NULL) + incctx_destroy(lctx->mctx, lctx->inc); + + if (lctx->f != NULL) { + 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); + DESTROYLOCK(&lctx->lock); + mctx = NULL; + isc_mem_attach(lctx->mctx, &mctx); + isc_mem_detach(&lctx->mctx); + isc_mem_put(mctx, lctx, sizeof(*lctx)); + isc_mem_detach(&mctx); +} + +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)); + if (ictx == NULL) + return (ISC_R_NOMEMORY); + + 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)); + if (lctx == NULL) + return (ISC_R_NOMEMORY); + result = isc_mutex_init(&lctx->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, lctx, sizeof(*lctx)); + return (result); + } + + 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) { + default: + INSIST(0); + 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; + } + + 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; + 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); + 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; + lctx->canceled = false; + lctx->mctx = NULL; + isc_mem_attach(mctx, &lctx->mctx); + 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 seperator. + * If value is non zero then we need to add another label and + * that requires a label seperator. + */ + 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]; + 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,%u,%1[doxXnN]}", + &delta, &width, mode); + switch (n) { + case 1: + break; + case 2: + n = snprintf(fmt, sizeof(fmt), + "%%0%ud", width); + break; + case 3: + if (mode[0] == 'n' || mode[0] == 'N') + nibblemode = true; + n = snprintf(fmt, sizeof(fmt), + "%%0%u%c", width, mode[0]); + break; + default: + return (DNS_R_SYNTAX); + } + if (n >= sizeof(fmt)) + return (ISC_R_NOSPACE); + /* Skip past closing brace. */ + while (*name != '\0' && *name++ != '}') + continue; + } + 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 i, n, start, stop, step = 0; + 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 ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0 && + 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 <= 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 ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0 && + (lctx->options & DNS_MASTER_KEY) == 0 && + !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)); + if (tmp == NULL) + return (ISC_R_NOMEMORY); + /* + * Catch both "1.2.3.4" and "1.2.3.4." + */ + if (tmp[strlen(tmp) - 1] == '.') + tmp[strlen(tmp) - 1] = '\0'; + if (inet_aton(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); + if (target_mem == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + 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)); + if (include_file == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + 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)); + if (range == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + /* LHS */ + GETTOKEN(lctx->lex, 0, &token, false); + lhs = isc_mem_strdup(mctx, DNS_AS_STR(token)); + if (lhs == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + 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)); + if (gtype == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + /* RHS */ + GETTOKEN(lctx->lex, ISC_LEXOPT_QSTRING, + &token, false); + rhs = isc_mem_strdup(mctx, DNS_AS_STR(token)); + if (rhs == NULL) { + result = ISC_R_NOMEMORY; + goto log_and_cleanup; + } + 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_compare(ictx->glue, new_name) != 0) { + 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_compare(ictx->current, new_name) != 0)) { + 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 ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0 && + (lctx->options & DNS_MASTER_KEY) == 0 && + !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 ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0 && + (type == dns_rdatatype_md || type == dns_rdatatype_mf)) { + char typename[DNS_RDATATYPE_FORMATSIZE]; + + result = DNS_R_OBSOLETE; + + dns_rdatatype_format(type, typename, sizeof(typename)); + (*callbacks->error)(callbacks, + "%s:%lu: %s '%s': %s", + source, line, + "type", typename, + 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 ((lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0 && + dns_rdatatype_ismeta(type)) + { + char typename[DNS_RDATATYPE_FORMATSIZE]; + + result = DNS_R_METATYPE; + + dns_rdatatype_format(type, typename, sizeof(typename)); + (*callbacks->error)(callbacks, + "%s:%lu: %s '%s': %s", + source, line, + "type", typename, + 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_compare(ictx->current, lctx->top) != 0) { + 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 (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 && (lctx->options & DNS_MASTER_ZONE) != 0 && + (lctx->options & DNS_MASTER_SLAVE) == 0) { + (*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, ignore it. + */ + if (lctx->ttl < ttl_offset) + continue; + 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 inline 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" + : "raw"); + return (ISC_R_NOTIMPLEMENTED); + } + + header.version = isc_buffer_getuint32(&target); + + switch (header.version) { + case 0: + remainder = sizeof(header.dumptime); + break; + case DNS_RAWFORMAT_VERSION: + remainder = sizeof(header) - commonlen; + break; + default: + (*callbacks->error)(callbacks, + "dns_master_load: " + "unsupported file format version"); + return (ISC_R_NOTIMPLEMENTED); + } + + result = isc_stdio_read(data + commonlen, 1, remainder, lctx->f, NULL); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__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); + if (target_mem == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx) +{ + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, 0, callbacks, NULL, NULL, + mctx, dns_masterformat_text, 0)); +} + +isc_result_t +dns_master_loadfile2(const char *master_file, dns_name_t *top, + dns_name_t *origin, + dns_rdataclass_t zclass, unsigned int options, + dns_rdatacallbacks_t *callbacks, isc_mem_t *mctx, + dns_masterformat_t format) +{ + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, 0, callbacks, NULL, NULL, + mctx, format, 0)); +} + +isc_result_t +dns_master_loadfile3(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_mem_t *mctx, + dns_masterformat_t format) +{ + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, resign, callbacks, NULL, NULL, + mctx, format, 0)); +} + +isc_result_t +dns_master_loadfile4(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) +{ + return (dns_master_loadfile5(master_file, top, origin, zclass, + options, resign, callbacks, + include_cb, include_arg, + mctx, format, 0)); +} + +isc_result_t +dns_master_loadfile5(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, dns_rdatacallbacks_t *callbacks, + isc_task_t *task, dns_loaddonefunc_t done, + void *done_arg, dns_loadctx_t **lctxp, isc_mem_t *mctx) +{ + return (dns_master_loadfileinc4(master_file, top, origin, zclass, + options, 0, callbacks, task, done, + done_arg, lctxp, NULL, NULL, mctx, + dns_masterformat_text)); +} + +isc_result_t +dns_master_loadfileinc2(const char *master_file, 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, + dns_masterformat_t format) +{ + return (dns_master_loadfileinc4(master_file, top, origin, zclass, + options, 0, callbacks, task, done, + done_arg, lctxp, NULL, NULL, mctx, + format)); +} + +isc_result_t +dns_master_loadfileinc3(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, isc_mem_t *mctx, + dns_masterformat_t format) +{ + return (dns_master_loadfileinc4(master_file, top, origin, zclass, + options, resign, callbacks, task, + done, done_arg, lctxp, NULL, NULL, + mctx, format)); +} + +isc_result_t +dns_master_loadfileinc4(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) +{ + options &= ~DNS_MASTER_CHECKTTL; + return (dns_master_loadfileinc5(master_file, top, origin, zclass, + options, resign, callbacks, task, + done, done_arg, lctxp, include_cb, + include_arg, mctx, format, 0)); +} + +isc_result_t +dns_master_loadfileinc5(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_compare(&name, owner) == 0) + 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 (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)); + if (event == NULL) + return (ISC_R_NOMEMORY); + isc_task_send(lctx->task, &event); + return (ISC_R_SUCCESS); +} + +void +dns_loadctx_cancel(dns_loadctx_t *lctx) { + REQUIRE(DNS_LCTX_VALID(lctx)); + + LOCK(&lctx->lock); + lctx->canceled = true; + UNLOCK(&lctx->lock); +} + +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..4d09d16 --- /dev/null +++ b/lib/dns/masterdump.c @@ -0,0 +1,2127 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DNS_DCTX_MAGIC ISC_MAGIC('D', 'c', 't', 'x') +#define DNS_DCTX_VALID(d) ISC_MAGIC_VALID(d, DNS_DCTX_MAGIC) + +#define RETERR(x) do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return (_r); \ + } while (0) + +#define CHECK(x) do { \ + if ((x) != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +struct dns_master_style { + dns_masterstyle_flags_t flags; /* DNS_STYLEFLAG_* */ + unsigned int ttl_column; + unsigned int class_column; + unsigned int type_column; + unsigned int rdata_column; + unsigned int line_length; + unsigned int tab_width; + unsigned int split_width; +}; + +/*% + * The maximum length of the newline+indentation that is output + * when inserting a line break in an RR. This effectively puts an + * upper limits on the value of "rdata_column", because if it is + * very large, the tabs and spaces needed to reach it will not fit. + */ +#define DNS_TOTEXT_LINEBREAK_MAXLEN 100 + +/*% + * 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_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_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 dns_master_indentstr). + */ +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 +}; + +/*% + * Default indent string. + */ +LIBDNS_EXTERNAL_DATA const char *dns_master_indentstr = "\t"; +LIBDNS_EXTERNAL_DATA unsigned int dns_master_indent = 1; + +#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; + unsigned int references; + bool canceled; + bool first; + 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; + unsigned int nodes; + /* dns_master_dumpinc() */ + char *file; + char *tmpfile; + dns_masterformat_t format; + dns_masterrawheader_t header; + isc_result_t (*dumpsets)(isc_mem_t *mctx, 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) + +/*% + * 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, dns_totext_ctx_t *ctx) { + isc_result_t result; + + REQUIRE(style->tab_width != 0); + + 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(dns_master_indentstr); + for (i = 0; i < dns_master_indent; i++) { + if (isc_buffer_availablelength(&buf) < len) + return (DNS_R_TEXTTOOLONG); + isc_buffer_putstr(&buf, dns_master_indentstr); + } + } + + 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; + + 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, + 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)) { + 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, + dns_name_t *owner_name, + dns_totext_ctx_t *ctx, + bool omit_final_dot, + isc_buffer_t *target) +{ + isc_result_t result; + unsigned int column; + bool first = true; + uint32_t current_ttl; + bool current_ttl_valid; + dns_rdatatype_t type; + unsigned int type_start; + dns_fixedname_t fixed; + dns_name_t *name = NULL; + unsigned int i; + + REQUIRE(DNS_RDATASET_VALID(rdataset)); + + rdataset->attributes |= DNS_RDATASETATTR_LOADORDER; + result = dns_rdataset_first(rdataset); + + current_ttl = ctx->current_ttl; + current_ttl_valid = ctx->current_ttl_valid; + + if (owner_name != NULL) { + name = dns_fixedname_initname(&fixed); + dns_name_copy(owner_name, name, NULL); + 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 < dns_master_indent; i++) + RETERR(str_totext(dns_master_indentstr, + target)); + + /* + * YAML enumerator? + */ + if ((ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) { + RETERR(str_totext("- ", target)); + } + + /* + * Comment? + */ + 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_totext2(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 == false)) + { + 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 ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + for (i = 0; i < dns_master_indent; i++) + RETERR(str_totext(dns_master_indentstr, + target)); + } + 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, + 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, + (unsigned int) + 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, + 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, + 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, &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(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, &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(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, &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, 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 == false || + ctx->current_ttl != rdataset->ttl) + { + if ((ctx->style.flags & DNS_STYLEFLAG_COMMENT) != 0) + { + isc_buffer_clear(buffer); + result = dns_ttl_totext(rdataset->ttl, + 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); + if (newmem == NULL) + return (ISC_R_NOMEMORY); + 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, 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 (ctx->style.flags & DNS_STYLEFLAG_TRUST) { + if ((ctx->style.flags & DNS_STYLEFLAG_INDENT) != 0 || + (ctx->style.flags & DNS_STYLEFLAG_YAML) != 0) + { + unsigned int j; + for (j = 0; j < dns_master_indent; j++) + fprintf(f, "%s", dns_master_indentstr); + } + 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 = + 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 && + rds->attributes & DNS_RDATASETATTR_RESIGN) { + 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 < dns_master_indent; j++) + fprintf(f, "%s", dns_master_indentstr); + } + 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, 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); + if (newmem == NULL) + return (ISC_R_NOMEMORY); + 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, dns_name_t *name, + dns_rdatasetiter_t *rdsiter, dns_totext_ctx_t *ctx, + isc_buffer_t *buffer, FILE *f) +{ + isc_result_t result; + dns_rdataset_t rdataset; + + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) { + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + 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, 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 +dumptostreaminc(dns_dumpctx_t *dctx); + +static void +dumpctx_destroy(dns_dumpctx_t *dctx) { + + dctx->magic = 0; + DESTROYLOCK(&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); + + LOCK(&source->lock); + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); /* Overflow? */ + UNLOCK(&source->lock); + + *target = source; +} + +void +dns_dumpctx_detach(dns_dumpctx_t **dctxp) { + dns_dumpctx_t *dctx; + bool need_destroy = false; + + REQUIRE(dctxp != NULL); + dctx = *dctxp; + REQUIRE(DNS_DCTX_VALID(dctx)); + + *dctxp = NULL; + + LOCK(&dctx->lock); + INSIST(dctx->references != 0); + dctx->references--; + if (dctx->references == 0) + need_destroy = true; + UNLOCK(&dctx->lock); + if (need_destroy) + 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)); + + LOCK(&dctx->lock); + dctx->canceled = true; + UNLOCK(&dctx->lock); +} + +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); +} + +static void +dump_quantum(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_result_t tresult; + dns_dumpctx_t *dctx; + + REQUIRE(event != NULL); + dctx = event->ev_arg; + REQUIRE(DNS_DCTX_VALID(dctx)); + if (dctx->canceled) + result = ISC_R_CANCELED; + else + result = dumptostreaminc(dctx); + if (result == DNS_R_CONTINUE) { + event->ev_arg = dctx; + isc_task_send(task, &event); + return; + } + + if (dctx->file != NULL) { + 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->done)(dctx->done_arg, result); + isc_event_free(&event); + dns_dumpctx_detach(&dctx); +} + +static isc_result_t +task_send(dns_dumpctx_t *dctx) { + isc_event_t *event; + + event = isc_event_allocate(dctx->mctx, NULL, DNS_EVENT_DUMPQUANTUM, + dump_quantum, dctx, sizeof(*event)); + if (event == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (dctx == NULL) + return (ISC_R_NOMEMORY); + + 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; + dctx->nodes = 0; + dctx->first = true; + 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: + INSIST(0); + break; + } + + result = totext_ctx_init(style, &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->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; + + result = isc_mutex_init(&dctx->lock); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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); + 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); + if (dctx != NULL) + 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); + if (bufmem == NULL) + return (ISC_R_NOMEMORY); + + 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) { + 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); +#if !defined(STDTIME_ON_32BITS) || (STDTIME_ON_32BITS + 0) != 1 + /* + * We assume isc_stdtime_t is a 32-bit integer, + * which should be the case on most platforms. + * If it turns out to be uncommon, we'll need + * to bump the version number and revise the + * header format. + */ + isc_log_write(dns_lctx, + ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, + ISC_LOG_INFO, + "dumping master file in raw " + "format: stdtime is not 32bits"); + now32 = 0; +#else + now32 = dctx->now; +#endif + 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: + INSIST(0); + } + + isc_mem_put(dctx->mctx, buffer.base, buffer.length); + return (result); +} + +static isc_result_t +dumptostreaminc(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 nodes; + isc_time_t start; + + bufmem = isc_mem_get(dctx->mctx, initial_buffer_length); + if (bufmem == NULL) + return (ISC_R_NOMEMORY); + + isc_buffer_init(&buffer, bufmem, initial_buffer_length); + + name = dns_fixedname_initname(&fixname); + + if (dctx->first) { + 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; + + dctx->first = false; + } else + result = ISC_R_SUCCESS; + + nodes = dctx->nodes; + isc_time_now(&start); + while (result == ISC_R_SUCCESS && (dctx->nodes == 0 || nodes--)) { + 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_db_allrdatasets(dctx->db, node, dctx->version, + 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); + } + + /* + * Work out how many nodes can be written 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 written in the + * next iteration. + */ + if (dctx->nodes != 0 && result == ISC_R_SUCCESS) { + unsigned int pps = dns_pps; /* packets per second */ + unsigned int interval; + uint64_t usecs; + isc_time_t end; + + isc_time_now(&end); + if (pps < 100) + pps = 100; + interval = 1000000 / pps; /* interval in usecs */ + if (interval == 0) + interval = 1; + usecs = isc_time_microdiff(&end, &start); + if (usecs == 0) { + dctx->nodes = dctx->nodes * 2; + if (dctx->nodes > 1000) + dctx->nodes = 1000; + } else { + nodes = dctx->nodes * interval; + nodes /= (unsigned int)usecs; + if (nodes == 0) + nodes = 1; + else if (nodes > 1000) + nodes = 1000; + + /* Smooth and assign. */ + dctx->nodes = (nodes + dctx->nodes * 7) / 8; + + isc_log_write(dns_lctx, ISC_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTERDUMP, + ISC_LOG_DEBUG(1), + "dumptostreaminc(%p) new nodes -> %d", + dctx, dctx->nodes); + } + result = DNS_R_CONTINUE; + } else 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_dumptostreaminc(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; + dctx->nodes = 100; + + result = task_send(dctx); + if (result == ISC_R_SUCCESS) { + dns_dumpctx_attach(dctx, dctxp); + return (DNS_R_CONTINUE); + } + + dns_dumpctx_detach(&dctx); + return (result); +} + +/* + * Dump an entire database into a master file. + */ +isc_result_t +dns_master_dumptostream(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, + const dns_master_style_t *style, + FILE *f) +{ + return (dns_master_dumptostream3(mctx, db, version, style, + dns_masterformat_text, NULL, f)); +} + +isc_result_t +dns_master_dumptostream2(isc_mem_t *mctx, dns_db_t *db, + dns_dbversion_t *version, + const dns_master_style_t *style, + dns_masterformat_t format, FILE *f) +{ + return (dns_master_dumptostream3(mctx, db, version, style, + format, NULL, f)); +} + +isc_result_t +dns_master_dumptostream3(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 = dumptostreaminc(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); + if (tempname == NULL) + return (ISC_R_NOMEMORY); + + 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; + } + *tempp = tempname; + *fp = f; + return (ISC_R_SUCCESS); + +cleanup: + isc_mem_free(mctx, tempname); + return (result); +} + +isc_result_t +dns_master_dumpinc(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) +{ + return (dns_master_dumpinc3(mctx, db, version, style, filename, task, + done, done_arg, dctxp, + dns_masterformat_text, NULL)); +} + +isc_result_t +dns_master_dumpinc2(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) +{ + return (dns_master_dumpinc3(mctx, db, version, style, filename, task, + done, done_arg, dctxp, format, NULL)); +} + +isc_result_t +dns_master_dumpinc3(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); + if (file == NULL) + return (ISC_R_NOMEMORY); + + 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->nodes = 100; + 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) +{ + return (dns_master_dump3(mctx, db, version, style, filename, + dns_masterformat_text, NULL)); +} + +isc_result_t +dns_master_dump2(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) +{ + return (dns_master_dump3(mctx, db, version, style, filename, + format, NULL)); +} + +isc_result_t +dns_master_dump3(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 = dumptostreaminc(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, 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; + + result = totext_ctx_init(style, &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); + if (bufmem == NULL) + return (ISC_R_NOMEMORY); + + isc_buffer_init(&buffer, bufmem, initial_buffer_length); + + result = dns_db_allrdatasets(db, node, version, 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, 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, unsigned int 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, + isc_mem_t *mctx) +{ + return (dns_master_stylecreate2(stylep, flags, ttl_column, + class_column, type_column, + rdata_column, line_length, + tab_width, 0xffffffff, mctx)); +} + +isc_result_t +dns_master_stylecreate2(dns_master_style_t **stylep, unsigned int 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)); + if (style == NULL) + return (ISC_R_NOMEMORY); + + 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..ac637a2 --- /dev/null +++ b/lib/dns/message.c @@ -0,0 +1,4362 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/*** + *** Imports + ***/ + +#include +#include +#include +#include + +#include +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#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 512 +#define NAME_COUNT 64 +#define OFFSET_COUNT 4 +#define RDATA_COUNT 8 +#define RDATALIST_COUNT 8 +#define RDATASET_COUNT 64 + +/*% + * 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" +}; + +/*% + * "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 inline dns_msgblock_t * +msgblock_allocate(isc_mem_t *, unsigned int, unsigned int); + +#define msgblock_get(block, type) \ + ((type *)msgblock_internalget(block, sizeof(type))) + +static inline void * +msgblock_internalget(dns_msgblock_t *, unsigned int); + +static inline void +msgblock_reset(dns_msgblock_t *); + +static inline void +msgblock_free(isc_mem_t *, dns_msgblock_t *, unsigned int); + +static void +logfmtpacket(dns_message_t *message, const char *description, + 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 inline 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); + if (block == NULL) + return (NULL); + + 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 inline 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 inline void +msgblock_reset(dns_msgblock_t *block) { + block->remaining = block->count; +} + +/* + * Release memory associated with a message block. + */ +static inline 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 inline isc_result_t +newbuffer(dns_message_t *msg, unsigned int size) { + isc_result_t result; + isc_buffer_t *dynbuf; + + dynbuf = NULL; + result = isc_buffer_allocate(msg->mctx, &dynbuf, size); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOMEMORY); + + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + return (ISC_R_SUCCESS); +} + +static inline isc_buffer_t * +currentbuffer(dns_message_t *msg) { + isc_buffer_t *dynbuf; + + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + + return (dynbuf); +} + +static inline void +releaserdata(dns_message_t *msg, dns_rdata_t *rdata) { + ISC_LIST_PREPEND(msg->freerdata, rdata, link); +} + +static inline 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 inline void +releaserdatalist(dns_message_t *msg, dns_rdatalist_t *rdatalist) { + ISC_LIST_PREPEND(msg->freerdatalist, rdatalist, link); +} + +static inline 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 inline 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 inline void +msginitheader(dns_message_t *m) { + m->id = 0; + m->flags = 0; + m->rcode = 0; + m->opcode = 0; + m->rdclass = 0; +} + +static inline 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->buffer = NULL; +} + +static inline 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 inline 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 = 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; +} + +static inline 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; + } + if (dns_name_dynamic(name)) + dns_name_free(name, msg->mctx); + isc_mempool_put(msg->namepool, 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); + } + } + if (dns_name_dynamic(msg->tsigname)) + dns_name_free(msg->tsigname, msg->mctx); + isc_mempool_put(msg->namepool, msg->tsigname); + msg->tsig = NULL; + msg->tsigname = 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) { + if (dns_name_dynamic(msg->sig0name)) + dns_name_free(msg->sig0name, msg->mctx); + isc_mempool_put(msg->namepool, 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); +} + +isc_result_t +dns_message_create(isc_mem_t *mctx, unsigned int intent, dns_message_t **msgp) +{ + dns_message_t *m; + isc_result_t result; + isc_buffer_t *dynbuf; + 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)); + if (m == NULL) + return (ISC_R_NOMEMORY); + + /* + * No allocations until further notice. Just initialize all lists + * and other members that are freed in the cleanup phase here. + */ + + m->magic = DNS_MESSAGE_MAGIC; + m->from_to_wire = intent; + msginit(m); + + for (i = 0; i < DNS_SECTION_MAX; i++) + ISC_LIST_INIT(m->sections[i]); + + m->mctx = NULL; + isc_mem_attach(mctx, &m->mctx); + + ISC_LIST_INIT(m->scratchpad); + ISC_LIST_INIT(m->cleanup); + m->namepool = NULL; + m->rdspool = NULL; + 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); + + /* + * Ok, it is safe to allocate (and then "goto cleanup" if failure) + */ + + result = isc_mempool_create(m->mctx, sizeof(dns_name_t), &m->namepool); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_mempool_setfillcount(m->namepool, NAME_COUNT); + isc_mempool_setfreemax(m->namepool, NAME_COUNT); + isc_mempool_setname(m->namepool, "msg:names"); + + result = isc_mempool_create(m->mctx, sizeof(dns_rdataset_t), + &m->rdspool); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_mempool_setfillcount(m->rdspool, RDATASET_COUNT); + isc_mempool_setfreemax(m->rdspool, RDATASET_COUNT); + isc_mempool_setname(m->rdspool, "msg:rdataset"); + + dynbuf = NULL; + result = isc_buffer_allocate(mctx, &dynbuf, SCRATCHPAD_SIZE); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_APPEND(m->scratchpad, dynbuf, link); + + m->cctx = NULL; + + *msgp = m; + return (ISC_R_SUCCESS); + + /* + * Cleanup for error returns. + */ + cleanup: + dynbuf = ISC_LIST_HEAD(m->scratchpad); + if (dynbuf != NULL) { + ISC_LIST_UNLINK(m->scratchpad, dynbuf, link); + isc_buffer_free(&dynbuf); + } + if (m->namepool != NULL) + isc_mempool_destroy(&m->namepool); + if (m->rdspool != NULL) + isc_mempool_destroy(&m->rdspool); + m->magic = 0; + isc_mem_putanddetach(&mctx, m, sizeof(dns_message_t)); + + return (ISC_R_NOMEMORY); +} + +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; +} + +void +dns_message_destroy(dns_message_t **msgp) { + dns_message_t *msg; + + REQUIRE(msgp != NULL); + REQUIRE(DNS_MESSAGE_VALID(*msgp)); + + msg = *msgp; + *msgp = NULL; + + msgreset(msg, true); + isc_mempool_destroy(&msg->namepool); + isc_mempool_destroy(&msg->rdspool); + msg->magic = 0; + isc_mem_putanddetach(&msg->mctx, msg, sizeof(dns_message_t)); +} + +static isc_result_t +findname(dns_name_t **foundname, 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(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(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, false, + 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); + } + } + + INSIST(0); /* Cannot get here... */ + return (ISC_R_UNEXPECTED); +} + +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; + dns_name_t *name2; + dns_offsets_t *offsets; + dns_rdataset_t *rdataset; + dns_rdatalist_t *rdatalist; + isc_result_t result; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section; + bool free_name; + bool best_effort; + bool seen_problem; + + section = &msg->sections[DNS_SECTION_QUESTION]; + + best_effort = (options & DNS_MESSAGEPARSE_BESTEFFORT); + seen_problem = false; + + name = NULL; + rdataset = NULL; + rdatalist = NULL; + + for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) { + name = isc_mempool_get(msg->namepool); + if (name == NULL) + return (ISC_R_NOMEMORY); + free_name = true; + + offsets = newoffsets(msg); + if (offsets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(name, *offsets); + + /* + * 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 { + isc_mempool_put(msg->namepool, 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 0 + if (rdatalist != NULL) + isc_mempool_put(msg->rdlpool, rdatalist); +#endif + if (free_name) + isc_mempool_put(msg->namepool, 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_offsets_t *offsets; + dns_rdataset_t *rdataset; + dns_rdatalist_t *rdatalist; + isc_result_t result; + dns_rdatatype_t rdtype, covers; + dns_rdataclass_t rdclass; + dns_rdata_t *rdata; + dns_ttl_t ttl; + dns_namelist_t *section; + bool free_name = false, free_rdataset = false; + bool preserve_order, best_effort, seen_problem; + bool issigzero; + + preserve_order = (options & DNS_MESSAGEPARSE_PRESERVEORDER); + best_effort = (options & DNS_MESSAGEPARSE_BESTEFFORT); + seen_problem = false; + + section = &msg->sections[sectionid]; + + 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; + + name = isc_mempool_get(msg->namepool); + if (name == NULL) + return (ISC_R_NOMEMORY); + free_name = true; + + offsets = newoffsets(msg); + if (offsets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(name, *offsets); + + /* + * 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); + msg->sigstart = recstart; + skip_name_search = true; + skip_type_search = 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); + skip_name_search = true; + skip_type_search = 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; + issigzero = false; + 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); + msg->sigstart = recstart; + 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 (rdtype != dns_rdatatype_opt && + rdtype != dns_rdatatype_tsig && + !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) { + isc_mempool_put(msg->namepool, 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 (rdtype != dns_rdatatype_opt && + rdtype != dns_rdatatype_tsig && + !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 (rdtype == dns_rdatatype_opt && msg->opt == NULL) { + 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; + isc_mempool_put(msg->namepool, name); + free_name = false; + } else if (issigzero && msg->sig0 == NULL) { + msg->sig0 = rdataset; + msg->sig0name = name; + rdataset = NULL; + free_rdataset = false; + free_name = false; + } else if (rdtype == dns_rdatatype_tsig && msg->tsig == NULL) { + msg->tsig = rdataset; + msg->tsigname = name; + /* 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) + isc_mempool_put(msg->namepool, name); + if (free_rdataset) + isc_mempool_put(msg->rdspool, rdataset); + free_name = free_rdataset = false; + } + INSIST(free_name == false); + INSIST(free_rdataset == false); + } + + /* + * 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) + isc_mempool_put(msg->namepool, 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); + + origsource = *source; + + msg->header_ok = 0; + msg->question_ok = 0; + + 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 ((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); + if (msg->saved.base == NULL) + return (ISC_R_NOMEMORY); + memmove(msg->saved.base, isc_buffer_base(&origsource), + msg->saved.length); + msg->free_saved = 1; + } + + if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) + return (DNS_R_RECOVERABLE); + if (seen_problem == true) + 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 inline 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); +} + +#ifdef ALLOW_FILTER_AAAA +/* + * Decide whether to not answer with an AAAA record and its RRSIG + */ +static inline bool +norender_rdataset(const dns_rdataset_t *rdataset, unsigned int options, + dns_section_t sectionid) +{ + if (sectionid == DNS_SECTION_QUESTION) + return (false); + + switch (rdataset->type) { + case dns_rdatatype_ns: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 || + sectionid != DNS_SECTION_AUTHORITY) + return (false); + break; + + case dns_rdatatype_aaaa: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0) + return (false); + break; + + case dns_rdatatype_rrsig: + if ((options & DNS_MESSAGERENDER_FILTER_AAAA) == 0 || + (rdataset->covers != dns_rdatatype_ns && + rdataset->covers != dns_rdatatype_aaaa)) + return (false); + if ((rdataset->covers == dns_rdatatype_ns) && + (sectionid != DNS_SECTION_AUTHORITY)) + return (false); + break; + + default: + return (false); + } + + if (rdataset->rdclass != dns_rdataclass_in) + return (false); + + return (true); +} +#endif + +static isc_result_t +renderset(dns_rdataset_t *rdataset, 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; + +#ifdef ALLOW_FILTER_AAAA + /* + * Suppress AAAAs if asked and we are + * not doing DNSSEC or are breaking DNSSEC. + * Say so in the AD bit if we break DNSSEC. + */ + if (norender_rdataset(rdataset, options, sectionid)) { + if (sectionid == DNS_SECTION_ANSWER || + sectionid == DNS_SECTION_AUTHORITY) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + if (OPTOUT(rdataset)) + msg->flags &= ~DNS_MESSAGEFLAG_AD; + goto next; + } + +#endif + 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. + */ + msg->opt->ttl &= ~DNS_MESSAGE_EDNSRCODE_MASK; + msg->opt->ttl |= ((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); + } + + /* + * 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, + 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) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = isc_mempool_get(msg->namepool); + if (*item == NULL) + return (ISC_R_NOMEMORY); + dns_name_init(*item, NULL); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_message_gettempoffsets(dns_message_t *msg, dns_offsets_t **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item == NULL); + + *item = newoffsets(msg); + if (*item == NULL) + return (ISC_R_NOMEMORY); + + 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 **item) { + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(item != NULL && *item != NULL); + + if (dns_name_dynamic(*item)) + dns_name_free(*item, msg->mctx); + isc_mempool_put(msg->namepool, *item); + *item = NULL; +} + +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, 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)); + REQUIRE(msg->state == DNS_SECTION_ANY); + + 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); + result = isc_buffer_allocate(msg->mctx, &buf, r.length); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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); + + result = isc_buffer_allocate(mctx, querytsig, r.length); + if (result != ISC_R_SUCCESS) + return (result); + isc_buffer_putmem(*querytsig, r.base, r.length); + return (ISC_R_SUCCESS); +} + +dns_rdataset_t * +dns_message_getsig0(dns_message_t *msg, 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; + result = isc_buffer_allocate(msg->mctx, &dynbuf, 512); + if (result != ISC_R_SUCCESS) + return (result); + 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 { + 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 + +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 + 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, msg->mctx); + if (result != ISC_R_SUCCESS) + return (result); + + dns_rdataset_init(&keyset); + if (view == NULL) + return (DNS_R_KEYUNAUTHORIZED); + 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 < dns_master_indent; __i++) { \ + ADD_STRING(target, dns_master_indentstr); \ + } \ + } 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; + unsigned int saveindent; + dns_masterstyle_flags_t sflags; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_SECTION(section)); + + saveindent = dns_master_indent; + sflags = dns_master_styleflags(style); + if (ISC_LIST_EMPTY(msg->sections[section])) + goto cleanup; + + + 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) { + dns_master_indent++; + } + 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, + 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) { + dns_master_indent--; + } + 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: + dns_master_indent = saveindent; + 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 +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; + 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; + unsigned char *optdata; + unsigned int saveindent = dns_master_indent; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(target != NULL); + REQUIRE(VALID_PSEUDOSECTION(section)); + + switch (section) { + case DNS_PSEUDOSECTION_OPT: + ps = dns_message_getopt(msg); + if (ps == NULL) { + goto cleanup; + } + + INDENT(style); + ADD_STRING(target, "OPT_PSEUDOSECTION:\n"); + dns_master_indent++; + + INDENT(style); + ADD_STRING(target, "EDNS:\n"); + dns_master_indent++; + + 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 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); + + 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) { + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + INDENT(style); + ADD_STRING(target, "EXPIRE: "); + snprintf(buf, sizeof(buf), "%u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, + true, + target); + if (result != ISC_R_SUCCESS) + goto cleanup; + ADD_STRING(target, ")\n"); + continue; + } + INDENT(style); + ADD_STRING(target, "EXPIRE"); + } 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 { + INDENT(style); + ADD_STRING(target, "OPT: "); + snprintf(buf, sizeof(buf), "%u", optcode); + ADD_STRING(target, buf); + ADD_STRING(target, "\n"); + } + + if (optlen != 0) { + int i; + ADD_STRING(target, ": "); + + optdata = isc_buffer_current(&optbuf); + 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 + */ + 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])) + isc_buffer_putmem(target, + &optdata[i], + 1); + else + isc_buffer_putstr(target, "."); + } + ADD_STRING(target, "\")"); + } + ADD_STRING(target, "\n"); + } + 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, 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, target); + if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0 && + (flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0) + ADD_STRING(target, "\n"); + goto cleanup; + } + + result = ISC_R_UNEXPECTED; + + cleanup: + dns_master_indent = saveindent; + 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; + dns_name_t *name = NULL; + isc_result_t result; + char buf[sizeof("1234567890")]; + 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 not 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_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) { + if (optlen == 4) { + uint32_t secs; + secs = isc_buffer_getuint32(&optbuf); + ADD_STRING(target, "; EXPIRE: "); + snprintf(buf, sizeof(buf), "%u", secs); + ADD_STRING(target, buf); + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, + true, + target); + if (result != ISC_R_SUCCESS) + return (result); + ADD_STRING(target, ")\n"); + continue; + } + ADD_STRING(target, "; EXPIRE"); + } else if (optcode == DNS_OPT_PAD) { + ADD_STRING(target, "; PAD"); + } 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 { + ADD_STRING(target, "; OPT="); + snprintf(buf, sizeof(buf), "%u", optcode); + ADD_STRING(target, buf); + } + + if (optlen != 0) { + int i; + ADD_STRING(target, ": "); + + optdata = isc_buffer_current(&optbuf); + 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 + */ + ADD_STRING(target, "(\""); + if (isc_buffer_availablelength(target) < optlen) + return (ISC_R_NOSPACE); + for (i = 0; i < optlen; i++) { + if (isprint(optdata[i])) + isc_buffer_putmem(target, + &optdata[i], + 1); + else + isc_buffer_putstr(target, "."); + } + 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, 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, 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_totext(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) && + (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), "%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"); + 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 { + 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 if ((flags & DNS_MESSAGETEXTFLAG_NOHEADERS) == 0) { + 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"); + } + 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); + if (result != ISC_R_SUCCESS) + return (result); + + cleanup: + 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, + const void *order_arg) +{ + REQUIRE(DNS_MESSAGE_VALID(msg)); + msg->order = order; + msg->order_arg = order_arg; +} + +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, + isc_logcategory_t *category, isc_logmodule_t *module, + int level, isc_mem_t *mctx) +{ + logfmtpacket(message, description, NULL, category, module, + &dns_master_style_debug, level, mctx); +} + +void +dns_message_logpacket2(dns_message_t *message, + const char *description, 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, + isc_logcategory_t *category, isc_logmodule_t *module, + const dns_master_style_t *style, int level, + isc_mem_t *mctx) +{ + logfmtpacket(message, description, NULL, category, module, style, + level, mctx); +} + +void +dns_message_logfmtpacket2(dns_message_t *message, + const char *description, 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, + 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); + if (buf == NULL) + break; + 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; + for (i = 0; i < count; i++) + len += ednsopts[i].length + 4; + + if (len > 0xffffU) { + result = ISC_R_NOSPACE; + goto cleanup; + } + + result = isc_buffer_allocate(message->mctx, &buf, len); + if (result != ISC_R_SUCCESS) + goto cleanup; + + for (i = 0; i < count; i++) { + 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); + } + } + rdata->data = isc_buffer_base(buf); + rdata->length = len; + dns_message_takebuffer(message, &buf); + } 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; +} diff --git a/lib/dns/name.c b/lib/dns/name.c new file mode 100644 index 0000000..66040fb --- /dev/null +++ b/lib/dns/name.c @@ -0,0 +1,2734 @@ +/* + * 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 http://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 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 dns_name_t *dns_rootname = &root; + +static unsigned char wild_ndata[] = { "\001*" }; +static unsigned char wild_offsets[] = { 0 }; + +static dns_name_t wild = + DNS_NAME_INITNONABSOLUTE(wild_ndata, wild_offsets); + +/* XXXDCL make const? */ +LIBDNS_EXTERNAL_DATA dns_name_t *dns_wildcardname = &wild; + +unsigned int +dns_fullname_hash(dns_name_t *name, bool case_sensitive); + +/* + * dns_name_t to text post-conversion procedure. + */ +#ifdef ISC_PLATFORM_USETHREADS +static int thread_key_initialized = 0; +static isc_mutex_t thread_key_mutex; +static isc_mem_t *thread_key_mctx = NULL; +static isc_thread_key_t totext_filter_proc_key; +static isc_once_t once = ISC_ONCE_INIT; +#else +static dns_name_totextfilter_t totext_filter_proc = NULL; +#endif + +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(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; + + return (isc_hash_function_reverse(name->ndata, length, + case_sensitive, NULL)); +} + +unsigned int +dns_name_fullhash(dns_name_t *name, bool case_sensitive) { + /* + * Provide a hash value for 'name'. + */ + REQUIRE(VALID_NAME(name)); + + if (name->labels == 0) + return (0); + + return (isc_hash_function_reverse(name->ndata, name->length, + case_sensitive, NULL)); +} + +unsigned int +dns_fullname_hash(dns_name_t *name, bool case_sensitive) { + /* + * This function was deprecated due to the breakage of the name space + * convention. We only keep this internally to provide binary backward + * compatibility. + */ + return (dns_name_fullhash(name, case_sensitive)); +} + +unsigned int +dns_name_hashbylabel(dns_name_t *name, bool case_sensitive) { + unsigned char *offsets; + dns_offsets_t odata; + dns_name_t tname; + unsigned int h = 0; + unsigned int i; + + /* + * Provide a hash value for 'name'. + */ + REQUIRE(VALID_NAME(name)); + + if (name->labels == 0) + return (0); + else if (name->labels == 1) + return (isc_hash_function_reverse(name->ndata, name->length, + case_sensitive, NULL)); + + SETUP_OFFSETS(name, offsets, odata); + DNS_NAME_INIT(&tname, NULL); + tname.labels = 1; + h = 0; + for (i = 0; i < name->labels; i++) { + tname.ndata = name->ndata + offsets[i]; + if (i == name->labels - 1) + tname.length = name->length - offsets[i]; + else + tname.length = offsets[i + 1] - offsets[i]; + h += isc_hash_function_reverse(tname.ndata, tname.length, + case_sensitive, NULL); + } + + return (h); +} + +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)); + +#if defined(__clang__) && \ + ( __clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 2)) + memset(&tname, 0, sizeof(tname)); +#endif + 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 = 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 = 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(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); + + 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(c & 0xff)) { + 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(c & 0xff)) + 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); + *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); +} + +#ifdef ISC_PLATFORM_USETHREADS +static void +free_specific(void *arg) { + dns_name_totextfilter_t *mem = arg; + isc_mem_put(thread_key_mctx, mem, sizeof(*mem)); + /* Stop use being called again. */ + (void)isc_thread_key_setspecific(totext_filter_proc_key, NULL); +} + +static void +thread_key_mutex_init(void) { + RUNTIME_CHECK(isc_mutex_init(&thread_key_mutex) == ISC_R_SUCCESS); +} + +static isc_result_t +totext_filter_proc_key_init(void) { + isc_result_t result; + + /* + * We need the call to isc_once_do() to support profiled mutex + * otherwise thread_key_mutex could be initialized at compile time. + */ + result = isc_once_do(&once, thread_key_mutex_init); + if (result != ISC_R_SUCCESS) + return (result); + + if (!thread_key_initialized) { + LOCK(&thread_key_mutex); + if (thread_key_mctx == NULL) + result = isc_mem_create2(0, 0, &thread_key_mctx, 0); + if (result != ISC_R_SUCCESS) + goto unlock; + isc_mem_setname(thread_key_mctx, "threadkey", NULL); + isc_mem_setdestroycheck(thread_key_mctx, false); + + if (!thread_key_initialized && + isc_thread_key_create(&totext_filter_proc_key, + free_specific) != 0) { + result = ISC_R_FAILURE; + isc_mem_detach(&thread_key_mctx); + } else + thread_key_initialized = 1; + unlock: + UNLOCK(&thread_key_mutex); + } + return (result); +} +#endif + +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 = target->used; +#ifdef ISC_PLATFORM_USETHREADS + dns_name_totextfilter_t *mem; + dns_name_totextfilter_t totext_filter_proc = NULL; + isc_result_t result; +#endif + bool omit_final_dot = (options & DNS_NAME_OMITFINALDOT); + + /* + * This function assumes the name is in proper uncompressed + * wire format. + */ + REQUIRE(VALID_NAME(name)); + REQUIRE(ISC_BUFFER_VALID(target)); + +#ifdef ISC_PLATFORM_USETHREADS + result = totext_filter_proc_key_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + 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); + /* NOTREACHED */ + } + + /* + * 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); + +#ifdef ISC_PLATFORM_USETHREADS + mem = isc_thread_key_getspecific(totext_filter_proc_key); + if (mem != NULL) + totext_filter_proc = *mem; +#endif + if (totext_filter_proc != NULL) + return ((*totext_filter_proc)(target, oused, saw_root)); + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_tofilenametext(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); + /* NOTREACHED */ + } + + /* + * 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(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); + + 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) +{ + 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 'name' doesn't have an offsets table, make a clone which + * has one. + */ + if (name->offsets == NULL) { +#if defined(__clang__) && \ + ( __clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 2)) + memset(&clname, 0, sizeof(clname)); +#endif + DNS_NAME_INIT(&clname, clo); + dns_name_clone(name, &clname); + name = &clname; + } + DNS_NAME_INIT(&gp, NULL); + + offset = target->used; /*XXX*/ + + methods = dns_compress_getmethods(cctx); + + if ((name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 && + (methods & DNS_COMPRESS_GLOBAL14) != 0) + gf = dns_compress_findglobal(cctx, name, &gp, &go); + else + gf = false; + + /* + * If the offset is too high for 14 bit global compression, we're + * out of luck. + */ + if (gf && go >= 0x4000) + gf = false; + + /* + * Will the compression pointer reduce the message size? + */ + if (gf && (gp.length + 2) >= name->length) + gf = false; + + if (gf) { + if (target->length - target->used < gp.length) + return (ISC_R_NOSPACE); + if (gp.length != 0) { + unsigned char *base = target->base; + (void)memmove(base + target->used, gp.ndata, + (size_t)gp.length); + } + isc_buffer_add(target, gp.length); + go |= 0xc000; + if (target->length - target->used < 2) + return (ISC_R_NOSPACE); + isc_buffer_putuint16(target, go); + if (gp.length != 0) + dns_compress_add(cctx, name, &gp, offset); + } else { + if (target->length - target->used < name->length) + return (ISC_R_NOSPACE); + if (name->length != 0) { + unsigned char *base = target->base; + (void)memmove(base + target->used, name->ndata, + (size_t)name->length); + } + isc_buffer_add(target, name->length); + dns_compress_add(cctx, name, name, offset); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_concatenate(dns_name_t *prefix, 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(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; +} + +isc_result_t +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); + if (target->ndata == NULL) + return (ISC_R_NOMEMORY); + + 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); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_name_dupwithoffsets(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); + if (target->ndata == NULL) + return (ISC_R_NOMEMORY); + + 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(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); + +#if defined(__clang__) && \ + ( __clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 2)) + memset(&downname, 0, sizeof(downname)); +#endif + 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(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(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) { +#ifdef ISC_PLATFORM_USETHREADS + isc_result_t result; + dns_name_totextfilter_t *mem; + int res; + + result = totext_filter_proc_key_init(); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * If we already have been here set / clear as appropriate. + * Otherwise allocate memory. + */ + mem = isc_thread_key_getspecific(totext_filter_proc_key); + if (mem != NULL && proc != NULL) { + *mem = proc; + return (ISC_R_SUCCESS); + } + if (proc == NULL) { + if (mem != NULL) + isc_mem_put(thread_key_mctx, mem, sizeof(*mem)); + res = isc_thread_key_setspecific(totext_filter_proc_key, NULL); + if (res != 0) + result = ISC_R_UNEXPECTED; + return (result); + } + + mem = isc_mem_get(thread_key_mctx, sizeof(*mem)); + if (mem == NULL) + return (ISC_R_NOMEMORY); + *mem = proc; + if (isc_thread_key_setspecific(totext_filter_proc_key, mem) != 0) { + isc_mem_put(thread_key_mctx, mem, sizeof(*mem)); + result = ISC_R_UNEXPECTED; + } + return (result); +#else + totext_filter_proc = proc; + return (ISC_R_SUCCESS); +#endif +} + +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) { + /* + * Null terminate. + */ + isc_region_t r; + isc_buffer_usedregion(&buf, &r); + ((char *) r.base)[r.length] = '\0'; + + } else + snprintf(cp, size, ""); +} + +/* + * dns_name_tostring() -- similar to dns_name_format() but allocates its own + * memory. + */ +isc_result_t +dns_name_tostring(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); + if (p == NULL) + return (ISC_R_NOMEMORY); + 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); +} + +isc_result_t +dns_name_copy(const dns_name_t *source, dns_name_t *dest, isc_buffer_t *target) { + unsigned char *ndata; + + /* + * Make dest a copy of source. + */ + + REQUIRE(VALID_NAME(source)); + REQUIRE(VALID_NAME(dest)); + REQUIRE(target != NULL || dest->buffer != NULL); + + if (target == NULL) { + target = dest->buffer; + isc_buffer_clear(dest->buffer); + } + + 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) + memmove(dest->offsets, source->offsets, source->labels); + else + set_offsets(dest, dest->offsets, NULL); + } + + isc_buffer_add(target, dest->length); + + return (ISC_R_SUCCESS); +} + +void +dns_name_destroy(void) { +#ifdef ISC_PLATFORM_USETHREADS + RUNTIME_CHECK(isc_once_do(&once, thread_key_mutex_init) + == ISC_R_SUCCESS); + + LOCK(&thread_key_mutex); + if (thread_key_initialized) { + isc_mem_detach(&thread_key_mctx); + isc_thread_key_delete(totext_filter_proc_key); + thread_key_initialized = 0; + } + UNLOCK(&thread_key_mutex); + +#endif +} + +/* + * 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..c5078de --- /dev/null +++ b/lib/dns/ncache.c @@ -0,0 +1,750 @@ +/* + * 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 http://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 + +#define DNS_NCACHE_RDATA 20U + +/* + * 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 maxttl, + bool optout, bool secure, + dns_rdataset_t *addedrdataset); + +static inline 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 maxttl, + dns_rdataset_t *addedrdataset) +{ + return (addoptout(message, cache, node, covers, now, 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 maxttl, + bool optout, dns_rdataset_t *addedrdataset) +{ + return (addoptout(message, cache, node, covers, now, 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 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[4096]; + 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 (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; +} + +static dns_rdatasetmethods_t rdataset_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + rdataset_settrust, + NULL, + NULL, + NULL, + NULL +}; + +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..d0f0705 --- /dev/null +++ b/lib/dns/nsec.c @@ -0,0 +1,445 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RETERR(x) do { \ + 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); +} + +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, 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; + + 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, &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, + 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 || + dnskey.algorithm == DST_ALG_DSA || + dnskey.algorithm == DST_ALG_ECC) + 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, dns_name_t *name, + 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. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent nsec"); + return (ISC_R_IGNORE); + } + + 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..e127893 --- /dev/null +++ b/lib/dns/nsec3.c @@ -0,0 +1,2113 @@ +/* + * 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 http://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, &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_hashname(dns_fixedname_t *result, + unsigned char rethash[NSEC3_MAX_HASH_LENGTH], + size_t *hash_length, dns_name_t *name, 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, 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, + (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, 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, + 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, + 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, 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, + 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, 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, 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 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; + + /* + * 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. + */ + 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, 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, 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) { + *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); +} + +isc_result_t +dns_nsec3_maxiterations(dns_db_t *db, dns_dbversion_t *version, + isc_mem_t *mctx, unsigned int *iterationsp) +{ + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dst_key_t *key = NULL; + isc_buffer_t buffer; + isc_result_t result; + unsigned int bits, minbits = 4096; + + result = dns_db_getoriginnode(db, &node); + if (result != ISC_R_SUCCESS) + return (result); + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, + 0, 0, &rdataset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_NOTFOUND) { + *iterationsp = 0; + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS) + goto failure; + + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&rdataset, &rdata); + isc_buffer_init(&buffer, rdata.data, rdata.length); + isc_buffer_add(&buffer, rdata.length); + CHECK(dst_key_fromdns(dns_db_origin(db), rdataset.rdclass, + &buffer, mctx, &key)); + bits = dst_key_size(key); + dst_key_free(&key); + if (minbits > bits) + minbits = bits; + } + if (result != ISC_R_NOMORE) + goto failure; + + if (minbits <= 1024) + *iterationsp = 150; + else if (minbits <= 2048) + *iterationsp = 500; + else + *iterationsp = 2500; + result = ISC_R_SUCCESS; + + failure: + if (dns_rdataset_isassociated(&rdataset)) + dns_rdataset_disassociate(&rdataset); + return (result); +} + +isc_result_t +dns_nsec3_noexistnodata(dns_rdatatype_t type, dns_name_t* name, + dns_name_t *nsec3name, dns_rdataset_t *nsec3set, + dns_name_t *zonename, bool *exists, + bool *data, bool *optout, + bool *unknown, bool *setclosest, + bool *setnearest, dns_name_t *closest, + dns_name_t *nearest, dns_nseclog_t logit, void *arg) +{ + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fzone; + dns_fixedname_t qfixed; + dns_label_t hashlabel; + dns_name_t *qname; + dns_name_t *zone; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata = DNS_RDATA_INIT; + int order; + int scope; + bool atparent; + bool first; + bool ns; + bool soa; + isc_buffer_t buffer; + isc_result_t answer = ISC_R_IGNORE; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + unsigned char owner[NSEC3_MAX_HASH_LENGTH]; + unsigned int length; + unsigned int qlabels; + unsigned int zlabels; + + REQUIRE((exists == NULL && data == NULL) || + (exists != NULL && data != NULL)); + REQUIRE(nsec3set != NULL && nsec3set->type == dns_rdatatype_nsec3); + REQUIRE((setclosest == NULL && closest == NULL) || + (setclosest != NULL && closest != NULL)); + REQUIRE((setnearest == NULL && nearest == NULL) || + (setnearest != NULL && nearest != NULL)); + + result = dns_rdataset_first(nsec3set); + if (result != ISC_R_SUCCESS) { + (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC3 set"); + return (result); + } + + dns_rdataset_current(nsec3set, &rdata); + + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC3"); + + zone = dns_fixedname_initname(&fzone); + zlabels = dns_name_countlabels(nsec3name); + + /* + * NSEC3 records must have two or more labels to be valid. + */ + if (zlabels < 2) + return (ISC_R_IGNORE); + + /* + * Strip off the NSEC3 hash to get the zone. + */ + zlabels--; + dns_name_split(nsec3name, zlabels, NULL, zone); + + /* + * If not below the zone name we can ignore this record. + */ + if (!dns_name_issubdomain(name, zone)) + return (ISC_R_IGNORE); + + /* + * Is this zone the same or deeper than the current zone? + */ + if (dns_name_countlabels(zonename) == 0 || + dns_name_issubdomain(zone, zonename)) + dns_name_copy(zone, zonename, NULL); + + 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 = isc_safe_memcompare(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) { + 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 = isc_safe_memcompare(hash, owner, length); + if (first && order == 0) { + /* + * The hashes are the same. + */ + atparent = dns_rdatatype_atparent(type); + ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns); + soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa); + if (ns && !soa) { + if (!atparent) { + /* + * This NSEC3 record is from somewhere + * higher in the DNS, and at the + * parent of a delegation. It can not + * be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + } else if (atparent && ns && soa) { + /* + * This NSEC3 record is from the child. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring child NSEC3"); + return (ISC_R_IGNORE); + } + if (type == dns_rdatatype_cname || + type == dns_rdatatype_nxt || + type == dns_rdatatype_nsec || + type == dns_rdatatype_key || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) { + *exists = true; + *data = dns_nsec3_typepresent(&rdata, type); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 proves name exists (owner) " + "data=%d", *data); + return (ISC_R_SUCCESS); + } + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 proves CNAME exists"); + return (ISC_R_IGNORE); + } + + if (order == 0 && + dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa)) + { + /* + * This NSEC3 record is from somewhere higher in + * the DNS, and at the parent of a delegation. + * It can not be legitimately used here. + */ + (*logit)(arg, ISC_LOG_DEBUG(3), + "ignoring parent NSEC3"); + return (ISC_R_IGNORE); + } + + /* + * Potential closest encloser. + */ + if (order == 0) { + if (closest != NULL && + (dns_name_countlabels(closest) == 0 || + dns_name_issubdomain(qname, closest)) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) && + !dns_nsec3_typepresent(&rdata, dns_rdatatype_dname) && + (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) || + !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns))) + { + + dns_name_format(qname, namebuf, + sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 indicates potential closest " + "encloser: '%s'", namebuf); + dns_name_copy(qname, closest, NULL); + *setclosest = true; + } + dns_name_format(qname, namebuf, sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 at super-domain %s", namebuf); + return (answer); + } + + /* + * Find if the name does not exist. + * + * We continue as we need to find the name closest to the + * closest encloser that doesn't exist. + * + * We also need to continue to ensure that we are not + * proving the non-existence of a record in a sub-zone. + * If that would be the case we will return ISC_R_IGNORE + * above. + */ + if ((scope < 0 && order > 0 && + memcmp(hash, nsec3.next, length) < 0) || + (scope >= 0 && (order > 0 || + memcmp(hash, nsec3.next, length) < 0))) + { + dns_name_format(qname, namebuf, sizeof(namebuf)); + (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 proves " + "name does not exist: '%s'", namebuf); + if (nearest != NULL && + (dns_name_countlabels(nearest) == 0 || + dns_name_issubdomain(nearest, qname))) { + dns_name_copy(qname, nearest, NULL); + *setnearest = true; + } + + *exists = false; + *data = false; + if (optout != NULL) { + if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0) + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 indicates optout"); + else + (*logit)(arg, ISC_LOG_DEBUG(3), + "NSEC3 indicates secure range"); + *optout = (nsec3.flags & DNS_NSEC3FLAG_OPTOUT); + } + 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..41e219b --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,722 @@ +/* + * 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 http://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 + +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, NULL); +} + +static void +nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { + unsigned int refs; + dns_nta_t *nta = *ntap; + + REQUIRE(VALID_NTA(nta)); + + *ntap = NULL; + isc_refcount_decrement(&nta->refcount, &refs); + if (refs == 0) { + nta->magic = 0; + if (nta->timer != NULL) { + (void) isc_timer_reset(nta->timer, + isc_timertype_inactive, + NULL, NULL, true); + isc_timer_detach(&nta->timer); + } + isc_refcount_destroy(&nta->refcount); + 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)); + if (ntatable == NULL) + return (ISC_R_NOMEMORY); + + 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; + + result = isc_rwlock_init(&ntatable->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_rbt; + + ntatable->timermgr = timermgr; + ntatable->taskmgr = taskmgr; + + ntatable->view = view; + ntatable->references = 1; + + ntatable->magic = NTATABLE_MAGIC; + *ntatablep = ntatable; + + return (ISC_R_SUCCESS); + + cleanup_rbt: + dns_rbt_destroy(&ntatable->table); + + cleanup_task: + isc_task_detach(&ntatable->task); + + cleanup_ntatable: + isc_mem_put(ntatable->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); + + RWLOCK(&source->rwlock, isc_rwlocktype_write); + + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); + + RWUNLOCK(&source->rwlock, isc_rwlocktype_write); + + *targetp = source; +} + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep) { + bool destroy = false; + dns_ntatable_t *ntatable; + + REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep)); + + ntatable = *ntatablep; + *ntatablep = NULL; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + INSIST(ntatable->references > 0); + ntatable->references--; + if (ntatable->references == 0) + destroy = true; + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (destroy) { + dns_rbt_destroy(&ntatable->table); + isc_rwlock_destroy(&ntatable->rwlock); + 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); +} + +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 = ntatable->view; + 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); + result = dns_resolver_createfetch(view->resolver, nta->name, + dns_rdatatype_nsec, + NULL, NULL, NULL, + DNS_FETCHOPT_NONTA, + task, fetch_done, nta, + &nta->rdataset, + &nta->sigrdataset, + &nta->fetch); + if (result != ISC_R_SUCCESS) + nta_detach(view->mctx, &nta); +} + +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); + return (result); +} + +static isc_result_t +nta_create(dns_ntatable_t *ntatable, dns_name_t *name, dns_nta_t **target) { + isc_result_t result; + 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)); + if (nta == NULL) + return (ISC_R_NOMEMORY); + + nta->ntatable = ntatable; + nta->expiry = 0; + nta->timer = NULL; + nta->fetch = NULL; + dns_rdataset_init(&nta->rdataset); + dns_rdataset_init(&nta->sigrdataset); + + result = isc_refcount_init(&nta->refcount, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(view->mctx, nta, sizeof(dns_nta_t)); + return (result); + } + + nta->name = dns_fixedname_initname(&nta->fn); + dns_name_copy(name, nta->name, NULL); + + nta->magic = NTA_MAGIC; + + *target = nta; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, dns_name_t *name, + bool force, isc_stdtime_t now, + uint32_t lifetime) +{ + isc_result_t result; + dns_nta_t *nta = NULL; + dns_rbtnode_t *node; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + + view = ntatable->view; + + result = nta_create(ntatable, name, &nta); + if (result != ISC_R_SUCCESS) + return (result); + + nta->expiry = now + lifetime; + nta->forced = force; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + 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; + } + + 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, 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, + dns_name_t *name, 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_detach(&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, 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, ntatable->view->mctx); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOTFOUND) + result = ISC_R_SUCCESS; + goto cleanup; + } + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *) node->data; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char obuf[DNS_NAME_FORMATSIZE + + ISC_FORMATHTTPTIMESTAMP_SIZE + + sizeof("expired: \n")]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + snprintf(obuf, sizeof(obuf), "%s%s: %s %s", + first ? "" : "\n", nbuf, + 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); +} + +#if 0 +isc_result_t +dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_stdtime_t now; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain, ntatable->view->mctx); + 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) { + dns_nta_t *n = (dns_nta_t *) node->data; + char nbuf[DNS_NAME_FORMATSIZE], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + fprintf(fp, "%s: %s %s\n", nbuf, + n->expiry <= now ? "expired" : "expiry", + tbuf); + } + 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); +} +#endif + +isc_result_t +dns_ntatable_dump(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + isc_buffer_t *text = NULL; + int len = 4096; + + result = isc_buffer_allocate(ntatable->view->mctx, &text, len); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_ntatable_totext(ntatable, &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, ntatable->view->mctx); + 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) { + dns_nta_t *n = (dns_nta_t *) node->data; + if (n->expiry > now) { + isc_buffer_t b; + char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + + 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); +} diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c new file mode 100644 index 0000000..a30a2ab --- /dev/null +++ b/lib/dns/openssl_link.c @@ -0,0 +1,464 @@ +/* + * Portions 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 http://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. + */ + +#ifdef OPENSSL + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" + +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif + +static RAND_METHOD *rm = NULL; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +static isc_mutex_t *locks = NULL; +static int nlocks; +#endif + +#if !defined(OPENSSL_NO_ENGINE) +static ENGINE *e = NULL; +#endif + +static int +entropy_get(unsigned char *buf, int num) { + isc_result_t result; + if (num < 0) + return (-1); + result = dst__entropy_getdata(buf, (unsigned int) num, false); + return (result == ISC_R_SUCCESS ? 1 : -1); +} + +static int +entropy_status(void) { + return (dst__entropy_status() > 32); +} + +static int +entropy_getpseudo(unsigned char *buf, int num) { + isc_result_t result; + if (num < 0) + return (-1); + result = dst__entropy_getdata(buf, (unsigned int) num, true); + return (result == ISC_R_SUCCESS ? 1 : -1); +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +static void +entropy_add(const void *buf, int num, double entropy) { + /* + * Do nothing. The only call to this provides no useful data anyway. + */ + UNUSED(buf); + UNUSED(num); + UNUSED(entropy); +} +#else +static int +entropy_add(const void *buf, int num, double entropy) { + /* + * Do nothing. The only call to this provides no useful data anyway. + */ + UNUSED(buf); + UNUSED(num); + UNUSED(entropy); + return (1); +} +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +static void +lock_callback(int mode, int type, const char *file, int line) { + UNUSED(file); + UNUSED(line); + if ((mode & CRYPTO_LOCK) != 0) + LOCK(&locks[type]); + else + UNLOCK(&locks[type]); +} +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10000000L || defined(LIBRESSL_VERSION_NUMBER) +static unsigned long +id_callback(void) { + return ((unsigned long)isc_thread_self()); +} +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + +#define FLARG +#define FILELINE +#if ISC_MEM_TRACKLINES +#define FLARG_PASS , __FILE__, __LINE__ +#else +#define FLARG_PASS +#endif + +#else + +#define FLARG , const char *file, int line +#define FILELINE , __FILE__, __LINE__ +#if ISC_MEM_TRACKLINES +#define FLARG_PASS , file, line +#else +#define FLARG_PASS +#endif + +#endif + +static void * +mem_alloc(size_t size FLARG) { +#ifdef OPENSSL_LEAKS + void *ptr; + + INSIST(dst__memory_pool != NULL); + ptr = isc__mem_allocate(dst__memory_pool, size FLARG_PASS); + return (ptr); +#else + INSIST(dst__memory_pool != NULL); + return (isc__mem_allocate(dst__memory_pool, size FLARG_PASS)); +#endif +} + +static void +mem_free(void *ptr FLARG) { + INSIST(dst__memory_pool != NULL); + if (ptr != NULL) + isc__mem_free(dst__memory_pool, ptr FLARG_PASS); +} + +static void * +mem_realloc(void *ptr, size_t size FLARG) { +#ifdef OPENSSL_LEAKS + void *rptr; + + INSIST(dst__memory_pool != NULL); + rptr = isc__mem_reallocate(dst__memory_pool, ptr, size FLARG_PASS); + return (rptr); +#else + INSIST(dst__memory_pool != NULL); + return (isc__mem_reallocate(dst__memory_pool, ptr, size FLARG_PASS)); +#endif +} + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L +static void +_set_thread_id(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)isc_thread_self()); +} +#endif + +isc_result_t +dst__openssl_init(const char *engine) { + isc_result_t result; +#if !defined(OPENSSL_NO_ENGINE) + ENGINE *re; +#else + UNUSED(engine); +#endif + +#ifdef DNS_CRYPTO_LEAKS + CRYPTO_malloc_debug_init(); + CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL); + CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); +#endif + CRYPTO_set_mem_functions(mem_alloc, mem_realloc, mem_free); +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + nlocks = CRYPTO_num_locks(); + locks = mem_alloc(sizeof(isc_mutex_t) * nlocks FILELINE); + if (locks == NULL) + return (ISC_R_NOMEMORY); + result = isc_mutexblock_init(locks, nlocks); + if (result != ISC_R_SUCCESS) + goto cleanup_mutexalloc; + CRYPTO_set_locking_callback(lock_callback); +# if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L + CRYPTO_THREADID_set_callback(_set_thread_id); +# else + CRYPTO_set_id_callback(id_callback); +# endif + + ERR_load_crypto_strings(); +#endif + + rm = mem_alloc(sizeof(RAND_METHOD) FILELINE); + if (rm == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_mutexinit; + } + rm->seed = NULL; + rm->bytes = entropy_get; + rm->cleanup = NULL; + rm->add = entropy_add; + rm->pseudorand = entropy_getpseudo; + rm->status = entropy_status; + +#if !defined(OPENSSL_NO_ENGINE) +#if !defined(CONF_MFLAGS_DEFAULT_SECTION) + OPENSSL_config(NULL); +#else + /* + * OPENSSL_config() can only be called a single time as of + * 1.0.2e so do the steps individually. + */ + OPENSSL_load_builtin_modules(); + ENGINE_load_builtin_engines(); + ERR_clear_error(); + CONF_modules_load_file(NULL, NULL, + CONF_MFLAGS_DEFAULT_SECTION | + CONF_MFLAGS_IGNORE_MISSING_FILE); +#endif + + 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; + } + } + + re = ENGINE_get_default_RAND(); + if (re == NULL) { + re = ENGINE_new(); + if (re == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_rm; + } + ENGINE_set_RAND(re, rm); + ENGINE_set_default_RAND(re); + ENGINE_free(re); + } else + ENGINE_finish(re); +#else + RAND_set_rand_method(rm); +#endif /* !defined(OPENSSL_NO_ENGINE) */ + return (ISC_R_SUCCESS); + +#if !defined(OPENSSL_NO_ENGINE) + cleanup_rm: + if (e != NULL) + ENGINE_free(e); + e = NULL; + mem_free(rm FILELINE); + rm = NULL; +#endif + cleanup_mutexinit: +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_set_locking_callback(NULL); + DESTROYMUTEXBLOCK(locks, nlocks); + cleanup_mutexalloc: + mem_free(locks FILELINE); + locks = NULL; +#endif + return (result); +} + +void +dst__openssl_destroy(void) { +#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L) + OPENSSL_cleanup(); + if (rm != NULL) { + mem_free(rm FILELINE); + rm = NULL; + } +#else + /* + * Sequence taken from apps_shutdown() in . + */ + if (rm != NULL) { +#if OPENSSL_VERSION_NUMBER >= 0x00907000L + RAND_cleanup(); +#endif + mem_free(rm FILELINE); + rm = NULL; + } +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + CONF_modules_free(); +#endif + OBJ_cleanup(); + EVP_cleanup(); +#if !defined(OPENSSL_NO_ENGINE) + if (e != NULL) + ENGINE_free(e); + e = NULL; +#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_VERSION_NUMBER >= 0x00907000L + ENGINE_cleanup(); +#endif +#endif +#if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + CRYPTO_cleanup_all_ex_data(); +#endif + ERR_clear_error(); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state(NULL); +#elif OPENSSL_VERSION_NUMBER < 0x10000000L || defined(LIBRESSL_VERSION_NUMBER) + ERR_remove_state(0); +#endif + ERR_free_strings(); + +#ifdef DNS_CRYPTO_LEAKS + CRYPTO_mem_leaks_fp(stderr); +#endif + + if (locks != NULL) { + CRYPTO_set_locking_callback(NULL); + DESTROYMUTEXBLOCK(locks, nlocks); + mem_free(locks FILELINE); + locks = NULL; + } +#endif +} + +static isc_result_t +toresult(isc_result_t fallback) { + isc_result_t result = fallback; + unsigned long err = ERR_peek_error(); +#if defined(HAVE_OPENSSL_ECDSA) && \ + defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) + int lib = ERR_GET_LIB(err); +#endif + 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(HAVE_OPENSSL_ECDSA) && \ + 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 + 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) ? 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 + +#else /* OPENSSL */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* OPENSSL */ +/*! \file */ diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c new file mode 100644 index 0000000..e8a3f7e --- /dev/null +++ b/lib/dns/openssldh_link.c @@ -0,0 +1,793 @@ +/* + * Portions 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 http://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. + */ + +#ifdef OPENSSL + +#include + +#include + +#ifndef PK11_DH_DISABLE + +#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" \ + "F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF" + +#define PRIME1024 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" \ + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2" \ + "5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406" \ + "B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" + +#define PRIME1536 "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" + + +static isc_result_t openssldh_todns(const dst_key_t *key, isc_buffer_t *data); + +static BIGNUM *bn2 = NULL, *bn768 = NULL, *bn1024 = NULL, *bn1536 = NULL; + +#if !defined(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 + +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); +} + +#if OPENSSL_VERSION_NUMBER > 0x00908000L +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); +} +#endif + +static isc_result_t +openssldh_generate(dst_key_t *key, int generator, void (*callback)(int)) { + DH *dh = NULL; +#if OPENSSL_VERSION_NUMBER > 0x00908000L + BN_GENCB *cb; +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + BN_GENCB _cb; +#endif + union { + void *dptr; + void (*fptr)(int); + } u; +#else + + UNUSED(callback); +#endif + + 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) { +#if OPENSSL_VERSION_NUMBER > 0x00908000L + 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 (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; +#else + dh = DH_generate_parameters(key->key_size, generator, + NULL, NULL); + if (dh == NULL) + return (dst__openssl_toresult2( + "DH_generate_parameters", + DST_R_OPENSSLFAILURE)); +#endif + } + + 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)); + if (bufs[i] == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + } + + 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); + fail: + 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); + BN_free(bn768); + BN_free(bn1024); + BN_free(bn1536); +} + +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); +} +#endif /* !PK11_DH_DISABLE */ + +#else /* OPENSSL */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* OPENSSL */ +/*! \file */ diff --git a/lib/dns/openssldsa_link.c b/lib/dns/openssldsa_link.c new file mode 100644 index 0000000..8abf4bb --- /dev/null +++ b/lib/dns/openssldsa_link.c @@ -0,0 +1,815 @@ +/* + * Portions 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 http://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. + */ + +#ifdef OPENSSL +#ifndef USE_EVP +#define USE_EVP 1 +#endif + +#include + +#include + +#ifndef PK11_DSA_DISABLE + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" + +#include + +static isc_result_t openssldsa_todns(const dst_key_t *key, isc_buffer_t *data); + +#if !defined(HAVE_DSA_GET0_PQG) +static void +DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, + const BIGNUM **g) +{ + if (p != NULL) + *p = d->p; + if (q != NULL) + *q = d->q; + if (g != NULL) + *g = d->g; +} + +static int +DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) { + if (p == NULL || q == NULL || g == NULL) + return 0; + BN_free(d->p); + BN_free(d->q); + BN_free(d->g); + d->p = p; + d->q = q; + d->g = g; + + return 1; +} + +static void +DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) { + if (pub_key != NULL) + *pub_key = d->pub_key; + if (priv_key != NULL) + *priv_key = d->priv_key; +} + +static int +DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) { + /* Note that it is valid for priv_key to be NULL */ + if (pub_key == NULL) + return 0; + + BN_free(d->pub_key); + BN_free(d->priv_key); + d->pub_key = pub_key; + d->priv_key = priv_key; + + return 1; +} + +static void +DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { + *pr = sig->r; + *ps = sig->s; +} + +static int +DSA_SIG_set0(DSA_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; +} + + +#define DSA_clear_flags(d, x) (d)->flags &= ~(x) + +#endif + +static isc_result_t +openssldsa_createctx(dst_key_t *key, dst_context_t *dctx) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx; + + UNUSED(key); + + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) + return (ISC_R_NOMEMORY); + + if (!EVP_DigestInit_ex(evp_md_ctx, EVP_dss1(), NULL)) { + EVP_MD_CTX_destroy(evp_md_ctx); + return (ISC_R_FAILURE); + } + + dctx->ctxdata.evp_md_ctx = evp_md_ctx; + + return (ISC_R_SUCCESS); +#else + isc_sha1_t *sha1ctx; + + UNUSED(key); + + sha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha1_t)); + if (sha1ctx == NULL) + return (ISC_R_NOMEMORY); + isc_sha1_init(sha1ctx); + dctx->ctxdata.sha1ctx = sha1ctx; + return (ISC_R_SUCCESS); +#endif +} + +static void +openssldsa_destroyctx(dst_context_t *dctx) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + dctx->ctxdata.evp_md_ctx = NULL; + } +#else + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + if (sha1ctx != NULL) { + isc_sha1_invalidate(sha1ctx); + isc_mem_put(dctx->mctx, sha1ctx, sizeof(isc_sha1_t)); + dctx->ctxdata.sha1ctx = NULL; + } +#endif +} + +static isc_result_t +openssldsa_adddata(dst_context_t *dctx, const isc_region_t *data) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) { + return (ISC_R_FAILURE); + } +#else + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + isc_sha1_update(sha1ctx, data->base, data->length); +#endif + 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 +openssldsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + dst_key_t *key = dctx->key; + DSA *dsa = key->keydata.dsa; + isc_region_t region; + DSA_SIG *dsasig; + const BIGNUM *r = 0, *s = NULL; + unsigned int klen; +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey; + unsigned char *sigbuf; + const unsigned char *sb; + unsigned int siglen; +#else + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + unsigned char digest[ISC_SHA1_DIGESTLENGTH]; +#endif + + isc_buffer_availableregion(sig, ®ion); + if (region.length < ISC_SHA1_DIGESTLENGTH * 2 + 1) + return (ISC_R_NOSPACE); + +#if USE_EVP + pkey = EVP_PKEY_new(); + if (pkey == NULL) + return (ISC_R_NOMEMORY); + if (!EVP_PKEY_set1_DSA(pkey, dsa)) { + EVP_PKEY_free(pkey); + return (ISC_R_FAILURE); + } + sigbuf = malloc(EVP_PKEY_size(pkey)); + if (sigbuf == NULL) { + EVP_PKEY_free(pkey); + return (ISC_R_NOMEMORY); + } + if (!EVP_SignFinal(evp_md_ctx, sigbuf, &siglen, pkey)) { + EVP_PKEY_free(pkey); + free(sigbuf); + return (dst__openssl_toresult3(dctx->category, + "EVP_SignFinal", + ISC_R_FAILURE)); + } + INSIST(EVP_PKEY_size(pkey) >= (int) siglen); + EVP_PKEY_free(pkey); + /* Convert from Dss-Sig-Value (RFC2459). */ + dsasig = DSA_SIG_new(); + if (dsasig == NULL) { + free(sigbuf); + return (ISC_R_NOMEMORY); + } + sb = sigbuf; + if (d2i_DSA_SIG(&dsasig, &sb, (long) siglen) == NULL) { + free(sigbuf); + return (dst__openssl_toresult3(dctx->category, + "d2i_DSA_SIG", + ISC_R_FAILURE)); + } + free(sigbuf); + +#elif 0 + /* Only use EVP for the Digest */ + if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &siglen)) { + return (dst__openssl_toresult3(dctx->category, + "EVP_DigestFinal_ex", + ISC_R_FAILURE)); + } + dsasig = DSA_do_sign(digest, ISC_SHA1_DIGESTLENGTH, dsa); + if (dsasig == NULL) + return (dst__openssl_toresult3(dctx->category, + "DSA_do_sign", + DST_R_SIGNFAILURE)); +#else + isc_sha1_final(sha1ctx, digest); + + dsasig = DSA_do_sign(digest, ISC_SHA1_DIGESTLENGTH, dsa); + if (dsasig == NULL) + return (dst__openssl_toresult3(dctx->category, + "DSA_do_sign", + DST_R_SIGNFAILURE)); +#endif + + klen = (key->key_size - 512)/64; + if (klen > 255) + return (ISC_R_FAILURE); + *region.base = klen; + isc_region_consume(®ion, 1); + + DSA_SIG_get0(dsasig, &r, &s); + BN_bn2bin_fixed(r, region.base, ISC_SHA1_DIGESTLENGTH); + isc_region_consume(®ion, ISC_SHA1_DIGESTLENGTH); + BN_bn2bin_fixed(s, region.base, ISC_SHA1_DIGESTLENGTH); + isc_region_consume(®ion, ISC_SHA1_DIGESTLENGTH); + DSA_SIG_free(dsasig); + isc_buffer_add(sig, ISC_SHA1_DIGESTLENGTH * 2 + 1); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssldsa_verify(dst_context_t *dctx, const isc_region_t *sig) { + dst_key_t *key = dctx->key; + DSA *dsa = key->keydata.dsa; + BIGNUM *r = NULL, *s = NULL; + int status = 0; + unsigned char *cp = sig->base; + DSA_SIG *dsasig; +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; +#if 0 + EVP_PKEY *pkey; + unsigned char *sigbuf; +#endif + unsigned int siglen; +#else + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; +#endif + unsigned char digest[ISC_SHA1_DIGESTLENGTH]; + + +#if USE_EVP +#if 1 + /* Only use EVP for the digest */ + if (!EVP_DigestFinal_ex(evp_md_ctx, digest, &siglen)) { + return (ISC_R_FAILURE); + } +#endif +#else + isc_sha1_final(sha1ctx, digest); +#endif + + if (sig->length != 2 * ISC_SHA1_DIGESTLENGTH + 1) { + return (DST_R_VERIFYFAILURE); + } + + cp++; /*%< Skip T */ + dsasig = DSA_SIG_new(); + if (dsasig == NULL) + return (ISC_R_NOMEMORY); + r = BN_bin2bn(cp, ISC_SHA1_DIGESTLENGTH, NULL); + cp += ISC_SHA1_DIGESTLENGTH; + s = BN_bin2bn(cp, ISC_SHA1_DIGESTLENGTH, NULL); + DSA_SIG_set0(dsasig, r, s); + +#if 0 + pkey = EVP_PKEY_new(); + if (pkey == NULL) + return (ISC_R_NOMEMORY); + if (!EVP_PKEY_set1_DSA(pkey, dsa)) { + EVP_PKEY_free(pkey); + return (ISC_R_FAILURE); + } + /* Convert to Dss-Sig-Value (RFC2459). */ + sigbuf = malloc(EVP_PKEY_size(pkey) + 50); + if (sigbuf == NULL) { + EVP_PKEY_free(pkey); + return (ISC_R_NOMEMORY); + } + siglen = (unsigned) i2d_DSA_SIG(dsasig, &sigbuf); + INSIST(EVP_PKEY_size(pkey) >= (int) siglen); + status = EVP_VerifyFinal(evp_md_ctx, sigbuf, siglen, pkey); + EVP_PKEY_free(pkey); + free(sigbuf); +#else + status = DSA_do_verify(digest, ISC_SHA1_DIGESTLENGTH, dsasig, dsa); +#endif + DSA_SIG_free(dsasig); + switch (status) { + case 1: + return (ISC_R_SUCCESS); + case 0: + return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); + default: + return (dst__openssl_toresult3(dctx->category, + "DSA_do_verify", + DST_R_VERIFYFAILURE)); + } +} + +static bool +openssldsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + DSA *dsa1, *dsa2; + const BIGNUM *pub_key1 = NULL, *priv_key1 = NULL; + const BIGNUM *pub_key2 = NULL, *priv_key2 = NULL; + const BIGNUM *p1 = NULL, *q1 = NULL, *g1 = NULL; + const BIGNUM *p2 = NULL, *q2 = NULL, *g2 = NULL; + + dsa1 = key1->keydata.dsa; + dsa2 = key2->keydata.dsa; + + if (dsa1 == NULL && dsa2 == NULL) + return (true); + else if (dsa1 == NULL || dsa2 == NULL) + return (false); + + DSA_get0_key(dsa1, &pub_key1, &priv_key1); + DSA_get0_key(dsa2, &pub_key2, &priv_key2); + DSA_get0_pqg(dsa1, &p1, &q1, &g1); + DSA_get0_pqg(dsa2, &p2, &q2, &g2); + + if (BN_cmp(p1, p2) != 0 || BN_cmp(q1, q2) != 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)) + return (false); + } + return (true); +} + +#if OPENSSL_VERSION_NUMBER > 0x00908000L +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); +} +#endif + +static isc_result_t +openssldsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + DSA *dsa; + unsigned char rand_array[ISC_SHA1_DIGESTLENGTH]; + isc_result_t result; +#if OPENSSL_VERSION_NUMBER > 0x00908000L + BN_GENCB *cb; +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + BN_GENCB _cb; +#endif + union { + void *dptr; + void (*fptr)(int); + } u; + +#else + + UNUSED(callback); +#endif + UNUSED(unused); + + result = dst__entropy_getdata(rand_array, sizeof(rand_array), + false); + if (result != ISC_R_SUCCESS) + return (result); + +#if OPENSSL_VERSION_NUMBER > 0x00908000L + dsa = DSA_new(); + if (dsa == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + cb = BN_GENCB_new(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + if (cb == NULL) { + DSA_free(dsa); + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } +#endif + if (callback == NULL) { + BN_GENCB_set_old(cb, NULL, NULL); + } else { + u.fptr = callback; + BN_GENCB_set(cb, &progress_cb, u.dptr); + } + + if (!DSA_generate_parameters_ex(dsa, key->key_size, rand_array, + ISC_SHA1_DIGESTLENGTH, NULL, NULL, + cb)) + { + DSA_free(dsa); + BN_GENCB_free(cb); + return (dst__openssl_toresult2("DSA_generate_parameters_ex", + DST_R_OPENSSLFAILURE)); + } + BN_GENCB_free(cb); + cb = NULL; +#else + dsa = DSA_generate_parameters(key->key_size, rand_array, + ISC_SHA1_DIGESTLENGTH, NULL, NULL, + NULL, NULL); + if (dsa == NULL) + return (dst__openssl_toresult2("DSA_generate_parameters", + DST_R_OPENSSLFAILURE)); +#endif + + if (DSA_generate_key(dsa) == 0) { + DSA_free(dsa); + return (dst__openssl_toresult2("DSA_generate_key", + DST_R_OPENSSLFAILURE)); + } + + DSA_clear_flags(dsa, DSA_FLAG_CACHE_MONT_P); + + key->keydata.dsa = dsa; + + return (ISC_R_SUCCESS); +} + +static bool +openssldsa_isprivate(const dst_key_t *key) { + DSA *dsa = key->keydata.dsa; + const BIGNUM *priv_key = NULL; + + DSA_get0_key(dsa, NULL, &priv_key); + return (dsa != NULL && priv_key != NULL); +} + +static void +openssldsa_destroy(dst_key_t *key) { + DSA *dsa = key->keydata.dsa; + DSA_free(dsa); + key->keydata.dsa = NULL; +} + + +static isc_result_t +openssldsa_todns(const dst_key_t *key, isc_buffer_t *data) { + DSA *dsa; + const BIGNUM *pub_key, *p = NULL, *q = NULL, *g = NULL; + isc_region_t r; + int dnslen; + unsigned int t, p_bytes; + + REQUIRE(key->keydata.dsa != NULL); + + dsa = key->keydata.dsa; + + isc_buffer_availableregion(data, &r); + + DSA_get0_key(dsa, &pub_key, NULL); + DSA_get0_pqg(dsa, &p, &q, &g); + + t = (BN_num_bytes(p) - 64) / 8; + if (t > 8) + return (DST_R_INVALIDPUBLICKEY); + p_bytes = 64 + 8 * t; + + dnslen = 1 + (key->key_size * 3)/8 + ISC_SHA1_DIGESTLENGTH; + if (r.length < (unsigned int) dnslen) + return (ISC_R_NOSPACE); + + *r.base = t; + isc_region_consume(&r, 1); + + BN_bn2bin_fixed(q, r.base, ISC_SHA1_DIGESTLENGTH); + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); + BN_bn2bin_fixed(p, r.base, key->key_size/8); + isc_region_consume(&r, p_bytes); + BN_bn2bin_fixed(g, r.base, key->key_size/8); + isc_region_consume(&r, p_bytes); + BN_bn2bin_fixed(pub_key, r.base, key->key_size/8); + isc_region_consume(&r, p_bytes); + + isc_buffer_add(data, dnslen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssldsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + DSA *dsa; + BIGNUM *pub_key, *p, *q, *g; + isc_region_t r; + unsigned int t, p_bytes; + isc_mem_t *mctx = key->mctx; + + UNUSED(mctx); + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + dsa = DSA_new(); + if (dsa == NULL) + return (ISC_R_NOMEMORY); + DSA_clear_flags(dsa, DSA_FLAG_CACHE_MONT_P); + + t = (unsigned int) *r.base; + isc_region_consume(&r, 1); + if (t > 8) { + DSA_free(dsa); + return (DST_R_INVALIDPUBLICKEY); + } + p_bytes = 64 + 8 * t; + + if (r.length < ISC_SHA1_DIGESTLENGTH + 3 * p_bytes) { + DSA_free(dsa); + return (DST_R_INVALIDPUBLICKEY); + } + + q = BN_bin2bn(r.base, ISC_SHA1_DIGESTLENGTH, NULL); + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); + + p = BN_bin2bn(r.base, p_bytes, NULL); + isc_region_consume(&r, p_bytes); + + g = BN_bin2bn(r.base, p_bytes, NULL); + isc_region_consume(&r, p_bytes); + + pub_key = BN_bin2bn(r.base, p_bytes, NULL); + isc_region_consume(&r, p_bytes); + + if (pub_key == NULL || p == NULL || q == NULL || g == NULL) { + DSA_free(dsa); + if (p != NULL) BN_free(p); + if (q != NULL) BN_free(q); + if (g != NULL) BN_free(g); + return (ISC_R_NOMEMORY); + } + + DSA_set0_key(dsa, pub_key, NULL); + DSA_set0_pqg(dsa, p, q, g); + + key->key_size = p_bytes * 8; + + isc_buffer_forward(data, 1 + ISC_SHA1_DIGESTLENGTH + 3 * p_bytes); + + key->keydata.dsa = dsa; + + return (ISC_R_SUCCESS); +} + + +static isc_result_t +openssldsa_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + DSA *dsa; + const BIGNUM *pub_key = NULL, *priv_key = NULL; + const BIGNUM *p = NULL, *q = NULL, *g = NULL; + dst_private_t priv; + unsigned char bufs[5][128]; + + if (key->keydata.dsa == NULL) + return (DST_R_NULLKEY); + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + dsa = key->keydata.dsa; + + DSA_get0_key(dsa, &pub_key, &priv_key); + DSA_get0_pqg(dsa, &p, &q, &g); + + priv.elements[cnt].tag = TAG_DSA_PRIME; + priv.elements[cnt].length = BN_num_bytes(p); + BN_bn2bin(p, bufs[cnt]); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_SUBPRIME; + priv.elements[cnt].length = BN_num_bytes(q); + BN_bn2bin(q, bufs[cnt]); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_BASE; + priv.elements[cnt].length = BN_num_bytes(g); + BN_bn2bin(g, bufs[cnt]); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_PRIVATE; + priv.elements[cnt].length = BN_num_bytes(priv_key); + BN_bn2bin(priv_key, bufs[cnt]); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_PUBLIC; + priv.elements[cnt].length = BN_num_bytes(pub_key); + BN_bn2bin(pub_key, bufs[cnt]); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +openssldsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + int i; + DSA *dsa = NULL; + BIGNUM *pub_key = NULL, *priv_key = NULL; + BIGNUM *p = NULL, *q = NULL, *g = NULL; + isc_mem_t *mctx = key->mctx; +#define DST_RET(a) {ret = a; goto err;} + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_DSA, 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); + } + + dsa = DSA_new(); + if (dsa == NULL) + DST_RET(ISC_R_NOMEMORY); + DSA_clear_flags(dsa, DSA_FLAG_CACHE_MONT_P); + key->keydata.dsa = dsa; + + 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_DSA_PRIME: + p = bn; + break; + case TAG_DSA_SUBPRIME: + q = bn; + break; + case TAG_DSA_BASE: + g = bn; + break; + case TAG_DSA_PRIVATE: + priv_key = bn; + break; + case TAG_DSA_PUBLIC: + pub_key = bn; + break; + } + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + DSA_set0_key(dsa, pub_key, priv_key); + DSA_set0_pqg(dsa, p, q, g); + key->key_size = BN_num_bits(p); + return (ISC_R_SUCCESS); + + err: + if (p != NULL) + BN_free(p); + if (q != NULL) + BN_free(q); + if (g != NULL) + BN_free(g); + openssldsa_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static dst_func_t openssldsa_functions = { + openssldsa_createctx, + NULL, /*%< createctx2 */ + openssldsa_destroyctx, + openssldsa_adddata, + openssldsa_sign, + openssldsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + openssldsa_compare, + NULL, /*%< paramcompare */ + openssldsa_generate, + openssldsa_isprivate, + openssldsa_destroy, + openssldsa_todns, + openssldsa_fromdns, + openssldsa_tofile, + openssldsa_parse, + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__openssldsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &openssldsa_functions; + return (ISC_R_SUCCESS); +} +#endif /* !PK11_DSA_DISABLE */ + +#else /* OPENSSL */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* OPENSSL */ +/*! \file */ diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c new file mode 100644 index 0000000..e9ea5ea --- /dev/null +++ b/lib/dns/opensslecdsa_link.c @@ -0,0 +1,657 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(OPENSSL) && defined(HAVE_OPENSSL_ECDSA) + +#if !defined(HAVE_EVP_SHA256) || !defined(HAVE_EVP_SHA384) +#error "ECDSA without EVP for SHA2?" +#endif + +#include + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" + +#include +#include +#include +#include + +#ifndef NID_X9_62_prime256v1 +#error "P-256 group is not known (NID_X9_62_prime256v1)" +#endif +#ifndef NID_secp384r1 +#error "P-384 group is not known (NID_secp384r1)" +#endif + +#define DST_RET(a) {ret = a; goto err;} + +#if !defined(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 + +static isc_result_t opensslecdsa_todns(const dst_key_t *key, + isc_buffer_t *data); + +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(evp_md_ctx, digest, &dgstlen)) + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestFinal", + 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: + if (eckey != NULL) + 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) + return (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); + if (eckey != NULL) + 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: + if (eckey != NULL) + 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: + if (eckey != NULL) + 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; + + 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) + DST_RET (ISC_R_FAILURE); + + buf = isc_mem_get(key->mctx, BN_num_bytes(privkey)); + if (buf == NULL) + DST_RET (ISC_R_NOMEMORY); + + priv.elements[0].tag = TAG_ECDSA_PRIVATEKEY; + priv.elements[0].length = BN_num_bytes(privkey); + BN_bn2bin(privkey, buf); + priv.elements[0].data = buf; + priv.nelements = 1; + ret = dst__privstruct_writefile(key, &priv, directory); + + err: + if (eckey != NULL) + 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, dst_key_t *pub) +{ + isc_result_t ret = ISC_R_FAILURE; + EVP_PKEY *pkey; + EC_KEY *pubeckey = NULL; + const EC_POINT *pubkey; + + if (pub == NULL) + return (ISC_R_SUCCESS); + pkey = pub->keydata.pkey; + if (pkey == NULL) + return (ISC_R_SUCCESS); + pubeckey = EVP_PKEY_get1_EC_KEY(pkey); + if (pubeckey == NULL) + return (ISC_R_SUCCESS); + pubkey = EC_KEY_get0_public_key(pubeckey); + if (pubkey == NULL) + DST_RET (ISC_R_SUCCESS); + if (EC_KEY_set_public_key(eckey, pubkey) != 1) + DST_RET (ISC_R_SUCCESS); + if (EC_KEY_check_key(eckey) == 1) + DST_RET (ISC_R_SUCCESS); + + err: + if (pubeckey != NULL) + EC_KEY_free(pubeckey); + return (ret); +} + +static isc_result_t +opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + EVP_PKEY *pkey; + EC_KEY *eckey = NULL; + BIGNUM *privkey = NULL; + int group_nid; + isc_mem_t *mctx = key->mctx; + + REQUIRE(key->key_alg == DST_ALG_ECDSA256 || + key->key_alg == DST_ALG_ECDSA384); + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, 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 (key->key_alg == DST_ALG_ECDSA256) + group_nid = NID_X9_62_prime256v1; + else + group_nid = NID_secp384r1; + + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + + privkey = BN_bin2bn(priv.elements[0].data, + priv.elements[0].length, NULL); + if (privkey == NULL) + DST_RET(ISC_R_NOMEMORY); + if (!EC_KEY_set_private_key(eckey, privkey)) + DST_RET(ISC_R_NOMEMORY); + if (ecdsa_check(eckey, pub) != ISC_R_SUCCESS) + DST_RET(DST_R_INVALIDPRIVATEKEY); + + 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(DST_R_OPENSSLFAILURE)); + } + key->keydata.pkey = pkey; + if (key->key_alg == DST_ALG_ECDSA256) + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + else + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + ret = ISC_R_SUCCESS; + + err: + if (privkey != NULL) + BN_clear_free(privkey); + if (eckey != NULL) + EC_KEY_free(eckey); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +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 */ + NULL, /*%< 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); +} + +#else /* HAVE_OPENSSL_ECDSA */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* HAVE_OPENSSL_ECDSA */ +/*! \file */ diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c new file mode 100644 index 0000000..b76fac9 --- /dev/null +++ b/lib/dns/openssleddsa_link.c @@ -0,0 +1,676 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(OPENSSL) && \ + (defined(HAVE_OPENSSL_ED25519) || defined(HAVE_OPENSSL_ED448)) + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" + +#include +#include +#include +#include + +#ifndef NID_ED25519 +#error "Ed25519 group is not known (NID_ED25519)" +#endif +#ifndef NID_ED448 +#error "Ed448 group is not known (NID_ED448)" +#endif + +#define DST_RET(a) {ret = a; goto err;} + +/* OpenSSL doesn't provide direct access to key values */ + +#define PUBPREFIXLEN 12 + +static const unsigned char ed25519_pub_prefix[] = { + 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, + 0x70, 0x03, 0x21, 0x00 +}; + +static EVP_PKEY *pub_ed25519_to_ossl(const unsigned char *key) +{ + unsigned char buf[PUBPREFIXLEN + DNS_KEY_ED25519SIZE]; + const unsigned char *p; + + memmove(buf, ed25519_pub_prefix, PUBPREFIXLEN); + memmove(buf + PUBPREFIXLEN, key, DNS_KEY_ED25519SIZE); + p = buf; + return (d2i_PUBKEY(NULL, &p, PUBPREFIXLEN + DNS_KEY_ED25519SIZE)); +} + +static isc_result_t pub_ed25519_from_ossl(EVP_PKEY *pkey, + unsigned char *key) +{ + unsigned char buf[PUBPREFIXLEN + DNS_KEY_ED25519SIZE]; + unsigned char *p; + int len; + + len = i2d_PUBKEY(pkey, NULL); + if ((len <= DNS_KEY_ED25519SIZE) || + (len > PUBPREFIXLEN + DNS_KEY_ED25519SIZE)) + return (DST_R_OPENSSLFAILURE); + p = buf; + len = i2d_PUBKEY(pkey, &p); + if ((len <= DNS_KEY_ED25519SIZE) || + (len > PUBPREFIXLEN + DNS_KEY_ED25519SIZE)) + return (DST_R_OPENSSLFAILURE); + memmove(key, buf + len - DNS_KEY_ED25519SIZE, DNS_KEY_ED25519SIZE); + return (ISC_R_SUCCESS); +} + +static const unsigned char ed448_pub_prefix[] = { + 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, + 0x71, 0x03, 0x21, 0x00 +}; + +static EVP_PKEY *pub_ed448_to_ossl(const unsigned char *key) +{ + unsigned char buf[PUBPREFIXLEN + DNS_KEY_ED448SIZE]; + const unsigned char *p; + + memmove(buf, ed448_pub_prefix, PUBPREFIXLEN); + memmove(buf + PUBPREFIXLEN, key, DNS_KEY_ED448SIZE); + p = buf; + return (d2i_PUBKEY(NULL, &p, PUBPREFIXLEN + DNS_KEY_ED448SIZE)); +} + +static isc_result_t pub_ed448_from_ossl(EVP_PKEY *pkey, + unsigned char *key) +{ + unsigned char buf[PUBPREFIXLEN + DNS_KEY_ED448SIZE]; + unsigned char *p; + int len; + + len = i2d_PUBKEY(pkey, NULL); + if ((len <= DNS_KEY_ED448SIZE) || + (len > PUBPREFIXLEN + DNS_KEY_ED448SIZE)) + return (DST_R_OPENSSLFAILURE); + p = buf; + len = i2d_PUBKEY(pkey, &p); + if ((len <= DNS_KEY_ED448SIZE) || + (len > PUBPREFIXLEN + DNS_KEY_ED448SIZE)) + return (DST_R_OPENSSLFAILURE); + memmove(key, buf + len - DNS_KEY_ED448SIZE, DNS_KEY_ED448SIZE); + return (ISC_R_SUCCESS); +} + +#define PRIVPREFIXLEN 16 + +static const unsigned char ed25519_priv_prefix[] = { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, + 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20 +}; + +static EVP_PKEY *priv_ed25519_to_ossl(const unsigned char *key) +{ + unsigned char buf[PRIVPREFIXLEN + DNS_KEY_ED25519SIZE]; + const unsigned char *p; + + memmove(buf, ed25519_priv_prefix, PRIVPREFIXLEN); + memmove(buf + PRIVPREFIXLEN, key, DNS_KEY_ED25519SIZE); + p = buf; + return (d2i_PrivateKey(NID_ED25519, NULL, &p, + PRIVPREFIXLEN + DNS_KEY_ED25519SIZE)); +} + +static isc_result_t priv_ed25519_from_ossl(EVP_PKEY *pkey, + unsigned char *key) +{ + unsigned char buf[PRIVPREFIXLEN + DNS_KEY_ED25519SIZE]; + unsigned char *p; + int len; + + len = i2d_PrivateKey(pkey, NULL); + if ((len <= DNS_KEY_ED25519SIZE) || + (len > PRIVPREFIXLEN + DNS_KEY_ED25519SIZE)) + return (DST_R_OPENSSLFAILURE); + p = buf; + len = i2d_PrivateKey(pkey, &p); + if ((len <= DNS_KEY_ED25519SIZE) || + (len > PRIVPREFIXLEN + DNS_KEY_ED25519SIZE)) + return (DST_R_OPENSSLFAILURE); + memmove(key, buf + len - DNS_KEY_ED25519SIZE, DNS_KEY_ED25519SIZE); + return (ISC_R_SUCCESS); +} + +static const unsigned char ed448_priv_prefix[] = { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, + 0x03, 0x2b, 0x65, 0x71, 0x04, 0x22, 0x04, 0x20 +}; + +static EVP_PKEY *priv_ed448_to_ossl(const unsigned char *key) +{ + unsigned char buf[PRIVPREFIXLEN + DNS_KEY_ED448SIZE]; + const unsigned char *p; + + memmove(buf, ed448_priv_prefix, PRIVPREFIXLEN); + memmove(buf + PRIVPREFIXLEN, key, DNS_KEY_ED448SIZE); + p = buf; + return (d2i_PrivateKey(NID_ED448, NULL, &p, + PRIVPREFIXLEN + DNS_KEY_ED448SIZE)); +} + +static isc_result_t priv_ed448_from_ossl(EVP_PKEY *pkey, + unsigned char *key) +{ + unsigned char buf[PRIVPREFIXLEN + DNS_KEY_ED448SIZE]; + unsigned char *p; + int len; + + len = i2d_PrivateKey(pkey, NULL); + if ((len <= DNS_KEY_ED448SIZE) || + (len > PRIVPREFIXLEN + DNS_KEY_ED448SIZE)) + return (DST_R_OPENSSLFAILURE); + p = buf; + len = i2d_PrivateKey(pkey, &p); + if ((len <= DNS_KEY_ED448SIZE) || + (len > PRIVPREFIXLEN + DNS_KEY_ED448SIZE)) + return (DST_R_OPENSSLFAILURE); + memmove(key, buf + len - DNS_KEY_ED448SIZE, DNS_KEY_ED448SIZE); + return (ISC_R_SUCCESS); +} + +static isc_result_t openssleddsa_todns(const dst_key_t *key, + isc_buffer_t *data); + +static isc_result_t +openssleddsa_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_buffer_t *buf = NULL; + isc_result_t result; + + UNUSED(key); + REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || + dctx->key->key_alg == DST_ALG_ED448); + + result = isc_buffer_allocate(dctx->mctx, &buf, 64); + dctx->ctxdata.generic = buf; + + return (result); +} + +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; + result = isc_buffer_allocate(dctx->mctx, &nbuf, length); + if (result != ISC_R_SUCCESS) + return (result); + 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)) + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestSignInit", + ISC_R_FAILURE)); + if (!EVP_DigestSign(ctx, sigreg.base, &siglen, + tbsreg.base, tbsreg.length)) + DST_RET(dst__openssl_toresult3(dctx->category, + "EVP_DigestSign", + DST_R_SIGNFAILURE)); + isc_buffer_add(sig, (unsigned int) siglen); + ret = ISC_R_SUCCESS; + + err: + if (ctx != NULL) + 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; + + 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; + + if (sig->length != siglen) + return (DST_R_VERIFYFAILURE); + + isc_buffer_usedregion(buf, &tbsreg); + + if (!EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey)) + 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: + if (ctx != NULL) + 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, status; + + REQUIRE(key->key_alg == DST_ALG_ED25519 || + key->key_alg == DST_ALG_ED448); + UNUSED(unused); + UNUSED(callback); + + if (key->key_alg == DST_ALG_ED25519) { + nid = NID_ED25519; + key->key_size = DNS_KEY_ED25519SIZE; + } else { + nid = NID_ED448; + key->key_size = DNS_KEY_ED448SIZE; + } + + 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: + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + return (ret); +} + +static bool +openssleddsa_isprivate(const dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + int len; + unsigned long err; + + if (pkey == NULL) + return (false); + + len = i2d_PrivateKey(pkey, NULL); + if (len > 0) + return (true); + /* can check if first error is EC_R_INVALID_PRIVATE_KEY */ + while ((err = 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; + isc_result_t result; + + REQUIRE(pkey != NULL); + + pkey = key->keydata.pkey; + switch (key->key_alg) { + case DST_ALG_ED25519: + isc_buffer_availableregion(data, &r); + if (r.length < DNS_KEY_ED25519SIZE) + return (ISC_R_NOSPACE); + result = pub_ed25519_from_ossl(pkey, r.base); + if (result == ISC_R_SUCCESS) + isc_buffer_add(data, DNS_KEY_ED25519SIZE); + return (result); + case DST_ALG_ED448: + isc_buffer_availableregion(data, &r); + if (r.length < DNS_KEY_ED448SIZE) + return (ISC_R_NOSPACE); + result = pub_ed448_from_ossl(pkey, r.base); + if (result == ISC_R_SUCCESS) + isc_buffer_add(data, DNS_KEY_ED448SIZE); + return (result); + default: + INSIST(0); + } +} + +static isc_result_t +openssleddsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + EVP_PKEY *pkey; + isc_region_t r; + unsigned int len; + + 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); + if (key->key_alg == DST_ALG_ED25519) { + len = DNS_KEY_ED25519SIZE; + if (r.length < len) + return (DST_R_INVALIDPUBLICKEY); + pkey = pub_ed25519_to_ossl(r.base); + } else { + len = DNS_KEY_ED448SIZE; + if (r.length < len) + return (DST_R_INVALIDPUBLICKEY); + pkey = pub_ed448_to_ossl(r.base); + } + if (pkey == NULL) + return (dst__openssl_toresult(ISC_R_FAILURE)); + isc_buffer_forward(data, len); + key->keydata.pkey = pkey; + key->key_size = len; + return (ISC_R_SUCCESS); +} + +static isc_result_t +openssleddsa_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + EVP_PKEY *pkey; + dst_private_t priv; + unsigned char *buf = NULL; + unsigned int len; + + 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)); + } + + pkey = key->keydata.pkey; + if (key->key_alg == DST_ALG_ED25519) { + len = DNS_KEY_ED25519SIZE; + buf = isc_mem_get(key->mctx, len); + if (buf == NULL) + return (ISC_R_NOMEMORY); + priv.elements[0].tag = TAG_EDDSA_PRIVATEKEY; + priv.elements[0].length = len; + ret = priv_ed25519_from_ossl(pkey, buf); + if (ret != ISC_R_SUCCESS) + DST_RET (dst__openssl_toresult(ret)); + priv.elements[0].data = buf; + priv.nelements = 1; + ret = dst__privstruct_writefile(key, &priv, directory); + } else { + len = DNS_KEY_ED448SIZE; + buf = isc_mem_get(key->mctx, len); + if (buf == NULL) + return (ISC_R_NOMEMORY); + priv.elements[0].tag = TAG_EDDSA_PRIVATEKEY; + priv.elements[0].length = len; + ret = priv_ed448_from_ossl(pkey, buf); + if (ret != ISC_R_SUCCESS) + DST_RET (dst__openssl_toresult(ret)); + priv.elements[0].data = buf; + priv.nelements = 1; + 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 *privkey, dst_key_t *pub) +{ + EVP_PKEY *pkey; + + if (pub == NULL) + return (ISC_R_SUCCESS); + pkey = pub->keydata.pkey; + if (pkey == NULL) + return (ISC_R_SUCCESS); + if (EVP_PKEY_cmp(privkey, pkey) == 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; + EVP_PKEY *pkey = NULL; + unsigned int 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 (key->key_alg == DST_ALG_ED25519) { + len = DNS_KEY_ED25519SIZE; + if (priv.elements[0].length < len) + DST_RET(DST_R_INVALIDPRIVATEKEY); + pkey = priv_ed25519_to_ossl(priv.elements[0].data); + } else { + len = DNS_KEY_ED448SIZE; + if (priv.elements[0].length < len) + DST_RET(DST_R_INVALIDPRIVATEKEY); + pkey = priv_ed448_to_ossl(priv.elements[0].data); + } + if (pkey == NULL) + DST_RET (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + if (eddsa_check(pkey, pub) != ISC_R_SUCCESS) { + EVP_PKEY_free(pkey); + DST_RET(DST_R_INVALIDPRIVATEKEY); + } + key->keydata.pkey = pkey; + key->key_size = len; + ret = ISC_R_SUCCESS; + + err: + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +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 */ + NULL, /*%< 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); +} + +#else /* HAVE_OPENSSL_EDxxx */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* HAVE_OPENSSL_EDxxx */ +/*! \file */ diff --git a/lib/dns/opensslgost_link.c b/lib/dns/opensslgost_link.c new file mode 100644 index 0000000..481b91c --- /dev/null +++ b/lib/dns/opensslgost_link.c @@ -0,0 +1,630 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(OPENSSL) && defined(HAVE_OPENSSL_GOST) + +#include + +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" +#include "dst_gost.h" + +#include +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +#define EVP_MD_CTX_new() &(ctx->_ctx), EVP_MD_CTX_init(&(ctx->_ctx)) +#define EVP_MD_CTX_free(ptr) EVP_MD_CTX_cleanup(ptr) +#endif + +static ENGINE *e = NULL; +static const EVP_MD *opensslgost_digest; +extern const EVP_MD *EVP_gost(void); + +const EVP_MD *EVP_gost(void) { + return (opensslgost_digest); +} + +/* ISC methods */ + +isc_result_t +isc_gost_init(isc_gost_t *ctx) { + const EVP_MD *md; + int ret; + + INSIST(ctx != NULL); + + md = EVP_gost(); + if (md == NULL) + return (DST_R_CRYPTOFAILURE); + ctx->ctx = EVP_MD_CTX_new(); + if (ctx->ctx == NULL) + return (ISC_R_NOMEMORY); + ret = EVP_DigestInit(ctx->ctx, md); + if (ret != 1) + return (DST_R_CRYPTOFAILURE); + return (ISC_R_SUCCESS); +} + +void +isc_gost_invalidate(isc_gost_t *ctx) { + EVP_MD_CTX_free(ctx->ctx); + ctx->ctx = NULL; +} + +isc_result_t +isc_gost_update(isc_gost_t *ctx, const unsigned char *data, + unsigned int len) +{ + int ret; + + INSIST(ctx != NULL); + INSIST(ctx->ctx != NULL); + INSIST(data != NULL); + + ret = EVP_DigestUpdate(ctx->ctx, (const void *) data, (size_t) len); + if (ret != 1) + return (DST_R_CRYPTOFAILURE); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_gost_final(isc_gost_t *ctx, unsigned char *digest) { + int ret; + + INSIST(ctx != NULL); + INSIST(ctx->ctx != NULL); + INSIST(digest != NULL); + + ret = EVP_DigestFinal(ctx->ctx, digest, NULL); + EVP_MD_CTX_free(ctx->ctx); + ctx->ctx = NULL; + if (ret != 1) + return (DST_R_CRYPTOFAILURE); + return (ISC_R_SUCCESS); +} + +/* DST methods */ + +#define DST_RET(a) {ret = a; goto err;} + +static isc_result_t opensslgost_todns(const dst_key_t *key, + isc_buffer_t *data); + +static isc_result_t +opensslgost_createctx(dst_key_t *key, dst_context_t *dctx) { + EVP_MD_CTX *evp_md_ctx; + const EVP_MD *md = EVP_gost(); + + UNUSED(key); + + if (md == NULL) + return (DST_R_OPENSSLFAILURE); + + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) + return (ISC_R_NOMEMORY); + + if (!EVP_DigestInit_ex(evp_md_ctx, md, NULL)) { + EVP_MD_CTX_destroy(evp_md_ctx); + return (ISC_R_FAILURE); + } + dctx->ctxdata.evp_md_ctx = evp_md_ctx; + + return (ISC_R_SUCCESS); +} + +static void +opensslgost_destroyctx(dst_context_t *dctx) { + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + dctx->ctxdata.evp_md_ctx = NULL; + } +} + +static isc_result_t +opensslgost_adddata(dst_context_t *dctx, const isc_region_t *data) { + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + + if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) + return (ISC_R_FAILURE); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslgost_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; + + 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 (ISC_R_FAILURE); + + isc_buffer_add(sig, siglen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslgost_verify(dst_context_t *dctx, const isc_region_t *sig) { + dst_key_t *key = dctx->key; + int status = 0; + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; + + 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 bool +opensslgost_compare(const dst_key_t *key1, const dst_key_t *key2) { + EVP_PKEY *pkey1, *pkey2; + + pkey1 = key1->keydata.pkey; + pkey2 = key2->keydata.pkey; + + if (pkey1 == NULL && pkey2 == NULL) + return (true); + else if (pkey1 == NULL || pkey2 == NULL) + return (false); + + if (EVP_PKEY_cmp(pkey1, pkey2) != 1) + return (false); + return (true); +} + +static int +progress_cb(EVP_PKEY_CTX *ctx) +{ + union { + void *dptr; + void (*fptr)(int); + } u; + int p; + + u.dptr = EVP_PKEY_CTX_get_app_data(ctx); + p = EVP_PKEY_CTX_get_keygen_info(ctx, 0); + if (u.fptr != NULL) + u.fptr(p); + return (1); +} + +static isc_result_t +opensslgost_generate(dst_key_t *key, int unused, void (*callback)(int)) { + EVP_PKEY_CTX *ctx; + union { + void *dptr; + void (*fptr)(int); + } u; + EVP_PKEY *pkey = NULL; + isc_result_t ret; + + UNUSED(unused); + ctx = EVP_PKEY_CTX_new_id(NID_id_GostR3410_2001, NULL); + if (ctx == NULL) + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_id", + DST_R_OPENSSLFAILURE)); + if (callback != NULL) { + u.fptr = callback; + EVP_PKEY_CTX_set_app_data(ctx, u.dptr); + EVP_PKEY_CTX_set_cb(ctx, &progress_cb); + } + if (EVP_PKEY_keygen_init(ctx) <= 0) + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + if (EVP_PKEY_CTX_ctrl_str(ctx, "paramset", "A") <= 0) + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_ctrl_str", + DST_R_OPENSSLFAILURE)); + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", + DST_R_OPENSSLFAILURE)); + key->keydata.pkey = pkey; + key->key_size = EVP_PKEY_bits(pkey); + EVP_PKEY_CTX_free(ctx); + return (ISC_R_SUCCESS); + +err: + if (pkey != NULL) + EVP_PKEY_free(pkey); + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + return (ret); +} + +static bool +opensslgost_isprivate(const dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + EC_KEY *ec; + + INSIST(pkey != NULL); + + ec = EVP_PKEY_get0(pkey); + return (ec != NULL && EC_KEY_get0_private_key(ec) != NULL); +} + +static void +opensslgost_destroy(dst_key_t *key) { + EVP_PKEY *pkey = key->keydata.pkey; + + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; +} + +static const unsigned char gost_prefix[37] = { + 0x30, 0x63, 0x30, 0x1c, 0x06, 0x06, 0x2a, 0x85, + 0x03, 0x02, 0x02, 0x13, 0x30, 0x12, 0x06, 0x07, + 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01, 0x06, + 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01, + 0x03, 0x43, 0x00, 0x04, 0x40 +}; + +static isc_result_t +opensslgost_todns(const dst_key_t *key, isc_buffer_t *data) { + EVP_PKEY *pkey; + isc_region_t r; + unsigned char der[37 + 64], *p; + int len; + + REQUIRE(key->keydata.pkey != NULL); + + pkey = key->keydata.pkey; + + isc_buffer_availableregion(data, &r); + if (r.length < 64) + return (ISC_R_NOSPACE); + + p = der; + len = i2d_PUBKEY(pkey, &p); + INSIST(len == sizeof(der)); + INSIST(isc_safe_memequal(gost_prefix, der, 37)); + memmove(r.base, der + 37, 64); + isc_buffer_add(data, 64); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslgost_fromdns(dst_key_t *key, isc_buffer_t *data) { + isc_region_t r; + EVP_PKEY *pkey = NULL; + unsigned char der[37 + 64]; + const unsigned char *p; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + if (r.length != 64) + return (DST_R_INVALIDPUBLICKEY); + memmove(der, gost_prefix, 37); + memmove(der + 37, r.base, 64); + isc_buffer_forward(data, 64); + + p = der; + if (d2i_PUBKEY(&pkey, &p, (long) sizeof(der)) == NULL) + return (dst__openssl_toresult2("d2i_PUBKEY", + DST_R_OPENSSLFAILURE)); + key->keydata.pkey = pkey; + key->key_size = EVP_PKEY_bits(pkey); + + return (ISC_R_SUCCESS); +} + +#ifdef PREFER_GOSTASN1 + +static isc_result_t +opensslgost_tofile(const dst_key_t *key, const char *directory) { + EVP_PKEY *pkey; + dst_private_t priv; + isc_result_t result; + unsigned char *der, *p; + int len; + + 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; + + len = i2d_PrivateKey(pkey, NULL); + der = isc_mem_get(key->mctx, (size_t) len); + if (der == NULL) + return (ISC_R_NOMEMORY); + + p = der; + if (i2d_PrivateKey(pkey, &p) != len) { + result = dst__openssl_toresult2("i2d_PrivateKey", + DST_R_OPENSSLFAILURE); + goto fail; + } + + priv.elements[0].tag = TAG_GOST_PRIVASN1; + priv.elements[0].length = len; + priv.elements[0].data = der; + priv.nelements = 1; + + result = dst__privstruct_writefile(key, &priv, directory); + fail: + if (der != NULL) + isc_mem_put(key->mctx, der, (size_t) len); + return (result); +} + +#else + +static isc_result_t +opensslgost_tofile(const dst_key_t *key, const char *directory) { + EVP_PKEY *pkey; + EC_KEY *eckey; + const BIGNUM *privkey; + dst_private_t priv; + isc_result_t ret; + unsigned char *buf = NULL; + + 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_get0(pkey); + if (eckey == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + privkey = EC_KEY_get0_private_key(eckey); + if (privkey == NULL) + return (ISC_R_FAILURE); + + buf = isc_mem_get(key->mctx, BN_num_bytes(privkey)); + if (buf == NULL) + return (ISC_R_NOMEMORY); + + priv.elements[0].tag = TAG_GOST_PRIVRAW; + priv.elements[0].length = BN_num_bytes(privkey); + BN_bn2bin(privkey, buf); + priv.elements[0].data = buf; + priv.nelements = 1; + + ret = dst__privstruct_writefile(key, &priv, directory); + + if (buf != NULL) + isc_mem_put(key->mctx, buf, BN_num_bytes(privkey)); + return (ret); +} +#endif + +static unsigned char gost_dummy_key[71] = { + 0x30, 0x45, 0x02, 0x01, 0x00, 0x30, 0x1c, 0x06, + 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x30, + 0x12, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x23, 0x01, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x1e, 0x01, 0x04, 0x22, 0x02, 0x20, 0x1b, + 0x3f, 0x94, 0xf7, 0x1a, 0x5f, 0x2f, 0xe7, 0xe5, + 0x74, 0x0b, 0x8c, 0xd4, 0xb7, 0x18, 0xdd, 0x65, + 0x68, 0x26, 0xd1, 0x54, 0xfb, 0x77, 0xba, 0x63, + 0x72, 0xd9, 0xf0, 0x63, 0x87, 0xe0, 0xd6 +}; + +static isc_result_t +opensslgost_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + isc_mem_t *mctx = key->mctx; + EVP_PKEY *pkey = NULL; + EC_KEY *eckey; + const EC_POINT *pubkey = NULL; + BIGNUM *privkey = NULL; + const unsigned char *p; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_ECCGOST, 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); + } + + INSIST((priv.elements[0].tag == TAG_GOST_PRIVASN1) || + (priv.elements[0].tag == TAG_GOST_PRIVRAW)); + + if (priv.elements[0].tag == TAG_GOST_PRIVASN1) { + p = priv.elements[0].data; + if (d2i_PrivateKey(NID_id_GostR3410_2001, &pkey, &p, + (long) priv.elements[0].length) == NULL) + DST_RET(dst__openssl_toresult2( + "d2i_PrivateKey", + DST_R_INVALIDPRIVATEKEY)); + } else { + if ((pub != NULL) && (pub->keydata.pkey != NULL)) { + eckey = EVP_PKEY_get0(pub->keydata.pkey); + pubkey = EC_KEY_get0_public_key(eckey); + } + + privkey = BN_bin2bn(priv.elements[0].data, + priv.elements[0].length, NULL); + if (privkey == NULL) + DST_RET(ISC_R_NOMEMORY); + + /* can't create directly the whole key */ + p = gost_dummy_key; + if (d2i_PrivateKey(NID_id_GostR3410_2001, &pkey, &p, + (long) sizeof(gost_dummy_key)) == NULL) + DST_RET(dst__openssl_toresult2( + "d2i_PrivateKey", + DST_R_INVALIDPRIVATEKEY)); + + eckey = EVP_PKEY_get0(pkey); + if (eckey == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + if (!EC_KEY_set_private_key(eckey, privkey)) + DST_RET(ISC_R_NOMEMORY); + + /* have to (re)set the public key */ +#ifdef notyet + (void) gost2001_compute_public(eckey); +#else + if ((pubkey != NULL) && !EC_KEY_set_public_key(eckey, pubkey)) + DST_RET(ISC_R_NOMEMORY); +#endif + BN_clear_free(privkey); + privkey = NULL; + } + key->keydata.pkey = pkey; + key->key_size = EVP_PKEY_bits(pkey); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ISC_R_SUCCESS); + + err: + if (privkey != NULL) + BN_clear_free(privkey); + if (pkey != NULL) + EVP_PKEY_free(pkey); + opensslgost_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static void +opensslgost_cleanup(void) { + if (e != NULL) { + ENGINE_finish(e); + ENGINE_free(e); + e = NULL; + } +} + +static dst_func_t opensslgost_functions = { + opensslgost_createctx, + NULL, /*%< createctx2 */ + opensslgost_destroyctx, + opensslgost_adddata, + opensslgost_sign, + opensslgost_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + opensslgost_compare, + NULL, /*%< paramcompare */ + opensslgost_generate, + opensslgost_isprivate, + opensslgost_destroy, + opensslgost_todns, + opensslgost_fromdns, + opensslgost_tofile, + opensslgost_parse, + opensslgost_cleanup, + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL /*%< restore */ +}; + +isc_result_t +dst__opensslgost_init(dst_func_t **funcp) { + isc_result_t ret; + + REQUIRE(funcp != NULL); + + /* check if the gost engine works properly */ + e = ENGINE_by_id("gost"); + if (e == NULL) + return (dst__openssl_toresult2("ENGINE_by_id", + DST_R_OPENSSLFAILURE)); + if (ENGINE_init(e) <= 0) { + ENGINE_free(e); + e = NULL; + return (dst__openssl_toresult2("ENGINE_init", + DST_R_OPENSSLFAILURE)); + } + /* better than to rely on digest_gost symbol */ + opensslgost_digest = ENGINE_get_digest(e, NID_id_GostR3411_94); + if (opensslgost_digest == NULL) + DST_RET(dst__openssl_toresult2("ENGINE_get_digest", + DST_R_OPENSSLFAILURE)); + /* from openssl.cnf */ + if (ENGINE_register_pkey_asn1_meths(e) <= 0) + DST_RET(dst__openssl_toresult2( + "ENGINE_register_pkey_asn1_meths", + DST_R_OPENSSLFAILURE)); + if (ENGINE_ctrl_cmd_string(e, + "CRYPT_PARAMS", + "id-Gost28147-89-CryptoPro-A-ParamSet", + 0) <= 0) + DST_RET(dst__openssl_toresult2("ENGINE_ctrl_cmd_string", + DST_R_OPENSSLFAILURE)); + + if (*funcp == NULL) + *funcp = &opensslgost_functions; + return (ISC_R_SUCCESS); + + err: + ENGINE_finish(e); + ENGINE_free(e); + e = NULL; + return (ret); +} + +#else /* HAVE_OPENSSL_GOST */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* HAVE_OPENSSL_GOST */ +/*! \file */ diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c new file mode 100644 index 0000000..c03fd72 --- /dev/null +++ b/lib/dns/opensslrsa_link.c @@ -0,0 +1,1830 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef OPENSSL +#include + +#ifndef USE_EVP +#if !defined(HAVE_EVP_SHA256) || !defined(HAVE_EVP_SHA512) +#define USE_EVP 0 +#else +#define USE_EVP 1 +#endif +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "dst_internal.h" +#include "dst_openssl.h" +#include "dst_parse.h" + +#include +#include +#include +#if OPENSSL_VERSION_NUMBER > 0x00908000L +#include +#endif +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif + +/* + * Limit the size of public exponents. + */ +#ifndef RSA_MAX_PUBEXP_BITS +#define RSA_MAX_PUBEXP_BITS 35 +#endif + +/* + * We don't use configure for windows so enforce the OpenSSL version + * here. Unlike with configure we don't support overriding this test. + */ +#ifdef WIN32 +#if !((OPENSSL_VERSION_NUMBER >= 0x009070cfL && \ + OPENSSL_VERSION_NUMBER < 0x00908000L) || \ + (OPENSSL_VERSION_NUMBER >= 0x0090804fL && \ + OPENSSL_VERSION_NUMBER < 0x10002000L) || \ + OPENSSL_VERSION_NUMBER >= 0x1000205fL) +#error Please upgrade OpenSSL to 0.9.8d/0.9.7l or greater. +#endif +#endif + + + /* + * XXXMPA Temporarily disable RSA_BLINDING as it requires + * good quality random data that cannot currently be guaranteed. + * XXXMPA Find which versions of openssl use pseudo random data + * and set RSA_FLAG_BLINDING for those. + */ + +#if 0 +#if OPENSSL_VERSION_NUMBER < 0x0090601fL +#define SET_FLAGS(rsa) \ + do { \ + (rsa)->flags &= ~(RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE); \ + (rsa)->flags |= RSA_FLAG_BLINDING; \ + } while (0) +#else +#define SET_FLAGS(rsa) \ + do { \ + (rsa)->flags |= RSA_FLAG_BLINDING; \ + } while (0) +#endif +#endif + +#if OPENSSL_VERSION_NUMBER < 0x0090601fL +#define SET_FLAGS(rsa) \ + do { \ + (rsa)->flags &= ~(RSA_FLAG_CACHE_PUBLIC | RSA_FLAG_CACHE_PRIVATE); \ + (rsa)->flags &= ~RSA_FLAG_BLINDING; \ + } while (0) +#elif OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +#if defined(RSA_FLAG_NO_BLINDING) +#define SET_FLAGS(rsa) \ + do { \ + (rsa)->flags &= ~RSA_FLAG_BLINDING; \ + (rsa)->flags |= RSA_FLAG_NO_BLINDING; \ + } while (0) +#else +#define SET_FLAGS(rsa) \ + do { \ + (rsa)->flags &= ~RSA_FLAG_BLINDING; \ + } while (0) +#endif +#else +#define SET_FLAGS(rsa) \ + do { \ + RSA_clear_flags(rsa, RSA_FLAG_BLINDING); \ + RSA_set_flags(rsa, RSA_FLAG_NO_BLINDING); \ + } while (0) +#endif +#define DST_RET(a) {ret = a; goto err;} + +#if !defined(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 + +static isc_result_t opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data); + +static isc_result_t +opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx; + const EVP_MD *type = NULL; +#endif + + UNUSED(key); +#ifndef PK11_MD5_DISABLE + REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || + 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); +#else + 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); +#endif + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + +#if USE_EVP + evp_md_ctx = EVP_MD_CTX_create(); + if (evp_md_ctx == NULL) + return (ISC_R_NOMEMORY); + + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + type = EVP_md5(); /* MD5 + RSA */ + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + type = EVP_sha1(); /* SHA1 + RSA */ + break; +#ifdef HAVE_EVP_SHA256 + case DST_ALG_RSASHA256: + type = EVP_sha256(); /* SHA256 + RSA */ + break; +#endif +#ifdef HAVE_EVP_SHA512 + case DST_ALG_RSASHA512: + type = EVP_sha512(); + break; +#endif + default: + INSIST(0); + } + + 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; +#else + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + { + isc_md5_t *md5ctx; + + md5ctx = isc_mem_get(dctx->mctx, sizeof(isc_md5_t)); + if (md5ctx == NULL) + return (ISC_R_NOMEMORY); + isc_md5_init(md5ctx); + dctx->ctxdata.md5ctx = md5ctx; + } + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + { + isc_sha1_t *sha1ctx; + + sha1ctx = isc_mem_get(dctx->mctx, sizeof(isc_sha1_t)); + if (sha1ctx == NULL) + return (ISC_R_NOMEMORY); + isc_sha1_init(sha1ctx); + dctx->ctxdata.sha1ctx = sha1ctx; + } + break; + case DST_ALG_RSASHA256: + { + isc_sha256_t *sha256ctx; + + sha256ctx = isc_mem_get(dctx->mctx, + sizeof(isc_sha256_t)); + if (sha256ctx == NULL) + return (ISC_R_NOMEMORY); + isc_sha256_init(sha256ctx); + dctx->ctxdata.sha256ctx = sha256ctx; + } + break; + case DST_ALG_RSASHA512: + { + isc_sha512_t *sha512ctx; + + sha512ctx = isc_mem_get(dctx->mctx, + sizeof(isc_sha512_t)); + if (sha512ctx == NULL) + return (ISC_R_NOMEMORY); + isc_sha512_init(sha512ctx); + dctx->ctxdata.sha512ctx = sha512ctx; + } + break; + default: + INSIST(0); + } +#endif + + return (ISC_R_SUCCESS); +} + +static void +opensslrsa_destroyctx(dst_context_t *dctx) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; +#endif + +#ifndef PK11_MD5_DISABLE + REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || + 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); +#else + 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); +#endif + +#if USE_EVP + if (evp_md_ctx != NULL) { + EVP_MD_CTX_destroy(evp_md_ctx); + dctx->ctxdata.evp_md_ctx = NULL; + } +#else + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + { + isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; + + if (md5ctx != NULL) { + isc_md5_invalidate(md5ctx); + isc_mem_put(dctx->mctx, md5ctx, + sizeof(isc_md5_t)); + dctx->ctxdata.md5ctx = NULL; + } + } + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + { + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + if (sha1ctx != NULL) { + isc_sha1_invalidate(sha1ctx); + isc_mem_put(dctx->mctx, sha1ctx, + sizeof(isc_sha1_t)); + dctx->ctxdata.sha1ctx = NULL; + } + } + break; + case DST_ALG_RSASHA256: + { + isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; + + if (sha256ctx != NULL) { + isc_sha256_invalidate(sha256ctx); + isc_mem_put(dctx->mctx, sha256ctx, + sizeof(isc_sha256_t)); + dctx->ctxdata.sha256ctx = NULL; + } + } + break; + case DST_ALG_RSASHA512: + { + isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; + + if (sha512ctx != NULL) { + isc_sha512_invalidate(sha512ctx); + isc_mem_put(dctx->mctx, sha512ctx, + sizeof(isc_sha512_t)); + dctx->ctxdata.sha512ctx = NULL; + } + } + break; + default: + INSIST(0); + } +#endif +} + +static isc_result_t +opensslrsa_adddata(dst_context_t *dctx, const isc_region_t *data) { +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; +#endif + +#ifndef PK11_MD5_DISABLE + REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || + 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); +#else + 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); +#endif + +#if USE_EVP + if (!EVP_DigestUpdate(evp_md_ctx, data->base, data->length)) { + return (dst__openssl_toresult3(dctx->category, + "EVP_DigestUpdate", + ISC_R_FAILURE)); + } +#else + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + { + isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; + + isc_md5_update(md5ctx, data->base, data->length); + } + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + { + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + isc_sha1_update(sha1ctx, data->base, data->length); + } + break; + case DST_ALG_RSASHA256: + { + isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; + + isc_sha256_update(sha256ctx, data->base, data->length); + } + break; + case DST_ALG_RSASHA512: + { + isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; + + isc_sha512_update(sha512ctx, data->base, data->length); + } + break; + default: + INSIST(0); + } +#endif + return (ISC_R_SUCCESS); +} + +#if ! USE_EVP && OPENSSL_VERSION_NUMBER < 0x00908000L +/* + * Digest prefixes from RFC 5702. + */ +static unsigned char sha256_prefix[] = + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; +static unsigned char sha512_prefix[] = + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}; +#define PREFIXLEN sizeof(sha512_prefix) +#else +#define PREFIXLEN 0 +#endif + +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; +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; +#else + RSA *rsa = key->keydata.rsa; + /* note: ISC_SHA512_DIGESTLENGTH >= ISC_*_DIGESTLENGTH */ + unsigned char digest[PREFIXLEN + ISC_SHA512_DIGESTLENGTH]; + int status; + int type = 0; + unsigned int digestlen = 0; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + unsigned int prefixlen = 0; + const unsigned char *prefix = NULL; +#endif +#endif + +#ifndef PK11_MD5_DISABLE + REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || + 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); +#else + 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); +#endif + + isc_buffer_availableregion(sig, &r); + +#if USE_EVP + 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)); + } +#else + if (r.length < (unsigned int) RSA_size(rsa)) + return (ISC_R_NOSPACE); + + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + { + isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; + + isc_md5_final(md5ctx, digest); + type = NID_md5; + digestlen = ISC_MD5_DIGESTLENGTH; + } + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + { + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + isc_sha1_final(sha1ctx, digest); + type = NID_sha1; + digestlen = ISC_SHA1_DIGESTLENGTH; + } + break; + case DST_ALG_RSASHA256: + { + isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; + + isc_sha256_final(digest, sha256ctx); + digestlen = ISC_SHA256_DIGESTLENGTH; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + prefix = sha256_prefix; + prefixlen = sizeof(sha256_prefix); +#else + type = NID_sha256; +#endif + } + break; + case DST_ALG_RSASHA512: + { + isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; + + isc_sha512_final(digest, sha512ctx); + digestlen = ISC_SHA512_DIGESTLENGTH; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + prefix = sha512_prefix; + prefixlen = sizeof(sha512_prefix); +#else + type = NID_sha512; +#endif + } + break; + default: + INSIST(0); + } + +#if OPENSSL_VERSION_NUMBER < 0x00908000L + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + INSIST(type != 0); + status = RSA_sign(type, digest, digestlen, r.base, + &siglen, rsa); + break; + + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + INSIST(prefix != NULL); + INSIST(prefixlen != 0); + INSIST(prefixlen + digestlen <= sizeof(digest)); + + memmove(digest + prefixlen, digest, digestlen); + memmove(digest, prefix, prefixlen); + status = RSA_private_encrypt(digestlen + prefixlen, + digest, r.base, rsa, + RSA_PKCS1_PADDING); + if (status < 0) + status = 0; + else + siglen = status; + break; + + default: + INSIST(0); + } +#else + INSIST(type != 0); + status = RSA_sign(type, digest, digestlen, r.base, &siglen, rsa); +#endif + if (status == 0) + return (dst__openssl_toresult3(dctx->category, + "RSA_sign", + DST_R_OPENSSLFAILURE)); +#endif + + 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; +#if USE_EVP + EVP_MD_CTX *evp_md_ctx = dctx->ctxdata.evp_md_ctx; + EVP_PKEY *pkey = key->keydata.pkey; + RSA *rsa; + int bits; +#else + /* note: ISC_SHA512_DIGESTLENGTH >= ISC_*_DIGESTLENGTH */ + unsigned char digest[ISC_SHA512_DIGESTLENGTH]; + int type = 0; + unsigned int digestlen = 0; + RSA *rsa = key->keydata.rsa; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + unsigned int prefixlen = 0; + const unsigned char *prefix = NULL; +#endif +#endif + +#ifndef PK11_MD5_DISABLE + REQUIRE(dctx->key->key_alg == DST_ALG_RSAMD5 || + 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); +#else + 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); +#endif + +#if USE_EVP + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + RSA_get0_key(rsa, NULL, &e, NULL); + 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)); + } +#else + RSA_get0_key(rsa, NULL, &e, NULL); + if (BN_num_bits(e) > maxbits && maxbits != 0) + return (DST_R_VERIFYFAILURE); + + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + { + isc_md5_t *md5ctx = dctx->ctxdata.md5ctx; + + isc_md5_final(md5ctx, digest); + type = NID_md5; + digestlen = ISC_MD5_DIGESTLENGTH; + } + break; +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + { + isc_sha1_t *sha1ctx = dctx->ctxdata.sha1ctx; + + isc_sha1_final(sha1ctx, digest); + type = NID_sha1; + digestlen = ISC_SHA1_DIGESTLENGTH; + } + break; + case DST_ALG_RSASHA256: + { + isc_sha256_t *sha256ctx = dctx->ctxdata.sha256ctx; + + isc_sha256_final(digest, sha256ctx); + digestlen = ISC_SHA256_DIGESTLENGTH; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + prefix = sha256_prefix; + prefixlen = sizeof(sha256_prefix); +#else + type = NID_sha256; +#endif + } + break; + case DST_ALG_RSASHA512: + { + isc_sha512_t *sha512ctx = dctx->ctxdata.sha512ctx; + + isc_sha512_final(digest, sha512ctx); + digestlen = ISC_SHA512_DIGESTLENGTH; +#if OPENSSL_VERSION_NUMBER < 0x00908000L + prefix = sha512_prefix; + prefixlen = sizeof(sha512_prefix); +#else + type = NID_sha512; +#endif + } + break; + default: + INSIST(0); + } + + if (sig->length != (unsigned int) RSA_size(rsa)) + return (DST_R_VERIFYFAILURE); + +#if OPENSSL_VERSION_NUMBER < 0x00908000L + switch (dctx->key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: +#endif + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + INSIST(type != 0); + status = RSA_verify(type, digest, digestlen, sig->base, + RSA_size(rsa), rsa); + break; + + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + { + /* + * 1024 is big enough for all valid RSA bit sizes + * for use with DNSSEC. + */ + unsigned char original[PREFIXLEN + 1024]; + + INSIST(prefix != NULL); + INSIST(prefixlen != 0U); + + if (RSA_size(rsa) > (int)sizeof(original)) + return (DST_R_VERIFYFAILURE); + + status = RSA_public_decrypt(sig->length, sig->base, + original, rsa, + RSA_PKCS1_PADDING); + if (status <= 0) + return (dst__openssl_toresult3( + dctx->category, + "RSA_public_decrypt", + DST_R_VERIFYFAILURE)); + if (status != (int)(prefixlen + digestlen)) + return (DST_R_VERIFYFAILURE); + if (!isc_safe_memequal(original, prefix, prefixlen)) + return (DST_R_VERIFYFAILURE); + if (!isc_safe_memequal(original + prefixlen, + digest, digestlen)) + return (DST_R_VERIFYFAILURE); + status = 1; + } + break; + + default: + INSIST(0); + } +#else + INSIST(type != 0); + status = RSA_verify(type, digest, digestlen, sig->base, + RSA_size(rsa), rsa); +#endif + if (status != 1) + return (dst__openssl_toresult(DST_R_VERIFYFAILURE)); + return (ISC_R_SUCCESS); +#endif +} + +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; +#if USE_EVP + EVP_PKEY *pkey1, *pkey2; +#endif + +#if USE_EVP + 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); + } +#else + rsa1 = key1->keydata.rsa; + rsa2 = key2->keydata.rsa; +#endif + + 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 USE_EVP + 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); + } +#endif + + 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, p1) || BN_cmp(q1, q2); + + if (status != 0) + return (false); + } + return (true); +} + +#if OPENSSL_VERSION_NUMBER > 0x00908000L +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); +} +#endif + +static isc_result_t +opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { +#if OPENSSL_VERSION_NUMBER > 0x00908000L + isc_result_t ret = DST_R_OPENSSLFAILURE; + union { + void *dptr; + void (*fptr)(int); + } u; + RSA *rsa = RSA_new(); + BIGNUM *e = BN_new(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + BN_GENCB _cb; +#endif + BN_GENCB *cb = BN_GENCB_new(); +#if USE_EVP + EVP_PKEY *pkey = EVP_PKEY_new(); +#endif + + /* + * Reject incorrect RSA key lengths. + */ + switch (key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + + if (rsa == NULL || e == NULL || cb == NULL) + goto err; +#if USE_EVP + if (pkey == NULL) + goto err; + if (!EVP_PKEY_set1_RSA(pkey, rsa)) + goto err; +#endif + + 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; + SET_FLAGS(rsa); +#if USE_EVP + key->keydata.pkey = pkey; + + RSA_free(rsa); +#else + key->keydata.rsa = rsa; +#endif + return (ISC_R_SUCCESS); + } + ret = dst__openssl_toresult2("RSA_generate_key_ex", + DST_R_OPENSSLFAILURE); + + err: +#if USE_EVP + if (pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } +#endif + 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)); +#else + RSA *rsa; + unsigned long e; +#if USE_EVP + EVP_PKEY *pkey = EVP_PKEY_new(); + + UNUSED(callback); + + if (pkey == NULL) + return (ISC_R_NOMEMORY); +#else + UNUSED(callback); +#endif + + if (exp == 0) + e = RSA_F4; + else + e = 0x40000003; + rsa = RSA_generate_key(key->key_size, e, NULL, NULL); + if (rsa == NULL) { +#if USE_EVP + EVP_PKEY_free(pkey); +#endif + return (dst__openssl_toresult2("RSA_generate_key", + DST_R_OPENSSLFAILURE)); + } + SET_FLAGS(rsa); +#if USE_EVP + 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); +#else + key->keydata.rsa = rsa; +#endif + + return (ISC_R_SUCCESS); +#endif +} + +static bool +opensslrsa_isprivate(const dst_key_t *key) { + const BIGNUM *d = NULL; +#if USE_EVP + 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. */ +#else + RSA *rsa = key->keydata.rsa; +#endif + 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) { +#if USE_EVP + EVP_PKEY *pkey = key->keydata.pkey; + EVP_PKEY_free(pkey); + key->keydata.pkey = NULL; +#else + RSA *rsa = key->keydata.rsa; + RSA_free(rsa); + key->keydata.rsa = NULL; +#endif +} + +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; +#if USE_EVP + EVP_PKEY *pkey; +#endif + const BIGNUM *e = NULL, *n = NULL; + +#if USE_EVP + REQUIRE(key->keydata.pkey != NULL); +#else + REQUIRE(key->keydata.rsa != NULL); +#endif + +#if USE_EVP + pkey = key->keydata.pkey; + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); +#else + rsa = key->keydata.rsa; +#endif + + isc_buffer_availableregion(data, &r); + + RSA_get0_key(rsa, &n, &e, NULL); + mod_bytes = BN_num_bytes(n); + e_bytes = BN_num_bytes(e); + + if (e_bytes < 256) { /*%< key exponent is <= 2040 bits */ + if (r.length < 1) + DST_RET(ISC_R_NOSPACE); + isc_buffer_putuint8(data, (uint8_t) e_bytes); + isc_region_consume(&r, 1); + } else { + if (r.length < 3) + DST_RET(ISC_R_NOSPACE); + isc_buffer_putuint8(data, 0); + isc_buffer_putuint16(data, (uint16_t) e_bytes); + isc_region_consume(&r, 3); + } + + if (r.length < e_bytes + mod_bytes) + DST_RET(ISC_R_NOSPACE); + + 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: +#if USE_EVP + if (rsa != NULL) + RSA_free(rsa); +#endif + 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; +#if USE_EVP + EVP_PKEY *pkey; +#endif + 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)); + SET_FLAGS(rsa); + + 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 (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); + +#if USE_EVP + 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); +#else + key->keydata.rsa = rsa; +#endif + + 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 USE_EVP + 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)); +#else + if (key->keydata.rsa == NULL) + return (DST_R_NULLKEY); + rsa = key->keydata.rsa; +#endif + 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); + + if (key->external) { + priv.nelements = 0; + result = dst__privstruct_writefile(key, &priv, directory); + goto fail; + } + + for (i = 0; i < 8; i++) { + bufs[i] = isc_mem_get(key->mctx, BN_num_bytes(n)); + if (bufs[i] == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + } + + 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); + fail: +#if USE_EVP + RSA_free(rsa); +#endif + 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 (e1 != NULL) { + if (BN_cmp(e1, e2) != 0) + return (DST_R_INVALIDPRIVATEKEY); + } else { + e = BN_dup(e2); + } + 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 + isc_mem_t *mctx = key->mctx; + const char *engine = NULL, *label = NULL; +#if !defined(OPENSSL_NO_ENGINE) || USE_EVP + EVP_PKEY *pkey = NULL; +#endif + 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 USE_EVP + if (pub != NULL && pub->keydata.pkey != NULL) + pubrsa = EVP_PKEY_get1_RSA(pub->keydata.pkey); +#else + if (pub != NULL && pub->keydata.rsa != NULL) { + pubrsa = pub->keydata.rsa; + pub->keydata.rsa = NULL; + } +#endif + + 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); +#if USE_EVP + key->keydata.pkey = pkey; + RSA_free(rsa); +#else + key->keydata.rsa = rsa; + EVP_PKEY_free(pkey); +#endif + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ISC_R_SUCCESS); +#else + DST_RET(DST_R_NOENGINE); +#endif + } + + rsa = RSA_new(); + if (rsa == NULL) + DST_RET(ISC_R_NOMEMORY); + SET_FLAGS(rsa); + +#if USE_EVP + 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; +#else + key->keydata.rsa = rsa; +#endif + + 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); +#if USE_EVP + RSA_free(rsa); +#endif + + return (ISC_R_SUCCESS); + + err: +#if USE_EVP + if (pkey != NULL) + EVP_PKEY_free(pkey); +#endif + 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; + char *colon, *tmpengine = NULL; + const BIGNUM *ex = NULL; + + UNUSED(pin); + + if (engine == NULL) { + if (strchr(label, ':') == NULL) + DST_RET(DST_R_NOENGINE); + tmpengine = isc_mem_strdup(key->mctx, label); + if (tmpengine == NULL) + DST_RET(ISC_R_NOMEMORY); + colon = strchr(tmpengine, ':'); + INSIST(colon != NULL); + *colon = '\0'; + } + 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)); + if (tmpengine != NULL) { + key->engine = tmpengine; + tmpengine = NULL; + } else { + key->engine = isc_mem_strdup(key->mctx, engine); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); +#if USE_EVP + key->keydata.pkey = pkey; + RSA_free(rsa); +#else + key->keydata.rsa = rsa; + EVP_PKEY_free(pkey); +#endif + return (ISC_R_SUCCESS); + + err: + if (tmpengine != NULL) + isc_mem_free(key->mctx, tmpengine); + if (rsa != NULL) + RSA_free(rsa); + if (pubrsa != NULL) + RSA_free(pubrsa); + if (pkey != NULL) + EVP_PKEY_free(pkey); + return (ret); +#else + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return(DST_R_NOENGINE); +#endif +} + +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 */ +}; + +isc_result_t +dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) { + REQUIRE(funcp != NULL); + + if (*funcp == NULL) { + switch (algorithm) { + case DST_ALG_RSASHA256: +#if defined(HAVE_EVP_SHA256) || !USE_EVP + *funcp = &opensslrsa_functions; +#endif + break; + case DST_ALG_RSASHA512: +#if defined(HAVE_EVP_SHA512) || !USE_EVP + *funcp = &opensslrsa_functions; +#endif + break; + default: + *funcp = &opensslrsa_functions; + break; + } + } + return (ISC_R_SUCCESS); +} + +#else /* OPENSSL */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* OPENSSL */ +/*! \file */ diff --git a/lib/dns/order.c b/lib/dns/order.c new file mode 100644 index 0000000..6ffc4a0 --- /dev/null +++ b/lib/dns/order.c @@ -0,0 +1,163 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: order.c,v 1.10 2007/06/19 23:47:16 tbox Exp $ */ + +/*! \file */ + +#include + +#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; + isc_result_t result; + + REQUIRE(orderp != NULL && *orderp == NULL); + + order = isc_mem_get(mctx, sizeof(*order)); + if (order == NULL) + return (ISC_R_NOMEMORY); + + ISC_LIST_INIT(order->ents); + + /* Implicit attach. */ + result = isc_refcount_init(&order->references, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, order, sizeof(*order)); + return (result); + } + + 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, 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 == 0 /* DNS_RDATASETATTR_CYCLIC */ ); + + ent = isc_mem_get(order->mctx, sizeof(*ent)); + if (ent == NULL) + return (ISC_R_NOMEMORY); + + dns_fixedname_init(&ent->name); + RUNTIME_CHECK(dns_name_copy(name, dns_fixedname_name(&ent->name), NULL) + == ISC_R_SUCCESS); + 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 inline bool +match(dns_name_t *name1, 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, 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_RANDOMIZE); +} + +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, NULL); + *target = source; +} + +void +dns_order_detach(dns_order_t **orderp) { + dns_order_t *order; + dns_order_ent_t *ent; + unsigned int references; + + REQUIRE(orderp != NULL); + order = *orderp; + REQUIRE(DNS_ORDER_VALID(order)); + isc_refcount_decrement(&order->references, &references); + *orderp = NULL; + if (references != 0) + return; + + order->magic = 0; + while ((ent = ISC_LIST_HEAD(order->ents)) != NULL) { + ISC_LIST_UNLINK(order->ents, ent, link); + isc_mem_put(order->mctx, ent, sizeof(*ent)); + } + isc_refcount_destroy(&order->references); + 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..3a781d2 --- /dev/null +++ b/lib/dns/peer.c @@ -0,0 +1,887 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: peer.c,v 1.33 2009/09/02 23:48:02 tbox Exp $ */ + +/*! \file */ + +#include + +#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 + +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)); + if (l == NULL) + return (ISC_R_NOMEMORY); + + ISC_LIST_INIT(l->elements); + l->mem = mem; + 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); + + source->refs++; + + ENSURE(source->refs != 0xffffffffU); + + *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; + + REQUIRE(plist->refs > 0); + + plist->refs--; + + if (plist->refs == 0) + 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; + + REQUIRE(l->refs == 0); + + 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)); + + *list = NULL; +} + +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, + 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, 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: + INSIST(0); + } + + return (dns_peer_newprefix(mem, addr, prefixlen, peerptr)); +} + +isc_result_t +dns_peer_newprefix(isc_mem_t *mem, isc_netaddr_t *addr, unsigned int prefixlen, + dns_peer_t **peerptr) +{ + dns_peer_t *peer; + + REQUIRE(peerptr != NULL); + + peer = isc_mem_get(mem, sizeof(*peer)); + if (peer == NULL) + return (ISC_R_NOMEMORY); + + peer->magic = DNS_PEER_MAGIC; + peer->address = *addr; + peer->prefixlen = prefixlen; + peer->mem = mem; + peer->bogus = false; + peer->transfer_format = dns_one_answer; + peer->transfers = 0; + peer->request_ixfr = false; + peer->provide_ixfr = false; + peer->key = NULL; + peer->refs = 1; + peer->transfer_source = NULL; + peer->notify_source = NULL; + peer->query_source = NULL; + + memset(&peer->bitflags, 0x0, sizeof(peer->bitflags)); + + 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); + + source->refs++; + + ENSURE(source->refs != 0xffffffffU); + + *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; + + REQUIRE(p->refs > 0); + + *peer = NULL; + p->refs--; + + if (p->refs == 0) + 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; + + REQUIRE(p->refs == 0); + + 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)); + + *peer = NULL; +} + +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_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)); + if (name == NULL) + return (ISC_R_NOMEMORY); + + dns_name_init(name, NULL); + result = dns_name_dup(dns_fixedname_name(&fname), peer->mem, name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(peer->mem, name, sizeof(dns_name_t)); + return (result); + } + + 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)); + if (peer->transfer_source == NULL) + return (ISC_R_NOMEMORY); + + *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)); + if (peer->notify_source == NULL) + return (ISC_R_NOMEMORY); + + *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)); + if (peer->query_source == NULL) + return (ISC_R_NOMEMORY); + + *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_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..5a2c502 --- /dev/null +++ b/lib/dns/pkcs11.c @@ -0,0 +1,45 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef PKCS11CRYPTO + +#include + +#include +#include + +#include +#include + +#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); +} + + +#else /* PKCS11CRYPTO */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO */ +/*! \file */ diff --git a/lib/dns/pkcs11dh_link.c b/lib/dns/pkcs11dh_link.c new file mode 100644 index 0000000..e2b60ea --- /dev/null +++ b/lib/dns/pkcs11dh_link.c @@ -0,0 +1,1136 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef PKCS11CRYPTO + +#include + +#include + +#ifndef PK11_DH_DISABLE + +#include +#include + +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +#include +#include +#define WANT_DH_PRIMES +#include + +#include + +/* + * PKCS#3 DH keys: + * mechanisms: + * CKM_DH_PKCS_PARAMETER_GEN, + * CKM_DH_PKCS_KEY_PAIR_GEN, + * CKM_DH_PKCS_DERIVE + * domain parameters: + * object class CKO_DOMAIN_PARAMETERS + * key type CKK_DH + * attribute CKA_PRIME (prime p) + * attribute CKA_BASE (base g) + * optional attribute CKA_PRIME_BITS (p length in bits) + * public key: + * object class CKO_PUBLIC_KEY + * key type CKK_DH + * attribute CKA_PRIME (prime p) + * attribute CKA_BASE (base g) + * attribute CKA_VALUE (public value y) + * private key: + * object class CKO_PRIVATE_KEY + * key type CKK_DH + * attribute CKA_PRIME (prime p) + * attribute CKA_BASE (base g) + * attribute CKA_VALUE (private value x) + * optional attribute CKA_VALUE_BITS (x length in bits) + * reuse CKA_PRIVATE_EXPONENT for key pair private value + */ + +#define CKA_VALUE2 CKA_PRIVATE_EXPONENT + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +#define DST_RET(a) {ret = a; goto err;} + +static void pkcs11dh_destroy(dst_key_t *key); +static isc_result_t pkcs11dh_todns(const dst_key_t *key, isc_buffer_t *data); + +static isc_result_t +pkcs11dh_loadpriv(const dst_key_t *key, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE *hKey) +{ + CK_RV rv; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_DH; + 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_DERIVE, &truevalue, (CK_ULONG) sizeof(truevalue) }, + { CKA_PRIME, NULL, 0 }, + { CKA_BASE, NULL, 0 }, + { CKA_VALUE, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + const pk11_object_t *priv; + isc_result_t ret; + unsigned int i; + + priv = key->keydata.pkey; + if ((priv->object != CK_INVALID_HANDLE) && priv->ontoken) { + *hKey = priv->object; + return (ISC_R_SUCCESS); + } + + attr = pk11_attribute_bytype(priv, CKA_PRIME); + if (attr == NULL) + return (DST_R_INVALIDPRIVATEKEY); + keyTemplate[6].pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[6].pValue, attr->pValue, attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + + attr = pk11_attribute_bytype(priv, CKA_BASE); + if (attr == NULL) + DST_RET(DST_R_INVALIDPRIVATEKEY); + keyTemplate[7].pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (keyTemplate[7].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[7].pValue, attr->pValue, attr->ulValueLen); + keyTemplate[7].ulValueLen = attr->ulValueLen; + + attr = pk11_attribute_bytype(priv, CKA_VALUE2); + if (attr == NULL) + DST_RET(DST_R_INVALIDPRIVATEKEY); + keyTemplate[8].pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (keyTemplate[8].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[8].pValue, attr->pValue, attr->ulValueLen); + keyTemplate[8].ulValueLen = attr->ulValueLen; + + PK11_CALL(pkcs_C_CreateObject, + (session, keyTemplate, (CK_ULONG) 9, hKey), + DST_R_COMPUTESECRETFAILURE); + if (rv == CKR_OK) + ret = ISC_R_SUCCESS; + + err: + for (i = 6; i <= 8; i++) + if (keyTemplate[i].pValue != NULL) { + isc_safe_memwipe(keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + isc_mem_put(key->mctx, + keyTemplate[i].pValue, + keyTemplate[i].ulValueLen); + } + return (ret); +} + +static isc_result_t +pkcs11dh_computesecret(const dst_key_t *pub, const dst_key_t *priv, + isc_buffer_t *secret) +{ + CK_RV rv; + CK_MECHANISM mech = { CKM_DH_PKCS_DERIVE, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; + CK_OBJECT_HANDLE hDerived = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE hKey = CK_INVALID_HANDLE; + CK_ATTRIBUTE *attr; + CK_ULONG secLen; + CK_ATTRIBUTE keyTemplate[] = + { + { CKA_CLASS, &keyClass, (CK_ULONG) sizeof(keyClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG) sizeof(keyType) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_SENSITIVE, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_EXTRACTABLE, &truevalue, (CK_ULONG) sizeof(truevalue) }, + { CKA_VALUE_LEN, &secLen, (CK_ULONG) sizeof(secLen) } + }; + CK_ATTRIBUTE valTemplate[] = + { + { CKA_VALUE, NULL, 0 } + }; + CK_BYTE *secValue; + pk11_context_t ctx; + isc_result_t ret; + unsigned int i; + isc_region_t r; + + REQUIRE(pub->keydata.pkey != NULL); + REQUIRE(priv->keydata.pkey != NULL); + REQUIRE(priv->keydata.pkey->repr != NULL); + attr = pk11_attribute_bytype(pub->keydata.pkey, CKA_PRIME); + if (attr == NULL) + return (DST_R_INVALIDPUBLICKEY); + REQUIRE(attr != NULL); + secLen = attr->ulValueLen; + attr = pk11_attribute_bytype(pub->keydata.pkey, CKA_VALUE); + if (attr == NULL) + return (DST_R_INVALIDPUBLICKEY); + + ret = pk11_get_session(&ctx, OP_DH, true, false, + priv->keydata.pkey->reqlogon, NULL, + pk11_get_best_token(OP_DH)); + if (ret != ISC_R_SUCCESS) + return (ret); + + mech.ulParameterLen = attr->ulValueLen; + mech.pParameter = isc_mem_get(pub->mctx, mech.ulParameterLen); + if (mech.pParameter == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(mech.pParameter, attr->pValue, mech.ulParameterLen); + + ret = pkcs11dh_loadpriv(priv, ctx.session, &hKey); + if (ret != ISC_R_SUCCESS) + goto err; + + PK11_RET(pkcs_C_DeriveKey, + (ctx.session, &mech, hKey, + keyTemplate, (CK_ULONG) 6, &hDerived), + DST_R_COMPUTESECRETFAILURE); + + attr = valTemplate; + PK11_RET(pkcs_C_GetAttributeValue, + (ctx.session, hDerived, attr, (CK_ULONG) 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(pub->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (ctx.session, hDerived, attr, (CK_ULONG) 1), + DST_R_CRYPTOFAILURE); + + /* strip leading zeros */ + secValue = (CK_BYTE_PTR) attr->pValue; + for (i = 0; i < attr->ulValueLen; i++) + if (secValue[i] != 0) + break; + isc_buffer_availableregion(secret, &r); + if (r.length < attr->ulValueLen - i) + DST_RET(ISC_R_NOSPACE); + memmove(r.base, secValue + i, attr->ulValueLen - i); + isc_buffer_add(secret, attr->ulValueLen - i); + ret = ISC_R_SUCCESS; + + err: + if (hDerived != CK_INVALID_HANDLE) + (void) pkcs_C_DestroyObject(ctx.session, hDerived); + if (valTemplate[0].pValue != NULL) { + isc_safe_memwipe(valTemplate[0].pValue, + valTemplate[0].ulValueLen); + isc_mem_put(pub->mctx, + valTemplate[0].pValue, + valTemplate[0].ulValueLen); + } + if ((hKey != CK_INVALID_HANDLE) && !priv->keydata.pkey->ontoken) + (void) pkcs_C_DestroyObject(ctx.session, hKey); + if (mech.pParameter != NULL) { + isc_safe_memwipe(mech.pParameter, mech.ulParameterLen); + isc_mem_put(pub->mctx, mech.pParameter, mech.ulParameterLen); + } + pk11_return_session(&ctx); + return (ret); +} + +static bool +pkcs11dh_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *dh1, *dh2; + CK_ATTRIBUTE *attr1, *attr2; + + dh1 = key1->keydata.pkey; + dh2 = key2->keydata.pkey; + + if ((dh1 == NULL) && (dh2 == NULL)) + return (true); + else if ((dh1 == NULL) || (dh2 == NULL)) + return (false); + + attr1 = pk11_attribute_bytype(dh1, CKA_PRIME); + attr2 = pk11_attribute_bytype(dh2, CKA_PRIME); + 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(dh1, CKA_BASE); + attr2 = pk11_attribute_bytype(dh2, CKA_BASE); + 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(dh1, CKA_VALUE); + attr2 = pk11_attribute_bytype(dh2, CKA_VALUE); + 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(dh1, CKA_VALUE2); + attr2 = pk11_attribute_bytype(dh2, CKA_VALUE2); + 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 (!dh1->ontoken && !dh2->ontoken) + return (true); + else if (dh1->ontoken || dh2->ontoken || + (dh1->object != dh2->object)) + return (false); + + return (true); +} + +static bool +pkcs11dh_paramcompare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *dh1, *dh2; + CK_ATTRIBUTE *attr1, *attr2; + + dh1 = key1->keydata.pkey; + dh2 = key2->keydata.pkey; + + if ((dh1 == NULL) && (dh2 == NULL)) + return (true); + else if ((dh1 == NULL) || (dh2 == NULL)) + return (false); + + attr1 = pk11_attribute_bytype(dh1, CKA_PRIME); + attr2 = pk11_attribute_bytype(dh2, CKA_PRIME); + 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(dh1, CKA_BASE); + attr2 = pk11_attribute_bytype(dh2, CKA_BASE); + 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); + + return (true); +} + +static isc_result_t +pkcs11dh_generate(dst_key_t *key, int generator, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_DH_PKCS_PARAMETER_GEN, NULL, 0 }; + CK_OBJECT_HANDLE domainparams = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dClass = CKO_DOMAIN_PARAMETERS; + CK_KEY_TYPE keyType = CKK_DH; + CK_ULONG bits = 0; + CK_ATTRIBUTE dTemplate[] = + { + { CKA_CLASS, &dClass, (CK_ULONG) sizeof(dClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG) sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_PRIME_BITS, &bits, (CK_ULONG) sizeof(bits) } + }; + CK_ATTRIBUTE pTemplate[] = + { + { CKA_PRIME, NULL, 0 }, + { CKA_BASE, NULL, 0 } + }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + 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_PRIME, NULL, 0 }, + { CKA_BASE, 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_DERIVE, &truevalue, (CK_ULONG) sizeof(truevalue) }, + }; + CK_ATTRIBUTE *attr; + pk11_object_t *dh = NULL; + pk11_context_t *pk11_ctx; + isc_result_t ret; + + UNUSED(callback); + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_DH, true, false, + false, NULL, pk11_get_best_token(OP_DH)); + if (ret != ISC_R_SUCCESS) + goto err; + + bits = key->key_size; + if ((generator == 0) && + ((bits == 768) || (bits == 1024) || (bits == 1536))) { + if (bits == 768) { + pubTemplate[4].pValue = + isc_mem_get(key->mctx, sizeof(pk11_dh_bn768)); + if (pubTemplate[4].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(pubTemplate[4].pValue, + pk11_dh_bn768, sizeof(pk11_dh_bn768)); + pubTemplate[4].ulValueLen = sizeof(pk11_dh_bn768); + } else if (bits == 1024) { + pubTemplate[4].pValue = + isc_mem_get(key->mctx, sizeof(pk11_dh_bn1024)); + if (pubTemplate[4].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(pubTemplate[4].pValue, + pk11_dh_bn1024, sizeof(pk11_dh_bn1024)); + pubTemplate[4].ulValueLen = sizeof(pk11_dh_bn1024); + } else { + pubTemplate[4].pValue = + isc_mem_get(key->mctx, sizeof(pk11_dh_bn1536)); + if (pubTemplate[4].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(pubTemplate[4].pValue, + pk11_dh_bn1536, sizeof(pk11_dh_bn1536)); + pubTemplate[4].ulValueLen = sizeof(pk11_dh_bn1536); + } + pubTemplate[5].pValue = isc_mem_get(key->mctx, + sizeof(pk11_dh_bn2)); + if (pubTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(pubTemplate[5].pValue, pk11_dh_bn2, + sizeof(pk11_dh_bn2)); + pubTemplate[5].ulValueLen = sizeof(pk11_dh_bn2); + } else { + PK11_RET(pkcs_C_GenerateKey, + (pk11_ctx->session, &mech, + dTemplate, (CK_ULONG) 5, &domainparams), + DST_R_CRYPTOFAILURE); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, domainparams, + pTemplate, (CK_ULONG) 2), + DST_R_CRYPTOFAILURE); + pTemplate[0].pValue = isc_mem_get(key->mctx, + pTemplate[0].ulValueLen); + if (pTemplate[0].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(pTemplate[0].pValue, 0, pTemplate[0].ulValueLen); + pTemplate[1].pValue = isc_mem_get(key->mctx, + pTemplate[1].ulValueLen); + if (pTemplate[1].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(pTemplate[1].pValue, 0, pTemplate[1].ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, domainparams, + pTemplate, (CK_ULONG) 2), + DST_R_CRYPTOFAILURE); + + pubTemplate[4].pValue = pTemplate[0].pValue; + pubTemplate[4].ulValueLen = pTemplate[0].ulValueLen; + pTemplate[0].pValue = NULL; + pubTemplate[5].pValue = pTemplate[1].pValue; + pubTemplate[5].ulValueLen = pTemplate[1].ulValueLen; + pTemplate[1].pValue = NULL; + } + + mech.mechanism = CKM_DH_PKCS_KEY_PAIR_GEN; + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, + pubTemplate, (CK_ULONG) 6, + privTemplate, (CK_ULONG) 7, + &pub, &priv), + DST_R_CRYPTOFAILURE); + + dh = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dh)); + if (dh == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dh, 0, sizeof(*dh)); + key->keydata.pkey = dh; + dh->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 4); + if (dh->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dh->repr, 0, sizeof(*attr) * 4); + dh->attrcnt = 4; + + attr = dh->repr; + attr[0].type = CKA_PRIME; + attr[0].pValue = pubTemplate[4].pValue; + attr[0].ulValueLen = pubTemplate[4].ulValueLen; + pubTemplate[4].pValue = NULL; + + attr[1].type = CKA_BASE; + attr[1].pValue = pubTemplate[5].pValue; + attr[1].ulValueLen = pubTemplate[5].ulValueLen; + pubTemplate[5].pValue =NULL; + + attr += 2; + attr->type = CKA_VALUE; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + + attr++; + attr->type = CKA_VALUE; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->type = CKA_VALUE2; + + (void) pkcs_C_DestroyObject(pk11_ctx->session, priv); + (void) pkcs_C_DestroyObject(pk11_ctx->session, pub); + (void) pkcs_C_DestroyObject(pk11_ctx->session, domainparams); + 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: + pkcs11dh_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); + if (domainparams != CK_INVALID_HANDLE) + (void) pkcs_C_DestroyObject(pk11_ctx->session, domainparams); + + if (pubTemplate[4].pValue != NULL) { + isc_safe_memwipe(pubTemplate[4].pValue, + pubTemplate[4].ulValueLen); + isc_mem_put(key->mctx, + pubTemplate[4].pValue, + pubTemplate[4].ulValueLen); + } + if (pubTemplate[5].pValue != NULL) { + isc_safe_memwipe(pubTemplate[5].pValue, + pubTemplate[5].ulValueLen); + isc_mem_put(key->mctx, + pubTemplate[5].pValue, + pubTemplate[5].ulValueLen); + } + if (pTemplate[0].pValue != NULL) { + isc_safe_memwipe(pTemplate[0].pValue, + pTemplate[0].ulValueLen); + isc_mem_put(key->mctx, + pTemplate[0].pValue, + pTemplate[0].ulValueLen); + } + if (pTemplate[1].pValue != NULL) { + isc_safe_memwipe(pTemplate[1].pValue, + pTemplate[1].ulValueLen); + isc_mem_put(key->mctx, + pTemplate[1].pValue, + pTemplate[1].ulValueLen); + } + + 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 +pkcs11dh_isprivate(const dst_key_t *key) { + pk11_object_t *dh = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (dh == NULL) + return (false); + attr = pk11_attribute_bytype(dh, CKA_VALUE2); + return (attr != NULL || dh->ontoken); +} + +static void +pkcs11dh_destroy(dst_key_t *key) { + pk11_object_t *dh = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (dh == NULL) + return; + + INSIST((dh->object == CK_INVALID_HANDLE) || dh->ontoken); + + for (attr = pk11_attribute_first(dh); + attr != NULL; + attr = pk11_attribute_next(dh, attr)) + switch (attr->type) { + case CKA_VALUE: + case CKA_VALUE2: + case CKA_PRIME: + case CKA_BASE: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (dh->repr != NULL) { + isc_safe_memwipe(dh->repr, dh->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, dh->repr, dh->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(dh, sizeof(*dh)); + isc_mem_put(key->mctx, dh, sizeof(*dh)); + key->keydata.pkey = 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 +pkcs11dh_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *dh; + CK_ATTRIBUTE *attr; + isc_region_t r; + uint16_t dnslen, plen = 0, glen = 0, publen = 0; + CK_BYTE *prime = NULL, *base = NULL, *pub = NULL; + + REQUIRE(key->keydata.pkey != NULL); + + dh = key->keydata.pkey; + + for (attr = pk11_attribute_first(dh); + attr != NULL; + attr = pk11_attribute_next(dh, attr)) + switch (attr->type) { + case CKA_VALUE: + pub = (CK_BYTE *) attr->pValue; + publen = (uint16_t) attr->ulValueLen; + break; + case CKA_PRIME: + prime = (CK_BYTE *) attr->pValue; + plen = (uint16_t) attr->ulValueLen; + break; + case CKA_BASE: + base = (CK_BYTE *) attr->pValue; + glen = (uint16_t) attr->ulValueLen; + break; + } + REQUIRE((prime != NULL) && (base != NULL) && (pub != NULL)); + + isc_buffer_availableregion(data, &r); + + if ((glen == 1) && isc_safe_memequal(pk11_dh_bn2, base, glen) && + (((plen == sizeof(pk11_dh_bn768)) && + isc_safe_memequal(pk11_dh_bn768, prime, plen)) || + ((plen == sizeof(pk11_dh_bn1024)) && + isc_safe_memequal(pk11_dh_bn1024, prime, plen)) || + ((plen == sizeof(pk11_dh_bn1536)) && + isc_safe_memequal(pk11_dh_bn1536, prime, plen)))) { + plen = 1; + glen = 0; + } + + dnslen = plen + glen + publen + 6; + if (r.length < (unsigned int) dnslen) + return (ISC_R_NOSPACE); + + uint16_toregion(plen, &r); + if (plen == 1) { + if (isc_safe_memequal(pk11_dh_bn768, prime, + sizeof(pk11_dh_bn768))) + *r.base = 1; + else if (isc_safe_memequal(pk11_dh_bn1024, prime, + sizeof(pk11_dh_bn1024))) + *r.base = 2; + else + *r.base = 3; + } + else + memmove(r.base, prime, plen); + isc_region_consume(&r, plen); + + uint16_toregion(glen, &r); + if (glen > 0) + memmove(r.base, base, glen); + isc_region_consume(&r, glen); + + uint16_toregion(publen, &r); + memmove(r.base, pub, publen); + isc_region_consume(&r, publen); + + isc_buffer_add(data, dnslen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11dh_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *dh = NULL; + isc_region_t r; + uint16_t plen, glen, plen_, glen_, publen; + CK_BYTE *prime = NULL, *base = NULL, *pub = NULL; + CK_ATTRIBUTE *attr; + int special = 0; + isc_result_t result; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + dh = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dh)); + if (dh == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + memset(dh, 0, sizeof(*dh)); + result = DST_R_INVALIDPUBLICKEY; + + /* + * Read the prime length. 1 & 2 are table entries, > 16 means a + * prime follows, otherwise an error. + */ + if (r.length < 2) + goto cleanup; + + plen = uint16_fromregion(&r); + if (plen < 16 && plen != 1 && plen != 2) + goto cleanup; + + if (r.length < plen) + goto cleanup; + + plen_ = plen; + 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: + prime = pk11_dh_bn768; + plen_ = sizeof(pk11_dh_bn768); + break; + case 2: + prime = pk11_dh_bn1024; + plen_ = sizeof(pk11_dh_bn1024); + break; + case 3: + prime = pk11_dh_bn1536; + plen_ = sizeof(pk11_dh_bn1536); + break; + default: + goto cleanup; + } + } + else { + prime = r.base; + 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) + goto cleanup; + + glen = uint16_fromregion(&r); + if (r.length < glen) + goto cleanup; + + glen_ = glen; + if (special != 0) { + if (glen == 0) { + base = pk11_dh_bn2; + glen_ = sizeof(pk11_dh_bn2); + } + else { + base = r.base; + if (!isc_safe_memequal(base, pk11_dh_bn2, glen)) + goto cleanup; + base = pk11_dh_bn2; + glen_ = sizeof(pk11_dh_bn2); + } + } + else { + if (glen == 0) + goto cleanup; + base = r.base; + } + isc_region_consume(&r, glen); + + if (r.length < 2) + goto cleanup; + + publen = uint16_fromregion(&r); + if (r.length < publen) + goto cleanup; + + pub = r.base; + isc_region_consume(&r, publen); + + key->key_size = pk11_numbits(prime, plen_); + + dh->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 3); + if (dh->repr == NULL) + goto nomemory; + memset(dh->repr, 0, sizeof(*attr) * 3); + dh->attrcnt = 3; + + attr = dh->repr; + attr[0].type = CKA_PRIME; + attr[0].pValue = isc_mem_get(key->mctx, plen_); + if (attr[0].pValue == NULL) + goto nomemory; + memmove(attr[0].pValue, prime, plen_); + attr[0].ulValueLen = (CK_ULONG) plen_; + + attr[1].type = CKA_BASE; + attr[1].pValue = isc_mem_get(key->mctx, glen_); + if (attr[1].pValue == NULL) + goto nomemory; + memmove(attr[1].pValue, base, glen_); + attr[1].ulValueLen = (CK_ULONG) glen_; + + attr[2].type = CKA_VALUE; + attr[2].pValue = isc_mem_get(key->mctx, publen); + if (attr[2].pValue == NULL) + goto nomemory; + memmove(attr[2].pValue, pub, publen); + attr[2].ulValueLen = (CK_ULONG) publen; + + isc_buffer_forward(data, plen + glen + publen + 6); + + key->keydata.pkey = dh; + + return (ISC_R_SUCCESS); + + nomemory: + for (attr = pk11_attribute_first(dh); + attr != NULL; + attr = pk11_attribute_next(dh, attr)) + switch (attr->type) { + case CKA_VALUE: + case CKA_PRIME: + case CKA_BASE: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (dh->repr != NULL) { + isc_safe_memwipe(dh->repr, dh->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, dh->repr, dh->attrcnt * sizeof(*attr)); + } + + result = ISC_R_NOMEMORY; + + cleanup: + if (dh != NULL) { + isc_safe_memwipe(dh, sizeof(*dh)); + isc_mem_put(key->mctx, dh, sizeof(*dh)); + } + return (result); +} + +static isc_result_t +pkcs11dh_tofile(const dst_key_t *key, const char *directory) { + int i; + pk11_object_t *dh; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *prime = NULL, *base = NULL, *pub = NULL, *prv = NULL; + dst_private_t priv; + unsigned char *bufs[4]; + isc_result_t result; + + if (key->keydata.pkey == NULL) + return (DST_R_NULLKEY); + + if (key->external) + return (DST_R_EXTERNALKEY); + + dh = key->keydata.pkey; + + for (attr = pk11_attribute_first(dh); + attr != NULL; + attr = pk11_attribute_next(dh, attr)) + switch (attr->type) { + case CKA_VALUE: + pub = attr; + break; + case CKA_VALUE2: + prv = attr; + break; + case CKA_PRIME: + prime = attr; + break; + case CKA_BASE: + base = attr; + break; + } + if ((prime == NULL) || (base == NULL) || + (pub == NULL) || (prv == NULL)) + return (DST_R_NULLKEY); + + memset(bufs, 0, sizeof(bufs)); + for (i = 0; i < 4; i++) { + bufs[i] = isc_mem_get(key->mctx, prime->ulValueLen); + if (bufs[i] == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + memset(bufs[i], 0, prime->ulValueLen); + } + + i = 0; + + priv.elements[i].tag = TAG_DH_PRIME; + priv.elements[i].length = (unsigned short) prime->ulValueLen; + memmove(bufs[i], prime->pValue, prime->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_GENERATOR; + priv.elements[i].length = (unsigned short) base->ulValueLen; + memmove(bufs[i], base->pValue, base->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_PRIVATE; + priv.elements[i].length = (unsigned short) prv->ulValueLen; + memmove(bufs[i], prv->pValue, prv->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + priv.elements[i].tag = TAG_DH_PUBLIC; + priv.elements[i].length = (unsigned short) pub->ulValueLen; + memmove(bufs[i], pub->pValue, pub->ulValueLen); + priv.elements[i].data = bufs[i]; + i++; + + priv.nelements = i; + result = dst__privstruct_writefile(key, &priv, directory); + fail: + for (i = 0; i < 4; i++) { + if (bufs[i] == NULL) + break; + isc_safe_memwipe(bufs[i], prime->ulValueLen); + isc_mem_put(key->mctx, bufs[i], prime->ulValueLen); + } + return (result); +} + +static isc_result_t +pkcs11dh_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 *dh = NULL; + CK_ATTRIBUTE *attr; + isc_mem_t *mctx; + + UNUSED(pub); + mctx = key->mctx; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_DH, lexer, mctx, &priv); + if (ret != ISC_R_SUCCESS) + return (ret); + + if (key->external) + DST_RET(DST_R_EXTERNALKEY); + + dh = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dh)); + if (dh == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dh, 0, sizeof(*dh)); + key->keydata.pkey = dh; + dh->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 4); + if (dh->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dh->repr, 0, sizeof(*attr) * 4); + dh->attrcnt = 4; + attr = dh->repr; + attr[0].type = CKA_PRIME; + attr[1].type = CKA_BASE; + attr[2].type = CKA_VALUE; + attr[3].type = CKA_VALUE2; + + for (i = 0; i < priv.nelements; i++) { + CK_BYTE *bn; + + bn = isc_mem_get(key->mctx, priv.elements[i].length); + if (bn == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(bn, priv.elements[i].data, priv.elements[i].length); + + switch (priv.elements[i].tag) { + case TAG_DH_PRIME: + attr = pk11_attribute_bytype(dh, CKA_PRIME); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DH_GENERATOR: + attr = pk11_attribute_bytype(dh, CKA_BASE); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DH_PRIVATE: + attr = pk11_attribute_bytype(dh, CKA_VALUE2); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DH_PUBLIC: + attr = pk11_attribute_bytype(dh, CKA_VALUE); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + } + } + dst__privstruct_free(&priv, mctx); + + attr = pk11_attribute_bytype(dh, CKA_PRIME); + INSIST(attr != NULL); + key->key_size = pk11_numbits(attr->pValue, attr->ulValueLen); + + return (ISC_R_SUCCESS); + + err: + pkcs11dh_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static dst_func_t pkcs11dh_functions = { + NULL, /*%< createctx */ + NULL, /*%< createctx2 */ + NULL, /*%< destroyctx */ + NULL, /*%< adddata */ + NULL, /*%< sign */ + NULL, /*%< verify */ + NULL, /*%< verify2 */ + pkcs11dh_computesecret, + pkcs11dh_compare, + pkcs11dh_paramcompare, + pkcs11dh_generate, + pkcs11dh_isprivate, + pkcs11dh_destroy, + pkcs11dh_todns, + pkcs11dh_fromdns, + pkcs11dh_tofile, + pkcs11dh_parse, + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11dh_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &pkcs11dh_functions; + return (ISC_R_SUCCESS); +} +#endif /* !PK11_DH_DISABLE */ + +#else /* PKCS11CRYPTO */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO */ +/*! \file */ diff --git a/lib/dns/pkcs11dsa_link.c b/lib/dns/pkcs11dsa_link.c new file mode 100644 index 0000000..12d707a --- /dev/null +++ b/lib/dns/pkcs11dsa_link.c @@ -0,0 +1,1126 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef PKCS11CRYPTO + +#include + +#include + +#ifndef PK11_DSA_DISABLE + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +#include + +/* + * FIPS 186-2 DSA keys: + * mechanisms: + * CKM_DSA_SHA1, + * CKM_DSA_KEY_PAIR_GEN, + * CKM_DSA_PARAMETER_GEN + * domain parameters: + * object class CKO_DOMAIN_PARAMETERS + * key type CKK_DSA + * attribute CKA_PRIME (prime p) + * attribute CKA_SUBPRIME (subprime q) + * attribute CKA_BASE (base g) + * optional attribute CKA_PRIME_BITS (p length in bits) + * public keys: + * object class CKO_PUBLIC_KEY + * key type CKK_DSA + * attribute CKA_PRIME (prime p) + * attribute CKA_SUBPRIME (subprime q) + * attribute CKA_BASE (base g) + * attribute CKA_VALUE (public value y) + * private keys: + * object class CKO_PRIVATE_KEY + * key type CKK_DSA + * attribute CKA_PRIME (prime p) + * attribute CKA_SUBPRIME (subprime q) + * attribute CKA_BASE (base g) + * attribute CKA_VALUE (private value x) + * reuse CKA_PRIVATE_EXPONENT for key pair private value + */ + +#define CKA_VALUE2 CKA_PRIVATE_EXPONENT + +#define DST_RET(a) {ret = a; goto err;} + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static isc_result_t pkcs11dsa_todns(const dst_key_t *key, isc_buffer_t *data); +static void pkcs11dsa_destroy(dst_key_t *key); + +static isc_result_t +pkcs11dsa_createctx_sign(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { CKM_DSA_SHA1, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_DSA; + 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_PRIME, NULL, 0 }, + { CKA_SUBPRIME, NULL, 0 }, + { CKA_BASE, NULL, 0 }, + { CKA_VALUE, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *dsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + REQUIRE(key != NULL); + dsa = key->keydata.pkey; + REQUIRE(dsa != NULL); + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_DSA, true, false, + dsa->reqlogon, NULL, + pk11_get_best_token(OP_DSA)); + if (ret != ISC_R_SUCCESS) + goto err; + + if (dsa->ontoken && (dsa->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = dsa->ontoken; + pk11_ctx->object = dsa->object; + goto token_key; + } + + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + case CKA_SUBPRIME: + INSIST(keyTemplate[7].type == attr->type); + keyTemplate[7].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[7].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[7].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[7].ulValueLen = attr->ulValueLen; + break; + case CKA_BASE: + INSIST(keyTemplate[8].type == attr->type); + keyTemplate[8].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[8].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[8].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[8].ulValueLen = attr->ulValueLen; + break; + case CKA_VALUE2: + INSIST(keyTemplate[9].type == CKA_VALUE); + keyTemplate[9].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[9].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[9].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[9].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) 10, + &pk11_ctx->object), + ISC_R_FAILURE); + + token_key: + + 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 <= 9; 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 <= 9; 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 +pkcs11dsa_createctx_verify(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { CKM_DSA_SHA1, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_DSA; + 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_PRIME, NULL, 0 }, + { CKA_SUBPRIME, NULL, 0 }, + { CKA_BASE, NULL, 0 }, + { CKA_VALUE, NULL, 0 } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *dsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + dsa = key->keydata.pkey; + REQUIRE(dsa != NULL); + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_DSA, true, false, + dsa->reqlogon, NULL, + pk11_get_best_token(OP_DSA)); + if (ret != ISC_R_SUCCESS) + goto err; + + if (dsa->ontoken && (dsa->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = dsa->ontoken; + pk11_ctx->object = dsa->object; + goto token_key; + } + + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_SUBPRIME: + INSIST(keyTemplate[6].type == attr->type); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + break; + case CKA_BASE: + INSIST(keyTemplate[7].type == attr->type); + keyTemplate[7].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[7].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[7].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[7].ulValueLen = attr->ulValueLen; + break; + case CKA_VALUE: + INSIST(keyTemplate[8].type == attr->type); + keyTemplate[8].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[8].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[8].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[8].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) 9, + &pk11_ctx->object), + ISC_R_FAILURE); + + token_key: + + 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 <= 8; 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 <= 8; 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 +pkcs11dsa_createctx(dst_key_t *key, dst_context_t *dctx) { + if (dctx->use == DO_SIGN) + return (pkcs11dsa_createctx_sign(key, dctx)); + else + return (pkcs11dsa_createctx_verify(key, dctx)); +} + +static void +pkcs11dsa_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 +pkcs11dsa_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 +pkcs11dsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { + CK_RV rv; + CK_ULONG siglen = ISC_SHA1_DIGESTLENGTH * 2; + isc_region_t r; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + unsigned int klen; + + isc_buffer_availableregion(sig, &r); + if (r.length < ISC_SHA1_DIGESTLENGTH * 2 + 1) + return (ISC_R_NOSPACE); + + PK11_RET(pkcs_C_SignFinal, + (pk11_ctx->session, (CK_BYTE_PTR) r.base + 1, &siglen), + DST_R_SIGNFAILURE); + if (siglen != ISC_SHA1_DIGESTLENGTH * 2) + return (DST_R_SIGNFAILURE); + + klen = (dctx->key->key_size - 512)/64; + if (klen > 255) + return (ISC_R_FAILURE); + *r.base = klen; + isc_buffer_add(sig, ISC_SHA1_DIGESTLENGTH * 2 + 1); + + err: + return (ret); +} + +static isc_result_t +pkcs11dsa_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 + 1, + (CK_ULONG) sig->length - 1), + DST_R_VERIFYFAILURE); + return (ret); +} + +static bool +pkcs11dsa_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *dsa1, *dsa2; + CK_ATTRIBUTE *attr1, *attr2; + + dsa1 = key1->keydata.pkey; + dsa2 = key2->keydata.pkey; + + if ((dsa1 == NULL) && (dsa2 == NULL)) + return (true); + else if ((dsa1 == NULL) || (dsa2 == NULL)) + return (false); + + attr1 = pk11_attribute_bytype(dsa1, CKA_PRIME); + attr2 = pk11_attribute_bytype(dsa2, CKA_PRIME); + 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(dsa1, CKA_SUBPRIME); + attr2 = pk11_attribute_bytype(dsa2, CKA_SUBPRIME); + 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(dsa1, CKA_BASE); + attr2 = pk11_attribute_bytype(dsa2, CKA_BASE); + 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(dsa1, CKA_VALUE); + attr2 = pk11_attribute_bytype(dsa2, CKA_VALUE); + 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(dsa1, CKA_VALUE2); + attr2 = pk11_attribute_bytype(dsa2, CKA_VALUE2); + 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 (!dsa1->ontoken && !dsa2->ontoken) + return (true); + else if (dsa1->ontoken || dsa2->ontoken || + (dsa1->object != dsa2->object)) + return (false); + + return (true); +} + +static isc_result_t +pkcs11dsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_DSA_PARAMETER_GEN, NULL, 0 }; + CK_OBJECT_HANDLE dp = CK_INVALID_HANDLE; + CK_OBJECT_CLASS dpClass = CKO_DOMAIN_PARAMETERS; + CK_KEY_TYPE keyType = CKK_DSA; + CK_ULONG bits = 0; + CK_ATTRIBUTE dpTemplate[] = + { + { CKA_CLASS, &dpClass, (CK_ULONG) sizeof(dpClass) }, + { CKA_KEY_TYPE, &keyType, (CK_ULONG) sizeof(keyType) }, + { CKA_TOKEN, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_PRIVATE, &falsevalue, (CK_ULONG) sizeof(falsevalue) }, + { CKA_PRIME_BITS, &bits, (CK_ULONG) sizeof(bits) }, + }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + 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_PRIME, NULL, 0 }, + { CKA_SUBPRIME, NULL, 0 }, + { CKA_BASE, 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 *dsa; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + UNUSED(unused); + UNUSED(callback); + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_DSA, true, false, + false, NULL, pk11_get_best_token(OP_DSA)); + if (ret != ISC_R_SUCCESS) + goto err; + + bits = key->key_size; + PK11_RET(pkcs_C_GenerateKey, + (pk11_ctx->session, &mech, dpTemplate, (CK_ULONG) 5, &dp), + DST_R_CRYPTOFAILURE); + + dsa = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dsa)); + if (dsa == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dsa, 0, sizeof(*dsa)); + key->keydata.pkey = dsa; + dsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 5); + if (dsa->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dsa->repr, 0, sizeof(*attr) * 5); + dsa->attrcnt = 5; + + attr = dsa->repr; + attr[0].type = CKA_PRIME; + attr[1].type = CKA_SUBPRIME; + attr[2].type = CKA_BASE; + attr[3].type = CKA_VALUE; + attr[4].type = CKA_VALUE2; + + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, dp, attr, 3), + DST_R_CRYPTOFAILURE); + + for (i = 0; i <= 2; i++) { + attr[i].pValue = isc_mem_get(key->mctx, attr[i].ulValueLen); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, dp, attr, 3), + DST_R_CRYPTOFAILURE); + pubTemplate[5].pValue = attr[0].pValue; + pubTemplate[5].ulValueLen = attr[0].ulValueLen; + pubTemplate[6].pValue = attr[1].pValue; + pubTemplate[6].ulValueLen = attr[1].ulValueLen; + pubTemplate[7].pValue = attr[2].pValue; + pubTemplate[7].ulValueLen = attr[2].ulValueLen; + + mech.mechanism = CKM_DSA_KEY_PAIR_GEN; + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, + pubTemplate, (CK_ULONG) 8, + privTemplate, (CK_ULONG) 7, + &pub, &priv), + DST_R_CRYPTOFAILURE); + + attr = dsa->repr; + attr += 3; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + + attr++; + attr->type = CKA_VALUE; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->type = CKA_VALUE2; + + (void) pkcs_C_DestroyObject(pk11_ctx->session, priv); + (void) pkcs_C_DestroyObject(pk11_ctx->session, pub); + (void) pkcs_C_DestroyObject(pk11_ctx->session, dp); + 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: + pkcs11dsa_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); + if (dp != CK_INVALID_HANDLE) + (void) pkcs_C_DestroyObject(pk11_ctx->session, dp); + 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 +pkcs11dsa_isprivate(const dst_key_t *key) { + pk11_object_t *dsa = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (dsa == NULL) + return (false); + attr = pk11_attribute_bytype(dsa, CKA_VALUE2); + return (attr != NULL || dsa->ontoken); +} + +static void +pkcs11dsa_destroy(dst_key_t *key) { + pk11_object_t *dsa = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (dsa == NULL) + return; + + INSIST((dsa->object == CK_INVALID_HANDLE) || dsa->ontoken); + + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + case CKA_SUBPRIME: + case CKA_BASE: + case CKA_VALUE: + case CKA_VALUE2: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (dsa->repr != NULL) { + isc_safe_memwipe(dsa->repr, dsa->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, + dsa->repr, + dsa->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(dsa, sizeof(*dsa)); + isc_mem_put(key->mctx, dsa, sizeof(*dsa)); + key->keydata.pkey = NULL; +} + + +static isc_result_t +pkcs11dsa_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *dsa; + CK_ATTRIBUTE *attr; + isc_region_t r; + int dnslen; + unsigned int t, p_bytes; + CK_ATTRIBUTE *prime = NULL, *subprime = NULL; + CK_ATTRIBUTE *base = NULL, *pub_key = NULL; + CK_BYTE *cp; + + REQUIRE(key->keydata.pkey != NULL); + + dsa = key->keydata.pkey; + + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + prime = attr; + break; + case CKA_SUBPRIME: + subprime = attr; + break; + case CKA_BASE: + base = attr; + break; + case CKA_VALUE: + pub_key = attr; + break; + } + REQUIRE((prime != NULL) && (subprime != NULL) && + (base != NULL) && (pub_key != NULL)); + + isc_buffer_availableregion(data, &r); + + t = (prime->ulValueLen - 64) / 8; + if (t > 8) + return (DST_R_INVALIDPUBLICKEY); + p_bytes = 64 + 8 * t; + + dnslen = 1 + (key->key_size * 3)/8 + ISC_SHA1_DIGESTLENGTH; + if (r.length < (unsigned int) dnslen) + return (ISC_R_NOSPACE); + + memset(r.base, 0, dnslen); + *r.base = t; + isc_region_consume(&r, 1); + + cp = (CK_BYTE *) subprime->pValue; + memmove(r.base + ISC_SHA1_DIGESTLENGTH - subprime->ulValueLen, + cp, subprime->ulValueLen); + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); + cp = (CK_BYTE *) prime->pValue; + memmove(r.base + key->key_size/8 - prime->ulValueLen, + cp, prime->ulValueLen); + isc_region_consume(&r, p_bytes); + cp = (CK_BYTE *) base->pValue; + memmove(r.base + key->key_size/8 - base->ulValueLen, + cp, base->ulValueLen); + isc_region_consume(&r, p_bytes); + cp = (CK_BYTE *) pub_key->pValue; + memmove(r.base + key->key_size/8 - pub_key->ulValueLen, + cp, pub_key->ulValueLen); + isc_region_consume(&r, p_bytes); + + isc_buffer_add(data, dnslen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11dsa_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *dsa; + isc_region_t r; + unsigned int t, p_bytes; + CK_BYTE *prime, *subprime, *base, *pub_key; + CK_ATTRIBUTE *attr; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + + dsa = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dsa)); + if (dsa == NULL) + return (ISC_R_NOMEMORY); + memset(dsa, 0, sizeof(*dsa)); + + t = (unsigned int) *r.base; + isc_region_consume(&r, 1); + if (t > 8) { + isc_safe_memwipe(dsa, sizeof(*dsa)); + isc_mem_put(key->mctx, dsa, sizeof(*dsa)); + return (DST_R_INVALIDPUBLICKEY); + } + p_bytes = 64 + 8 * t; + + if (r.length < ISC_SHA1_DIGESTLENGTH + 3 * p_bytes) { + isc_safe_memwipe(dsa, sizeof(*dsa)); + isc_mem_put(key->mctx, dsa, sizeof(*dsa)); + return (DST_R_INVALIDPUBLICKEY); + } + + subprime = r.base; + isc_region_consume(&r, ISC_SHA1_DIGESTLENGTH); + + prime = r.base; + isc_region_consume(&r, p_bytes); + + base = r.base; + isc_region_consume(&r, p_bytes); + + pub_key = r.base; + isc_region_consume(&r, p_bytes); + + key->key_size = p_bytes * 8; + + isc_buffer_forward(data, 1 + ISC_SHA1_DIGESTLENGTH + 3 * p_bytes); + + dsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 4); + if (dsa->repr == NULL) + goto nomemory; + memset(dsa->repr, 0, sizeof(*attr) * 4); + dsa->attrcnt = 4; + + attr = dsa->repr; + attr[0].type = CKA_PRIME; + attr[0].pValue = isc_mem_get(key->mctx, p_bytes); + if (attr[0].pValue == NULL) + goto nomemory; + memmove(attr[0].pValue, prime, p_bytes); + attr[0].ulValueLen = p_bytes; + + attr[1].type = CKA_SUBPRIME; + attr[1].pValue = isc_mem_get(key->mctx, ISC_SHA1_DIGESTLENGTH); + if (attr[1].pValue == NULL) + goto nomemory; + memmove(attr[1].pValue, subprime, ISC_SHA1_DIGESTLENGTH); + attr[1].ulValueLen = ISC_SHA1_DIGESTLENGTH; + + attr[2].type = CKA_BASE; + attr[2].pValue = isc_mem_get(key->mctx, p_bytes); + if (attr[2].pValue == NULL) + goto nomemory; + memmove(attr[2].pValue, base, p_bytes); + attr[2].ulValueLen = p_bytes; + + attr[3].type = CKA_VALUE; + attr[3].pValue = isc_mem_get(key->mctx, p_bytes); + if (attr[3].pValue == NULL) + goto nomemory; + memmove(attr[3].pValue, pub_key, p_bytes); + attr[3].ulValueLen = p_bytes; + + key->keydata.pkey = dsa; + + return (ISC_R_SUCCESS); + + nomemory: + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + case CKA_SUBPRIME: + case CKA_BASE: + case CKA_VALUE: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (dsa->repr != NULL) { + isc_safe_memwipe(dsa->repr, dsa->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, + dsa->repr, + dsa->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(dsa, sizeof(*dsa)); + isc_mem_put(key->mctx, dsa, sizeof(*dsa)); + return (ISC_R_NOMEMORY); +} + +static isc_result_t +pkcs11dsa_tofile(const dst_key_t *key, const char *directory) { + int cnt = 0; + pk11_object_t *dsa; + CK_ATTRIBUTE *attr; + CK_ATTRIBUTE *prime = NULL, *subprime = NULL, *base = NULL; + CK_ATTRIBUTE *pub_key = NULL, *priv_key = NULL; + dst_private_t priv; + unsigned char bufs[5][128]; + + if (key->keydata.pkey == NULL) + return (DST_R_NULLKEY); + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + dsa = key->keydata.pkey; + + for (attr = pk11_attribute_first(dsa); + attr != NULL; + attr = pk11_attribute_next(dsa, attr)) + switch (attr->type) { + case CKA_PRIME: + prime = attr; + break; + case CKA_SUBPRIME: + subprime = attr; + break; + case CKA_BASE: + base = attr; + break; + case CKA_VALUE: + pub_key = attr; + break; + case CKA_VALUE2: + priv_key = attr; + break; + } + if ((prime == NULL) || (subprime == NULL) || (base == NULL) || + (pub_key == NULL) || (priv_key ==NULL)) + return (DST_R_NULLKEY); + + priv.elements[cnt].tag = TAG_DSA_PRIME; + priv.elements[cnt].length = (unsigned short) prime->ulValueLen; + memmove(bufs[cnt], prime->pValue, prime->ulValueLen); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_SUBPRIME; + priv.elements[cnt].length = (unsigned short) subprime->ulValueLen; + memmove(bufs[cnt], subprime->pValue, subprime->ulValueLen); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_BASE; + priv.elements[cnt].length = (unsigned short) base->ulValueLen; + memmove(bufs[cnt], base->pValue, base->ulValueLen); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_PRIVATE; + priv.elements[cnt].length = (unsigned short) priv_key->ulValueLen; + memmove(bufs[cnt], priv_key->pValue, priv_key->ulValueLen); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.elements[cnt].tag = TAG_DSA_PUBLIC; + priv.elements[cnt].length = (unsigned short) pub_key->ulValueLen; + memmove(bufs[cnt], pub_key->pValue, pub_key->ulValueLen); + priv.elements[cnt].data = bufs[cnt]; + cnt++; + + priv.nelements = cnt; + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static isc_result_t +pkcs11dsa_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 *dsa = NULL; + CK_ATTRIBUTE *attr; + isc_mem_t *mctx = key->mctx; + + /* read private key file */ + ret = dst__privstruct_parse(key, DST_ALG_DSA, 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); + } + + dsa = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*dsa)); + if (dsa == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dsa, 0, sizeof(*dsa)); + key->keydata.pkey = dsa; + + dsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 5); + if (dsa->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(dsa->repr, 0, sizeof(*attr) * 5); + dsa->attrcnt = 5; + attr = dsa->repr; + attr[0].type = CKA_PRIME; + attr[1].type = CKA_SUBPRIME; + attr[2].type = CKA_BASE; + attr[3].type = CKA_VALUE; + attr[4].type = CKA_VALUE2; + + for (i = 0; i < priv.nelements; i++) { + CK_BYTE *bn; + + bn = isc_mem_get(key->mctx, priv.elements[i].length); + if (bn == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(bn, priv.elements[i].data, priv.elements[i].length); + + switch (priv.elements[i].tag) { + case TAG_DSA_PRIME: + attr = pk11_attribute_bytype(dsa, CKA_PRIME); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DSA_SUBPRIME: + attr = pk11_attribute_bytype(dsa, + CKA_SUBPRIME); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DSA_BASE: + attr = pk11_attribute_bytype(dsa, CKA_BASE); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DSA_PRIVATE: + attr = pk11_attribute_bytype(dsa, CKA_VALUE2); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + case TAG_DSA_PUBLIC: + attr = pk11_attribute_bytype(dsa, CKA_VALUE); + INSIST(attr != NULL); + attr->pValue = bn; + attr->ulValueLen = priv.elements[i].length; + break; + } + } + dst__privstruct_free(&priv, mctx); + + attr = pk11_attribute_bytype(dsa, CKA_PRIME); + INSIST(attr != NULL); + key->key_size = pk11_numbits(attr->pValue, attr->ulValueLen); + + return (ISC_R_SUCCESS); + + err: + pkcs11dsa_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static dst_func_t pkcs11dsa_functions = { + pkcs11dsa_createctx, + NULL, /*%< createctx2 */ + pkcs11dsa_destroyctx, + pkcs11dsa_adddata, + pkcs11dsa_sign, + pkcs11dsa_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + pkcs11dsa_compare, + NULL, /*%< paramcompare */ + pkcs11dsa_generate, + pkcs11dsa_isprivate, + pkcs11dsa_destroy, + pkcs11dsa_todns, + pkcs11dsa_fromdns, + pkcs11dsa_tofile, + pkcs11dsa_parse, + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11dsa_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &pkcs11dsa_functions; + return (ISC_R_SUCCESS); +} +#endif /* !PK11_DSA_DISABLE */ + +#else /* PKCS11CRYPTO */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO */ +/*! \file */ diff --git a/lib/dns/pkcs11ecdsa_link.c b/lib/dns/pkcs11ecdsa_link.c new file mode 100644 index 0000000..4a39cc4 --- /dev/null +++ b/lib/dns/pkcs11ecdsa_link.c @@ -0,0 +1,1198 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(PKCS11CRYPTO) && defined(HAVE_PKCS11_ECDSA) + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +#include +#include +#define WANT_ECC_CURVES +#include + +#include + +/* + * 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 isc_result_t pkcs11ecdsa_todns(const dst_key_t *key, + isc_buffer_t *data); +static void pkcs11ecdsa_destroy(dst_key_t *key); +static isc_result_t pkcs11ecdsa_fetch(dst_key_t *key, const char *engine, + const char *label, dst_key_t *pub); + +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 = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) + slotid = ec->slot; + else + slotid = pk11_get_best_token(OP_EC); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + + if (key->key_alg == DST_ALG_ECDSA256) { + dgstlen = ISC_SHA256_DIGESTLENGTH; + siglen = DNS_SIG_ECDSA256SIZE; + } else { + siglen = DNS_SIG_ECDSA384SIZE; + dgstlen = ISC_SHA384_DIGESTLENGTH; + } + + 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); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + + if (key->key_alg == DST_ALG_ECDSA256) + dgstlen = ISC_SHA256_DIGESTLENGTH; + else + dgstlen = ISC_SHA384_DIGESTLENGTH; + + 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); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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() \ + if (key->key_alg == DST_ALG_ECDSA256) { \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(pk11_ecc_prime256v1)); \ + if (attr->pValue == NULL) \ + DST_RET(ISC_R_NOMEMORY); \ + memmove(attr->pValue, \ + pk11_ecc_prime256v1, sizeof(pk11_ecc_prime256v1)); \ + attr->ulValueLen = sizeof(pk11_ecc_prime256v1); \ + } else { \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(pk11_ecc_secp384r1)); \ + if (attr->pValue == NULL) \ + DST_RET(ISC_R_NOMEMORY); \ + memmove(attr->pValue, \ + pk11_ecc_secp384r1, sizeof(pk11_ecc_secp384r1)); \ + attr->ulValueLen = sizeof(pk11_ecc_secp384r1); \ + } + +#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 = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, true, false, + false, NULL, pk11_get_best_token(OP_EC)); + if (ret != ISC_R_SUCCESS) + goto err; + + ec = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 3); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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)); + + 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); + + 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); + + if (key->key_alg == DST_ALG_ECDSA256) + len = DNS_KEY_ECDSA256SIZE; + else + len = DNS_KEY_ECDSA384SIZE; + + 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); + + if (key->key_alg == DST_ALG_ECDSA256) + len = DNS_KEY_ECDSA256SIZE; + else + len = DNS_KEY_ECDSA384SIZE; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + if (r.length != len) + return (DST_R_INVALIDPUBLICKEY); + + ec = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + return (ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + goto nomemory; + ec->attrcnt = 2; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + if (key->key_alg == DST_ALG_ECDSA256) { + attr->pValue = + isc_mem_get(key->mctx, sizeof(pk11_ecc_prime256v1)); + if (attr->pValue == NULL) + goto nomemory; + memmove(attr->pValue, + pk11_ecc_prime256v1, sizeof(pk11_ecc_prime256v1)); + attr->ulValueLen = sizeof(pk11_ecc_prime256v1); + } else { + attr->pValue = + isc_mem_get(key->mctx, sizeof(pk11_ecc_secp384r1)); + if (attr->pValue == NULL) + goto nomemory; + memmove(attr->pValue, + pk11_ecc_secp384r1, sizeof(pk11_ecc_secp384r1)); + attr->ulValueLen = sizeof(pk11_ecc_secp384r1); + } + + attr++; + attr->type = CKA_EC_POINT; + attr->pValue = isc_mem_get(key->mctx, len + 3); + if (attr->pValue == NULL) + goto nomemory; + ((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); + + nomemory: + for (attr = pk11_attribute_first(ec); + attr != NULL; + attr = pk11_attribute_next(ec, attr)) + switch (attr->type) { + case CKA_EC_PARAMS: + case CKA_EC_POINT: + 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)); + return (ISC_R_NOMEMORY); +} + +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); + if (buf == NULL) + return (ISC_R_NOMEMORY); + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + return (ISC_R_NOMEMORY); + 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); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_EC); + if (ret != ISC_R_SUCCESS) + goto err; + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + + 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + DST_RET(ISC_R_NOMEMORY); + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 3); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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)); + 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); + + 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + return (ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + key->keydata.pkey = ec; + + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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_EC); + if (ret != ISC_R_SUCCESS) + goto err; + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + if (key->key_alg == DST_ALG_ECDSA256) + key->key_size = DNS_KEY_ECDSA256SIZE * 4; + else + key->key_size = DNS_KEY_ECDSA384SIZE * 4; + + 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); +} + +#else /* PKCS11CRYPTO && HAVE_PKCS11_ECDSA */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO && HAVE_PKCS11_ECDSA */ +/*! \file */ diff --git a/lib/dns/pkcs11eddsa_link.c b/lib/dns/pkcs11eddsa_link.c new file mode 100644 index 0000000..015d0e8 --- /dev/null +++ b/lib/dns/pkcs11eddsa_link.c @@ -0,0 +1,1185 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(PKCS11CRYPTO) && \ + defined(HAVE_PKCS11_ED25519) || defined(HAVE_PKCS11_ED448) + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +#include +#include +#define WANT_ECC_CURVES +#include + +#include +#include + +/* + * FIPS 186-3 EDDSA keys: + * mechanisms: + * CKM_EDDSA, + * CKM_EDDSA_KEY_PAIR_GEN + * domain parameters: + * CKA_EC_PARAMS (choice with OID namedCurve) + * public keys: + * object class CKO_PUBLIC_KEY + * key type CKK_EDDSA + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_EC_POINT (big int A, CKA_VALUE on the token) + * private keys: + * object class CKO_PRIVATE_KEY + * key type CKK_EDDSA + * attribute CKA_EC_PARAMS (choice with OID namedCurve) + * attribute CKA_VALUE (big int k) + */ + +#define DST_RET(a) {ret = a; goto err;} + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static isc_result_t pkcs11eddsa_todns(const dst_key_t *key, + isc_buffer_t *data); +static void pkcs11eddsa_destroy(dst_key_t *key); +static isc_result_t pkcs11eddsa_fetch(dst_key_t *key, const char *engine, + const char *label, dst_key_t *pub); + +static isc_result_t +pkcs11eddsa_createctx(dst_key_t *key, dst_context_t *dctx) { + isc_buffer_t *buf = NULL; + isc_result_t result; + + UNUSED(key); + REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || + dctx->key->key_alg == DST_ALG_ED448); + + result = isc_buffer_allocate(dctx->mctx, &buf, 16); + isc_buffer_setautorealloc(buf, true); + dctx->ctxdata.generic = buf; + + return (result); +} + +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_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; + result = isc_buffer_allocate(dctx->mctx, &nbuf, length); + if (result != ISC_R_SUCCESS) + return (result); + 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 +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_EDDSA; + 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); + + if (key->key_alg == DST_ALG_ED25519) + siglen = DNS_SIG_ED25519SIZE; + else + siglen = DNS_SIG_ED448SIZE; + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) + slotid = ec->slot; + else + slotid = pk11_get_best_token(OP_EC); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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_EDDSA; + 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_VALUE, 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 = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + memset(pk11_ctx, 0, sizeof(*pk11_ctx)); + if (ec->ontoken && (dctx->use == DO_SIGN)) + slotid = ec->slot; + else + slotid = pk11_get_best_token(OP_EC); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].ulValueLen = attr->ulValueLen; + break; + case CKA_EC_POINT: + /* keyTemplate[6].type is CKA_VALUE */ + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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() \ + if (key->key_alg == DST_ALG_ED25519) { \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(pk11_ecc_ed25519)); \ + if (attr->pValue == NULL) \ + DST_RET(ISC_R_NOMEMORY); \ + memmove(attr->pValue, \ + pk11_ecc_ed25519, sizeof(pk11_ecc_ed25519)); \ + attr->ulValueLen = sizeof(pk11_ecc_ed25519); \ + } else { \ + attr->pValue = isc_mem_get(key->mctx, \ + sizeof(pk11_ecc_ed448)); \ + if (attr->pValue == NULL) \ + DST_RET(ISC_R_NOMEMORY); \ + memmove(attr->pValue, \ + pk11_ecc_ed448, sizeof(pk11_ecc_ed448)); \ + attr->ulValueLen = sizeof(pk11_ecc_ed448); \ + } + +#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_EDDSA_KEY_PAIR_GEN, NULL, 0 }; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_EDDSA; + 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 = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, true, false, + false, NULL, pk11_get_best_token(OP_EC)); + if (ret != ISC_R_SUCCESS) + goto err; + + ec = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + key->keydata.pkey = ec; + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 3); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(ec->repr, 0, sizeof(*attr) * 3); + ec->attrcnt = 3; + + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_VALUE; + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->type = CKA_EC_POINT; + + attr++; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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)); + + if (key->key_alg == DST_ALG_ED25519) + key->key_size = DNS_KEY_ED25519SIZE; + else + key->key_size = DNS_KEY_ED448SIZE; + + 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); + + if (key->key_alg == DST_ALG_ED25519) + len = DNS_KEY_ED25519SIZE; + else + len = DNS_KEY_ED448SIZE; + + ec = key->keydata.pkey; + attr = pk11_attribute_bytype(ec, CKA_EC_POINT); + if ((attr == NULL) || (attr->ulValueLen != 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, 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); + + if (key->key_alg == DST_ALG_ED25519) + len = DNS_KEY_ED25519SIZE; + else + len = DNS_KEY_ED448SIZE; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + if (r.length != len) + return (DST_R_INVALIDPUBLICKEY); + + ec = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + return (ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + goto nomemory; + ec->attrcnt = 2; + + attr = ec->repr; + attr->type = CKA_EC_PARAMS; + if (key->key_alg == DST_ALG_ED25519) { + attr->pValue = + isc_mem_get(key->mctx, sizeof(pk11_ecc_ed25519)); + if (attr->pValue == NULL) + goto nomemory; + memmove(attr->pValue, + pk11_ecc_ed25519, sizeof(pk11_ecc_ed25519)); + attr->ulValueLen = sizeof(pk11_ecc_ed25519); + } else { + attr->pValue = + isc_mem_get(key->mctx, sizeof(pk11_ecc_ed448)); + if (attr->pValue == NULL) + goto nomemory; + memmove(attr->pValue, + pk11_ecc_ed448, sizeof(pk11_ecc_ed448)); + attr->ulValueLen = sizeof(pk11_ecc_ed448); + } + + attr++; + attr->type = CKA_EC_POINT; + attr->pValue = isc_mem_get(key->mctx, len); + if (attr->pValue == NULL) + goto nomemory; + memmove((CK_BYTE_PTR) attr->pValue, r.base, len); + attr->ulValueLen = len; + + isc_buffer_forward(data, len); + key->keydata.pkey = ec; + key->key_size = len; + return (ISC_R_SUCCESS); + + nomemory: + for (attr = pk11_attribute_first(ec); + attr != NULL; + attr = pk11_attribute_next(ec, attr)) + switch (attr->type) { + case CKA_EC_PARAMS: + case CKA_EC_POINT: + 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)); + return (ISC_R_NOMEMORY); +} + +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); + if (buf == NULL) + return (ISC_R_NOMEMORY); + 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_EDDSA; + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + return (ISC_R_NOMEMORY); + 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); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(attr->pValue, pubattr->pValue, pubattr->ulValueLen); + attr->ulValueLen = pubattr->ulValueLen; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_EC); + if (ret != ISC_R_SUCCESS) + goto err; + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + + 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + DST_RET(ISC_R_NOMEMORY); + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 3); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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)); + if (key->key_alg == DST_ALG_ED25519) + key->key_size = DNS_KEY_ED25519SIZE; + else + key->key_size = DNS_KEY_ED448SIZE; + + 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_EDDSA; + 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*ec)); + if (ec == NULL) + return (ISC_R_NOMEMORY); + memset(ec, 0, sizeof(*ec)); + ec->object = CK_INVALID_HANDLE; + ec->ontoken = true; + ec->reqlogon = true; + key->keydata.pkey = ec; + + ec->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (ec->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(ec->repr, 0, sizeof(*attr) * 2); + ec->attrcnt = 2; + attr = ec->repr; + attr[0].type = CKA_EC_PARAMS; + attr[1].type = CKA_VALUE; + + ret = pk11_parse_uri(ec, label, key->mctx, OP_EC); + if (ret != ISC_R_SUCCESS) + goto err; + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_EC, 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); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr[i].pValue, 0, attr[i].ulValueLen); + } + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, hKey, attr, 2), + DST_R_CRYPTOFAILURE); + attr[1].type = CKA_EC_POINT; + + 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + if (key->key_alg == DST_ALG_ED25519) + key->key_size = DNS_KEY_ED25519SIZE; + else + key->key_size = DNS_KEY_ED448SIZE; + + 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); +} + +#else /* PKCS11CRYPTO && HAVE_PKCS11_EDxxx */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO && HAVE_PKCS11_EDxxx */ +/*! \file */ diff --git a/lib/dns/pkcs11gost_link.c b/lib/dns/pkcs11gost_link.c new file mode 100644 index 0000000..b5c98f4 --- /dev/null +++ b/lib/dns/pkcs11gost_link.c @@ -0,0 +1,957 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if defined(PKCS11CRYPTO) && defined(HAVE_PKCS11_GOST) + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" +#include "dst_gost.h" + +#include +#include +#define WANT_GOST_PARAMS +#include + +#include + +/* + * RU CryptoPro GOST keys: + * mechanisms: + * CKM_GOSTR3411 + * CKM_GOSTR3410_WITH_GOSTR3411 + * CKM_GOSTR3410_KEY_PAIR_GEN + * domain parameters: + * CKA_GOSTR3410_PARAMS (fixed BER OID 1.2.643.2.2.35.1) + * CKA_GOSTR3411_PARAMS (fixed BER OID 1.2.643.2.2.30.1) + * CKA_GOST28147_PARAMS (optional, don't use) + * public keys: + * object class CKO_PUBLIC_KEY + * key type CKK_GOSTR3410 + * attribute CKA_VALUE (point Q) + * attribute CKA_GOSTR3410_PARAMS + * attribute CKA_GOSTR3411_PARAMS + * attribute CKA_GOST28147_PARAMS + * private keys: + * object class CKO_PRIVATE_KEY + * key type CKK_GOSTR3410 + * attribute CKA_VALUE (big int d) + * attribute CKA_GOSTR3410_PARAMS + * attribute CKA_GOSTR3411_PARAMS + * attribute CKA_GOST28147_PARAMS + * point format: (little endian) + */ + +#define CKA_VALUE2 CKA_PRIVATE_EXPONENT + +#define ISC_GOST_SIGNATURELENGTH 64 +#define ISC_GOST_PUBKEYLENGTH 64 +#define ISC_GOST_KEYSIZE 256 + +/* HASH methods */ + +isc_result_t +isc_gost_init(isc_gost_t *ctx) { + CK_RV rv; + CK_MECHANISM mech = { CKM_GOSTR3411, NULL, 0 }; + int ret = ISC_R_SUCCESS; + + ret = pk11_get_session(ctx, OP_GOST, true, false, + false, NULL, 0); + if (ret != ISC_R_SUCCESS) + return (ret); + PK11_CALL(pkcs_C_DigestInit, (ctx->session, &mech), ISC_R_FAILURE); + return (ret); +} + +void +isc_gost_invalidate(isc_gost_t *ctx) { + CK_BYTE garbage[ISC_GOST_DIGESTLENGTH]; + CK_ULONG len = ISC_GOST_DIGESTLENGTH; + + if (ctx->handle == NULL) + return; + (void) pkcs_C_DigestFinal(ctx->session, garbage, &len); + isc_safe_memwipe(garbage, sizeof(garbage)); + pk11_return_session(ctx); +} + +isc_result_t +isc_gost_update(isc_gost_t *ctx, const unsigned char *buf, unsigned int len) { + CK_RV rv; + CK_BYTE_PTR pPart; + int ret = ISC_R_SUCCESS; + + DE_CONST(buf, pPart); + PK11_CALL(pkcs_C_DigestUpdate, + (ctx->session, pPart, (CK_ULONG) len), + ISC_R_FAILURE); + return (ret); +} + +isc_result_t +isc_gost_final(isc_gost_t *ctx, unsigned char *digest) { + CK_RV rv; + CK_ULONG len = ISC_GOST_DIGESTLENGTH; + int ret = ISC_R_SUCCESS; + + PK11_CALL(pkcs_C_DigestFinal, + (ctx->session, (CK_BYTE_PTR) digest, &len), + ISC_R_FAILURE); + pk11_return_session(ctx); + return (ret); +} + +/* DST methods */ + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +#define DST_RET(a) {ret = a; goto err;} + +static isc_result_t pkcs11gost_todns(const dst_key_t *key, isc_buffer_t *data); +static void pkcs11gost_destroy(dst_key_t *key); + +static isc_result_t +pkcs11gost_createctx_sign(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { CKM_GOSTR3410_WITH_GOSTR3411, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_GOSTR3410; + 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_VALUE, NULL, 0 }, + { CKA_GOSTR3410_PARAMS, pk11_gost_a_paramset, + (CK_ULONG) sizeof(pk11_gost_a_paramset) }, + { CKA_GOSTR3411_PARAMS, pk11_gost_paramset, + (CK_ULONG) sizeof(pk11_gost_paramset) } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *gost; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + REQUIRE(key != NULL); + gost = key->keydata.pkey; + REQUIRE(gost != NULL); + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_GOST, true, false, + gost->reqlogon, NULL, + pk11_get_best_token(OP_GOST)); + if (ret != ISC_R_SUCCESS) + goto err; + + if (gost->ontoken && (gost->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = gost->ontoken; + pk11_ctx->object = gost->object; + goto token_key; + } + + for (attr = pk11_attribute_first(gost); + attr != NULL; + attr = pk11_attribute_next(gost, attr)) + switch (attr->type) { + case CKA_VALUE2: + INSIST(keyTemplate[6].type == CKA_VALUE); + keyTemplate[6].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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) 9, + &pk11_ctx->object), + ISC_R_FAILURE); + + token_key: + + 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 <= 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 = 6; 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 +pkcs11gost_createctx_verify(dst_key_t *key, dst_context_t *dctx) { + CK_RV rv; + CK_MECHANISM mech = { CKM_GOSTR3410_WITH_GOSTR3411, NULL, 0 }; + CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY; + CK_KEY_TYPE keyType = CKK_GOSTR3410; + 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_VALUE, NULL, 0 }, + { CKA_GOSTR3410_PARAMS, pk11_gost_a_paramset, + (CK_ULONG) sizeof(pk11_gost_a_paramset) }, + { CKA_GOSTR3411_PARAMS, pk11_gost_paramset, + (CK_ULONG) sizeof(pk11_gost_paramset) } + }; + CK_ATTRIBUTE *attr; + pk11_object_t *gost; + pk11_context_t *pk11_ctx; + isc_result_t ret; + unsigned int i; + + REQUIRE(key != NULL); + gost = key->keydata.pkey; + REQUIRE(gost != NULL); + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_GOST, true, false, + gost->reqlogon, NULL, + pk11_get_best_token(OP_GOST)); + if (ret != ISC_R_SUCCESS) + goto err; + + if (gost->ontoken && (gost->object != CK_INVALID_HANDLE)) { + pk11_ctx->ontoken = gost->ontoken; + pk11_ctx->object = gost->object; + goto token_key; + } + + for (attr = pk11_attribute_first(gost); + attr != NULL; + attr = pk11_attribute_next(gost, attr)) + switch (attr->type) { + case CKA_VALUE: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[5].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[5].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) 8, + &pk11_ctx->object), + ISC_R_FAILURE); + + token_key: + + 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 <= 5; 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 <= 5; 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 +pkcs11gost_createctx(dst_key_t *key, dst_context_t *dctx) { + if (dctx->use == DO_SIGN) + return (pkcs11gost_createctx_sign(key, dctx)); + else + return (pkcs11gost_createctx_verify(key, dctx)); +} + +static void +pkcs11gost_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 +pkcs11gost_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 +pkcs11gost_sign(dst_context_t *dctx, isc_buffer_t *sig) { + CK_RV rv; + CK_ULONG siglen = ISC_GOST_SIGNATURELENGTH; + isc_region_t r; + pk11_context_t *pk11_ctx = dctx->ctxdata.pk11_ctx; + isc_result_t ret = ISC_R_SUCCESS; + + isc_buffer_availableregion(sig, &r); + if (r.length < ISC_GOST_SIGNATURELENGTH) + return (ISC_R_NOSPACE); + + PK11_RET(pkcs_C_SignFinal, + (pk11_ctx->session, (CK_BYTE_PTR) r.base, &siglen), + DST_R_SIGNFAILURE); + if (siglen != ISC_GOST_SIGNATURELENGTH) + return (DST_R_SIGNFAILURE); + + isc_buffer_add(sig, ISC_GOST_SIGNATURELENGTH); + + err: + return (ret); +} + +static isc_result_t +pkcs11gost_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); +} + +static bool +pkcs11gost_compare(const dst_key_t *key1, const dst_key_t *key2) { + pk11_object_t *gost1, *gost2; + CK_ATTRIBUTE *attr1, *attr2; + + gost1 = key1->keydata.pkey; + gost2 = key2->keydata.pkey; + + if ((gost1 == NULL) && (gost2 == NULL)) + return (true); + else if ((gost1 == NULL) || (gost2 == NULL)) + return (false); + + attr1 = pk11_attribute_bytype(gost1, CKA_VALUE); + attr2 = pk11_attribute_bytype(gost2, CKA_VALUE); + 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(gost1, CKA_VALUE2); + attr2 = pk11_attribute_bytype(gost2, CKA_VALUE2); + 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 (!gost1->ontoken && !gost2->ontoken) + return (true); + else if (gost1->ontoken || gost2->ontoken || + (gost1->object != gost2->object)) + return (false); + + return (true); +} + +static isc_result_t +pkcs11gost_generate(dst_key_t *key, int unused, void (*callback)(int)) { + CK_RV rv; + CK_MECHANISM mech = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL, 0 }; + CK_KEY_TYPE keyType = CKK_GOSTR3410; + CK_OBJECT_HANDLE pub = CK_INVALID_HANDLE; + CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY; + 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_GOSTR3410_PARAMS, pk11_gost_a_paramset, + (CK_ULONG) sizeof(pk11_gost_a_paramset) }, + { CKA_GOSTR3411_PARAMS, pk11_gost_paramset, + (CK_ULONG) sizeof(pk11_gost_paramset) } + }; + 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 *gost; + pk11_context_t *pk11_ctx; + isc_result_t ret; + + UNUSED(unused); + UNUSED(callback); + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + ret = pk11_get_session(pk11_ctx, OP_GOST, true, false, + false, NULL, pk11_get_best_token(OP_GOST)); + if (ret != ISC_R_SUCCESS) + goto err; + + PK11_RET(pkcs_C_GenerateKeyPair, + (pk11_ctx->session, &mech, + pubTemplate, (CK_ULONG) 7, + privTemplate, (CK_ULONG) 7, + &pub, &priv), + DST_R_CRYPTOFAILURE); + + gost = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*gost)); + if (gost == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(gost, 0, sizeof(*gost)); + key->keydata.pkey = gost; + key->key_size = ISC_GOST_KEYSIZE; + gost->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, + sizeof(*attr) * 2); + if (gost->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(gost->repr, 0, sizeof(*attr) * 2); + gost->attrcnt = 2; + + attr = gost->repr; + attr[0].type = CKA_VALUE; + attr[1].type = CKA_VALUE2; + + attr = gost->repr; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, pub, attr, 1), + DST_R_CRYPTOFAILURE); + + attr++; + attr->type = CKA_VALUE; + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->pValue = isc_mem_get(key->mctx, attr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(attr->pValue, 0, attr->ulValueLen); + PK11_RET(pkcs_C_GetAttributeValue, + (pk11_ctx->session, priv, attr, 1), + DST_R_CRYPTOFAILURE); + attr->type = CKA_VALUE2; + + (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: + pkcs11gost_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 +pkcs11gost_isprivate(const dst_key_t *key) { + pk11_object_t *gost = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (gost == NULL) + return (false); + attr = pk11_attribute_bytype(gost, CKA_VALUE2); + return (attr != NULL || gost->ontoken); +} + +static void +pkcs11gost_destroy(dst_key_t *key) { + pk11_object_t *gost = key->keydata.pkey; + CK_ATTRIBUTE *attr; + + if (gost == NULL) + return; + + INSIST((gost->object == CK_INVALID_HANDLE) || gost->ontoken); + + for (attr = pk11_attribute_first(gost); + attr != NULL; + attr = pk11_attribute_next(gost, attr)) + switch (attr->type) { + case CKA_VALUE: + case CKA_VALUE2: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (gost->repr != NULL) { + isc_safe_memwipe(gost->repr, gost->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, + gost->repr, gost->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(gost, sizeof(*gost)); + isc_mem_put(key->mctx, gost, sizeof(*gost)); + key->keydata.pkey = NULL; +} + +static isc_result_t +pkcs11gost_todns(const dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *gost; + isc_region_t r; + CK_ATTRIBUTE *attr; + + REQUIRE(key->keydata.pkey != NULL); + + gost = key->keydata.pkey; + attr = pk11_attribute_bytype(gost, CKA_VALUE); + if ((attr == NULL) || (attr->ulValueLen != ISC_GOST_PUBKEYLENGTH)) + return (ISC_R_FAILURE); + + isc_buffer_availableregion(data, &r); + if (r.length < ISC_GOST_PUBKEYLENGTH) + return (ISC_R_NOSPACE); + memmove(r.base, (CK_BYTE_PTR) attr->pValue, ISC_GOST_PUBKEYLENGTH); + isc_buffer_add(data, ISC_GOST_PUBKEYLENGTH); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +pkcs11gost_fromdns(dst_key_t *key, isc_buffer_t *data) { + pk11_object_t *gost; + isc_region_t r; + CK_ATTRIBUTE *attr; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + if (r.length != ISC_GOST_PUBKEYLENGTH) + return (DST_R_INVALIDPUBLICKEY); + + gost = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*gost)); + if (gost == NULL) + return (ISC_R_NOMEMORY); + memset(gost, 0, sizeof(*gost)); + gost->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr)); + if (gost->repr == NULL) + goto nomemory; + gost->attrcnt = 1; + + attr = gost->repr; + attr->type = CKA_VALUE; + attr->pValue = isc_mem_get(key->mctx, ISC_GOST_PUBKEYLENGTH); + if (attr->pValue == NULL) + goto nomemory; + memmove((CK_BYTE_PTR) attr->pValue, r.base, ISC_GOST_PUBKEYLENGTH); + attr->ulValueLen = ISC_GOST_PUBKEYLENGTH; + + isc_buffer_forward(data, ISC_GOST_PUBKEYLENGTH); + key->keydata.pkey = gost; + key->key_size = ISC_GOST_KEYSIZE; + return (ISC_R_SUCCESS); + + nomemory: + for (attr = pk11_attribute_first(gost); + attr != NULL; + attr = pk11_attribute_next(gost, attr)) + switch (attr->type) { + case CKA_VALUE: + if (attr->pValue != NULL) { + isc_safe_memwipe(attr->pValue, + attr->ulValueLen); + isc_mem_put(key->mctx, + attr->pValue, + attr->ulValueLen); + } + break; + } + if (gost->repr != NULL) { + isc_safe_memwipe(gost->repr, gost->attrcnt * sizeof(*attr)); + isc_mem_put(key->mctx, + gost->repr, gost->attrcnt * sizeof(*attr)); + } + isc_safe_memwipe(gost, sizeof(*gost)); + isc_mem_put(key->mctx, gost, sizeof(*gost)); + return (ISC_R_NOMEMORY); +} + +static unsigned char gost_private_der[39] = { + 0x30, 0x45, 0x02, 0x01, 0x00, 0x30, 0x1c, 0x06, + 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x30, + 0x12, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x23, 0x01, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x1e, 0x01, 0x04, 0x22, 0x02, 0x20 +}; + +#ifdef PREFER_GOSTASN1 + +static isc_result_t +pkcs11gost_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + pk11_object_t *gost; + dst_private_t priv; + unsigned char *buf = NULL; + unsigned int i = 0; + CK_ATTRIBUTE *attr; + int adj; + + if (key->keydata.pkey == NULL) + return (DST_R_NULLKEY); + + if (key->external) { + priv.nelements = 0; + return (dst__privstruct_writefile(key, &priv, directory)); + } + + gost = key->keydata.pkey; + attr = pk11_attribute_bytype(gost, CKA_VALUE2); + if (attr != NULL) { + buf = isc_mem_get(key->mctx, attr->ulValueLen + 39); + if (buf == NULL) + return (ISC_R_NOMEMORY); + priv.elements[i].tag = TAG_GOST_PRIVASN1; + priv.elements[i].length = + (unsigned short) attr->ulValueLen + 39; + memmove(buf, gost_private_der, 39); + memmove(buf + 39, attr->pValue, attr->ulValueLen); + adj = (int) attr->ulValueLen - 32; + if (adj != 0) { + buf[1] += adj; + buf[36] += adj; + buf[38] += adj; + } + priv.elements[i].data = buf; + i++; + } else + return (DST_R_CRYPTOFAILURE); + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + + if (buf != NULL) { + isc_safe_memwipe(buf, attr->ulValueLen); + isc_mem_put(key->mctx, buf, attr->ulValueLen); + } + return (ret); +} + +#else + +static isc_result_t +pkcs11gost_tofile(const dst_key_t *key, const char *directory) { + isc_result_t ret; + pk11_object_t *gost; + 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)); + } + + gost = key->keydata.pkey; + attr = pk11_attribute_bytype(gost, CKA_VALUE2); + if (attr != NULL) { + buf = isc_mem_get(key->mctx, attr->ulValueLen); + if (buf == NULL) + return (ISC_R_NOMEMORY); + priv.elements[i].tag = TAG_GOST_PRIVRAW; + priv.elements[i].length = (unsigned short) attr->ulValueLen; + memmove(buf, attr->pValue, attr->ulValueLen); + priv.elements[i].data = buf; + i++; + } else + return (DST_R_CRYPTOFAILURE); + + priv.nelements = i; + ret = dst__privstruct_writefile(key, &priv, directory); + + if (buf != NULL) { + isc_safe_memwipe(buf, attr->ulValueLen); + isc_mem_put(key->mctx, buf, attr->ulValueLen); + } + return (ret); +} +#endif + +static isc_result_t +pkcs11gost_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t ret; + pk11_object_t *gost = NULL; + CK_ATTRIBUTE *attr, *pattr; + isc_mem_t *mctx = key->mctx; + + 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); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ISC_R_SUCCESS); + } + + if (priv.elements[0].tag == TAG_GOST_PRIVASN1) { + int adj = (int) priv.elements[0].length - (39 + 32); + unsigned char buf[39]; + + if ((adj > 0) || (adj < -31)) + DST_RET(DST_R_INVALIDPRIVATEKEY); + memmove(buf, gost_private_der, 39); + if (adj != 0) { + buf[1] += adj; + buf[36] += adj; + buf[38] += adj; + } + if (!isc_safe_memequal(priv.elements[0].data, buf, 39)) + DST_RET(DST_R_INVALIDPRIVATEKEY); + priv.elements[0].tag = TAG_GOST_PRIVRAW; + priv.elements[0].length -= 39; + memmove(priv.elements[0].data, + priv.elements[0].data + 39, + 32 + adj); + } + + gost = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*gost)); + if (gost == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(gost, 0, sizeof(*gost)); + key->keydata.pkey = gost; + key->key_size = ISC_GOST_KEYSIZE; + + gost->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, + sizeof(*attr) * 2); + if (gost->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(gost->repr, 0, sizeof(*attr) * 2); + gost->attrcnt = 2; + + attr = gost->repr; + attr->type = CKA_VALUE; + pattr = pk11_attribute_bytype(pub->keydata.pkey, CKA_VALUE); + INSIST(pattr != NULL); + attr->pValue = isc_mem_get(key->mctx, pattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(attr->pValue, pattr->pValue, pattr->ulValueLen); + attr->ulValueLen = pattr->ulValueLen; + + attr++; + attr->type = CKA_VALUE2; + attr->pValue = isc_mem_get(key->mctx, priv.elements[0].length); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(attr->pValue, priv.elements[0].data, priv.elements[0].length); + attr->ulValueLen = priv.elements[0].length; + + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + + return (ISC_R_SUCCESS); + + err: + pkcs11gost_destroy(key); + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (ret); +} + +static dst_func_t pkcs11gost_functions = { + pkcs11gost_createctx, + NULL, /*%< createctx2 */ + pkcs11gost_destroyctx, + pkcs11gost_adddata, + pkcs11gost_sign, + pkcs11gost_verify, + NULL, /*%< verify2 */ + NULL, /*%< computesecret */ + pkcs11gost_compare, + NULL, /*%< paramcompare */ + pkcs11gost_generate, + pkcs11gost_isprivate, + pkcs11gost_destroy, + pkcs11gost_todns, + pkcs11gost_fromdns, + pkcs11gost_tofile, + pkcs11gost_parse, + NULL, /*%< cleanup */ + NULL, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ +}; + +isc_result_t +dst__pkcs11gost_init(dst_func_t **funcp) { + REQUIRE(funcp != NULL); + if (*funcp == NULL) + *funcp = &pkcs11gost_functions; + return (ISC_R_SUCCESS); +} + +#else /* PKCS11CRYPTO && HAVE_PKCS11_GOST */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO && HAVE_PKCS11_GOST */ +/*! \file */ diff --git a/lib/dns/pkcs11rsa_link.c b/lib/dns/pkcs11rsa_link.c new file mode 100644 index 0000000..eb782c8 --- /dev/null +++ b/lib/dns/pkcs11rsa_link.c @@ -0,0 +1,2237 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#ifdef PKCS11CRYPTO + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dst_internal.h" +#include "dst_parse.h" +#include "dst_pkcs11.h" + +#include +#include + +/* + * Limit the size of public exponents. + */ +#ifndef RSA_MAX_PUBEXP_BITS +#define RSA_MAX_PUBEXP_BITS 35 +#endif + +#define DST_RET(a) {ret = a; goto err;} + +static CK_BBOOL truevalue = TRUE; +static CK_BBOOL falsevalue = FALSE; + +static isc_result_t pkcs11rsa_todns(const dst_key_t *key, isc_buffer_t *data); +static void pkcs11rsa_destroy(dst_key_t *key); +static isc_result_t pkcs11rsa_fetch(dst_key_t *key, const char *engine, + const char *label, dst_key_t *pub); + +#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; + +#ifndef PK11_MD5_DISABLE + REQUIRE(key->key_alg == DST_ALG_RSAMD5 || + key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); +#else + 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); +#endif + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + + rsa = key->keydata.pkey; + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[7].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[8].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[9].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[10].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[11].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[12].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[13].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + mech.mechanism = CKM_MD5_RSA_PKCS; + break; +#endif + 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: + INSIST(0); + } + + 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; + +#ifndef PK11_MD5_DISABLE + REQUIRE(key->key_alg == DST_ALG_RSAMD5 || + key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); +#else + 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); +#endif + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + + rsa = key->keydata.pkey; + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + 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)) + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + if (pk11_numbits(attr->pValue, + attr->ulValueLen) > 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) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + mech.mechanism = CKM_MD5_RSA_PKCS; + break; +#endif + 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: + INSIST(0); + } + + 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 + +/* + * 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; + +#ifndef PK11_MD5_DISABLE + REQUIRE(key->key_alg == DST_ALG_RSAMD5 || + key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); +#else + 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); +#endif + REQUIRE(rsa != NULL); + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + + switch (key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + mech.mechanism = CKM_MD5; + break; +#endif + 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: + INSIST(0); + } + + pk11_ctx = (pk11_context_t *) isc_mem_get(dctx->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + 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; + +#ifndef PK11_MD5_DISABLE + REQUIRE(key->key_alg == DST_ALG_RSAMD5 || + key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); +#else + 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); +#endif + REQUIRE(rsa != NULL); + + /* + * Reject incorrect RSA key lengths. + */ + switch (dctx->key->key_alg) { + case DST_ALG_RSAMD5: + 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: + INSIST(0); + } + + switch (key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + der = md5_der; + derlen = sizeof(md5_der); + hashlen = ISC_MD5_DIGESTLENGTH; + break; +#endif + 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: + INSIST(0); + } + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[7].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[8].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[9].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[10].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[11].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[12].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[13].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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; + +#ifndef PK11_MD5_DISABLE + REQUIRE(key->key_alg == DST_ALG_RSAMD5 || + key->key_alg == DST_ALG_RSASHA1 || + key->key_alg == DST_ALG_NSEC3RSASHA1 || + key->key_alg == DST_ALG_RSASHA256 || + key->key_alg == DST_ALG_RSASHA512); +#else + 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); +#endif + REQUIRE(rsa != NULL); + + switch (key->key_alg) { +#ifndef PK11_MD5_DISABLE + case DST_ALG_RSAMD5: + der = md5_der; + derlen = sizeof(md5_der); + hashlen = ISC_MD5_DIGESTLENGTH; + break; +#endif + 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: + INSIST(0); + } + 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)) + switch (attr->type) { + case CKA_MODULUS: + INSIST(keyTemplate[5].type == attr->type); + keyTemplate[5].pValue = isc_mem_get(dctx->mctx, + attr->ulValueLen); + if (keyTemplate[5].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (keyTemplate[6].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + memmove(keyTemplate[6].pValue, attr->pValue, + attr->ulValueLen); + keyTemplate[6].ulValueLen = attr->ulValueLen; + if (pk11_numbits(attr->pValue, + attr->ulValueLen) + > 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 + +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_RSAMD5: + 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: + INSIST(0); + } + + pk11_ctx = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + return (ISC_R_NOMEMORY); + 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*rsa)); + if (rsa == NULL) + DST_RET(ISC_R_NOMEMORY); + memset(rsa, 0, sizeof(*rsa)); + key->keydata.pkey = rsa; + rsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 8); + if (rsa->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) + return (ISC_R_SUCCESS); + length = r.length; + + rsa = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*rsa)); + if (rsa == NULL) + return (ISC_R_NOMEMORY); + + memset(rsa, 0, sizeof(*rsa)); + + e_bytes = *r.base; + isc_region_consume(&r, 1); + + if (e_bytes == 0) { + if (r.length < 2) { + isc_safe_memwipe(rsa, sizeof(*rsa)); + isc_mem_put(key->mctx, rsa, sizeof(*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) { + isc_safe_memwipe(rsa, sizeof(*rsa)); + isc_mem_put(key->mctx, rsa, sizeof(*rsa)); + return (DST_R_INVALIDPUBLICKEY); + } + exponent = r.base; + isc_region_consume(&r, e_bytes); + modulus = r.base; + mod_bytes = r.length; + + key->key_size = pk11_numbits(modulus, mod_bytes); + + isc_buffer_forward(data, length); + + rsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (rsa->repr == NULL) + goto nomemory; + 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); + if (attr[0].pValue == NULL) + goto nomemory; + 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); + if (attr[1].pValue == NULL) + goto nomemory; + memmove(attr[1].pValue, exponent, e_bytes); + attr[1].ulValueLen = (CK_ULONG) e_bytes; + + key->keydata.pkey = rsa; + + return (ISC_R_SUCCESS); + + nomemory: + for (attr = pk11_attribute_first(rsa); + attr != NULL; + attr = pk11_attribute_next(rsa, attr)) + switch (attr->type) { + case CKA_MODULUS: + case CKA_PUBLIC_EXPONENT: + 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)); + return (ISC_R_NOMEMORY); +} + +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); + if (bufs[i] == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + 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); + fail: + 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; + + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (rsa->repr == NULL) + return (ISC_R_NOMEMORY); + memset(rsa->repr, 0, sizeof(*attr) * 2); + rsa->attrcnt = 2; + attr = rsa->repr; + + attr->type = CKA_MODULUS; + pubattr = pk11_attribute_bytype(pubrsa, CKA_MODULUS); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + attr->pValue = isc_mem_get(key->mctx, pubattr->ulValueLen); + if (attr->pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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 = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + + 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); + key->key_size = pk11_numbits(attr->pValue, attr->ulValueLen); + + 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; + + /* 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 = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*rsa)); + if (rsa == NULL) + DST_RET(ISC_R_NOMEMORY); + 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 = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 8); + if (rsa->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (bn == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + key->key_size = pk11_numbits(attr->pValue, attr->ulValueLen); + + attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(attr != NULL); + if (pk11_numbits(attr->pValue, attr->ulValueLen) > 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; + + UNUSED(pin); + + rsa = (pk11_object_t *) isc_mem_get(key->mctx, sizeof(*rsa)); + if (rsa == NULL) + return (ISC_R_NOMEMORY); + memset(rsa, 0, sizeof(*rsa)); + rsa->object = CK_INVALID_HANDLE; + rsa->ontoken = true; + rsa->reqlogon = true; + key->keydata.pkey = rsa; + + rsa->repr = (CK_ATTRIBUTE *) isc_mem_get(key->mctx, sizeof(*attr) * 2); + if (rsa->repr == NULL) + DST_RET(ISC_R_NOMEMORY); + 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 = (pk11_context_t *) isc_mem_get(key->mctx, + sizeof(*pk11_ctx)); + if (pk11_ctx == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (attr[i].pValue == NULL) + DST_RET(ISC_R_NOMEMORY); + 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); + if (key->engine == NULL) + DST_RET(ISC_R_NOMEMORY); + } + + key->label = isc_mem_strdup(key->mctx, label); + if (key->label == NULL) + DST_RET(ISC_R_NOMEMORY); + + attr = pk11_attribute_bytype(rsa, CKA_PUBLIC_EXPONENT); + INSIST(attr != NULL); + if (pk11_numbits(attr->pValue, attr->ulValueLen) > RSA_MAX_PUBEXP_BITS) + DST_RET(ISC_R_RANGE); + + attr = pk11_attribute_bytype(rsa, CKA_MODULUS); + INSIST(attr != NULL); + key->key_size = pk11_numbits(attr->pValue, attr->ulValueLen); + + 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 + NULL, /*%< createctx2 */ +#endif + 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); +} + +#else /* PKCS11CRYPTO */ + +#include + +EMPTY_TRANSLATION_UNIT + +#endif /* PKCS11CRYPTO */ +/*! \file */ diff --git a/lib/dns/portlist.c b/lib/dns/portlist.c new file mode 100644 index 0000000..7de7178 --- /dev/null +++ b/lib/dns/portlist.c @@ -0,0 +1,261 @@ +/* + * 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 http://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 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; + isc_result_t result; + + REQUIRE(portlistp != NULL && *portlistp == NULL); + + portlist = isc_mem_get(mctx, sizeof(*portlist)); + if (portlist == NULL) + return (ISC_R_NOMEMORY); + result = isc_mutex_init(&portlist->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, portlist, sizeof(*portlist)); + return (result); + } + result = isc_refcount_init(&portlist->refcount, 1); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&portlist->lock); + isc_mem_put(mctx, portlist, sizeof(*portlist)); + return (result); + } + 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 (el == NULL) { + result = ISC_R_NOMEMORY; + goto unlock; + } + 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, NULL); + *portlistp = portlist; +} + +void +dns_portlist_detach(dns_portlist_t **portlistp) { + dns_portlist_t *portlist; + unsigned int count; + + REQUIRE(portlistp != NULL); + portlist = *portlistp; + REQUIRE(DNS_VALID_PORTLIST(portlist)); + *portlistp = NULL; + isc_refcount_decrement(&portlist->refcount, &count); + if (count == 0) { + portlist->magic = 0; + isc_refcount_destroy(&portlist->refcount); + if (portlist->list != NULL) + isc_mem_put(portlist->mctx, portlist->list, + portlist->allocated * + sizeof(*portlist->list)); + DESTROYLOCK(&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..b694f8c --- /dev/null +++ b/lib/dns/private.c @@ -0,0 +1,367 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include "config.h" + +#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); + init = (nsec3param.flags & DNS_NSEC3FLAG_INITIAL); + nonsec = (nsec3param.flags & DNS_NSEC3FLAG_NONSEC); + + 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[BUFSIZ], algbuf[DNS_SECALG_FORMATSIZE]; + bool del = (private->data[3] != 0); + bool complete = (private->data[4] != 0); + + 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..62d0826 --- /dev/null +++ b/lib/dns/rbt.c @@ -0,0 +1,3672 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include /* uintptr_t */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*% + * This define is so dns/name.h (included by dns/fixedname.h) uses more + * efficient macro calls instead of functions for a few operations. + */ +#define DNS_NAME_USEINLINE 1 + +#include +#include +#include +#include +#include + +#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_SIZE 64 + +#ifdef RBT_MEM_TEST +#undef RBT_HASH_SIZE +#define RBT_HASH_SIZE 2 /*%< To give the reallocation code a workout. */ +#endif + +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; + size_t hashsize; + 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); +/* + * 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 inline dns_rbtnode_t * +getparent(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->parent); + adjusted_address += node->parent_is_relative * (uintptr_t)header; + + return ((dns_rbtnode_t *)adjusted_address); +} + +static inline dns_rbtnode_t * +getleft(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->left); + adjusted_address += node->left_is_relative * (uintptr_t)header; + + return ((dns_rbtnode_t *)adjusted_address); +} + +static inline dns_rbtnode_t * +getright(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->right); + adjusted_address += node->right_is_relative * (uintptr_t)header; + + return ((dns_rbtnode_t *)adjusted_address); +} + +static inline dns_rbtnode_t * +getdown(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->down); + adjusted_address += node->down_is_relative * (uintptr_t)header; + + return ((dns_rbtnode_t *)adjusted_address); +} + +static inline dns_rbtnode_t * +getdata(dns_rbtnode_t *node, file_header_t *header) { + char *adjusted_address = (char *)(node->data); + adjusted_address += node->data_is_relative * (uintptr_t)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) +#ifdef DNS_RBT_USEHASH +#define UPPERNODE(node) ((node)->uppernode) +#endif /* DNS_RBT_USEHASH */ +#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 == 1) +#define FINDCALLBACK(node) ((node)->find_callback == 1) + +/*% + * 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 offets 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 inline 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; +} + +void +dns_rbtnode_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; +} + +dns_rbtnode_t * +dns_rbt_root(dns_rbt_t *rbt) { + return rbt->root; +} + +#ifdef DNS_RBT_USEHASH +static isc_result_t +inithash(dns_rbt_t *rbt); +#endif + +#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 */ + +#ifdef DNS_RBT_USEHASH + +/* + * Upper node is the parent of the root of the passed node's + * subtree. The passed node must not be NULL. + */ +static inline 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); +} + +#else + +/* The passed node must not be NULL. */ +static inline dns_rbtnode_t * +get_subtree_root(dns_rbtnode_t *node) { + while (!IS_ROOT(node)) { + node = PARENT(node); + } + + return (node); +} + +/* Upper node is the parent of the root of the passed node's + * subtree. The passed node must not be NULL. + */ +static inline dns_rbtnode_t * +get_upper_node(dns_rbtnode_t *node) { + dns_rbtnode_t *root = get_subtree_root(node); + + /* + * Return the node in the level above the argument node that points + * to the level the argument node is in. If the argument node is in + * the top level, the return value is NULL. + */ + return (PARENT(root)); +} + +#endif /* DNS_RBT_USEHASH */ + +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, dns_name_t *name, dns_rbtnode_t **nodep); + +#ifdef DNS_RBT_USEHASH +static inline void +hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, dns_name_t *name); +static inline void +unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node); +static void +rehash(dns_rbt_t *rbt, unsigned int newcount); +#else +#define hash_node(rbt, node, name) +#define unhash_node(rbt, node) +#define rehash(rbt, newcount) +#endif + +static inline void +rotate_left(dns_rbtnode_t *node, dns_rbtnode_t **rootp); +static inline 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, 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 + header.rdataset_fixed = 0; +#endif + + 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); +} + +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) +{ + dns_rbtnode_t temp_node; + off_t file_position; + unsigned char *node_data; + size_t datasize; + isc_result_t result; +#ifdef DEBUG + dns_name_t nodename; +#endif + + 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; + } + + 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 + + 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 + + /* 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, + 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; + unsigned char *node_data; + dns_rbtnode_t header; + size_t datasize, nodemax = filesize - sizeof(dns_rbtnode_t); + + if (n == NULL) + return (ISC_R_SUCCESS); + + CONFIRM((void *) n >= base); + CONFIRM((char *) n - (char *) base <= (int) nodemax); + 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(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(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(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(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); + } 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 + 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 + if (header->rdataset_fixed != 0) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } +#endif + + 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; + } + 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 + + /* Check file hash */ + if (header->crc != crc) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + + if (header->nodecount != rbt->nodecount) { + result = ISC_R_INVALIDFILE; + goto cleanup; + } + +#ifdef DNS_RBT_USEHASH + fixup_uppernodes(rbt); +#endif /* DNS_RBT_USEHASH */ + + *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) +{ +#ifdef DNS_RBT_USEHASH + isc_result_t result; +#endif + dns_rbt_t *rbt; + + REQUIRE(mctx != NULL); + REQUIRE(rbtp != NULL && *rbtp == NULL); + REQUIRE(deleter == NULL ? deleter_arg == NULL : 1); + + rbt = (dns_rbt_t *)isc_mem_get(mctx, sizeof(*rbt)); + if (rbt == NULL) + return (ISC_R_NOMEMORY); + + 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->hashsize = 0; + rbt->mmap_location = NULL; + +#ifdef DNS_RBT_USEHASH + result = inithash(rbt); + if (result != ISC_R_SUCCESS) { + isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt)); + return (result); + } +#endif + + 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); + + INSIST(rbt->nodecount == 0); + + rbt->mmap_location = NULL; + + if (rbt->hashtable != NULL) + isc_mem_put(rbt->mctx, rbt->hashtable, + rbt->hashsize * sizeof(dns_rbtnode_t *)); + + rbt->magic = 0; + + isc_mem_putanddetach(&rbt->mctx, rbt, sizeof(*rbt)); + *rbtp = NULL; + 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 (rbt->hashsize); +} + +static inline 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); + result = dns_name_copy(&nodename, name, NULL); + if (result != ISC_R_SUCCESS) + return (result); + } 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 inline 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, 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); + 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; +#ifdef DNS_RBT_USEHASH + UPPERNODE(new_current) = NULL; +#endif /* DNS_RBT_USEHASH */ + 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); + + 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); +#ifdef DNS_RBT_USEHASH + UPPERNODE(new_current) = UPPERNODE(current); + UPPERNODE(current) = new_current; +#endif /* DNS_RBT_USEHASH */ + + 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)) { +#ifdef DNS_RBT_USEHASH + if (*root == NULL) + UPPERNODE(new_current) = current; + else + UPPERNODE(new_current) = PARENT(*root); +#endif /* DNS_RBT_USEHASH */ + 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, 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, rbt->mctx); + } 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); + 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) { +#ifdef DNS_RBT_USEHASH + /* + * 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; + unsigned int 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 % rbt->hashsize]; + 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 /* DNS_RBT_USEHASH */ + + /* + * Standard binary search tree movement. + */ + if (order < 0) + current = LEFT(current); + else + current = RIGHT(current); + +#endif /* DNS_RBT_USEHASH */ + + } 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) { +#ifdef DNS_RBT_USEHASH + subdomain: +#endif + /* + * 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 (DATA(current) != NULL || + (options & DNS_RBTFIND_EMPTYDATA) != 0) + *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 && + (DATA(current) != NULL || + (options & DNS_RBTFIND_EMPTYDATA) != 0)) { + /* + * 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 && + (DATA(node) != NULL || (options & DNS_RBTFIND_EMPTYDATA) != 0)) + *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, 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 + dns_rbtnode_refdestroy(node); + + 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, 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 = (dns_rbtnode_t *)isc_mem_get(mctx, nodelen); + if (node == NULL) + return (ISC_R_NOMEMORY); + 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; + +#ifdef DNS_RBT_USEHASH + HASHNEXT(node) = NULL; + HASHVAL(node) = 0; +#endif + + ISC_LINK_INIT(node, deadlink); + + LOCKNUM(node) = 0; + WILD(node) = 0; + DIRTY(node) = 0; + dns_rbtnode_refinit(node, 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 + *nodep = node; + + return (ISC_R_SUCCESS); +} + +#ifdef DNS_RBT_USEHASH +static inline void +hash_add_node(dns_rbt_t *rbt, dns_rbtnode_t *node, dns_name_t *name) { + unsigned int hash; + + REQUIRE(name != NULL); + + HASHVAL(node) = dns_name_fullhash(name, false); + + hash = HASHVAL(node) % rbt->hashsize; + HASHNEXT(node) = rbt->hashtable[hash]; + + rbt->hashtable[hash] = node; +} + +static isc_result_t +inithash(dns_rbt_t *rbt) { + unsigned int bytes; + + rbt->hashsize = RBT_HASH_SIZE; + bytes = (unsigned int)rbt->hashsize * sizeof(dns_rbtnode_t *); + rbt->hashtable = isc_mem_get(rbt->mctx, bytes); + + if (rbt->hashtable == NULL) + return (ISC_R_NOMEMORY); + + memset(rbt->hashtable, 0, bytes); + + return (ISC_R_SUCCESS); +} + +static void +rehash(dns_rbt_t *rbt, unsigned int newcount) { + unsigned int oldsize; + dns_rbtnode_t **oldtable; + dns_rbtnode_t *node; + dns_rbtnode_t *nextnode; + unsigned int hash; + unsigned int i; + + oldsize = (unsigned int)rbt->hashsize; + oldtable = rbt->hashtable; + do { + INSIST((rbt->hashsize * 2 + 1) > rbt->hashsize); + rbt->hashsize = rbt->hashsize * 2 + 1; + } while (newcount >= (rbt->hashsize * 3)); + rbt->hashtable = isc_mem_get(rbt->mctx, + rbt->hashsize * sizeof(dns_rbtnode_t *)); + if (rbt->hashtable == NULL) { + rbt->hashtable = oldtable; + rbt->hashsize = oldsize; + return; + } + + for (i = 0; i < rbt->hashsize; i++) + rbt->hashtable[i] = NULL; + + for (i = 0; i < oldsize; i++) { + for (node = oldtable[i]; node != NULL; node = nextnode) { + hash = HASHVAL(node) % rbt->hashsize; + nextnode = HASHNEXT(node); + HASHNEXT(node) = rbt->hashtable[hash]; + rbt->hashtable[hash] = node; + } + } + + isc_mem_put(rbt->mctx, oldtable, oldsize * sizeof(dns_rbtnode_t *)); +} + +static inline void +hash_node(dns_rbt_t *rbt, dns_rbtnode_t *node, dns_name_t *name) { + REQUIRE(DNS_RBTNODE_VALID(node)); + + if (rbt->nodecount >= (rbt->hashsize * 3)) + rehash(rbt, rbt->nodecount); + + hash_add_node(rbt, node, name); +} + +static inline void +unhash_node(dns_rbt_t *rbt, dns_rbtnode_t *node) { + unsigned int bucket; + dns_rbtnode_t *bucket_node; + + REQUIRE(DNS_RBTNODE_VALID(node)); + + bucket = HASHVAL(node) % rbt->hashsize; + 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); + } +} +#endif /* DNS_RBT_USEHASH */ + +static inline 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 inline 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 holder, *tmp = &holder; + + /* + * 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. + */ + memmove(tmp, successor, sizeof(dns_rbtnode_t)); + + 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. PARENT(tmp) + * is the successor's original parent. + */ + INSIST(! IS_ROOT(item)); + + if (PARENT(tmp) == item) { + /* + * Node being deleted was successor's parent. + */ + RIGHT(successor) = item; + PARENT(item) = successor; + + } else { + LEFT(PARENT(tmp)) = item; + PARENT(item) = PARENT(tmp); + } + + /* + * Original location of successor node has no left. + */ + LEFT(item) = NULL; + RIGHT(item) = RIGHT(tmp); + COLOR(item) = COLOR(tmp); + } + + /* + * Remove the node by removing the links from its parent. + */ + if (! IS_ROOT(item)) { + if (LEFT(PARENT(item)) == item) + LEFT(PARENT(item)) = child; + else + RIGHT(PARENT(item)) = child; + + if (child != NULL) + PARENT(child) = PARENT(item); + + } else { + /* + * This is the root being deleted, and at this point + * it is known to have just one child. + */ + *rootp = child; + child->is_root = 1; + PARENT(child) = PARENT(item); + } + + /* + * Fix color violations. + */ + if (IS_BLACK(item)) { + parent = PARENT(item); + + while (child != *rootp && IS_BLACK(child)) { + INSIST(child == NULL || ! IS_ROOT(child)); + + if (LEFT(parent) == child) { + sibling = RIGHT(parent); + + if (IS_RED(sibling)) { + MAKE_BLACK(sibling); + MAKE_RED(parent); + rotate_left(parent, rootp); + sibling = RIGHT(parent); + } + + INSIST(sibling != NULL); + + if (IS_BLACK(LEFT(sibling)) && + IS_BLACK(RIGHT(sibling))) { + MAKE_RED(sibling); + child = parent; + + } else { + + if (IS_BLACK(RIGHT(sibling))) { + MAKE_BLACK(LEFT(sibling)); + MAKE_RED(sibling); + rotate_right(sibling, rootp); + sibling = RIGHT(parent); + } + + COLOR(sibling) = COLOR(parent); + MAKE_BLACK(parent); + INSIST(RIGHT(sibling) != NULL); + MAKE_BLACK(RIGHT(sibling)); + rotate_left(parent, rootp); + child = *rootp; + } + + } else { + /* + * Child is parent's right child. + * Everything is done the same as above, + * except mirrored. + */ + sibling = LEFT(parent); + + if (IS_RED(sibling)) { + MAKE_BLACK(sibling); + MAKE_RED(parent); + rotate_right(parent, rootp); + sibling = LEFT(parent); + } + + INSIST(sibling != NULL); + + if (IS_BLACK(LEFT(sibling)) && + IS_BLACK(RIGHT(sibling))) { + MAKE_RED(sibling); + child = parent; + + } else { + if (IS_BLACK(LEFT(sibling))) { + MAKE_BLACK(RIGHT(sibling)); + MAKE_RED(sibling); + rotate_left(sibling, rootp); + sibling = LEFT(parent); + } + + COLOR(sibling) = COLOR(parent); + MAKE_BLACK(parent); + INSIST(LEFT(sibling) != NULL); + MAKE_BLACK(LEFT(sibling)); + rotate_right(parent, rootp); + child = *rootp; + } + } + + parent = PARENT(child); + } + + if (IS_RED(child)) + MAKE_BLACK(child); + } +} + +static void +freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep) { + dns_rbtnode_t *node = *nodep; + + if (node->is_mmapped == 0) { + isc_mem_put(rbt->mctx, node, NODE_SIZE(node)); + } + *nodep = NULL; + + 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 (DATA(node) != NULL && rbt->data_deleter != 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 + freenode(rbt, &node); + if (quantum != 0 && --quantum == 0) + break; + } + } + + *nodep = root; +} + +static size_t +getheight_helper(dns_rbtnode_t *node) { + size_t dl, dr; + size_t this_height, down_height; + + if (node == NULL) + return (0); + + dl = getheight_helper(LEFT(node)); + dr = getheight_helper(RIGHT(node)); + + this_height = ISC_MAX(dl + 1, dr + 1); + down_height = getheight_helper(DOWN(node)); + + return (ISC_MAX(this_height, down_height)); +} + +size_t +dns__rbt_getheight(dns_rbt_t *rbt) { + return (getheight_helper(rbt->root)); +} + +static bool +check_properties_helper(dns_rbtnode_t *node) { + if (node == NULL) + return (true); + + if (IS_RED(node)) { + /* Root nodes must be BLACK. */ + if (IS_ROOT(node)) + return (false); + + /* Both children of RED nodes must be BLACK. */ + if (IS_RED(LEFT(node)) || IS_RED(RIGHT(node))) + return (false); + } + + if ((DOWN(node) != NULL) && (!IS_ROOT(DOWN(node)))) + return (false); + + if (IS_ROOT(node)) { + if ((PARENT(node) != NULL) && + (DOWN(PARENT(node)) != node)) + return (false); + + if (get_upper_node(node) != PARENT(node)) + return (false); + } + + /* If node is assigned to the down_ pointer of its parent, it is + * a subtree root and must have the flag set. + */ + if (((!PARENT(node)) || + (DOWN(PARENT(node)) == node)) && + (!IS_ROOT(node))) + { + return (false); + } + + /* Repeat tests with this node's children. */ + return (check_properties_helper(LEFT(node)) && + check_properties_helper(RIGHT(node)) && + check_properties_helper(DOWN(node))); +} + +static bool +check_black_distance_helper(dns_rbtnode_t *node, size_t *distance) { + size_t dl, dr, dd; + + if (node == NULL) { + *distance = 1; + return (true); + } + + if (!check_black_distance_helper(LEFT(node), &dl)) + return (false); + + if (!check_black_distance_helper(RIGHT(node), &dr)) + return (false); + + if (!check_black_distance_helper(DOWN(node), &dd)) + return (false); + + /* Left and right side black node counts must match. */ + if (dl != dr) + return (false); + + if (IS_BLACK(node)) + dl++; + + *distance = dl; + + return (true); +} + +bool +dns__rbt_checkproperties(dns_rbt_t *rbt) { + size_t dd; + + if (!check_properties_helper(rbt->root)) + return (false); + + /* Path from a given node to all its leaves must contain the + * same number of BLACK child nodes. This is done separately + * here instead of inside check_properties_helper() as + * it would take (n log n) complexity otherwise. + */ + return (check_black_distance_helper(rbt->root, &dd)); +} + +static void +dns_rbt_indent(FILE *f, int depth) { + int i; + + fprintf(f, "%4d ", depth); + + for (i = 0; i < depth; i++) + fprintf(f, "- "); +} + +void +dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f) { + 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); + fprintf(f, " (%s, %s", direction, + IS_RED(root) ? "RED" : "BLACK"); + + if ((! IS_ROOT(root) && PARENT(root) != parent) || + ( IS_ROOT(root) && depth > 0 && + DOWN(PARENT(root)) != root)) { + + fprintf(f, " (BAD parent pointer! -> "); + if (PARENT(root) != NULL) + printnodename(PARENT(root), true, f); + else + fprintf(f, "NULL"); + fprintf(f, ")"); + } + + fprintf(f, ")"); + + if (root->data != NULL && data_printer != NULL) { + fprintf(f, " data@%p: ", root->data); + data_printer(f, root->data); + } + fprintf(f, "\n"); + + depth++; + + if (IS_RED(root) && IS_RED(LEFT(root))) + fprintf(f, "** Red/Red color violation on left\n"); + print_text_helper(LEFT(root), root, depth, "left", + data_printer, f); + + if (IS_RED(root) && 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, isc_mem_t *mctx) { + + REQUIRE(chain != NULL); + + /* + * Initialize 'chain'. + */ + chain->mctx = mctx; + 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 + result = dns_name_copy(dns_rootname, origin, NULL); + } + + 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..0861139 --- /dev/null +++ b/lib/dns/rbtdb.c @@ -0,0 +1,10431 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +/* #define inline */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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 +#define PROT_READ 0x01 +#define PROT_WRITE 0x02 +#define MAP_PRIVATE 0x0002 +#define MAP_FAILED ((void *)-1) +#endif + +#ifdef DNS_RBTDB_VERSION64 +#include "rbtdb64.h" +#else +#include "rbtdb.h" +#endif + +#ifdef DNS_RBTDB_VERSION64 +#define RBTDB_MAGIC ISC_MAGIC('R', 'B', 'D', '8') +#else +#define RBTDB_MAGIC ISC_MAGIC('R', 'B', 'D', '4') +#endif + +#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) + +#ifdef DNS_RBTDB_VERSION64 +typedef uint64_t rbtdb_serial_t; +/*% + * Make casting easier in symbolic debuggers by using different names + * for the 64 bit version. + */ +#define dns_rbtdb_t dns_rbtdb64_t +#define rdatasetheader_t rdatasetheader64_t +#define rbtdb_version_t rbtdb_version64_t + +#define once once64 +#define FILE_VERSION FILE_VERSION64 +#define init_count init_count64 + +#define cache_methods cache_methods64 +#define dbiterator_methods dbiterator_methods64 +#define rdataset_methods rdataset_methods64 +#define rdatasetiter_methods rdatasetiter_methods64 +#define slab_methods slab_methods64 +#define zone_methods zone_methods64 + +#define acache_callback acache_callback64 +#define acache_cancelentry acache_cancelentry64 +#define activeempty activeempty64 +#define activeemtpynode activeemtpynode64 +#define add32 add64 +#define add_changed add_changed64 +#define add_empty_wildcards add_empty_wildcards64 +#define add_wildcard_magic add_wildcard_magic64 +#define addclosest addclosest64 +#define addnoqname addnoqname64 +#define addrdataset addrdataset64 +#define adjust_quantum adjust_quantum64 +#define allocate_version allocate_version64 +#define allrdatasets allrdatasets64 +#define attach attach64 +#define attachnode attachnode64 +#define attachversion attachversion64 +#define beginload beginload64 +#define bind_rdataset bind_rdataset64 +#define cache_find cache_find64 +#define cache_findrdataset cache_findrdataset64 +#define cache_findzonecut cache_findzonecut64 +#define cache_zonecut_callback cache_zonecut_callback64 +#define check_stale_header check_stale_header64 +#define clean_cache_node clean_cache_node64 +#define clean_stale_headers clean_stale_headers64 +#define clean_zone_node clean_zone_node64 +#define cleanup_dead_nodes cleanup_dead_nodes64 +#define cleanup_dead_nodes_callback cleanup_dead_nodes_callback64 +#define cleanup_nondirty cleanup_nondirty64 +#define closeversion closeversion64 +#define cname_and_other_data cname_and_other_data64 +#define createiterator createiterator64 +#define currentversion currentversion64 +#define dbiterator_current dbiterator_current64 +#define dbiterator_destroy dbiterator_destroy64 +#define dbiterator_first dbiterator_first64 +#define dbiterator_last dbiterator_last64 +#define dbiterator_next dbiterator_next64 +#define dbiterator_origin dbiterator_origin64 +#define dbiterator_pause dbiterator_pause64 +#define dbiterator_prev dbiterator_prev64 +#define dbiterator_seek dbiterator_seek64 +#define decrement_reference decrement_reference64 +#define delegating_type delegating_type64 +#define delete_callback delete_callback64 +#define delete_node delete_node64 +#define deleterdataset deleterdataset64 +#define dereference_iter_node dereference_iter_node64 +#define deserialize32 deserialize64 +#define detach detach64 +#define detachnode detachnode64 +#define dump dump64 +#define endload endload64 +#define expire_header expire_header64 +#define expirenode expirenode64 +#define find_closest_nsec find_closest_nsec64 +#define find_coveringnsec find_coveringnsec64 +#define find_deepest_zonecut find_deepest_zonecut64 +#define find_wildcard find_wildcard64 +#define findnode findnode64 +#define findnodeintree findnodeintree64 +#define findnsec3node findnsec3node64 +#define flush_deletions flush_deletions64 +#define free_acachearray free_acachearray64 +#define free_noqname free_noqname64 +#define free_rbtdb free_rbtdb64 +#define free_rbtdb_callback free_rbtdb_callback64 +#define free_rdataset free_rdataset64 +#define getnsec3parameters getnsec3parameters64 +#define getoriginnode getoriginnode64 +#define getrrsetstats getrrsetstats64 +#define getsigningtime getsigningtime64 +#define getsize getsize64 +#define hashsize hashsize64 +#define init_file_version init_file_version64 +#define init_rdataset init_rdataset64 +#define isdnssec isdnssec64 +#define ispersistent ispersistent64 +#define issecure issecure64 +#define iszonesecure iszonesecure64 +#define loading_addrdataset loading_addrdataset64 +#define loadnode loadnode64 +#define make_least_version make_least_version64 +#define mark_stale_header mark_stale_header64 +#define match_header_version match_header_version64 +#define matchparams matchparams64 +#define maybe_free_rbtdb maybe_free_rbtdb64 +#define need_headerupdate need_headerupdate64 +#define new_rdataset new_rdataset64 +#define new_reference new_reference64 +#define newversion newversion64 +#define nodecount nodecount64 +#define nodefullname nodefullname64 +#define overmem overmem64 +#define overmem_purge overmem_purge64 +#define previous_closest_nsec previous_closest_nsec64 +#define printnode printnode64 +#define prune_tree prune_tree64 +#define rbt_datafixer rbt_datafixer64 +#define rbt_datawriter rbt_datawriter64 +#define rbtdb_write_header rbtdb_write_header64 +#define rbtdb_zero_header rbtdb_zero_header64 +#define rdataset_clearprefetch rdataset_clearprefetch64 +#define rdataset_clone rdataset_clone64 +#define rdataset_count rdataset_count64 +#define rdataset_current rdataset_current64 +#define rdataset_disassociate rdataset_disassociate64 +#define rdataset_expire rdataset_expire64 +#define rdataset_first rdataset_first64 +#define rdataset_getadditional rdataset_getadditional64 +#define rdataset_getclosest rdataset_getclosest64 +#define rdataset_getnoqname rdataset_getnoqname64 +#define rdataset_getownercase rdataset_getownercase64 +#define rdataset_next rdataset_next64 +#define rdataset_putadditional rdataset_putadditional64 +#define rdataset_setadditional rdataset_setadditional64 +#define rdataset_setownercase rdataset_setownercase64 +#define rdataset_settrust rdataset_settrust64 +#define rdatasetiter_current rdatasetiter_current64 +#define rdatasetiter_destroy rdatasetiter_destroy64 +#define rdatasetiter_first rdatasetiter_first64 +#define rdatasetiter_next rdatasetiter_next64 +#define reactivate_node reactivate_node64 +#define reference_iter_node reference_iter_node64 +#define resign_delete resign_delete64 +#define resign_insert resign_insert64 +#define resign_sooner resign_sooner64 +#define resigned resigned64 +#define resume_iteration resume_iteration64 +#define rollback_node rollback_node64 +#define rpz_attach rpz_attach64 +#define rpz_ready rpz_ready64 +#define serialize serialize64 +#define set_index set_index64 +#define set_ttl set_ttl64 +#define setcachestats setcachestats64 +#define setnsec3parameters setnsec3parameters64 +#define setownercase setownercase64 +#define setsigningtime setsigningtime64 +#define settask settask64 +#define setup_delegation setup_delegation64 +#define subtractrdataset subtractrdataset64 +#define ttl_sooner ttl_sooner64 +#define update_cachestats update_cachestats64 +#define update_header update_header64 +#define update_newheader update_newheader64 +#define update_recordsandbytes update_recordsandbytes64 +#define update_rrsetstats update_rrsetstats64 +#define valid_glue valid_glue64 +#define zone_find zone_find64 +#define zone_findrdataset zone_findrdataset64 +#define zone_findzonecut zone_findzonecut64 +#define zone_zonecut_callback zone_zonecut_callback64 + +#else +typedef uint32_t rbtdb_serial_t; +#endif + +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_SIGDDS \ + RBTDB_RDATATYPE_VALUE(dns_rdatatype_rrsig, dns_rdatatype_ds) +#define RBTDB_RDATATYPE_NCACHEANY \ + RBTDB_RDATATYPE_VALUE(0, dns_rdatatype_any) + +/* + * We use rwlock for DB lock only when ISC_RWLOCK_USEATOMIC is non 0. + * Using rwlock is effective with regard to lookup performance only when + * it is implemented in an efficient way. + * Otherwise, it is generally wise to stick to the simple locking since rwlock + * would require more memory or can even make lookups slower due to its own + * overhead (when it internally calls mutex locks). + */ +#ifdef ISC_RWLOCK_USEATOMIC +#define DNS_RBTDB_USERWLOCK 1 +#else +#define DNS_RBTDB_USERWLOCK 0 +#endif + +#if DNS_RBTDB_USERWLOCK +#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)) +#else +#define RBTDB_INITLOCK(l) isc_mutex_init(l) +#define RBTDB_DESTROYLOCK(l) DESTROYLOCK(l) +#define RBTDB_LOCK(l, t) LOCK(l) +#define RBTDB_UNLOCK(l, t) UNLOCK(l) +#endif + +/* + * 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(). + */ +#if defined(ISC_RWLOCK_USEATOMIC) && defined(DNS_RBT_USEISCREFCOUNT) +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_STRONGLOCK(l) ((void)0) +#define NODE_STRONGUNLOCK(l) ((void)0) +#define NODE_WEAKLOCK(l, t) NODE_LOCK(l, t) +#define NODE_WEAKUNLOCK(l, t) NODE_UNLOCK(l, t) +#define NODE_WEAKDOWNGRADE(l) isc_rwlock_downgrade(l) +#else +typedef isc_mutex_t nodelock_t; + +#define NODE_INITLOCK(l) isc_mutex_init(l) +#define NODE_DESTROYLOCK(l) DESTROYLOCK(l) +#define NODE_LOCK(l, t) LOCK(l) +#define NODE_UNLOCK(l, t) UNLOCK(l) +#define NODE_TRYUPGRADE(l) ISC_R_SUCCESS + +#define NODE_STRONGLOCK(l) LOCK(l) +#define NODE_STRONGUNLOCK(l) UNLOCK(l) +#define NODE_WEAKLOCK(l, t) ((void)0) +#define NODE_WEAKUNLOCK(l, t) ((void)0) +#define NODE_WEAKDOWNGRADE(l) ((void)0) +#endif + +/*% + * Whether to rate-limit updating the LRU to avoid possible thread contention. + * Our performance measurement has shown the cost is marginal, so it's defined + * to be 0 by default either with or without threads. + */ +#ifndef DNS_RBTDB_LIMITLRUUPDATE +#define DNS_RBTDB_LIMITLRUUPDATE 0 +#endif + +/* + * 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 acachectl acachectl_t; + +typedef struct rdatasetheader { + /*% + * Locked by the owning node's lock. + */ + rbtdb_serial_t serial; + dns_ttl_t rdh_ttl; + rbtdb_rdatatype_t type; + uint16_t attributes; + dns_trust_t trust; + 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. + */ + + uint32_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. Since the ordering + * should not be so crucial, no lock is set for the counter for + * performance reasons. + */ + + acachectl_t *additional_auth; + acachectl_t *additional_glue; + + 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 +#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 + +typedef struct acache_cbarg { + dns_rdatasetadditional_t type; + unsigned int count; + dns_db_t *db; + dns_dbnode_t *node; + rdatasetheader_t *header; +} acache_cbarg_t; + +struct acachectl { + dns_acacheentry_t *entry; + acache_cbarg_t *cbarg; +}; + +/* + * 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) \ + (((header)->attributes & RDATASET_ATTR_NONEXISTENT) == 0) +#define NONEXISTENT(header) \ + (((header)->attributes & RDATASET_ATTR_NONEXISTENT) != 0) +#define IGNORE(header) \ + (((header)->attributes & RDATASET_ATTR_IGNORE) != 0) +#define RETAIN(header) \ + (((header)->attributes & RDATASET_ATTR_RETAIN) != 0) +#define NXDOMAIN(header) \ + (((header)->attributes & RDATASET_ATTR_NXDOMAIN) != 0) +#define STALE(header) \ + (((header)->attributes & RDATASET_ATTR_STALE) != 0) +#define RESIGN(header) \ + (((header)->attributes & RDATASET_ATTR_RESIGN) != 0) +#define OPTOUT(header) \ + (((header)->attributes & RDATASET_ATTR_OPTOUT) != 0) +#define NEGATIVE(header) \ + (((header)->attributes & RDATASET_ATTR_NEGATIVE) != 0) +#define PREFETCH(header) \ + (((header)->attributes & RDATASET_ATTR_PREFETCH) != 0) +#define CASESET(header) \ + (((header)->attributes & RDATASET_ATTR_CASESET) != 0) +#define ZEROTTL(header) \ + (((header)->attributes & RDATASET_ATTR_ZEROTTL) != 0) +#define CASEFULLYLOWER(header) \ + (((header)->attributes & RDATASET_ATTR_CASEFULLYLOWER) != 0) + + +#define ACTIVE(header, now) \ + (((header)->rdh_ttl > (now)) || \ + ((header)->rdh_ttl == (now) && ZEROTTL(header))) + +#define DEFAULT_NODE_LOCK_COUNT 7 /*%< Should be prime. */ + +/*% + * 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 +#define DEFAULT_CACHE_NODE_LOCK_COUNT DNS_RBTDB_CACHE_NODE_LOCK_COUNT +#endif +#else +#define DEFAULT_CACHE_NODE_LOCK_COUNT 16 +#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_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 bytes are covered by rwlock. + */ + isc_rwlock_t rwlock; + uint64_t records; + uint64_t bytes; +} 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 */ +#if DNS_RBTDB_USERWLOCK + isc_rwlock_t lock; +#else + isc_mutex_t lock; +#endif + /* 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 */ + /* 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; + + /* + * 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; + dns_rpz_zones_t *rpzs; + dns_rpz_num_t rpz_num; + dns_rpz_zones_t *load_rpzs; + + /* Unlocked */ + unsigned int quantum; +}; + +#define RBTDB_ATTR_LOADED 0x01 +#define RBTDB_ATTR_LOADING 0x02 + +/*% + * 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 isc_result_t rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now); +static isc_result_t rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname); +static isc_result_t rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype); +static inline 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, + isc_stdtime_t now, bool tree_locked); +static isc_result_t 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 dns_rdatasetmethods_t rdataset_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, + rdataset_getnoqname, + NULL, + rdataset_getclosest, + rdataset_getadditional, + rdataset_setadditional, + rdataset_putadditional, + rdataset_settrust, + rdataset_expire, + rdataset_clearprefetch, + rdataset_setownercase, + rdataset_getownercase +}; + +static dns_rdatasetmethods_t slab_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +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, + 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 unsigned int init_count; + +/* + * 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 + + +/* + * 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, NULL); + + *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 void +update_rrsetstats(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, + bool increment) +{ + dns_rdatastatstype_t statattributes = 0; + dns_rdatastatstype_t base = 0; + dns_rdatastatstype_t type; + + /* 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; + + 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); +} + +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)); +} + +/*% + * 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_ondestroy_t ondest; + isc_result_t result; + char buf[DNS_NAME_FORMATSIZE]; + dns_rbt_t **treep; + isc_time_t start; + dns_dbonupdatelistener_t *listener, *listener_next; + + 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) { + unsigned int refs; + + isc_refcount_decrement(&rbtdb->current_version->references, + &refs); + INSIST(refs == 0); + UNLINK(rbtdb->open_versions, rbtdb->current_version, link); + 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)); + if (event == NULL) + continue; + 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->load_rpzs != NULL) { + /* + * We must be cleaning up after a failed zone loading. + */ + REQUIRE(rbtdb->rpzs != NULL && + rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&rbtdb->load_rpzs); + } + if (rbtdb->rpzs != NULL) { + REQUIRE(rbtdb->rpz_num < rbtdb->rpzs->p.num_zones); + dns_rpz_detach_rpzs(&rbtdb->rpzs); + } + + 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; + ondest = rbtdb->common.ondest; + isc_mem_detach(&rbtdb->hmctx); + + if (rbtdb->mmap_location != NULL) + isc_file_munmap(rbtdb->mmap_location, + (size_t) rbtdb->mmap_size); + + for (listener = ISC_LIST_HEAD(rbtdb->common.update_listeners); + listener != NULL; + listener = listener_next) + { + listener_next = ISC_LIST_NEXT(listener, link); + ISC_LIST_UNLINK(rbtdb->common.update_listeners, listener, link); + isc_mem_put(rbtdb->common.mctx, listener, + sizeof(dns_dbonupdatelistener_t)); + } + + isc_mem_putanddetach(&rbtdb->common.mctx, rbtdb, sizeof(*rbtdb)); + isc_ondestroy_notify(&ondest, rbtdb); +} + +static inline 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); + + /* + * 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; + NODE_UNLOCK(&rbtdb->node_locks[i].lock, isc_rwlocktype_write); + if (isc_refcount_current(&rbtdb->node_locks[i].references) + == 0) { + inactive++; + } + } + + 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) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)(*dbp); + unsigned int refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + + isc_refcount_decrement(&rbtdb->references, &refs); + + if (refs == 0) + maybe_free_rbtdb(rbtdb); + + *dbp = NULL; +} + +static void +currentversion(dns_db_t *db, dns_dbversion_t **versionp) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *version; + unsigned int refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_read); + version = rbtdb->current_version; + isc_refcount_increment(&version->references, &refs); + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_read); + + *versionp = (dns_dbversion_t *)version; +} + +static inline rbtdb_version_t * +allocate_version(isc_mem_t *mctx, rbtdb_serial_t serial, + unsigned int references, bool writer) +{ + isc_result_t result; + rbtdb_version_t *version; + + version = isc_mem_get(mctx, sizeof(*version)); + if (version == NULL) + return (NULL); + version->serial = serial; + result = isc_refcount_init(&version->references, references); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, version, sizeof(*version)); + return (NULL); + } + 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) { + isc_result_t result; + 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); + if (version != NULL) { + 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)); + } + result = isc_rwlock_init(&version->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) { + isc_refcount_destroy(&version->references); + isc_mem_put(rbtdb->common.mctx, version, + sizeof(*version)); + version = NULL; + } else { + RWLOCK(&rbtdb->current_version->rwlock, + isc_rwlocktype_read); + version->records = rbtdb->current_version->records; + version->bytes = rbtdb->current_version->bytes; + RWUNLOCK(&rbtdb->current_version->rwlock, + isc_rwlocktype_read); + rbtdb->next_serial++; + rbtdb->future_version = version; + } + } else + result = ISC_R_NOMEMORY; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + if (version == NULL) + return (result); + + *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; + unsigned int refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + INSIST(rbtversion != NULL && rbtversion->rbtdb == rbtdb); + + isc_refcount_increment(&rbtversion->references, &refs); + INSIST(refs > 1); + + *targetp = rbtversion; +} + +static rbtdb_changed_t * +add_changed(dns_rbtdb_t *rbtdb, rbtdb_version_t *version, + dns_rbtnode_t *node) +{ + rbtdb_changed_t *changed; + unsigned int refs; + + /* + * 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) { + dns_rbtnode_refincrement(node, &refs); + INSIST(refs != 0); + 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_acachearray(isc_mem_t *mctx, rdatasetheader_t *header, + acachectl_t *array) +{ + unsigned int count; + unsigned int i; + unsigned char *raw; /* RDATASLAB */ + + /* + * The caller must be holding the corresponding node lock. + */ + + if (array == NULL) + return; + + raw = (unsigned char *)header + sizeof(*header); + count = raw[0] * 256 + raw[1]; + + /* + * Sanity check: since an additional cache entry has a reference to + * the original DB node (in the callback arg), there should be no + * acache entries when the node can be freed. + */ + for (i = 0; i < count; i++) + INSIST(array[i].entry == NULL && array[i].cbarg == NULL); + + isc_mem_put(mctx, array, count * sizeof(acachectl_t)); +} + +static inline 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 inline 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; + +#if TRACE_HEADER + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) + fprintf(stderr, "initialized header: %p\n", h); +#else + UNUSED(rbtdb); +#endif +} + +/* + * 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)) { + uint16_t attr; + + memmove(newh->upper, old->upper, sizeof(old->upper)); + attr = old->attributes & (RDATASET_ATTR_CASESET | + RDATASET_ATTR_CASEFULLYLOWER); + newh->attributes |= attr; + } +} + +static inline rdatasetheader_t * +new_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx) { + rdatasetheader_t *h; + + h = isc_mem_get(mctx, sizeof(*h)); + if (h == NULL) + return (NULL); + +#if TRACE_HEADER + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) + fprintf(stderr, "allocated header: %p\n", h); +#endif + memset(h->upper, 0xeb, sizeof(h->upper)); + init_rdataset(rbtdb, h); + h->rdh_ttl = 0; + return (h); +} + +static inline void +free_rdataset(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *rdataset) { + unsigned int size; + int idx; + + if (EXISTS(rdataset) && + (rdataset->attributes & RDATASET_ATTR_STATCOUNT) != 0) { + update_rrsetstats(rbtdb, rdataset, 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); + + free_acachearray(mctx, rdataset, rdataset->additional_auth); + free_acachearray(mctx, rdataset, rdataset->additional_glue); + + 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 inline 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) { + header->attributes |= RDATASET_ATTR_IGNORE; + make_dirty = true; + } + for (dcurrent = header->down; + dcurrent != NULL; + dcurrent = dcurrent->down) { + if (dcurrent->serial == serial) { + dcurrent->attributes |= RDATASET_ATTR_IGNORE; + make_dirty = true; + } + } + } + if (make_dirty) + node->dirty = 1; +} + +static inline void +mark_stale_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { + + /* + * If we are already stale there is nothing to do. + */ + if ((header->attributes & RDATASET_ATTR_STALE) != 0) + return; + + header->attributes |= RDATASET_ATTR_STALE; + header->node->dirty = 1; + + /* + * If we have not been counted then there is nothing to do. + */ + if ((header->attributes & RDATASET_ATTR_STATCOUNT) == 0) + return; + + if (EXISTS(header)) + update_rrsetstats(rbtdb, header, true); +} + +static inline 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 inline 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 or stale, we can clean it up. + */ + if ((current->attributes & + (RDATASET_ATTR_NONEXISTENT|RDATASET_ATTR_STALE)) != 0) { + 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 inline 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; +} + +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; + unsigned int node_has_rpz; + + 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); + + /* + * dns_rbt_deletenode() may keep the node if it has a + * down pointer, but we mustn't call dns_rpz_delete() on + * it again. + */ + node_has_rpz = node->rpz; + node->rpz = 0; + result = dns_rbt_deletenode(rbtdb->tree, node, false); + if (result == ISC_R_SUCCESS && + rbtdb->rpzs != NULL && node_has_rpz) + dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); + 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)); + } + } + /* + * dns_rbt_deletenode() may keep the node if it has a + * down pointer, but we mustn't call dns_rpz_delete() on + * it again. + */ + node_has_rpz = node->rpz; + node->rpz = 0; + result = dns_rbt_deletenode(rbtdb->tree, node, false); + if (result == ISC_R_SUCCESS && + rbtdb->rpzs != NULL && node_has_rpz) + dns_rpz_delete(rbtdb->rpzs, rbtdb->rpz_num, name); + 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 inline void +new_reference(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { + unsigned int lockrefs, noderefs; + isc_refcount_t *lockref; + + INSIST(!ISC_LINK_LINKED(node, deadlink)); + dns_rbtnode_refincrement0(node, &noderefs); + if (noderefs == 1) { /* this is the first reference to the node */ + lockref = &rbtdb->node_locks[node->locknum].references; + isc_refcount_increment0(lockref, &lockrefs); + INSIST(lockrefs != 0); + } + INSIST(noderefs != 0); +} + +/*% + * 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); + + /* + * Since we're holding a tree write lock, it should be + * impossible for this node to be referenced by others. + */ + INSIST(dns_rbtnode_refcurrent(node) == 0 && + node->data == NULL); + + if (node->parent != NULL && + node->parent->down == node && node->left == NULL && + node->right == NULL && rbtdb->task != NULL) + { + 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)); + if (ev != NULL) { + new_reference(rbtdb, node); + db = NULL; + attach((dns_db_t *)rbtdb, &db); + ev->ev_sender = db; + isc_task_send(rbtdb->task, &ev); + } else { + ISC_LIST_APPEND(rbtdb->deadnodes[bucketnum], + node, deadlink); + } + } else { + delete_node(rbtdb, node); + } + 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 inline 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_STRONGLOCK(nodelock); + NODE_WEAKLOCK(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_WEAKUNLOCK(nodelock, locktype); + locktype = isc_rwlocktype_write; + POST(locktype); + NODE_WEAKLOCK(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); + + NODE_WEAKUNLOCK(nodelock, locktype); + NODE_STRONGUNLOCK(nodelock); +} + +/* + * 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; + rbtdb_nodelock_t *nodelock; + unsigned int refs, nrefs; + int bucket = node->locknum; + bool no_reference = true; + + nodelock = &rbtdb->node_locks[bucket]; + +#define KEEP_NODE(n, r) \ + ((n)->data != NULL || (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)) { + dns_rbtnode_refdecrement(node, &nrefs); + INSIST((int)nrefs >= 0); + if (nrefs == 0) { + isc_refcount_decrement(&nodelock->references, &refs); + INSIST((int)refs >= 0); + } + return ((nrefs == 0) ? true : false); + } + + /* Upgrade the lock? */ + if (nlock == isc_rwlocktype_read) { + NODE_WEAKUNLOCK(&nodelock->lock, isc_rwlocktype_read); + NODE_WEAKLOCK(&nodelock->lock, isc_rwlocktype_write); + } + + dns_rbtnode_refdecrement(node, &nrefs); + INSIST((int)nrefs >= 0); + if (nrefs > 0) { + /* Restore the lock? */ + if (nlock == isc_rwlocktype_read) + NODE_WEAKDOWNGRADE(&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; + + isc_refcount_decrement(&nodelock->references, &refs); + INSIST((int)refs >= 0); + + if (KEEP_NODE(node, rbtdb)) + 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 && node->parent != NULL && + node->parent->down == node && node->left == NULL && + node->right == NULL && rbtdb->task != NULL) { + 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)); + if (ev != NULL) { + new_reference(rbtdb, node); + db = NULL; + attach((dns_db_t *)rbtdb, &db); + ev->ev_sender = db; + isc_task_send(rbtdb->task, &ev); + no_reference = false; + } else { + /* + * XXX: this is a weird situation. We could + * ignore this error case, but then the stale + * node will unlikely be purged except via a + * rare condition such as manual cleanup. So + * we queue it in the deadnodes list, hoping + * the memory shortage is temporary and the node + * will be deleted later. + */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_CACHE, + ISC_LOG_INFO, + "decrement_reference: failed to " + "allocate pruning event"); + INSIST(node->data == NULL); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node, + deadlink); + } + } else { + delete_node(rbtdb, node); + } + } else { + INSIST(node->data == NULL); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + ISC_LIST_APPEND(rbtdb->deadnodes[bucket], node, deadlink); + } + + restore_locks: + /* Restore the lock? */ + if (nlock == isc_rwlocktype_read) + NODE_WEAKDOWNGRADE(&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. In addition, + * if the node is in the dead-nodes list, extract it + * from the list beforehand as we do in + * reactivate_node(). + */ + if (ISC_LINK_LINKED(parent, deadlink)) + ISC_LIST_UNLINK(rbtdb->deadnodes[locknum], + parent, deadlink); + new_reference(rbtdb, parent); + } 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 inline 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 inline 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 */ +#if DNS_RDATASET_FIXED + raw += count * 4 + 2; +#else + raw += 2; +#endif + while (count-- > 0U) { + length = raw[0] * 256 + raw[1]; +#if DNS_RDATASET_FIXED + raw += 4; +#else + raw += 2; +#endif + 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; + unsigned int refs; + + 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); + isc_refcount_decrement(&rbtdb->references, &refs); + if (refs == 0) + 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; + unsigned int refs; + 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); + + isc_refcount_decrement(&version->references, &refs); + if (refs > 0) { /* 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; + isc_refcount_decrement(&cur_version->references, + &cur_ref); + if (cur_ref == 0) { + 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 == 0) { + 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(). + */ + isc_refcount_increment0(&version->references, + &cur_ref); + INSIST(cur_ref == 1); + 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)); + 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)) { + isc_result_t result; + result = resign_insert(rbtdb, header->node->locknum, + header); + if (result != ISC_R_SUCCESS) + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_ZONE, ISC_LOG_ERROR, + "Unable to reinsert header to " + "re-signing heap: %s\n", + dns_result_totext(result)); + } + 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, NULL); + 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. + */ +static isc_result_t +add_wildcard_magic(dns_rbtdb_t *rbtdb, dns_name_t *name) { + 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; + node->wild = 1; + return (ISC_R_SUCCESS); +} + +static isc_result_t +add_empty_wildcards(dns_rbtdb_t *rbtdb, dns_name_t *name) { + 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); + 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, 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); +#ifdef DNS_RBT_USEHASH + node->locknum = node->hashval % rbtdb->node_lock_count; +#else + node->locknum = dns_name_hash(&nodename, true) % + rbtdb->node_lock_count; +#endif + if (tree == rbtdb->tree) { + add_empty_wildcards(rbtdb, name); + + if (dns_name_iswildcard(name)) { + result = add_wildcard_magic(rbtdb, name); + 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); + + /* + * Always try to add the policy zone data, because this node might + * already have been implicitly created by the previous addition of + * a longer domain. A common example is adding *.example.com + * (implicitly creating example.com) followed by explicitly adding + * example.com. + */ + if (create && rbtdb->rpzs != NULL && tree == rbtdb->tree) { + dns_fixedname_t fnamef; + dns_name_t *fname; + + dns_fixedname_init(&fnamef); + fname = dns_fixedname_name(&fnamef); + dns_rbt_fullnamefromnode(node, fname); + result = dns_rpz_add(rbtdb->rpzs, rbtdb->rpz_num, fname); + if (result == ISC_R_SUCCESS) + node->rpz = 1; + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + /* + * It is too late to give up, so merely complain. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dns_rpz_add(): %s", + isc_result_totext(result)); + } + } + + RWUNLOCK(&rbtdb->tree_lock, locktype); + + *nodep = (dns_dbnode_t *)node; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnode(dns_db_t *db, 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, 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); + 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); + RUNTIME_CHECK(dns_name_copy(name, zcname, NULL) == + ISC_R_SUCCESS); + 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 inline void +bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + rdatasetheader_t *header, isc_stdtime_t now, + dns_rdataset_t *rdataset) +{ + unsigned char *raw; /* RDATASLAB */ + + /* + * 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); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ + + 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; + rdataset->private1 = rbtdb; + rdataset->private2 = node; + raw = (unsigned char *)header + sizeof(*header); + rdataset->private3 = raw; + rdataset->count = header->count++; + 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 inline 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) +{ + isc_result_t result; + 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); + result = dns_name_copy(zcname, foundname, NULL); + if (result != ISC_R_SUCCESS) + return (result); + } + 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, rdataset); + if (sigrdataset != NULL && search->zonecut_sigrdataset != NULL) + bind_rdataset(search->rbtdb, node, + search->zonecut_sigrdataset, + search->now, 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 inline 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]; +#if DNS_RDATASET_FIXED + raw += 2 + (4 * count); +#else + raw += 2; +#endif + + while (count > 0) { + count--; + size = raw[0] * 256 + raw[1]; +#if DNS_RDATASET_FIXED + raw += 4; +#else + raw += 2; +#endif + 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 inline bool +activeempty(rbtdb_search_t *search, dns_rbtnodechain_t *chain, + 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 inline bool +activeemtpynode(rbtdb_search_t *search, 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 inline isc_result_t +find_wildcard(rbtdb_search_t *search, dns_rbtnode_t **nodep, + 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)) + 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, NULL); + 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)) + break; + } + NODE_UNLOCK(lock, isc_rwlocktype_read); + if (header != NULL || + activeempty(search, &wchain, wname)) { + if (activeemtpynode(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 */ +#if DNS_RDATASET_FIXED + raw += count * 4 + 2; +#else + raw += 2; +#endif + while (count-- > 0) { + rdlen = raw[0] * 256 + raw[1]; +#if DNS_RDATASET_FIXED + raw += 4; +#else + raw += 2; +#endif + 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 inline 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); + + 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, NULL); + 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_NOOPTIONS, + 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_NOOPTIONS, 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 inline 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); + *nodep = node; + } + bind_rdataset(search->rbtdb, node, + found, search->now, + rdataset); + if (foundsig != NULL) + bind_rdataset(search->rbtdb, + node, + foundsig, + search->now, + 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, 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; + dns_rbtnodechain_t chain; + 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.rbtdb->common.mctx); + 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) { + result = dns_name_copy(name, foundname, NULL); + if (result != ISC_R_SUCCESS) + goto tree_exit; + wild = true; + goto found; + } + else if (result != ISC_R_NOTFOUND) + goto tree_exit; + } + + 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 delgation 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); + 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); + *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, rdataset); + if (nsecsig != NULL) + bind_rdataset(search.rbtdb, node, + nsecsig, 0, 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); + else + search.need_cleanup = false; + *nodep = node; + } + + if (type != dns_rdatatype_any) { + bind_rdataset(search.rbtdb, node, found, 0, rdataset); + if (foundsig != NULL) + bind_rdataset(search.rbtdb, node, foundsig, 0, + 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, dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, + dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) +{ + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + UNUSED(rdataset); + UNUSED(sigrdataset); + + FATAL_ERROR(__FILE__, __LINE__, "zone_findzonecut() called!"); + + /* NOTREACHED */ + 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 !defined(ISC_RWLOCK_USEATOMIC) || !defined(DNS_RBT_USEISCREFCOUNT) + UNUSED(lock); +#endif + + if (!ACTIVE(header, search->now)) { + /* + * This rdataset is stale. If no one else is using the + * node, we can clean it up right now, otherwise we mark + * it as stale, 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 (dns_rbtnode_refcurrent(node) == 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_stale_header(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)) { + dname_header = header; + header_prev = header; + } else if (header->type == RBTDB_RDATATYPE_SIGDNAME && + EXISTS(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); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + 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 inline 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)) { + /* + * 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); + result = dns_name_copy(&name, foundname, NULL); + while (result == ISC_R_SUCCESS && 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) { + *nodep = NULL; + goto node_exit; + } + } + result = DNS_R_DELEGATION; + if (nodep != NULL) { + new_reference(search->rbtdb, node); + *nodep = node; + } + bind_rdataset(search->rbtdb, node, found, search->now, + rdataset); + if (foundsig != NULL) + bind_rdataset(search->rbtdb, node, foundsig, + search->now, 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; + + 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(&search->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; + } + 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, rdataset); + if (foundsig != NULL) + bind_rdataset(search->rbtdb, node, foundsig, + now, sigrdataset); + new_reference(search->rbtdb, node); + *nodep = node; + result = DNS_R_COVERINGNSEC; + } else if (!empty_node) { + result = ISC_R_NOTFOUND; + } else + result = dns_rbtnodechain_prev(&search->chain, NULL, + NULL); + unlock_node: + NODE_UNLOCK(lock, locktype); + } while (empty_node && result == ISC_R_SUCCESS); + return (result); +} + +/* + * Connect this RBTDB to the response policy zone summary data for the view. + */ +static void +rpz_attach(dns_db_t *db, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { + dns_rbtdb_t * rbtdb; + + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + REQUIRE(rbtdb->rpzs == NULL && rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); + dns_rpz_attach_rpzs(rpzs, &rbtdb->rpzs); + rbtdb->rpz_num = rpz_num; + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); +} + +/* + * Enable this RBTDB as a response policy zone. + */ +static isc_result_t +rpz_ready(dns_db_t *db) { + dns_rbtdb_t * rbtdb; + isc_result_t result; + + rbtdb = (dns_rbtdb_t *)db; + REQUIRE(VALID_RBTDB(rbtdb)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + if (rbtdb->rpzs == NULL) { + INSIST(rbtdb->rpz_num == DNS_RPZ_INVALID_NUM); + result = ISC_R_SUCCESS; + } else { + result = dns_rpz_ready(rbtdb->rpzs, &rbtdb->load_rpzs, + rbtdb->rpz_num); + } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + return (result); +} + +static isc_result_t +cache_find(dns_db_t *db, 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; + 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.rbtdb->common.mctx); + 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; + nssig = 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) && (!STALE(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 (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))) { + /* + * 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); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + *nodep = node; + } + bind_rdataset(search.rbtdb, node, nsheader, search.now, + rdataset); + if (need_headerupdate(nsheader, search.now)) + update = nsheader; + if (nssig != NULL) { + bind_rdataset(search.rbtdb, node, nssig, + search.now, 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); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + *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, + rdataset); + if (need_headerupdate(found, search.now)) + update = found; + if (!NEGATIVE(found) && foundsig != NULL) { + bind_rdataset(search.rbtdb, node, foundsig, search.now, + 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, dns_name_t *name, 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; + 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; + + 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.rbtdb->common.mctx); + search.now = now; + + 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, foundname, &node, + &search.chain, rbtoptions, NULL, &search); + + if (result == DNS_R_PARTIALMATCH) { + find_ns: + result = find_deepest_zonecut(&search, node, nodep, foundname, + rdataset, sigrdataset); + goto tree_exit; + } else if (result != ISC_R_SUCCESS) + goto tree_exit; + + /* + * 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)) { + /* Do nothing. */ + } else if (EXISTS(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); + goto find_ns; + } + + if (nodep != NULL) { + new_reference(search.rbtdb, node); + INSIST(!ISC_LINK_LINKED(node, deadlink)); + *nodep = node; + } + + bind_rdataset(search.rbtdb, node, found, search.now, rdataset); + if (foundsig != NULL) + bind_rdataset(search.rbtdb, node, foundsig, search.now, + 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; + unsigned int refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(targetp != NULL && *targetp == NULL); + + NODE_STRONGLOCK(&rbtdb->node_locks[node->locknum].lock); + dns_rbtnode_refincrement(node, &refs); + INSIST(refs != 0); + NODE_STRONGUNLOCK(&rbtdb->node_locks[node->locknum].lock); + + *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)) { + uint32_t val; + + isc_random_get(&val); + /* + * XXXDCL Could stand to have a better policy, like LRU. + */ + force_expire = (rbtnode->down == NULL && val % 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 <= 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_stale_header(rbtdb, header); + if (log) + isc_log_write(dns_lctx, category, module, + level, "overmem cache: stale %s", + printname); + } else if (force_expire) { + if (! RETAIN(header)) { + set_ttl(rbtdb, header, 0); + mark_stale_header(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; + + REQUIRE(VALID_RBTDB(rbtdb)); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + + fprintf(out, "node %p, %u references, locknum = %u\n", + rbtnode, dns_rbtnode_refcurrent(rbtnode), + 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 { + if (!first) + fprintf(out, "\t"); + first = false; + fprintf(out, + "\tserial = %lu, ttl = %u, " + "trust = %u, attributes = %u, " + "resign = %u\n", + (unsigned long)current->serial, + current->rdh_ttl, + current->trust, + current->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)); + if (rbtdbiter == NULL) + return (ISC_R_NOMEMORY); + + rbtdbiter->common.methods = &dbiterator_methods; + rbtdbiter->common.db = NULL; + dns_db_attach(db, &rbtdbiter->common.db); + rbtdbiter->common.relative_names = (options & DNS_DB_RELATIVENAMES); + 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); + rbtdbiter->nonsec3 = (options & DNS_DB_NONSEC3); + memset(rbtdbiter->deletions, 0, sizeof(rbtdbiter->deletions)); + dns_rbtnodechain_init(&rbtdbiter->chain, db->mctx); + dns_rbtnodechain_init(&rbtdbiter->nsec3chain, db->mctx); + 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, rdataset); + if (foundsig != NULL) + bind_rdataset(rbtdb, rbtnode, foundsig, now, + 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 < 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_stale_header(rbtdb, header); + } + } else if (EXISTS(header) && (!STALE(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, rdataset); + if (!NEGATIVE(found) && foundsig != NULL) + bind_rdataset(rbtdb, rbtnode, foundsig, now, + 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, + 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; + unsigned int refs; + + REQUIRE(VALID_RBTDB(rbtdb)); + + iterator = isc_mem_get(rbtdb->common.mctx, sizeof(*iterator)); + if (iterator == NULL) + return (ISC_R_NOMEMORY); + + if ((db->attributes & DNS_DBATTR_CACHE) == 0) { + now = 0; + if (rbtversion == NULL) + currentversion(db, + (dns_dbversion_t **) (void *)(&rbtversion)); + else { + INSIST(rbtversion->rbtdb == rbtdb); + + isc_refcount_increment(&rbtversion->references, + &refs); + INSIST(refs > 1); + } + } 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.now = now; + + NODE_STRONGLOCK(&rbtdb->node_locks[rbtnode->locknum].lock); + + dns_rbtnode_refincrement(rbtnode, &refs); + INSIST(refs != 0); + + iterator->current = NULL; + + NODE_STRONGUNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock); + + *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 isc_result_t +resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader) { + isc_result_t result; + + INSIST(!IS_CACHE(rbtdb)); + INSIST(newheader->heap_index == 0); + INSIST(!ISC_LINK_LINKED(newheader, link)); + + result = isc_heap_insert(rbtdb->heaps[idx], newheader); + return (result); +} + +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_LIST_APPEND(version->resigned_list, header, link); + } + } +} + +static void +update_recordsandbytes(bool add, rbtdb_version_t *rbtversion, + rdatasetheader_t *header) +{ + unsigned char *hdr = (unsigned char *)header; + size_t hdrsize = sizeof (*header); + + if (add) { + rbtversion->records += dns_rdataslab_count(hdr, hdrsize); + rbtversion->bytes += dns_rdataslab_size(hdr, hdrsize); + } else { + rbtversion->records -= dns_rdataslab_count(hdr, hdrsize); + rbtversion->bytes -= dns_rdataslab_size(hdr, hdrsize); + } +} + +static isc_result_t +add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, 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, *topheader_prev, *header, *sigheader; + unsigned char *merged; + 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 stale 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_stale_header(rbtdb, topheader); + } + goto find_header; + } + /* + * Otherwise look for any RRSIGs of the given + * type so they can be marked stale 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-stale NXDOMAIN/NODATA(QTYPE=ANY) negative + * cache entry. If we're adding an RRSIG, also + * check for an extant non-stale 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, + addedrdataset); + return (DNS_R_UNCHANGED); + } + /* + * The new rdataset is better. Expire the + * ncache entry. + */ + set_ttl(rbtdb, topheader, 0); + mark_stale_header(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 (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, + 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; + 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. + */ + 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, + 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_SIGDDS) && + !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, + 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); + result = isc_heap_insert(rbtdb->heaps[idx], + newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, + rbtdb->common.mctx, + newheader); + return (result); + } + } else if (RESIGN(newheader)) { + result = resign_insert(rbtdb, idx, newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, + rbtdb->common.mctx, + newheader); + return (result); + } + /* + * 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) { + RWLOCK(&rbtversion->rwlock, + isc_rwlocktype_write); + update_recordsandbytes(false, rbtversion, + header); + RWUNLOCK(&rbtversion->rwlock, + isc_rwlocktype_write); + } + free_rdataset(rbtdb, rbtdb->common.mctx, header); + } else { + idx = newheader->node->locknum; + if (IS_CACHE(rbtdb)) { + INSIST(rbtdb->heaps != NULL); + result = isc_heap_insert(rbtdb->heaps[idx], + newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, + rbtdb->common.mctx, + newheader); + return (result); + } + if (ZEROTTL(newheader)) + ISC_LIST_APPEND(rbtdb->rdatasets[idx], + newheader, link); + else + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], + newheader, link); + } else if (RESIGN(newheader)) { + result = resign_insert(rbtdb, idx, newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, + rbtdb->common.mctx, + newheader); + return (result); + } + 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_stale_header(rbtdb, header); + if (sigheader != NULL) { + set_ttl(rbtdb, sigheader, 0); + mark_stale_header(rbtdb, sigheader); + } + } + if (rbtversion != NULL && !header_nx) { + RWLOCK(&rbtversion->rwlock, + isc_rwlocktype_write); + update_recordsandbytes(false, rbtversion, + header); + RWUNLOCK(&rbtversion->rwlock, + isc_rwlocktype_write); + } + } + } 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)) { + result = isc_heap_insert(rbtdb->heaps[idx], newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + return (result); + } + if (ZEROTTL(newheader)) + ISC_LIST_APPEND(rbtdb->rdatasets[idx], + newheader, link); + else + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], + newheader, link); + } else if (RESIGN(newheader)) { + result = resign_insert(rbtdb, idx, newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, rbtdb->common.mctx, + newheader); + return (result); + } + 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) { + RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write); + update_recordsandbytes(true, rbtversion, newheader); + RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write); + } + + /* + * 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, addedrdataset); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline 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)); + if (noqname == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(&noqname->name, NULL); + noqname->neg = NULL; + noqname->negsig = NULL; + noqname->type = neg.type; + result = dns_name_dup(&name, mctx, &noqname->name); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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); + if (noqname != NULL) + free_noqname(mctx, &noqname); + return(result); +} + +static inline 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)); + if (closest == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + dns_name_init(&closest->name, NULL); + closest->neg = NULL; + closest->negsig = NULL; + closest->type = neg.type; + result = dns_name_dup(&name, mctx, &closest->name); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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); + if (closest != NULL) + free_noqname(mctx, &closest); + return(result); +} + +static dns_dbmethods_t zone_methods; + +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) + 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))); + + 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); + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + dns_rbt_fullnamefromnode(node, name); + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + 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); + newheader->attributes = 0; + if (rdataset->ttl == 0U) + newheader->attributes |= RDATASET_ATTR_ZEROTTL; + newheader->noqname = NULL; + newheader->closest = NULL; + newheader->count = init_count++; + newheader->trust = rdataset->trust; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + newheader->last_used = now; + newheader->node = rbtnode; + if (rbtversion != NULL) { + newheader->serial = rbtversion->serial; + now = 0; + + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + newheader->attributes |= 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) + newheader->attributes |= RDATASET_ATTR_PREFETCH; + if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) + newheader->attributes |= RDATASET_ATTR_NEGATIVE; + if ((rdataset->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) + newheader->attributes |= RDATASET_ATTR_NXDOMAIN; + if ((rdataset->attributes & DNS_RDATASETATTR_OPTOUT) != 0) + newheader->attributes |= 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. + */ + if (rbtnode->nsec != DNS_RBT_NSEC_HAS_NSEC && + rdataset->type == dns_rdatatype_nsec) + newnsec = true; + else + newnsec = false; + + /* + * 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 stale 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, now, tree_locked); + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + if (rbtdb->rrsetstats != NULL) { + newheader->attributes |= RDATASET_ATTR_STATCOUNT; + update_rrsetstats(rbtdb, newheader, 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 && header->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; + + dns_rbt_fullnamefromnode(rbtnode, name); + 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, 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; + 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) + 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))); + + 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); + newheader->attributes = 0; + newheader->serial = rbtversion->serial; + newheader->trust = 0; + newheader->noqname = NULL; + newheader->closest = NULL; + newheader->count = init_count++; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + newheader->last_used = 0; + newheader->node = rbtnode; + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + newheader->attributes |= 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)) { + newheader->attributes |= RDATASET_ATTR_RESIGN; + newheader->resign = header->resign; + newheader->resign_lsb = header->resign_lsb; + result = resign_insert(rbtdb, rbtnode->locknum, + newheader); + if (result != ISC_R_SUCCESS) { + free_rdataset(rbtdb, + rbtdb->common.mctx, + newheader); + goto unlock; + } + } + /* + * 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. + */ + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + update_recordsandbytes(true, rbtversion, newheader); + } 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; + newheader->attributes = RDATASET_ATTR_NONEXISTENT; + newheader->trust = 0; + newheader->serial = rbtversion->serial; + newheader->noqname = NULL; + newheader->closest = NULL; + newheader->count = 0; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + 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_recordsandbytes(false, 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; + 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, newrdataset); + + if (result == DNS_R_NXRRSET && newrdataset != NULL && + (options & DNS_DBSUB_WANTOLD) != 0) + bind_rdataset(rbtdb, rbtnode, header, 0, 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)) + iszonesecure(db, rbtdb->current_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; + 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); + newheader->attributes = RDATASET_ATTR_NONEXISTENT; + newheader->trust = 0; + newheader->noqname = NULL; + newheader->closest = NULL; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + if (rbtversion != NULL) + newheader->serial = rbtversion->serial; + else + newheader->serial = 0; + newheader->count = 0; + newheader->last_used = 0; + newheader->node = rbtnode; + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_write); + + result = add32(rbtdb, rbtnode, 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)) + iszonesecure(db, rbtdb->current_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, dns_name_t *name, dns_rbtnode_t **nodep, + bool hasnsec) +{ + isc_result_t noderesult, rpzresult, nsecresult, tmpresult; + dns_rbtnode_t *nsecnode = NULL, *node = NULL; + + noderesult = dns_rbt_addnode(rbtdb->tree, name, &node); + if (rbtdb->rpzs != NULL && + (noderesult == ISC_R_SUCCESS || noderesult == ISC_R_EXISTS)) { + rpzresult = dns_rpz_add(rbtdb->load_rpzs, rbtdb->rpz_num, + name); + if (rpzresult == ISC_R_SUCCESS) { + node->rpz = 1; + } else if (noderesult != ISC_R_EXISTS) { + /* + * 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(ISC_R_SUCCESS)); + noderesult = rpzresult; + } + } + 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 + node->nsec = DNS_RBT_NSEC_HAS_NSEC; + goto done; + } + + if (noderesult == ISC_R_SUCCESS) { + unsigned int node_has_rpz; + + /* + * Remove the node we just added above. + * dns_rbt_deletenode() may keep the node if it has a + * down pointer, but we mustn't call dns_rpz_delete() on + * it again. + */ + node_has_rpz = node->rpz; + node->rpz = 0; + tmpresult = dns_rbt_deletenode(rbtdb->tree, node, false); + if (tmpresult == ISC_R_SUCCESS) { + /* + * Clean rpz entries added above. + */ + if (rbtdb->rpzs != NULL && node_has_rpz) + dns_rpz_delete(rbtdb->load_rpzs, + rbtdb->rpz_num, name); + } else { + 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, 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); + + /* + * This routine does no node locking. See comments in + * 'load' below for more information on loading and + * locking. + */ + + /* + * 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); + + 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); + 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) { + dns_name_t foundname; + dns_name_init(&foundname, NULL); + dns_rbt_namefromnode(node, &foundname); +#ifdef DNS_RBT_USEHASH + node->locknum = node->hashval % rbtdb->node_lock_count; +#else + node->locknum = dns_name_hash(&foundname, true) % + rbtdb->node_lock_count; +#endif + } + + 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); + newheader->attributes = 0; + newheader->trust = rdataset->trust; + newheader->serial = 1; + newheader->noqname = NULL; + newheader->closest = NULL; + newheader->count = init_count++; + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; + newheader->last_used = 0; + newheader->node = node; + setownercase(newheader, name); + + if ((rdataset->attributes & DNS_RDATASETATTR_RESIGN) != 0) { + newheader->attributes |= 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; + } + + result = add32(rbtdb, node, rbtdb->current_version, newheader, + DNS_DBADD_MERGE, true, NULL, 0); + 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) +{ + isc_result_t result; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *) arg; + rdatasetheader_t *header; + unsigned char *limit = ((unsigned char *) base) + filesize; + unsigned char *p; + size_t size; + unsigned int count; + + REQUIRE(rbtnode != NULL); + + for (header = rbtnode->data; header != NULL; header = header->next) { + p = (unsigned char *) header; + + size = dns_rdataslab_size(p, sizeof(*header)); + count = dns_rdataslab_count(p, sizeof(*header));; + rbtdb->current_version->records += count; + rbtdb->current_version->bytes += size; + 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 + header->serial = 1; + header->is_mmapped = 1; + header->node = rbtnode; + header->node_is_relative = 0; + + if (rbtdb != NULL && RESIGN(header) && + (header->resign != 0 || header->resign_lsb != 0)) + { + int idx = header->node->locknum; + result = isc_heap_insert(rbtdb->heaps[idx], header); + if (result != ISC_R_SUCCESS) + return (result); + } + + 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); + } + } + + return (ISC_R_SUCCESS); +} + +/* + * Load the RBT database from the image in 'f' + */ +static isc_result_t +deserialize32(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 + + 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)); + if (loadctx == NULL) + return (ISC_R_NOMEMORY); + + loadctx->rbtdb = rbtdb; + if (IS_CACHE(rbtdb)) + isc_stdtime_get(&loadctx->now); + else + loadctx->now = 0; + + RBTDB_LOCK(&rbtdb->lock, isc_rwlocktype_write); + + if (rbtdb->rpzs != NULL) { + isc_result_t result; + + result = dns_rpz_beginload(&rbtdb->load_rpzs, + rbtdb->rpzs, rbtdb->rpz_num); + if (result != ISC_R_SUCCESS) { + isc_mem_put(rbtdb->common.mctx, loadctx, + sizeof(*loadctx)); + return (result); + } + } + + 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 = deserialize32; + 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; + + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + /* + * 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) + iszonesecure(db, rbtdb->current_version, rbtdb->origin_node); + + 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 + 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_dump2(rbtdb->common.mctx, db, version, + &dns_master_style_default, + filename, masterformat)); +} + +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)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + secure = (rbtdb->current_version->secure == dns_db_secure); + RWUNLOCK(&rbtdb->tree_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)); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + dnssec = (rbtdb->current_version->secure != dns_db_insecure); + RWUNLOCK(&rbtdb->tree_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 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) { + NODE_STRONGLOCK(&rbtdb->node_locks[onode->locknum].lock); + new_reference(rbtdb, onode); + NODE_STRONGUNLOCK(&rbtdb->node_locks[onode->locknum].lock); + + *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); + + RWLOCK(&rbtdb->tree_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; + } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_read); + + return (result); +} + +static isc_result_t +getsize(dns_db_t *db, dns_dbversion_t *version, uint64_t *records, + uint64_t *bytes) +{ + 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); + + if (rbtversion == NULL) + rbtversion = rbtdb->current_version; + + RWLOCK(&rbtversion->rwlock, isc_rwlocktype_read); + if (records != NULL) + *records = rbtversion->records; + + if (bytes != NULL) + *bytes = rbtversion->bytes; + RWUNLOCK(&rbtversion->rwlock, 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; + isc_result_t result = ISC_R_SUCCESS; + 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) { + header->attributes |= RDATASET_ATTR_RESIGN; + result = resign_insert(rbtdb, header->node->locknum, header); + } + NODE_UNLOCK(&rbtdb->node_locks[header->node->locknum].lock, + isc_rwlocktype_write); + return (result); +} + +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; + + 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); + this = isc_heap_element(rbtdb->heaps[i], 1); + if (this == NULL) { + NODE_UNLOCK(&rbtdb->node_locks[i].lock, + isc_rwlocktype_read); + continue; + } + if (header == NULL) + header = this; + else if (resign_sooner(this, header)) { + locknum = header->node->locknum; + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_read); + header = this; + } else + NODE_UNLOCK(&rbtdb->node_locks[i].lock, + isc_rwlocktype_read); + } + + if (header == NULL) + goto unlock; + + bind_rdataset(rbtdb, header->node, header, 0, rdataset); + + if (foundname != NULL) + dns_rbt_fullnamefromnode(header->node, foundname); + + NODE_UNLOCK(&rbtdb->node_locks[header->node->locknum].lock, + isc_rwlocktype_read); + + result = ISC_R_SUCCESS; + + unlock: + 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 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 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, + getnsec3parameters, + findnsec3node, + setsigningtime, + getsigningtime, + resigned, + isdnssec, + NULL, + rpz_attach, + rpz_ready, + NULL, + NULL, + NULL, + hashsize, + nodefullname, + getsize +}; + +static dns_dbmethods_t cache_methods = { + attach, + detach, + beginload, + endload, + NULL, + 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, + NULL, + NULL, + NULL, + NULL, + NULL, + isdnssec, + getrrsetstats, + NULL, + NULL, + NULL, + NULL, + setcachestats, + hashsize, + nodefullname, + NULL +}; + +isc_result_t +#ifdef DNS_RBTDB_VERSION64 +dns_rbtdb64_create +#else +dns_rbtdb_create +#endif + (isc_mem_t *mctx, 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 (rbtdb == NULL) + return (ISC_R_NOMEMORY); + + /* + * 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); + + result = RBTDB_INITLOCK(&rbtdb->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_rbtdb; + + result = isc_rwlock_init(&rbtdb->tree_lock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_lock; + + /* + * 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)); + if (rbtdb->node_locks == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_tree_lock; + } + + rbtdb->cachestats = 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)); + if (rbtdb->rdatasets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_rrsetstats; + } + 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 *)); + if (rbtdb->heaps == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_rdatasets; + } + 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++) { + result = isc_heap_create(hmctx, sooner, set_index, 0, + &rbtdb->heaps[i]); + if (result != ISC_R_SUCCESS) + goto cleanup_heaps; + } + + /* + * Create deadnode lists. + */ + rbtdb->deadnodes = isc_mem_get(mctx, rbtdb->node_lock_count * + sizeof(rbtnodelist_t)); + if (rbtdb->deadnodes == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_heaps; + } + 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++) { + result = NODE_INITLOCK(&rbtdb->node_locks[i].lock); + if (result == ISC_R_SUCCESS) { + result = isc_refcount_init(&rbtdb->node_locks[i].references, 0); + if (result != ISC_R_SUCCESS) + NODE_DESTROYLOCK(&rbtdb->node_locks[i].lock); + } + if (result != ISC_R_SUCCESS) { + while (i-- > 0) { + NODE_DESTROYLOCK(&rbtdb->node_locks[i].lock); + isc_refcount_destroy(&rbtdb->node_locks[i].references); + } + goto cleanup_deadnodes; + } + 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); + + /* + * Must be initialized before free_rbtdb() is called. + */ + isc_ondestroy_init(&rbtdb->common.ondest); + + /* + * 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); +#ifdef DNS_RBT_USEHASH + rbtdb->origin_node->locknum = + rbtdb->origin_node->hashval % + rbtdb->node_lock_count; +#else + rbtdb->origin_node->locknum = + dns_name_hash(&name, true) % + rbtdb->node_lock_count; +#endif + /* + * 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); +#ifdef DNS_RBT_USEHASH + rbtdb->nsec3_origin_node->locknum = + rbtdb->nsec3_origin_node->hashval % + rbtdb->node_lock_count; +#else + rbtdb->nsec3_origin_node->locknum = + dns_name_hash(&name, true) % + rbtdb->node_lock_count; +#endif + } + + /* + * Misc. Initialization. + */ + result = isc_refcount_init(&rbtdb->references, 1); + if (result != ISC_R_SUCCESS) { + free_rbtdb(rbtdb, false, NULL); + return (result); + } + rbtdb->attributes = 0; + rbtdb->task = NULL; + rbtdb->rpzs = NULL; + rbtdb->load_rpzs = NULL; + rbtdb->rpz_num = DNS_RPZ_INVALID_NUM; + + /* + * Version Initialization. + */ + rbtdb->current_serial = 1; + rbtdb->least_serial = 1; + rbtdb->next_serial = 2; + rbtdb->current_version = allocate_version(mctx, 1, 1, false); + if (rbtdb->current_version == NULL) { + isc_refcount_decrement(&rbtdb->references, NULL); + free_rbtdb(rbtdb, false, NULL); + return (ISC_R_NOMEMORY); + } + 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)); + result = isc_rwlock_init(&rbtdb->current_version->rwlock, 0, 0); + if (result != ISC_R_SUCCESS) { + isc_refcount_destroy(&rbtdb->current_version->references); + isc_mem_put(mctx, rbtdb->current_version, + sizeof(*rbtdb->current_version)); + rbtdb->current_version = NULL; + isc_refcount_decrement(&rbtdb->references, NULL); + free_rbtdb(rbtdb, false, NULL); + return (result); + } + + rbtdb->current_version->records = 0; + rbtdb->current_version->bytes = 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_deadnodes: + isc_mem_put(mctx, rbtdb->deadnodes, + rbtdb->node_lock_count * sizeof(rbtnodelist_t)); + + cleanup_heaps: + if (rbtdb->heaps != NULL) { + for (i = 0 ; i < (int)rbtdb->node_lock_count ; i++) + if (rbtdb->heaps[i] != NULL) + isc_heap_destroy(&rbtdb->heaps[i]); + isc_mem_put(hmctx, rbtdb->heaps, + rbtdb->node_lock_count * sizeof(isc_heap_t *)); + } + + cleanup_rdatasets: + if (rbtdb->rdatasets != NULL) + isc_mem_put(mctx, rbtdb->rdatasets, rbtdb->node_lock_count * + sizeof(rdatasetheaderlist_t)); + cleanup_rrsetstats: + if (rbtdb->rrsetstats != NULL) + dns_stats_detach(&rbtdb->rrsetstats); + + 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); + + cleanup_lock: + RBTDB_DESTROYLOCK(&rbtdb->lock); + + cleanup_rbtdb: + 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 DNS_RDATASET_FIXED + if ((rdataset->attributes & DNS_RDATASETATTR_LOADORDER) == 0) + raw += 2 + (4 * count); + else +#endif + 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. + * + * 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 + length = raw[0] * 256 + raw[1]; + raw += length; +#if DNS_RDATASET_FIXED + } + rdataset->private5 = raw + 4; /* length(2) + order(2) */ +#else + rdataset->private5 = raw + 2; /* length(2) */ +#endif + + return (ISC_R_SUCCESS); +} + +static void +rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) { + unsigned char *raw = rdataset->private5; /* RDATASLAB */ +#if DNS_RDATASET_FIXED + unsigned int offset; +#endif + 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) { + offset = (raw[0] << 24) + (raw[1] << 16) + + (raw[2] << 8) + raw[3]; + raw = rdataset->private3; + raw += offset; + } +#endif + length = raw[0] * 256 + raw[1]; +#if DNS_RDATASET_FIXED + raw += 4; +#else + raw += 2; +#endif + 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; + 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; + 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); + header->attributes &= ~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 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; + isc_stdtime_t now; + + if (IS_CACHE(rbtdb)) { + serial = 1; + now = rbtiterator->common.now; + } else { + serial = rbtversion->serial; + now = 0; + } + + 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 (header->serial <= serial && !IGNORE(header)) { + /* + * Is this a "this rdataset doesn't exist" + * record? Or is it too old in the cache? + * + * Note: unlike everywhere else, we + * check for now > header->rdh_ttl instead + * of now >= header->rdh_ttl. This allows + * ANY and RRSIG queries for 0 TTL + * rdatasets to work. + */ + if (NONEXISTENT(header) || + (now != 0 && now > header->rdh_ttl)) + 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; + isc_stdtime_t now; + rbtdb_rdatatype_t type, negtype; + dns_rdatatype_t rdtype, covers; + + header = rbtiterator->current; + if (header == NULL) + return (ISC_R_NOMORE); + + if (IS_CACHE(rbtdb)) { + serial = 1; + now = rbtiterator->common.now; + } else { + serial = rbtversion->serial; + now = 0; + } + + 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); + for (header = header->next; header != NULL; header = top_next) { + top_next = header->next; + /* + * If not walking back up the down list. + */ + if (header->type != type && header->type != negtype) { + do { + if (header->serial <= serial && + !IGNORE(header)) { + /* + * Is this a "this rdataset doesn't + * exist" record? + * + * Note: unlike everywhere else, we + * check for now > header->ttl instead + * of now >= header->ttl. This allows + * ANY and RRSIG queries for 0 TTL + * rdatasets to work. + */ + if (NONEXISTENT(header) || + (now != 0 && now > header->rdh_ttl)) + 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 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, + rdataset); + + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); +} + + +/* + * Database Iterator Methods + */ + +static inline 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 inline 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 occurence 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 inline 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, 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; + + NODE_STRONGLOCK(&rbtdb->node_locks[node->locknum].lock); + new_reference(rbtdb, node); + NODE_STRONGUNLOCK(&rbtdb->node_locks[node->locknum].lock); + + *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) { + unsigned int refs; + + rbtdbiter->deletions[rbtdbiter->delcnt++] = node; + NODE_STRONGLOCK(&rbtdb->node_locks[node->locknum].lock); + dns_rbtnode_refincrement(node, &refs); + INSIST(refs != 0); + NODE_STRONGUNLOCK(&rbtdb->node_locks[node->locknum].lock); + } + } + + 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); + + return (dns_name_copy(origin, name, NULL)); +} + +/*% + * Additional cache routines. + */ +static isc_result_t +rdataset_getadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, dns_acache_t *acache, + dns_zone_t **zonep, dns_db_t **dbp, + dns_dbversion_t **versionp, dns_dbnode_t **nodep, + dns_name_t *fname, dns_message_t *msg, + isc_stdtime_t now) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + unsigned int current_count = rdataset->privateuint4; + unsigned int count; + rdatasetheader_t *header; + nodelock_t *nodelock; + unsigned int total_count; + acachectl_t *acarray; + dns_acacheentry_t *entry; + isc_result_t result; + + UNUSED(qtype); /* we do not use this value at least for now */ + UNUSED(acache); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = raw[0] * 256 + raw[1]; + INSIST(total_count > current_count); + count = total_count - current_count - 1; + + acarray = NULL; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + NODE_LOCK(nodelock, isc_rwlocktype_read); + + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromcache: + acarray = NULL; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + if (type != dns_rdatasetadditional_fromcache) + dns_acache_countquerymiss(acache); + NODE_UNLOCK(nodelock, isc_rwlocktype_read); + return (ISC_R_NOTFOUND); + } + + if (acarray[count].entry == NULL) { + dns_acache_countquerymiss(acache); + NODE_UNLOCK(nodelock, isc_rwlocktype_read); + return (ISC_R_NOTFOUND); + } + + entry = NULL; + dns_acache_attachentry(acarray[count].entry, &entry); + + NODE_UNLOCK(nodelock, isc_rwlocktype_read); + + result = dns_acache_getentry(entry, zonep, dbp, versionp, + nodep, fname, msg, now); + + dns_acache_detachentry(&entry); + + return (result); +} + +static void +acache_callback(dns_acacheentry_t *entry, void **arg) { + dns_rbtdb_t *rbtdb; + dns_rbtnode_t *rbtnode; + nodelock_t *nodelock; + acachectl_t *acarray = NULL; + acache_cbarg_t *cbarg; + unsigned int count; + + REQUIRE(arg != NULL); + cbarg = *arg; + + /* + * The caller must hold the entry lock. + */ + + rbtdb = (dns_rbtdb_t *)cbarg->db; + rbtnode = (dns_rbtnode_t *)cbarg->node; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + NODE_LOCK(nodelock, isc_rwlocktype_write); + + switch (cbarg->type) { + case dns_rdatasetadditional_fromauth: + acarray = cbarg->header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = cbarg->header->additional_glue; + break; + default: + INSIST(0); + } + + count = cbarg->count; + if (acarray != NULL && acarray[count].entry == entry) { + acarray[count].entry = NULL; + INSIST(acarray[count].cbarg == cbarg); + acarray[count].cbarg = NULL; + isc_mem_put(rbtdb->common.mctx, cbarg, sizeof(acache_cbarg_t)); + dns_acache_detachentry(&entry); + } + + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + + dns_db_detachnode((dns_db_t *)rbtdb, (dns_dbnode_t **)(void*)&rbtnode); + dns_db_detach((dns_db_t **)(void*)&rbtdb); + + *arg = NULL; +} + +static void +acache_cancelentry(isc_mem_t *mctx, dns_acacheentry_t *entry, + acache_cbarg_t **cbargp) +{ + acache_cbarg_t *cbarg; + + REQUIRE(mctx != NULL); + REQUIRE(entry != NULL); + REQUIRE(cbargp != NULL && *cbargp != NULL); + + cbarg = *cbargp; + + if (dns_acache_cancelentry(entry)) { + dns_db_detachnode(cbarg->db, &cbarg->node); + dns_db_detach(&cbarg->db); + } + + isc_mem_put(mctx, cbarg, sizeof(acache_cbarg_t)); + + *cbargp = NULL; +} + +static isc_result_t +rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, dns_acache_t *acache, + dns_zone_t *zone, dns_db_t *db, + dns_dbversion_t *version, dns_dbnode_t *node, + dns_name_t *fname) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + unsigned int current_count = rdataset->privateuint4; + rdatasetheader_t *header; + unsigned int total_count, count; + nodelock_t *nodelock; + isc_result_t result; + acachectl_t *acarray; + dns_acacheentry_t *newentry, *oldentry = NULL; + acache_cbarg_t *newcbarg, *oldcbarg = NULL; + + UNUSED(qtype); + + if (type == dns_rdatasetadditional_fromcache) + return (ISC_R_SUCCESS); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = raw[0] * 256 + raw[1]; + INSIST(total_count > current_count); + count = total_count - current_count - 1; /* should be private data */ + + newcbarg = isc_mem_get(rbtdb->common.mctx, sizeof(*newcbarg)); + if (newcbarg == NULL) + return (ISC_R_NOMEMORY); + newcbarg->type = type; + newcbarg->count = count; + newcbarg->header = header; + newcbarg->db = NULL; + dns_db_attach((dns_db_t *)rbtdb, &newcbarg->db); + newcbarg->node = NULL; + dns_db_attachnode((dns_db_t *)rbtdb, (dns_dbnode_t *)rbtnode, + &newcbarg->node); + newentry = NULL; + result = dns_acache_createentry(acache, (dns_db_t *)rbtdb, + acache_callback, newcbarg, &newentry); + if (result != ISC_R_SUCCESS) + goto fail; + + /* Set cache data in the new entry. */ + result = dns_acache_setentry(acache, newentry, zone, db, + version, node, fname); + if (result != ISC_R_SUCCESS) + goto fail; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + NODE_LOCK(nodelock, isc_rwlocktype_write); + + acarray = NULL; + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + unsigned int i; + + acarray = isc_mem_get(rbtdb->common.mctx, total_count * + sizeof(acachectl_t)); + + if (acarray == NULL) { + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + goto fail; + } + + for (i = 0; i < total_count; i++) { + acarray[i].entry = NULL; + acarray[i].cbarg = NULL; + } + } + switch (type) { + case dns_rdatasetadditional_fromauth: + header->additional_auth = acarray; + break; + case dns_rdatasetadditional_fromglue: + header->additional_glue = acarray; + break; + default: + INSIST(0); + } + + if (acarray[count].entry != NULL) { + /* + * Swap the entry. Delay cleaning-up the old entry since + * it would require a node lock. + */ + oldentry = acarray[count].entry; + INSIST(acarray[count].cbarg != NULL); + oldcbarg = acarray[count].cbarg; + } + acarray[count].entry = newentry; + acarray[count].cbarg = newcbarg; + + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + + if (oldentry != NULL) { + acache_cancelentry(rbtdb->common.mctx, oldentry, &oldcbarg); + dns_acache_detachentry(&oldentry); + } + + return (ISC_R_SUCCESS); + + fail: + if (newcbarg != NULL) { + if (newentry != NULL) { + acache_cancelentry(rbtdb->common.mctx, newentry, + &newcbarg); + dns_acache_detachentry(&newentry); + } else { + dns_db_detachnode((dns_db_t *)rbtdb, &newcbarg->node); + dns_db_detach(&newcbarg->db); + isc_mem_put(rbtdb->common.mctx, newcbarg, + sizeof(*newcbarg)); + } + } + + return (result); +} + +static isc_result_t +rdataset_putadditional(dns_acache_t *acache, dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, dns_rdatatype_t qtype) +{ + dns_rbtdb_t *rbtdb = rdataset->private1; + dns_rbtnode_t *rbtnode = rdataset->private2; + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + unsigned int current_count = rdataset->privateuint4; + rdatasetheader_t *header; + nodelock_t *nodelock; + unsigned int total_count, count; + acachectl_t *acarray; + dns_acacheentry_t *entry; + acache_cbarg_t *cbarg; + + UNUSED(qtype); /* we do not use this value at least for now */ + UNUSED(acache); + + if (type == dns_rdatasetadditional_fromcache) + return (ISC_R_SUCCESS); + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + + total_count = raw[0] * 256 + raw[1]; + INSIST(total_count > current_count); + count = total_count - current_count - 1; + + acarray = NULL; + entry = NULL; + + nodelock = &rbtdb->node_locks[rbtnode->locknum].lock; + NODE_LOCK(nodelock, isc_rwlocktype_write); + + switch (type) { + case dns_rdatasetadditional_fromauth: + acarray = header->additional_auth; + break; + case dns_rdatasetadditional_fromglue: + acarray = header->additional_glue; + break; + default: + INSIST(0); + } + + if (acarray == NULL) { + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + return (ISC_R_NOTFOUND); + } + + entry = acarray[count].entry; + if (entry == NULL) { + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + return (ISC_R_NOTFOUND); + } + + acarray[count].entry = NULL; + cbarg = acarray[count].cbarg; + acarray[count].cbarg = NULL; + + NODE_UNLOCK(nodelock, isc_rwlocktype_write); + + if (entry != NULL) { + if (cbarg != NULL) + acache_cancelentry(rbtdb->common.mctx, entry, &cbarg); + dns_acache_detachentry(&entry); + } + + 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 (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a) { + header->upper[i/8] |= 1 << (i%8); + fully_lower = false; + } + header->attributes |= RDATASET_ATTR_CASESET; + if (ISC_LIKELY(fully_lower)) + header->attributes |= RDATASET_ATTR_CASEFULLYLOWER; +} + +static void +rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) { + unsigned char *raw = rdataset->private3; /* RDATASLAB */ + rdatasetheader_t *header; + + header = (struct rdatasetheader *)(raw - sizeof(*header)); + setownercase(header, name); +} + +static const unsigned char charmask[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +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 +}; + +static void +rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) { + const unsigned char *raw = rdataset->private3; /* RDATASLAB */ + const rdatasetheader_t *header; + unsigned int i, j; + unsigned char bits; + unsigned char c, flip; + + header = (const struct rdatasetheader *)(raw - sizeof(*header)); + + if (!CASESET(header)) + return; + +#if 0 + /* + * This was the original code, and is implemented differently in + * the #else block that follows. + */ + 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 && + (header->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 && + (header->upper[i/8] & (1 << (i%8))) == 0) + name->ndata[i] |= 0x20; /* set the lower case bit */ + } +#else + if (ISC_LIKELY(CASEFULLYLOWER(header))) { + unsigned char *bp, *be; + bp = name->ndata; + be = bp + name->length; + + while (bp <= be - 4) { + c = bp[0]; + bp[0] = maptolower[c]; + c = bp[1]; + bp[1] = maptolower[c]; + c = bp[2]; + bp[2] = maptolower[c]; + c = bp[3]; + bp[3] = maptolower[c]; + bp += 4; + } + while (bp < be) { + c = *bp; + *bp++ = maptolower[c]; + } + return; + } + + i = 0; + for (j = 0; j < (name->length >> 3); j++) { + unsigned int k; + + bits = ~(header->upper[j]); + + for (k = 0; k < 8; k++) { + c = name->ndata[i]; + flip = (bits & 1) << 5; + flip ^= c; + flip &= charmask[c]; + name->ndata[i] ^= flip; + + i++; + bits >>= 1; + } + } + + if (ISC_UNLIKELY(i == name->length)) + return; + + bits = ~(header->upper[j]); + + for (; i < name->length; i++) { + c = name->ndata[i]; + flip = (bits & 1) << 5; + flip ^= c; + flip &= charmask[c]; + name->ndata[i] ^= flip; + + bits >>= 1; + } +#endif +} + +/*% + * 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 inline bool +need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) { + if ((header->attributes & + (RDATASET_ATTR_NONEXISTENT | + RDATASET_ATTR_STALE | + 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 60 seconds have passed + * since the previous update time. + */ + return (header->last_used + 60 <= now); + } + + /* Other records are updated if 5 minutes have passed. */ + return (header->last_used + 300 <= now); +#else + UNUSED(now); + + return (true); +#endif +} + +/*% + * 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); +} + +/*% + * Purge some expired and/or stale (i.e. unused for some period) cache entries + * under an overmem condition. To recover from this condition quickly, up to + * 2 entries will be purged. 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, + isc_stdtime_t now, bool tree_locked) +{ + rdatasetheader_t *header, *header_prev; + unsigned int locknum; + int purgecount = 2; + + for (locknum = (locknum_start + 1) % rbtdb->node_lock_count; + locknum != locknum_start && purgecount > 0; + locknum = (locknum + 1) % rbtdb->node_lock_count) { + NODE_LOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + + header = isc_heap_element(rbtdb->heaps[locknum], 1); + if (header && header->rdh_ttl < now - RBTDB_VIRTUAL) { + expire_header(rbtdb, header, tree_locked, + expire_ttl); + purgecount--; + } + + for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); + header != NULL && purgecount > 0; + 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); + expire_header(rbtdb, header, tree_locked, + expire_lru); + purgecount--; + } + + 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_stale_header(rbtdb, header); + + /* + * Caller must hold the node (write) lock. + */ + + if (dns_rbtnode_refcurrent(header->node) == 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); + 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..a7cb42b --- /dev/null +++ b/lib/dns/rbtdb.h @@ -0,0 +1,50 @@ +/* + * 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 http://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, 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/rbtdb64.c b/lib/dns/rbtdb64.c new file mode 100644 index 0000000..6f4a659 --- /dev/null +++ b/lib/dns/rbtdb64.c @@ -0,0 +1,17 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: rbtdb64.c,v 1.11 2007/06/19 23:47:16 tbox Exp $ */ + +/*! \file */ + +#define DNS_RBTDB_VERSION64 1 +#include "rbtdb.c" diff --git a/lib/dns/rbtdb64.h b/lib/dns/rbtdb64.h new file mode 100644 index 0000000..faf85e5 --- /dev/null +++ b/lib/dns/rbtdb64.h @@ -0,0 +1,39 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* $Id: rbtdb64.h,v 1.17 2007/06/19 23:47:16 tbox Exp $ */ + +#ifndef DNS_RBTDB64_H +#define DNS_RBTDB64_H 1 + +#include + +/***** + ***** Module Info + *****/ + +/*! \file + * \brief + * DNS Red-Black Tree DB Implementation with 64-bit version numbers + */ + +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +dns_rbtdb64_create(isc_mem_t *mctx, dns_name_t *base, dns_dbtype_t type, + dns_rdataclass_t rdclass, unsigned int argc, char *argv[], + void *driverarg, dns_db_t **dbp); + +ISC_LANG_ENDDECLS + +#endif /* DNS_RBTDB64_H */ diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c new file mode 100644 index 0000000..6a5948e --- /dev/null +++ b/lib/dns/rcode.c @@ -0,0 +1,593 @@ +/* + * 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 http://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 + +#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 */ + +#ifndef PK11_MD5_DISABLE +#define MD5_SECALGNAMES \ + { DNS_KEYALG_RSAMD5, "RSAMD5", 0 }, \ + { DNS_KEYALG_RSAMD5, "RSA", 0 }, +#else +#define MD5_SECALGNAMES +#endif +#ifndef PK11_DH_DISABLE +#define DH_SECALGNAMES \ + { DNS_KEYALG_DH, "DH", 0 }, +#else +#define DH_SECALGNAMES +#endif +#ifndef PK11_DSA_DISABLE +#define DSA_SECALGNAMES \ + { DNS_KEYALG_DSA, "DSA", 0 }, \ + { DNS_KEYALG_NSEC3DSA, "NSEC3DSA", 0 }, +#else +#define DSA_SECALGNAMES +#endif + +#define SECALGNAMES \ + MD5_SECALGNAMES \ + DH_SECALGNAMES \ + DSA_SECALGNAMES \ + { DNS_KEYALG_ECC, "ECC", 0 }, \ + { DNS_KEYALG_RSASHA1, "RSASHA1", 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_ECDSA384, "ECDSAP384SHA384", 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_SHA256, "SHA-256", 0 }, \ + { DNS_DSDIGEST_GOST, "GOST", 0 }, \ + { DNS_DSDIGEST_SHA384, "SHA-384", 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]; + + if (! isdigit(source->base[0] & 0xff) || + 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. + */ + snprintf(buffer, sizeof(buffer), "%.*s", + (int)source->length, source->base); + + 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, mask; + + 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; + value = mask = 0; + + 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"); +#endif + mask |= p->mask; + 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..93f5559 --- /dev/null +++ b/lib/dns/rdata.c @@ -0,0 +1,2432 @@ +/* + * 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 http://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 + +#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, dns_name_t *origin, \ + unsigned int options, isc_buffer_t *target, \ + dns_rdatacallbacks_t *callbacks + +#define ARGS_TOTEXT dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, \ + isc_buffer_t *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 ARGS_TOWIRE dns_rdata_t *rdata, dns_compress_t *cctx, \ + isc_buffer_t *target + +#define ARGS_COMPARE const dns_rdata_t *rdata1, const dns_rdata_t *rdata2 + +#define ARGS_FROMSTRUCT int rdclass, dns_rdatatype_t type, \ + void *source, isc_buffer_t *target + +#define ARGS_TOSTRUCT const dns_rdata_t *rdata, void *target, isc_mem_t *mctx + +#define ARGS_FREESTRUCT void *source + +#define ARGS_ADDLDATA dns_rdata_t *rdata, dns_additionaldatafunc_t add, \ + void *arg + +#define ARGS_DIGEST dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg + +#define ARGS_CHECKOWNER dns_name_t *name, dns_rdataclass_t rdclass, \ + dns_rdatatype_t type, bool wildcard + +#define ARGS_CHECKNAMES dns_rdata_t *rdata, dns_name_t *owner, dns_name_t *bad + + +/*% + * Context structure for the totext_ functions. + * Contains formatting options for rdata-to-text + * conversion. + */ +typedef struct dns_rdata_textctx { + dns_name_t *origin; /*%< Current origin, or NULL. */ + unsigned int 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 +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, dns_name_t *origin, dns_name_t *target); + +static unsigned int +name_length(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, 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(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 isc_result_t +btoa_totext(unsigned char *inbuf, int inbuflen, isc_buffer_t *target); + +static isc_result_t +atob_tobuffer(isc_lex_t *lexer, isc_buffer_t *target); + +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(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 inline isc_result_t +generic_fromtext_key(ARGS_FROMTEXT); + +static inline isc_result_t +generic_totext_key(ARGS_TOTEXT); + +static inline isc_result_t +generic_fromwire_key(ARGS_FROMWIRE); + +static inline isc_result_t +generic_fromstruct_key(ARGS_FROMSTRUCT); + +static inline isc_result_t +generic_tostruct_key(ARGS_TOSTRUCT); + +static inline 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); + +/*% INT16 Size */ +#define NS_INT16SZ 2 +/*% IPv6 Address Size */ +#define NS_LOCATORSZ 8 + +/* + * Active Diretory 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 inline 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 inline int +getquad(const void *src, struct in_addr *dst, + isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks) +{ + int result; + struct in_addr tmp; + + result = inet_aton(src, dst); + if (result == 1 && callbacks != NULL && + inet_pton(AF_INET, src, &tmp) != 1) { + const char *name = isc_lex_getsourcename(lexer); + if (name == NULL) + name = "UNKNOWN"; + (*callbacks->warn)(callbacks, "%s:%lu: \"%s\" " + "is not a decimal dotted quad", name, + isc_lex_getsourceline(lexer), src); + } + return (result); +} + +static inline isc_result_t +name_duporclone(dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) { + + if (mctx != NULL) + return (dns_name_dup(source, mctx, target)); + dns_name_clone(source, target); + return (ISC_R_SUCCESS); +} + +static inline 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); + if (copy != NULL) + memmove(copy, source, length); + + return (copy); +} + +static inline 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 inline 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); + result = isc_buffer_allocate(mctx, &buf, token.value.as_ulong); + if (result != ISC_R_SUCCESS) + return (result); + + 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, + 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) == true); + 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, + false); + 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) == true); + + /* + * 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, 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, dns_name_t *origin, + unsigned int 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)); + + TOSTRUCTSWITCH + + if (use_default) + (void)NULL; + + return (result); +} + +void +dns_rdata_freestruct(void *source) { + dns_rdatacommon_t *common = source; + REQUIRE(source != 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(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, 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(dns_name_t *name) { + return (name->length); +} + +static isc_result_t +txt_totext(isc_region_t *source, bool quote, 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 == true); + + if (quote) { + if (tl < 1) + return (ISC_R_NOSPACE); + *tp++ = '"'; + tl--; + } + while (n--) { + /* + * \DDD space (0x20) if not quoting. + */ + if (*sp < (quote ? 0x20 : 0x21) || *sp >= 0x7f) { + if (tl < 4) + return (ISC_R_NOSPACE); + *tp++ = 0x5c; + *tp++ = 0x30 + ((*sp / 100) % 10); + *tp++ = 0x30 + ((*sp / 10) % 10); + *tp++ = 0x30 + (*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. + */ + if (*sp == 0x22 || *sp == 0x5c || + (!quote && (*sp == 0x40 || *sp == 0x3b))) { + if (tl < 2) + return (ISC_R_NOSPACE); + *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_fromtext(isc_textregion_t *source, isc_buffer_t *target) { + isc_region_t tregion; + bool escape; + 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; + escape = false; + 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; + if (nrem == 0) + return ((tregion.length <= 256U) ? + ISC_R_NOSPACE : DNS_R_SYNTAX); + *t++ = c; + nrem--; + } + if (escape) + 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_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 < 0x20 || *sp >= 0x7f) { + if (tl < 4) + return (ISC_R_NOSPACE); + *tp++ = 0x5c; + *tp++ = 0x30 + ((*sp / 100) % 10); + *tp++ = 0x30 + ((*sp / 10) % 10); + *tp++ = 0x30 + (*sp % 10); + sp++; + tl -= 4; + continue; + } + /* double quote, backslash */ + if (*sp == 0x22 || *sp == 0x5c) { + 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, 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, 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); + 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(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 const char atob_digits[86] = + "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" \ + "abcdefghijklmnopqrstu"; +/* + * Subroutines to convert between 8 bit binary bytes and printable ASCII. + * Computes the number of bytes, and three kinds of simple checksums. + * Incoming bytes are collected into 32-bit words, then printed in base 85: + * exp(85,5) > exp(2,32) + * The ASCII characters used are between '!' and 'u'; + * 'z' encodes 32-bit zero; 'x' is used to mark the end of encoded data. + * + * Originally by Paul Rutter (philabs!per) and Joe Orost (petsd!joe) for + * the atob/btoa programs, released with the compress program, in mod.sources. + * Modified by Mike Schwartz 8/19/86 for use in BIND. + * Modified to be re-entrant 3/2/99. + */ + + +struct state { + int32_t Ceor; + int32_t Csum; + int32_t Crot; + int32_t word; + int32_t bcount; +}; + +#define Ceor state->Ceor +#define Csum state->Csum +#define Crot state->Crot +#define word state->word +#define bcount state->bcount + +#define times85(x) ((((((x<<2)+x)<<2)+x)<<2)+x) + +static isc_result_t byte_atob(int c, isc_buffer_t *target, + struct state *state); +static isc_result_t putbyte(int c, isc_buffer_t *, struct state *state); +static isc_result_t byte_btoa(int c, isc_buffer_t *, struct state *state); + +/* + * Decode ASCII-encoded byte c into binary representation and + * place into *bufp, advancing bufp. + */ +static isc_result_t +byte_atob(int c, isc_buffer_t *target, struct state *state) { + const char *s; + if (c == 'z') { + if (bcount != 0) + return(DNS_R_SYNTAX); + else { + RETERR(putbyte(0, target, state)); + RETERR(putbyte(0, target, state)); + RETERR(putbyte(0, target, state)); + RETERR(putbyte(0, target, state)); + } + } else if ((s = strchr(atob_digits, c)) != NULL) { + if (bcount == 0) { + word = (int32_t)(s - atob_digits); + ++bcount; + } else if (bcount < 4) { + word = times85(word); + word += (int32_t)(s - atob_digits); + ++bcount; + } else { + word = times85(word); + word += (int32_t)(s - atob_digits); + RETERR(putbyte((word >> 24) & 0xff, target, state)); + RETERR(putbyte((word >> 16) & 0xff, target, state)); + RETERR(putbyte((word >> 8) & 0xff, target, state)); + RETERR(putbyte(word & 0xff, target, state)); + word = 0; + bcount = 0; + } + } else + return(DNS_R_SYNTAX); + return(ISC_R_SUCCESS); +} + +/* + * Compute checksum info and place c into target. + */ +static isc_result_t +putbyte(int c, isc_buffer_t *target, struct state *state) { + isc_region_t tr; + + Ceor ^= c; + Csum += c; + Csum += 1; + if ((Crot & 0x80000000)) { + Crot <<= 1; + Crot += 1; + } else { + Crot <<= 1; + } + Crot += c; + isc_buffer_availableregion(target, &tr); + if (tr.length < 1) + return (ISC_R_NOSPACE); + tr.base[0] = c; + isc_buffer_add(target, 1); + return (ISC_R_SUCCESS); +} + +/* + * Read the ASCII-encoded data from inbuf, of length inbuflen, and convert + * it into T_UNSPEC (binary data) in outbuf, not to exceed outbuflen bytes; + * outbuflen must be divisible by 4. (Note: this is because outbuf is filled + * in 4 bytes at a time. If the actual data doesn't end on an even 4-byte + * boundary, there will be no problem...it will be padded with 0 bytes, and + * numbytes will indicate the correct number of bytes. The main point is + * that since the buffer is filled in 4 bytes at a time, even if there is + * not a full 4 bytes of data at the end, there has to be room to 0-pad the + * data, so the buffer must be of size divisible by 4). Place the number of + * output bytes in numbytes, and return a failure/success status. + */ + +static isc_result_t +atob_tobuffer(isc_lex_t *lexer, isc_buffer_t *target) { + long oeor, osum, orot; + struct state statebuf, *state= &statebuf; + isc_token_t token; + char c; + char *e; + + Ceor = Csum = Crot = word = bcount = 0; + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + while (token.value.as_textregion.length != 0) { + if ((c = token.value.as_textregion.base[0]) == 'x') { + break; + } else + RETERR(byte_atob(c, target, state)); + isc_textregion_consume(&token.value.as_textregion, 1); + } + + /* + * Number of bytes. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if ((token.value.as_ulong % 4) != 0U) { + unsigned long padding = 4 - (token.value.as_ulong % 4); + if (isc_buffer_usedlength(target) < padding) + return (DNS_R_SYNTAX); + isc_buffer_subtract(target, padding); + } + + /* + * Checksum. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + oeor = strtol(DNS_AS_STR(token), &e, 16); + if (*e != 0) + return (DNS_R_SYNTAX); + + /* + * Checksum. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + osum = strtol(DNS_AS_STR(token), &e, 16); + if (*e != 0) + return (DNS_R_SYNTAX); + + /* + * Checksum. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + orot = strtol(DNS_AS_STR(token), &e, 16); + if (*e != 0) + return (DNS_R_SYNTAX); + + if ((oeor != Ceor) || (osum != Csum) || (orot != Crot)) + return(DNS_R_BADCKSUM); + return (ISC_R_SUCCESS); +} + +/* + * Encode binary byte c into ASCII representation and place into *bufp, + * advancing bufp. + */ +static isc_result_t +byte_btoa(int c, isc_buffer_t *target, struct state *state) { + isc_region_t tr; + + isc_buffer_availableregion(target, &tr); + Ceor ^= c; + Csum += c; + Csum += 1; + if ((Crot & 0x80000000)) { + Crot <<= 1; + Crot += 1; + } else { + Crot <<= 1; + } + Crot += c; + + word <<= 8; + word |= c; + if (bcount == 3) { + if (word == 0) { + if (tr.length < 1) + return (ISC_R_NOSPACE); + tr.base[0] = 'z'; + isc_buffer_add(target, 1); + } else { + register int tmp = 0; + register int32_t tmpword = word; + + if (tmpword < 0) { + /* + * Because some don't support u_long. + */ + tmp = 32; + tmpword -= (int32_t)(85 * 85 * 85 * 85 * 32); + } + if (tmpword < 0) { + tmp = 64; + tmpword -= (int32_t)(85 * 85 * 85 * 85 * 32); + } + if (tr.length < 5) + return (ISC_R_NOSPACE); + tr.base[0] = atob_digits[(tmpword / + (int32_t)(85 * 85 * 85 * 85)) + + tmp]; + tmpword %= (int32_t)(85 * 85 * 85 * 85); + tr.base[1] = atob_digits[tmpword / (85 * 85 * 85)]; + tmpword %= (85 * 85 * 85); + tr.base[2] = atob_digits[tmpword / (85 * 85)]; + tmpword %= (85 * 85); + tr.base[3] = atob_digits[tmpword / 85]; + tmpword %= 85; + tr.base[4] = atob_digits[tmpword]; + isc_buffer_add(target, 5); + } + bcount = 0; + } else { + bcount += 1; + } + return (ISC_R_SUCCESS); +} + + +/* + * Encode the binary data from inbuf, of length inbuflen, into a + * target. Return success/failure status + */ +static isc_result_t +btoa_totext(unsigned char *inbuf, int inbuflen, isc_buffer_t *target) { + int inc; + struct state statebuf, *state = &statebuf; + char buf[sizeof("x 2000000000 ffffffff ffffffff ffffffff")]; + + Ceor = Csum = Crot = word = bcount = 0; + for (inc = 0; inc < inbuflen; inbuf++, inc++) + RETERR(byte_btoa(*inbuf, target, state)); + + while (bcount != 0) + RETERR(byte_btoa(0, target, state)); + + /* + * Put byte count and checksum information at end of buffer, + * delimited by 'x' + */ + snprintf(buf, sizeof(buf), "x %d %x %x %x", inbuflen, Ceor, Csum, Crot); + return (str_totext(buf, target)); +} + + +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(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_atparent(dns_rdatatype_t type) { + if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) != 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_DNSSEC | 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..5cb24b2 --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.c @@ -0,0 +1,598 @@ +/* + * 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 http://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 inline 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 = isc_string_touint64(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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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 inline void +freestruct_any_tsig(ARGS_FREESTRUCT) { + dns_rdata_any_tsig_t *tsig = (dns_rdata_any_tsig_t *) source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..6ace1d2 --- /dev/null +++ b/lib/dns/rdata/any_255/tsig_250.h @@ -0,0 +1,31 @@ +/* + * 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 http://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..94cae92 --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.c @@ -0,0 +1,314 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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 inline void +freestruct_ch_a(ARGS_FREESTRUCT) { + dns_rdata_ch_a_t *a = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..a969534 --- /dev/null +++ b/lib/dns/rdata/ch_3/a_1.h @@ -0,0 +1,28 @@ +/* + * 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 http://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..812dfa6 --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.c @@ -0,0 +1,304 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_afsdb(ARGS_FROMSTRUCT) { + dns_rdata_afsdb_t *afsdb = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_afsdb); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_afsdb(ARGS_FREESTRUCT) { + dns_rdata_afsdb_t *afsdb = source; + + REQUIRE(source != NULL); + REQUIRE(afsdb->common.rdtype == dns_rdatatype_afsdb); + + if (afsdb->mctx == NULL) + return; + + dns_name_free(&afsdb->server, afsdb->mctx); + afsdb->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_afsdb(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_afsdb); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..24da1aa --- /dev/null +++ b/lib/dns/rdata/generic/afsdb_18.h @@ -0,0 +1,27 @@ +/* + * 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 http://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/avc_258.c b/lib/dns/rdata/generic/avc_258.c new file mode 100644 index 0000000..31bbaff --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.c @@ -0,0 +1,163 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_avc(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_avc); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + return (generic_fromtext_txt(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_avc(ARGS_TOTEXT) { + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_avc); + + return (generic_totext_txt(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_avc(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_avc); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + return (generic_fromwire_txt(rdclass, type, source, dctx, options, + target)); +} + +static inline isc_result_t +towire_avc(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline isc_result_t +fromstruct_avc(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_avc); + + return (generic_fromstruct_txt(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_avc(ARGS_TOSTRUCT) { + dns_rdata_avc_t *avc = target; + + REQUIRE(rdata->type == dns_rdatatype_avc); + REQUIRE(target != NULL); + + avc->common.rdclass = rdata->rdclass; + avc->common.rdtype = rdata->type; + ISC_LINK_INIT(&avc->common, link); + + return (generic_tostruct_txt(rdata, target, mctx)); +} + +static inline void +freestruct_avc(ARGS_FREESTRUCT) { + dns_rdata_avc_t *txt = source; + + REQUIRE(source != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_avc); + + generic_freestruct_txt(source); +} + +static inline isc_result_t +additionaldata_avc(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_avc(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_avc); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_avc(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_avc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..88abadd --- /dev/null +++ b/lib/dns/rdata/generic/avc_258.h @@ -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 http://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..4dc3639 --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.c @@ -0,0 +1,365 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 + */ + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline 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 inline 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 inline 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(source != 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 inline isc_result_t +tostruct_caa(ARGS_TOSTRUCT) { + dns_rdata_caa_t *caa = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_caa); + REQUIRE(target != 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 inline void +freestruct_caa(ARGS_FREESTRUCT) { + dns_rdata_caa_t *caa = (dns_rdata_caa_t *) source; + + REQUIRE(source != 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 inline 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 inline 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 inline bool +checkowner_caa(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_caa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..c16e427 --- /dev/null +++ b/lib/dns/rdata/generic/caa_257.h @@ -0,0 +1,26 @@ +/* + * 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 http://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..afcdbc2 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.c @@ -0,0 +1,170 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_cdnskey(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromtext_key(rdclass, type, lexer, origin, + options, target, callbacks)); +} + +static inline isc_result_t +totext_cdnskey(ARGS_TOTEXT) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + return (generic_totext_key(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_cdnskey(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromwire_key(rdclass, type, source, dctx, + options, target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_cdnskey(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_cdnskey); + + return (generic_fromstruct_key(rdclass, type, source, target)); +} + +static inline 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(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_cdnskey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_cdnskey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_cdnskey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_cdnskey(ARGS_CHECKNAMES) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_cdnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline int +casecompare_cdnskey(ARGS_COMPARE) { + + /* + * Treat ALG 253 (private DNS) subtype name case sensistively. + */ + 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..541a4c2 --- /dev/null +++ b/lib/dns/rdata/generic/cdnskey_60.h @@ -0,0 +1,18 @@ +/* + * 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 http://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..e1ea7ce --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.c @@ -0,0 +1,174 @@ +/* + * 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 http://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 +#include + +#include + +#include "dst_gost.h" + +static inline isc_result_t +fromtext_cds(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromtext_ds(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_cds(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_cds); + + return (generic_totext_ds(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_cds(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromwire_ds(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_cds(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_cds); + + return (generic_fromstruct_ds(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_cds(ARGS_TOSTRUCT) { + dns_rdata_cds_t *cds = target; + + REQUIRE(rdata->type == dns_rdatatype_cds); + REQUIRE(target != 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(rdata, target, mctx)); +} + +static inline void +freestruct_cds(ARGS_FREESTRUCT) { + dns_rdata_cds_t *ds = source; + + REQUIRE(ds != NULL); + REQUIRE(ds->common.rdtype == dns_rdatatype_cds); + + if (ds->mctx == NULL) + return; + + if (ds->digest != NULL) + isc_mem_free(ds->mctx, ds->digest); + ds->mctx = NULL; +} + +static inline isc_result_t +additionaldata_cds(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cds); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_cds(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_cds); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_cds(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_cds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..0797bca --- /dev/null +++ b/lib/dns/rdata/generic/cds_59.h @@ -0,0 +1,18 @@ +/* + * 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 http://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..237a806 --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.c @@ -0,0 +1,277 @@ +/* + * 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 http://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 inline 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, -1)); +} + +static inline 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 inline 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 < 5) + return (ISC_R_UNEXPECTEDEND); + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_cert(ARGS_FROMSTRUCT) { + dns_rdata_cert_t *cert = source; + + REQUIRE(type == dns_rdatatype_cert); + REQUIRE(source != 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 inline isc_result_t +tostruct_cert(ARGS_TOSTRUCT) { + dns_rdata_cert_t *cert = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_cert); + REQUIRE(target != 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 inline 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 inline isc_result_t +additionaldata_cert(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_cert); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_cert(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_cert); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_cert(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_cert); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + + +static inline 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..db760e1 --- /dev/null +++ b/lib/dns/rdata/generic/cert_37.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..3da82eb --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.c @@ -0,0 +1,228 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_cname(ARGS_FROMSTRUCT) { + dns_rdata_cname_t *cname = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_cname); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_cname(ARGS_FREESTRUCT) { + dns_rdata_cname_t *cname = source; + + REQUIRE(source != NULL); + + if (cname->mctx == NULL) + return; + + dns_name_free(&cname->cname, cname->mctx); + cname->mctx = NULL; +} + +static inline isc_result_t +additionaldata_cname(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_cname); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_cname(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_cname); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_cname(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_cname); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..6a5b645 --- /dev/null +++ b/lib/dns/rdata/generic/cname_5.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..c639128 --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.c @@ -0,0 +1,269 @@ +/* + * 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 http://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 inline 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 inline 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 /* inline */ 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 inline 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 inline 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 inline isc_result_t +fromstruct_csync(ARGS_FROMSTRUCT) { + dns_rdata_csync_t *csync = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_csync); + REQUIRE(source != 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 inline isc_result_t +tostruct_csync(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_csync_t *csync = target; + + REQUIRE(rdata->type == dns_rdatatype_csync); + REQUIRE(target != 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 inline void +freestruct_csync(ARGS_FREESTRUCT) { + dns_rdata_csync_t *csync = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_csync(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_csync); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_csync(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_csync); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_csync(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_csync); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..a1f63d5 --- /dev/null +++ b/lib/dns/rdata/generic/csync_62.h @@ -0,0 +1,28 @@ +/* + * 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 http://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..0f05ba8 --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.c @@ -0,0 +1,170 @@ +/* + * 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 http://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 +#include + +#include + +#include "dst_gost.h" + +static inline isc_result_t +fromtext_dlv(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromtext_ds(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_dlv(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_dlv); + + return (generic_totext_ds(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_dlv(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromwire_ds(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_dlv(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_dlv); + + return (generic_fromstruct_ds(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_dlv(ARGS_TOSTRUCT) { + dns_rdata_dlv_t *dlv = target; + + REQUIRE(rdata->type == dns_rdatatype_dlv); + + dlv->common.rdclass = rdata->rdclass; + dlv->common.rdtype = rdata->type; + ISC_LINK_INIT(&dlv->common, link); + + return (generic_tostruct_ds(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_dlv(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_dlv); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_dlv(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_dlv); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_dlv(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_dlv); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..b94df71 --- /dev/null +++ b/lib/dns/rdata/generic/dlv_32769.h @@ -0,0 +1,19 @@ +/* + * 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 http://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..50c5838 --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.c @@ -0,0 +1,228 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_dname(ARGS_FROMSTRUCT) { + dns_rdata_dname_t *dname = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_dname); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_dname(ARGS_FREESTRUCT) { + dns_rdata_dname_t *dname = source; + + REQUIRE(source != NULL); + REQUIRE(dname->common.rdtype == dns_rdatatype_dname); + + if (dname->mctx == NULL) + return; + + dns_name_free(&dname->dname, dname->mctx); + dname->mctx = NULL; +} + +static inline isc_result_t +additionaldata_dname(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_dname); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_dname(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_dname); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_dname(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_dname); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..12b02e2 --- /dev/null +++ b/lib/dns/rdata/generic/dname_39.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..f20a8f6 --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.c @@ -0,0 +1,172 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_dnskey(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromtext_key(rdclass, type, lexer, origin, + options, target, callbacks)); +} + +static inline isc_result_t +totext_dnskey(ARGS_TOTEXT) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + return (generic_totext_key(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_dnskey(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromwire_key(rdclass, type, source, dctx, + options, target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_dnskey(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_dnskey); + + return (generic_fromstruct_key(rdclass, type, source, target)); +} + +static inline 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(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_dnskey(ARGS_ADDLDATA) { + + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_dnskey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_dnskey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_dnskey(ARGS_CHECKNAMES) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_dnskey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline int +casecompare_dnskey(ARGS_COMPARE) { + + /* + * Treat ALG 253 (private DNS) subtype name case sensistively. + */ + 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..ac4a6de --- /dev/null +++ b/lib/dns/rdata/generic/dnskey_48.h @@ -0,0 +1,21 @@ +/* + * 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 http://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..16c0703 --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.c @@ -0,0 +1,358 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_doa(ARGS_FROMSTRUCT) { + dns_rdata_doa_t *doa = source; + + REQUIRE(type == dns_rdatatype_doa); + REQUIRE(source != 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 inline 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(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 inline void +freestruct_doa(ARGS_FREESTRUCT) { + dns_rdata_doa_t *doa = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_doa(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_doa); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_doa(ARGS_CHECKOWNER) { + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + REQUIRE(type == dns_rdatatype_doa); + + return (true); +} + +static inline bool +checknames_doa(ARGS_CHECKNAMES) { + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + REQUIRE(rdata->type == dns_rdatatype_doa); + + return (true); +} + +static inline 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..5ac5fbb --- /dev/null +++ b/lib/dns/rdata/generic/doa_259.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..d25c1a6 --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.c @@ -0,0 +1,400 @@ +/* + * 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 http://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_ATPARENT) + +#include +#include + +#include + +#include "dst_gost.h" + +static inline 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; +#ifdef ISC_GOST_DIGESTLENGTH + case DNS_DSDIGEST_GOST: + length = ISC_GOST_DIGESTLENGTH; + break; +#endif + case DNS_DSDIGEST_SHA384: + length = ISC_SHA384_DIGESTLENGTH; + break; + default: + length = -1; + break; + } + return (isc_hex_tobuffer(lexer, target, length)); +} + +static inline isc_result_t +fromtext_ds(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromtext_ds(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline 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 inline isc_result_t +totext_ds(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_ds); + + return (generic_totext_ds(rdata, tctx, target)); +} + +static inline 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 < 4 || + (sr.base[3] == DNS_DSDIGEST_SHA1 && + sr.length < 4 + ISC_SHA1_DIGESTLENGTH) || + (sr.base[3] == DNS_DSDIGEST_SHA256 && + sr.length < 4 + ISC_SHA256_DIGESTLENGTH) || +#ifdef ISC_GOST_DIGESTLENGTH + (sr.base[3] == DNS_DSDIGEST_GOST && + sr.length < 4 + ISC_GOST_DIGESTLENGTH) || +#endif + (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; +#ifdef ISC_GOST_DIGESTLENGTH + else if (sr.base[3] == DNS_DSDIGEST_GOST) + sr.length = 4 + ISC_GOST_DIGESTLENGTH; +#endif + 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 inline isc_result_t +fromwire_ds(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromwire_ds(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +generic_fromstruct_ds(ARGS_FROMSTRUCT) { + dns_rdata_ds_t *ds = source; + + REQUIRE(source != 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; +#ifdef ISC_GOST_DIGESTLENGTH + case DNS_DSDIGEST_GOST: + REQUIRE(ds->length == ISC_GOST_DIGESTLENGTH); + break; +#endif + 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 inline isc_result_t +fromstruct_ds(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_ds); + + return (generic_fromstruct_ds(rdclass, type, source, target)); +} + +static inline isc_result_t +generic_tostruct_ds(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + isc_region_t region; + + REQUIRE(target != 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 inline isc_result_t +tostruct_ds(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + + REQUIRE(rdata->type == dns_rdatatype_ds); + REQUIRE(target != NULL); + + ds->common.rdclass = rdata->rdclass; + ds->common.rdtype = rdata->type; + ISC_LINK_INIT(&ds->common, link); + + return (generic_tostruct_ds(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_ds(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ds); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_ds(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_ds); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_ds(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_ds); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..0d373ec --- /dev/null +++ b/lib/dns/rdata/generic/ds_43.h @@ -0,0 +1,28 @@ +/* + * 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 http://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; + uint8_t algorithm; + uint8_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..686f23f --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.c @@ -0,0 +1,210 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_eui48(ARGS_FROMSTRUCT) { + dns_rdata_eui48_t *eui48 = source; + + REQUIRE(type == dns_rdatatype_eui48); + REQUIRE(source != 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 inline isc_result_t +tostruct_eui48(ARGS_TOSTRUCT) { + dns_rdata_eui48_t *eui48 = target; + + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(target != 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 inline void +freestruct_eui48(ARGS_FREESTRUCT) { + dns_rdata_eui48_t *eui48 = source; + + REQUIRE(source != NULL); + REQUIRE(eui48->common.rdtype == dns_rdatatype_eui48); + + return; +} + +static inline 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 inline 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 inline bool +checkowner_eui48(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_eui48); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_eui48(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_eui48); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..1bb168a --- /dev/null +++ b/lib/dns/rdata/generic/eui48_108.h @@ -0,0 +1,21 @@ +/* + * 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 http://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..0a37326 --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.c @@ -0,0 +1,215 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_eui64(ARGS_FROMSTRUCT) { + dns_rdata_eui64_t *eui64 = source; + + REQUIRE(type == dns_rdatatype_eui64); + REQUIRE(source != 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 inline isc_result_t +tostruct_eui64(ARGS_TOSTRUCT) { + dns_rdata_eui64_t *eui64 = target; + + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(target != 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 inline void +freestruct_eui64(ARGS_FREESTRUCT) { + dns_rdata_eui64_t *eui64 = source; + + REQUIRE(source != NULL); + REQUIRE(eui64->common.rdtype == dns_rdatatype_eui64); + + return; +} + +static inline 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 inline 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 inline bool +checkowner_eui64(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_eui64); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_eui64(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_eui64); + REQUIRE(rdata->length == 8); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..638c72e --- /dev/null +++ b/lib/dns/rdata/generic/eui64_109.h @@ -0,0 +1,21 @@ +/* + * 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 http://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..6242932 --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.c @@ -0,0 +1,247 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_gpos(ARGS_FROMSTRUCT) { + dns_rdata_gpos_t *gpos = source; + + REQUIRE(type == dns_rdatatype_gpos); + REQUIRE(source != 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 inline isc_result_t +tostruct_gpos(ARGS_TOSTRUCT) { + dns_rdata_gpos_t *gpos = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_gpos); + REQUIRE(target != 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 inline void +freestruct_gpos(ARGS_FREESTRUCT) { + dns_rdata_gpos_t *gpos = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_gpos(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_gpos); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_gpos(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_gpos); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_gpos(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_gpos); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..fac72cd --- /dev/null +++ b/lib/dns/rdata/generic/gpos_27.h @@ -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 http://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..528be5e --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.c @@ -0,0 +1,216 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_hinfo(ARGS_FROMSTRUCT) { + dns_rdata_hinfo_t *hinfo = source; + + REQUIRE(type == dns_rdatatype_hinfo); + REQUIRE(source != 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 inline isc_result_t +tostruct_hinfo(ARGS_TOSTRUCT) { + dns_rdata_hinfo_t *hinfo = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + REQUIRE(target != 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 inline void +freestruct_hinfo(ARGS_FREESTRUCT) { + dns_rdata_hinfo_t *hinfo = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_hinfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_hinfo); + + UNUSED(add); + UNUSED(arg); + UNUSED(rdata); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_hinfo(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_hinfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_hinfo(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_hinfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..323be22 --- /dev/null +++ b/lib/dns/rdata/generic/hinfo_13.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..36c9273 --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.c @@ -0,0 +1,497 @@ +/* + * 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 http://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 inline 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 inline 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; + 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 inline isc_result_t +fromwire_hip(ARGS_FROMWIRE) { + isc_region_t region, rr; + dns_name_t name; + uint8_t hit_len; + uint16_t key_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); + if (region.length < (unsigned) (hit_len + key_len)) + RETERR(DNS_R_FORMERR); + + RETERR(mem_tobuffer(target, rr.base, 4 + hit_len + key_len)); + isc_buffer_forward(source, 4 + hit_len + key_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 inline 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 inline 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 inline 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(source != 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)) + /* empty */; + + return(mem_tobuffer(target, hip->servers, hip->servers_len)); +} + +static inline isc_result_t +tostruct_hip(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_hip_t *hip = target; + + REQUIRE(rdata->type == dns_rdatatype_hip); + REQUIRE(target != 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 inline void +freestruct_hip(ARGS_FREESTRUCT) { + dns_rdata_hip_t *hip = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_hip(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_hip); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_hip(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_hip); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 (ISC_R_SUCCESS); +} + +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 inline 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..12c195c --- /dev/null +++ b/lib/dns/rdata/generic/hip_55.h @@ -0,0 +1,41 @@ +/* + * 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 http://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..f68f0b8 --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.c @@ -0,0 +1,496 @@ +/* + * 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 http://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 inline 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 (getquad(DNS_AS_STR(token), &addr, lexer, callbacks) != 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, -1)); +} + +static inline 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, ®ion, target)); + isc_region_consume(®ion, 4); + break; + + case 2: + RETERR(inet_totext(AF_INET6, ®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 inline 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: + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 1: + if (region.length < 7) + return (ISC_R_UNEXPECTEDEND); + isc_buffer_forward(source, region.length); + return (mem_tobuffer(target, region.base, region.length)); + + case 2: + if (region.length < 19) + 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); + return(mem_tobuffer(target, region.base, region.length)); + + default: + return (ISC_R_NOTIMPLEMENTED); + } +} + +static inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_ipseckey(ARGS_FREESTRUCT) { + dns_rdata_ipseckey_t *ipseckey = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_ipseckey(ARGS_ADDLDATA) { + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_ipseckey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_ipseckey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_ipseckey(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_ipseckey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..e3f5804 --- /dev/null +++ b/lib/dns/rdata/generic/ipseckey_45.h @@ -0,0 +1,29 @@ +/* + * 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 http://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..a73cd1f --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.c @@ -0,0 +1,237 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_isdn(ARGS_FROMSTRUCT) { + dns_rdata_isdn_t *isdn = source; + + REQUIRE(type == dns_rdatatype_isdn); + REQUIRE(source != 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 inline isc_result_t +tostruct_isdn(ARGS_TOSTRUCT) { + dns_rdata_isdn_t *isdn = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_isdn); + REQUIRE(target != 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 inline void +freestruct_isdn(ARGS_FREESTRUCT) { + dns_rdata_isdn_t *isdn = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_isdn(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_isdn); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_isdn(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_isdn); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_isdn(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_isdn); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..48758bb --- /dev/null +++ b/lib/dns/rdata/generic/isdn_20.h @@ -0,0 +1,28 @@ +/* + * 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 http://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..6dd2eab --- /dev/null +++ b/lib/dns/rdata/generic/key_25.c @@ -0,0 +1,436 @@ +/* + * 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 http://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 (0) + +static inline isc_result_t +generic_fromtext_key(ARGS_FROMTEXT) { + isc_result_t result; + isc_token_t token; + dns_secalg_t alg; + dns_secproto_t proto; + dns_keyflags_t flags; + + UNUSED(type); + 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)); + 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 ((flags & 0xc000) == 0xc000) + return (ISC_R_SUCCESS); + + result = isc_base64_tobuffer(lexer, target, -1); + if (result != ISC_R_SUCCESS) + return (result); + + /* Ensure there's at least enough data to compute a key ID for MD5 */ + if (alg == DST_ALG_RSAMD5 && isc_buffer_usedlength(target) < 7) + return (ISC_R_UNEXPECTEDEND); + + return (ISC_R_SUCCESS); +} + +static inline 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 ((flags & 0xc000) == 0xc000) + 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, algorithm)); + 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, algorithm)); + RETERR(str_totext(buf, target)); + } + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +generic_fromwire_key(ARGS_FROMWIRE) { + unsigned char algorithm; + isc_region_t sr; + + UNUSED(type); + UNUSED(rdclass); + UNUSED(dctx); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 4) + return (ISC_R_UNEXPECTEDEND); + + algorithm = sr.base[3]; + RETERR(mem_tobuffer(target, sr.base, 4)); + isc_region_consume(&sr, 4); + isc_buffer_forward(source, 4); + + 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)); + } + + /* + * RSAMD5 computes key ID differently from other + * algorithms: we need to ensure there's enough data + * present for the computation + */ + if (algorithm == DST_ALG_RSAMD5 && sr.length < 3) + return (ISC_R_UNEXPECTEDEND); + + isc_buffer_activeregion(source, &sr); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline isc_result_t +fromtext_key(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromtext_key(rdclass, type, lexer, origin, + options, target, callbacks)); +} + +static inline isc_result_t +totext_key(ARGS_TOTEXT) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + return (generic_totext_key(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_key(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromwire_key(rdclass, type, source, dctx, + options, target)); +} + +static inline 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 inline 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 inline 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); + + /* 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 inline isc_result_t +generic_tostruct_key(ARGS_TOSTRUCT) { + dns_rdata_key_t *key = target; + isc_region_t sr; + + REQUIRE(rdata != 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 inline 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 inline isc_result_t +fromstruct_key(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_key); + + return (generic_fromstruct_key(rdclass, type, source, target)); +} + +static inline 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(rdata, target, mctx)); +} + +static inline 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 inline 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 inline 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 inline bool +checkowner_key(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_key); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_key(ARGS_CHECKNAMES) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_key); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..330363b --- /dev/null +++ b/lib/dns/rdata/generic/key_25.h @@ -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 http://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; + uint8_t protocol; + uint8_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..06b56c1 --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.c @@ -0,0 +1,441 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_keydata(ARGS_FROMTEXT) { + isc_result_t result; + 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)); + + /* No Key? */ + if ((flags & 0xc000) == 0xc000) + return (ISC_R_SUCCESS); + + result = isc_base64_tobuffer(lexer, target, -1); + if (result != ISC_R_SUCCESS) + return (result); + + /* Ensure there's at least enough data to compute a key ID for MD5 */ + if (alg == DST_ALG_RSAMD5 && isc_buffer_usedlength(target) < 19) + return (ISC_R_UNEXPECTEDEND); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +totext_keydata(ARGS_TOTEXT) { + isc_region_t sr; + char buf[sizeof("64000")]; + unsigned int flags; + unsigned char 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) + 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 ((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, algorithm)); + 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_keydata(ARGS_FROMSTRUCT) { + dns_rdata_keydata_t *keydata = source; + + REQUIRE(type == dns_rdatatype_keydata); + REQUIRE(source != 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 inline isc_result_t +tostruct_keydata(ARGS_TOSTRUCT) { + dns_rdata_keydata_t *keydata = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_keydata); + REQUIRE(target != 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 inline void +freestruct_keydata(ARGS_FREESTRUCT) { + dns_rdata_keydata_t *keydata = (dns_rdata_keydata_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_keydata(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_keydata); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_keydata(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_keydata); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_keydata(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_keydata); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..40061e1 --- /dev/null +++ b/lib/dns/rdata/generic/keydata_65533.h @@ -0,0 +1,29 @@ +/* + * 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 http://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 */ + uint8_t protocol; + uint8_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..7d92cc2 --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.c @@ -0,0 +1,228 @@ +/* + * 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 http://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 inline 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 (getquad(DNS_AS_STR(token), &addr, lexer, callbacks) != 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 inline 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, ®ion, target)); +} + +static inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_l32(ARGS_FROMSTRUCT) { + dns_rdata_l32_t *l32 = source; + uint32_t n; + + REQUIRE(type == dns_rdatatype_l32); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_l32(ARGS_FREESTRUCT) { + dns_rdata_l32_t *l32 = source; + + REQUIRE(source != NULL); + REQUIRE(l32->common.rdtype == dns_rdatatype_l32); + + return; +} + +static inline 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 inline 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 inline bool +checkowner_l32(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_l32); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_l32(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_l32); + REQUIRE(rdata->length == 6); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..3fca0f2 --- /dev/null +++ b/lib/dns/rdata/generic/l32_105.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..277c6a9 --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.c @@ -0,0 +1,223 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_l64(ARGS_FROMSTRUCT) { + dns_rdata_l64_t *l64 = source; + + REQUIRE(type == dns_rdatatype_l64); + REQUIRE(source != 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 inline isc_result_t +tostruct_l64(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_l64_t *l64 = target; + + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(target != 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 inline void +freestruct_l64(ARGS_FREESTRUCT) { + dns_rdata_l64_t *l64 = source; + + REQUIRE(source != NULL); + REQUIRE(l64->common.rdtype == dns_rdatatype_l64); + + return; +} + +static inline 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 inline 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 inline bool +checkowner_l64(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_l64); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_l64(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_l64); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..968a138 --- /dev/null +++ b/lib/dns/rdata/generic/l64_106.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..50c00ff --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.c @@ -0,0 +1,811 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_loc(ARGS_FROMTEXT) { + isc_token_t token; + int d1, m1, s1; + int d2, m2, s2; + unsigned char size; + unsigned char hp; + unsigned char vp; + unsigned char version; + bool east = false; + bool north = false; + long tmp; + long m; + long cm; + long poweroften[8] = { 1, 10, 100, 1000, + 10000, 100000, 1000000, 10000000 }; + int man; + int exp; + char *e; + int i; + unsigned long latitude; + unsigned long longitude; + unsigned long altitude; + + REQUIRE(type == dns_rdatatype_loc); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + + /* + * Defaults. + */ + m1 = s1 = 0; + m2 = s2 = 0; + size = 0x12; /* 1.00m */ + hp = 0x16; /* 10000.00 m */ + vp = 0x13; /* 10.00 m */ + version = 0; + + /* + * Degrees. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 90U) + RETTOK(ISC_R_RANGE); + d1 = (int)token.value.as_ulong; + /* + * Minutes. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "N") == 0) + north = true; + if (north || strcasecmp(DNS_AS_STR(token), "S") == 0) + goto getlong; + m1 = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0) + RETTOK(DNS_R_SYNTAX); + if (m1 < 0 || m1 > 59) + RETTOK(ISC_R_RANGE); + if (d1 == 90 && m1 != 0) + RETTOK(ISC_R_RANGE); + + /* + * Seconds. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "N") == 0) + north = true; + if (north || strcasecmp(DNS_AS_STR(token), "S") == 0) + goto getlong; + s1 = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.') + RETTOK(DNS_R_SYNTAX); + if (s1 < 0 || s1 > 59) + RETTOK(ISC_R_RANGE); + if (*e == '.') { + const char *l; + e++; + for (i = 0; i < 3; i++) { + if (*e == 0) + break; + if ((tmp = decvalue(*e++)) < 0) + RETTOK(DNS_R_SYNTAX); + s1 *= 10; + s1 += tmp; + } + for (; i < 3; i++) + s1 *= 10; + l = e; + while (*e != 0) { + if (decvalue(*e++) < 0) + RETTOK(DNS_R_SYNTAX); + } + if (*l != '\0' && callbacks != NULL) { + const char *file = isc_lex_getsourcename(lexer); + unsigned long line = isc_lex_getsourceline(lexer); + + if (file == NULL) + file = "UNKNOWN"; + (*callbacks->warn)(callbacks, "%s: %s:%u: '%s' extra " + "precision digits ignored", + "dns_rdata_fromtext", file, line, + DNS_AS_STR(token)); + } + } else + s1 *= 1000; + if (d1 == 90 && s1 != 0) + RETTOK(ISC_R_RANGE); + + /* + * Direction. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "N") == 0) + north = true; + if (!north && strcasecmp(DNS_AS_STR(token), "S") != 0) + RETTOK(DNS_R_SYNTAX); + + getlong: + /* + * Degrees. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number, + false)); + if (token.value.as_ulong > 180U) + RETTOK(ISC_R_RANGE); + d2 = (int)token.value.as_ulong; + + /* + * Minutes. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "E") == 0) + east = true; + if (east || strcasecmp(DNS_AS_STR(token), "W") == 0) + goto getalt; + m2 = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0) + RETTOK(DNS_R_SYNTAX); + if (m2 < 0 || m2 > 59) + RETTOK(ISC_R_RANGE); + if (d2 == 180 && m2 != 0) + RETTOK(ISC_R_RANGE); + + /* + * Seconds. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "E") == 0) + east = true; + if (east || strcasecmp(DNS_AS_STR(token), "W") == 0) + goto getalt; + s2 = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.') + RETTOK(DNS_R_SYNTAX); + if (s2 < 0 || s2 > 59) + RETTOK(ISC_R_RANGE); + if (*e == '.') { + const char *l; + e++; + for (i = 0; i < 3; i++) { + if (*e == 0) + break; + if ((tmp = decvalue(*e++)) < 0) + RETTOK(DNS_R_SYNTAX); + s2 *= 10; + s2 += tmp; + } + for (; i < 3; i++) + s2 *= 10; + l = e; + while (*e != 0) { + if (decvalue(*e++) < 0) + RETTOK(DNS_R_SYNTAX); + } + if (*l != '\0' && callbacks != NULL) { + const char *file = isc_lex_getsourcename(lexer); + unsigned long line = isc_lex_getsourceline(lexer); + + if (file == NULL) + file = "UNKNOWN"; + (*callbacks->warn)(callbacks, "%s: %s:%u: '%s' extra " + "precision digits ignored", + "dns_rdata_fromtext", + file, line, DNS_AS_STR(token)); + } + } else + s2 *= 1000; + if (d2 == 180 && s2 != 0) + RETTOK(ISC_R_RANGE); + + /* + * Direction. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + if (strcasecmp(DNS_AS_STR(token), "E") == 0) + east = true; + if (!east && strcasecmp(DNS_AS_STR(token), "W") != 0) + RETTOK(DNS_R_SYNTAX); + + getalt: + /* + * Altitude. + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + m = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.' && *e != 'm') + RETTOK(DNS_R_SYNTAX); + if (m < -100000 || m > 42849672) + RETTOK(ISC_R_RANGE); + cm = 0; + if (*e == '.') { + e++; + for (i = 0; i < 2; i++) { + if (*e == 0 || *e == 'm') + break; + if ((tmp = decvalue(*e++)) < 0) + return (DNS_R_SYNTAX); + cm *= 10; + if (m < 0) + cm -= tmp; + else + cm += tmp; + } + for (; i < 2; i++) + cm *= 10; + } + if (*e == 'm') + e++; + if (*e != 0) + RETTOK(DNS_R_SYNTAX); + if (m == -100000 && cm != 0) + RETTOK(ISC_R_RANGE); + if (m == 42849672 && cm > 95) + RETTOK(ISC_R_RANGE); + /* + * Adjust base. + */ + altitude = m + 100000; + altitude *= 100; + altitude += cm; + + /* + * Size: optional. + */ + 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); + goto encode; + } + m = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.' && *e != 'm') + RETTOK(DNS_R_SYNTAX); + if (m < 0 || m > 90000000) + RETTOK(ISC_R_RANGE); + cm = 0; + if (*e == '.') { + e++; + for (i = 0; i < 2; i++) { + if (*e == 0 || *e == 'm') + break; + if ((tmp = decvalue(*e++)) < 0) + RETTOK(DNS_R_SYNTAX); + cm *= 10; + cm += tmp; + } + for (; i < 2; i++) + cm *= 10; + } + if (*e == 'm') + e++; + if (*e != 0) + RETTOK(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; + } + } + size = (man << 4) + exp; + + /* + * Horizontal precision: optional. + */ + 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); + goto encode; + } + m = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.' && *e != 'm') + RETTOK(DNS_R_SYNTAX); + if (m < 0 || m > 90000000) + RETTOK(ISC_R_RANGE); + cm = 0; + if (*e == '.') { + e++; + for (i = 0; i < 2; i++) { + if (*e == 0 || *e == 'm') + break; + if ((tmp = decvalue(*e++)) < 0) + RETTOK(DNS_R_SYNTAX); + cm *= 10; + cm += tmp; + } + for (; i < 2; i++) + cm *= 10; + } + if (*e == 'm') + e++; + if (*e != 0) + RETTOK(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; + } + hp = (man << 4) + exp; + + /* + * Vertical precision: optional. + */ + 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); + goto encode; + } + m = strtol(DNS_AS_STR(token), &e, 10); + if (*e != 0 && *e != '.' && *e != 'm') + RETTOK(DNS_R_SYNTAX); + if (m < 0 || m > 90000000) + RETTOK(ISC_R_RANGE); + cm = 0; + if (*e == '.') { + e++; + for (i = 0; i < 2; i++) { + if (*e == 0 || *e == 'm') + break; + if ((tmp = decvalue(*e++)) < 0) + RETTOK(DNS_R_SYNTAX); + cm *= 10; + cm += tmp; + } + for (; i < 2; i++) + cm *= 10; + } + if (*e == 'm') + e++; + if (*e != 0) + RETTOK(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; + } + vp = (man << 4) + exp; + + 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)); + if (north) + latitude = 0x80000000 + ( d1 * 3600 + m1 * 60 ) * 1000 + s1; + else + latitude = 0x80000000 - ( d1 * 3600 + m1 * 60 ) * 1000 - s1; + RETERR(uint32_tobuffer(latitude, target)); + + if (east) + longitude = 0x80000000 + ( d2 * 3600 + m2 * 60 ) * 1000 + s2; + else + longitude = 0x80000000 - ( d2 * 3600 + m2 * 60 ) * 1000 - s2; + RETERR(uint32_tobuffer(longitude, target)); + + return (uint32_tobuffer(altitude, target)); +} + +static inline 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 buf[sizeof("89 59 59.999 N 179 59 59.999 E " + "-42849672.95m 90000000m 90000000m 90000000m")]; + char sbuf[sizeof("90000000m")]; + char hbuf[sizeof("90000000m")]; + char vbuf[sizeof("90000000m")]; + 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 inline 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. + */ + 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. + */ + 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 inline 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 inline 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 inline isc_result_t +fromstruct_loc(ARGS_FROMSTRUCT) { + dns_rdata_loc_t *loc = source; + uint8_t c; + + REQUIRE(type == dns_rdatatype_loc); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_loc(ARGS_FREESTRUCT) { + dns_rdata_loc_t *loc = source; + + REQUIRE(source != NULL); + REQUIRE(loc->common.rdtype == dns_rdatatype_loc); + + UNUSED(source); + UNUSED(loc); +} + +static inline isc_result_t +additionaldata_loc(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_loc); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_loc(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_loc); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_loc(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_loc); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..c879581 --- /dev/null +++ b/lib/dns/rdata/generic/loc_29.h @@ -0,0 +1,36 @@ +/* + * 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 http://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..49bc799 --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.c @@ -0,0 +1,271 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_lp(ARGS_FROMSTRUCT) { + dns_rdata_lp_t *lp = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_lp); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_lp(ARGS_FREESTRUCT) { + dns_rdata_lp_t *lp = source; + + REQUIRE(source != NULL); + REQUIRE(lp->common.rdtype == dns_rdatatype_lp); + + if (lp->mctx == NULL) + return; + + dns_name_free(&lp->lp, lp->mctx); + lp->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_lp(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_lp); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(name); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_lp(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_lp); + + UNUSED(bad); + UNUSED(owner); + + return (true); +} + +static inline 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..4ccffac --- /dev/null +++ b/lib/dns/rdata/generic/lp_107.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..299f89d --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.c @@ -0,0 +1,230 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_mb(ARGS_FROMSTRUCT) { + dns_rdata_mb_t *mb = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mb); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_mb(ARGS_FREESTRUCT) { + dns_rdata_mb_t *mb = source; + + REQUIRE(source != NULL); + + if (mb->mctx == NULL) + return; + + dns_name_free(&mb->mb, mb->mctx); + mb->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_mb(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_mb); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_ismailbox(name)); +} + +static inline bool +checknames_mb(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_mb); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..59f4115 --- /dev/null +++ b/lib/dns/rdata/generic/mb_7.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..9eefe69 --- /dev/null +++ b/lib/dns/rdata/generic/md_3.c @@ -0,0 +1,232 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_md(ARGS_FROMSTRUCT) { + dns_rdata_md_t *md = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_md); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_md(ARGS_FREESTRUCT) { + dns_rdata_md_t *md = source; + + REQUIRE(source != NULL); + REQUIRE(md->common.rdtype == dns_rdatatype_md); + + if (md->mctx == NULL) + return; + + dns_name_free(&md->md, md->mctx); + md->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_md(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_md); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_md(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_md); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..396c3c9 --- /dev/null +++ b/lib/dns/rdata/generic/md_3.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..5965981 --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.c @@ -0,0 +1,231 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_mf(ARGS_FROMSTRUCT) { + dns_rdata_mf_t *mf = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mf); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_mf(ARGS_FREESTRUCT) { + dns_rdata_mf_t *mf = source; + + REQUIRE(source != NULL); + REQUIRE(mf->common.rdtype == dns_rdatatype_mf); + + if (mf->mctx == NULL) + return; + dns_name_free(&mf->mf, mf->mctx); + mf->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_mf(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_mf); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_mf(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_mf); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..d882155 --- /dev/null +++ b/lib/dns/rdata/generic/mf_4.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..45a38ce --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.c @@ -0,0 +1,226 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_mg(ARGS_FROMSTRUCT) { + dns_rdata_mg_t *mg = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mg); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_mg(ARGS_FREESTRUCT) { + dns_rdata_mg_t *mg = source; + + REQUIRE(source != NULL); + REQUIRE(mg->common.rdtype == dns_rdatatype_mg); + + if (mg->mctx == NULL) + return; + dns_name_free(&mg->mg, mg->mctx); + mg->mctx = NULL; +} + +static inline isc_result_t +additionaldata_mg(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_mg); + + UNUSED(add); + UNUSED(arg); + UNUSED(rdata); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_mg(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_mg); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_ismailbox(name)); +} + +static inline bool +checknames_mg(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_mg); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..0e0380e --- /dev/null +++ b/lib/dns/rdata/generic/mg_8.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..23506e2 --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.c @@ -0,0 +1,321 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_minfo(ARGS_FROMSTRUCT) { + dns_rdata_minfo_t *minfo = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_minfo); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_minfo(ARGS_FREESTRUCT) { + dns_rdata_minfo_t *minfo = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_minfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_minfo); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_minfo(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_minfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..10080fb --- /dev/null +++ b/lib/dns/rdata/generic/minfo_14.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..edea7b1 --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.c @@ -0,0 +1,227 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_mr(ARGS_FROMSTRUCT) { + dns_rdata_mr_t *mr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mr); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_mr(ARGS_FREESTRUCT) { + dns_rdata_mr_t *mr = source; + + REQUIRE(source != NULL); + REQUIRE(mr->common.rdtype == dns_rdatatype_mr); + + if (mr->mctx == NULL) + return; + dns_name_free(&mr->mr, mr->mctx); + mr->mctx = NULL; +} + +static inline isc_result_t +additionaldata_mr(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_mr); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_mr(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_mr); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_mr(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_mr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..888a82f --- /dev/null +++ b/lib/dns/rdata/generic/mr_9.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..c333740 --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.c @@ -0,0 +1,336 @@ +/* + * 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 http://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_aton(tmp, &addr) == 1 || + inet_pton(AF_INET6, tmp, &addr6) == 1) + return (false); + + return (true); +} + +static inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_mx(ARGS_FROMSTRUCT) { + dns_rdata_mx_t *mx = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_mx); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_mx(ARGS_FREESTRUCT) { + dns_rdata_mx_t *mx = source; + + REQUIRE(source != 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 inline 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 inline 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 inline bool +checkowner_mx(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_mx); + + UNUSED(type); + UNUSED(rdclass); + + return (dns_name_ishostname(name, wildcard)); +} + +static inline 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 inline 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..ed96beb --- /dev/null +++ b/lib/dns/rdata/generic/mx_15.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..80c8f16 --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.c @@ -0,0 +1,662 @@ +/* + * 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 http://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 embeded NUL's. + */ +static inline 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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_naptr(ARGS_FROMSTRUCT) { + dns_rdata_naptr_t *naptr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_naptr); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_naptr(ARGS_FREESTRUCT) { + dns_rdata_naptr_t *naptr = source; + + REQUIRE(source != 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 inline 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 inline 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 inline bool +checkowner_naptr(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_naptr); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_naptr(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_naptr); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..e72b609 --- /dev/null +++ b/lib/dns/rdata/generic/naptr_35.h @@ -0,0 +1,33 @@ +/* + * 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 http://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..fee61fd --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.c @@ -0,0 +1,223 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_nid(ARGS_FROMSTRUCT) { + dns_rdata_nid_t *nid = source; + + REQUIRE(type == dns_rdatatype_nid); + REQUIRE(source != 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 inline isc_result_t +tostruct_nid(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nid_t *nid = target; + + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(target != 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 inline void +freestruct_nid(ARGS_FREESTRUCT) { + dns_rdata_nid_t *nid = source; + + REQUIRE(source != NULL); + REQUIRE(nid->common.rdtype == dns_rdatatype_nid); + + return; +} + +static inline 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 inline 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 inline bool +checkowner_nid(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_nid); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_nid(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_nid); + REQUIRE(rdata->length == 10); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..9362e2b --- /dev/null +++ b/lib/dns/rdata/generic/nid_104.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..fe8957a --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.c @@ -0,0 +1,191 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_ninfo(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_ninfo); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + return (generic_fromtext_txt(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_ninfo(ARGS_TOTEXT) { + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + return (generic_totext_txt(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_ninfo(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_ninfo); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + return (generic_fromwire_txt(rdclass, type, source, dctx, options, + target)); +} + +static inline isc_result_t +towire_ninfo(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline isc_result_t +fromstruct_ninfo(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_ninfo); + + return (generic_fromstruct_txt(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_ninfo(ARGS_TOSTRUCT) { + dns_rdata_ninfo_t *txt = target; + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + txt->common.rdclass = rdata->rdclass; + txt->common.rdtype = rdata->type; + ISC_LINK_INIT(&txt->common, link); + + return (generic_tostruct_txt(rdata, target, mctx)); +} + +static inline void +freestruct_ninfo(ARGS_FREESTRUCT) { + dns_rdata_ninfo_t *ninfo = source; + + REQUIRE(source != NULL); + REQUIRE(ninfo->common.rdtype == dns_rdatatype_ninfo); + + generic_freestruct_txt(source); +} + +static inline isc_result_t +additionaldata_ninfo(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_ninfo(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_ninfo); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_ninfo(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_ninfo); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..68e9212 --- /dev/null +++ b/lib/dns/rdata/generic/ninfo_56.h @@ -0,0 +1,34 @@ +/* + * 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 http://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..7f3979f --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.c @@ -0,0 +1,247 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_ns(ARGS_FROMSTRUCT) { + dns_rdata_ns_t *ns = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ns); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_ns(ARGS_FREESTRUCT) { + dns_rdata_ns_t *ns = source; + + REQUIRE(source != NULL); + + if (ns->mctx == NULL) + return; + + dns_name_free(&ns->name, ns->mctx); + ns->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_ns(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_ns); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..db9b6c0 --- /dev/null +++ b/lib/dns/rdata/generic/ns_2.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..1aedf22 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.c @@ -0,0 +1,402 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 (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 inline 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 inline 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 inline isc_result_t +fromstruct_nsec3(ARGS_FROMSTRUCT) { + dns_rdata_nsec3_t *nsec3 = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsec3); + REQUIRE(source != 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 inline isc_result_t +tostruct_nsec3(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nsec3_t *nsec3 = target; + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + REQUIRE(target != 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); + 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); + 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 inline void +freestruct_nsec3(ARGS_FREESTRUCT) { + dns_rdata_nsec3_t *nsec3 = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_nsec3(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline 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 inline bool +checknames_nsec3(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_nsec3); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..36b9695 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3_50.h @@ -0,0 +1,112 @@ +/* + * 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 http://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..8a24dee --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.c @@ -0,0 +1,313 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_nsec3param(ARGS_FROMSTRUCT) { + dns_rdata_nsec3param_t *nsec3param = source; + + REQUIRE(type == dns_rdatatype_nsec3param); + REQUIRE(source != 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 inline isc_result_t +tostruct_nsec3param(ARGS_TOSTRUCT) { + isc_region_t region; + dns_rdata_nsec3param_t *nsec3param = target; + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + REQUIRE(target != 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); + 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 inline void +freestruct_nsec3param(ARGS_FREESTRUCT) { + dns_rdata_nsec3param_t *nsec3param = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_nsec3param(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_nsec3param(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_nsec3param); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_nsec3param(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_nsec3param); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..b8bc375 --- /dev/null +++ b/lib/dns/rdata/generic/nsec3param_51.h @@ -0,0 +1,32 @@ +/* + * 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 http://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..05e575b --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.c @@ -0,0 +1,282 @@ +/* + * 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 http://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) + +static inline 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 inline 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 /* inline */ 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 inline 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 inline 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 inline isc_result_t +fromstruct_nsec(ARGS_FROMSTRUCT) { + dns_rdata_nsec_t *nsec = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nsec); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_nsec(ARGS_FREESTRUCT) { + dns_rdata_nsec_t *nsec = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_nsec(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nsec); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_nsec(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_nsec); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_nsec(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_nsec); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..b597864 --- /dev/null +++ b/lib/dns/rdata/generic/nsec_47.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..624adad --- /dev/null +++ b/lib/dns/rdata/generic/null_10.c @@ -0,0 +1,183 @@ +/* + * 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 http://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 inline 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 inline isc_result_t +totext_null(ARGS_TOTEXT) { + REQUIRE(rdata->type == dns_rdatatype_null); + + return (unknown_totext(rdata, tctx, target)); +} + +static inline 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 inline isc_result_t +towire_null(ARGS_TOWIRE) { + REQUIRE(rdata->type == dns_rdatatype_null); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline isc_result_t +fromstruct_null(ARGS_FROMSTRUCT) { + dns_rdata_null_t *null = source; + + REQUIRE(type == dns_rdatatype_null); + REQUIRE(source != 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 inline isc_result_t +tostruct_null(ARGS_TOSTRUCT) { + dns_rdata_null_t *null = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_null); + REQUIRE(target != 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 inline void +freestruct_null(ARGS_FREESTRUCT) { + dns_rdata_null_t *null = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_null(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_null); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_null(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_null); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_null(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_null); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..0b01dd8 --- /dev/null +++ b/lib/dns/rdata/generic/null_10.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..91a95b7 --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.c @@ -0,0 +1,325 @@ +/* + * 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 http://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 inline 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 inline 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("65535")]; + snprintf(buf, sizeof(buf), + "%u", t); + RETERR(str_totext(buf, + target)); + } + } + } + return (ISC_R_SUCCESS); +} + +static inline 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 inline 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 inline 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); + + return (isc_region_compare(&r1, &r2)); +} + +static inline isc_result_t +fromstruct_nxt(ARGS_FROMSTRUCT) { + dns_rdata_nxt_t *nxt = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_nxt); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_nxt(ARGS_FREESTRUCT) { + dns_rdata_nxt_t *nxt = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_nxt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_nxt); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_nxt(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_nxt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_nxt(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_nxt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..63bbc0f --- /dev/null +++ b/lib/dns/rdata/generic/nxt_30.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..52c87d4 --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.c @@ -0,0 +1,242 @@ +/* + * 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 http://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 inline 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, -1)); +} + +static inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_openpgpkey(ARGS_FROMSTRUCT) { + dns_rdata_openpgpkey_t *sig = source; + + REQUIRE(type == dns_rdatatype_openpgpkey); + REQUIRE(source != 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 inline isc_result_t +tostruct_openpgpkey(ARGS_TOSTRUCT) { + isc_region_t sr; + dns_rdata_openpgpkey_t *sig = target; + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + REQUIRE(target != 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 inline void +freestruct_openpgpkey(ARGS_FREESTRUCT) { + dns_rdata_openpgpkey_t *sig = (dns_rdata_openpgpkey_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_openpgpkey(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_openpgpkey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_openpgpkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_openpgpkey(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_openpgpkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..7c68e7f --- /dev/null +++ b/lib/dns/rdata/generic/openpgpkey_61.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..b102cf2 --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.c @@ -0,0 +1,413 @@ +/* + * 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 http://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) + +static inline 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 inline 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 inline 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_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: + 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; + 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 inline isc_result_t +towire_opt(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline 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(source != 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 inline isc_result_t +tostruct_opt(ARGS_TOSTRUCT) { + dns_rdata_opt_t *opt = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_opt); + REQUIRE(target != 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 inline void +freestruct_opt(ARGS_FREESTRUCT) { + dns_rdata_opt_t *opt = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_opt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_opt(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_opt); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (dns_name_equal(name, dns_rootname)); +} + +static inline bool +checknames_opt(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_opt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..5b31f4d --- /dev/null +++ b/lib/dns/rdata/generic/opt_41.h @@ -0,0 +1,48 @@ +/* + * 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 http://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..c31b29f --- /dev/null +++ b/lib/dns/rdata/generic/proforma.c @@ -0,0 +1,183 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_#(ARGS_FROMSTRUCT) { + dns_rdata_#_t *# = source; + + REQUIRE(type == dns_rdatatype_proforma.c#); + REQUIRE(rdclass == #); + REQUIRE(source != NULL); + REQUIRE(#->common.rdtype == dns_rdatatype_proforma.ctype); + REQUIRE(#->common.rdclass == rdclass); + + return (ISC_R_NOTIMPLEMENTED); +} + +static inline 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 inline void +freestruct_#(ARGS_FREESTRUCT) { + dns_rdata_#_t *# = source; + + REQUIRE(source != NULL); + REQUIRE(#->common.rdtype == dns_rdatatype_proforma.c#); + REQUIRE(#->common.rdclass == #); + +} + +static inline 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 inline 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 inline bool +checkowner_#(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_proforma.c#); + REQUIRE(rdclass == #); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_#(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_proforma.c#); + REQUIRE(rdata->rdclass == #); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..cb49ee4 --- /dev/null +++ b/lib/dns/rdata/generic/proforma.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..a2f5ef1 --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.c @@ -0,0 +1,268 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_ptr(ARGS_FROMSTRUCT) { + dns_rdata_ptr_t *ptr = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_ptr); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_ptr(ARGS_FREESTRUCT) { + dns_rdata_ptr_t *ptr = source; + + REQUIRE(source != NULL); + REQUIRE(ptr->common.rdtype == dns_rdatatype_ptr); + + if (ptr->mctx == NULL) + return; + + dns_name_free(&ptr->ptr, ptr->mctx); + ptr->mctx = NULL; +} + +static inline isc_result_t +additionaldata_ptr(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ptr); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline 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 inline 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 inline 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..d71ebd9 --- /dev/null +++ b/lib/dns/rdata/generic/ptr_12.h @@ -0,0 +1,23 @@ +/* + * 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 http://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..2475a48 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.c @@ -0,0 +1,168 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_rkey(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromtext_key(rdclass, type, lexer, origin, + options, target, callbacks)); +} + +static inline isc_result_t +totext_rkey(ARGS_TOTEXT) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + return (generic_totext_key(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_rkey(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromwire_key(rdclass, type, source, dctx, + options, target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_rkey(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_rkey); + + return (generic_fromstruct_key(rdclass, type, source, target)); +} + +static inline 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(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_rkey(ARGS_ADDLDATA) { + + REQUIRE(rdata->type == dns_rdatatype_rkey); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_rkey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_rkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_rkey(ARGS_CHECKNAMES) { + + REQUIRE(rdata != NULL); + REQUIRE(rdata->type == dns_rdatatype_rkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline int +casecompare_rkey(ARGS_COMPARE) { + + /* + * Treat ALG 253 (private DNS) subtype name case sensistively. + */ + 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..000a725 --- /dev/null +++ b/lib/dns/rdata/generic/rkey_57.h @@ -0,0 +1,17 @@ +/* + * 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 http://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..2e91565 --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.c @@ -0,0 +1,312 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_rp(ARGS_FROMSTRUCT) { + dns_rdata_rp_t *rp = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_rp); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_rp(ARGS_FREESTRUCT) { + dns_rdata_rp_t *rp = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_rp(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_rp); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_rp(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_rp); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..31b5768 --- /dev/null +++ b/lib/dns/rdata/generic/rp_17.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..fb945ff --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.c @@ -0,0 +1,613 @@ +/* + * 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 http://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) + +static inline 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, -1)); +} + +static inline 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 inline 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); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_rrsig(ARGS_FROMSTRUCT) { + dns_rdata_rrsig_t *sig = source; + + REQUIRE(type == dns_rdatatype_rrsig); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_rrsig(ARGS_FREESTRUCT) { + dns_rdata_rrsig_t *sig = (dns_rdata_rrsig_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_rrsig(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +digest_rrsig(ARGS_DIGEST) { + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static inline 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 inline bool +checkowner_rrsig(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_rrsig); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_rrsig(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_rrsig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..6dfbd42 --- /dev/null +++ b/lib/dns/rdata/generic/rrsig_46.h @@ -0,0 +1,34 @@ +/* + * 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 http://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..e6450cb --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.c @@ -0,0 +1,307 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_rt(ARGS_FROMSTRUCT) { + dns_rdata_rt_t *rt = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_rt); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_rt(ARGS_FREESTRUCT) { + dns_rdata_rt_t *rt = source; + + REQUIRE(source != NULL); + REQUIRE(rt->common.rdtype == dns_rdatatype_rt); + + if (rt->mctx == NULL) + return; + + dns_name_free(&rt->host, rt->mctx); + rt->mctx = NULL; +} + +static inline 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 inline 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 inline bool +checkowner_rt(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_rt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..a9082c7 --- /dev/null +++ b/lib/dns/rdata/generic/rt_21.h @@ -0,0 +1,26 @@ +/* + * 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 http://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..ca0ce4e --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.c @@ -0,0 +1,575 @@ +/* + * 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 http://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 inline 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, -1)); +} + +static inline 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 inline 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); + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_sig(ARGS_FROMSTRUCT) { + dns_rdata_sig_t *sig = source; + + REQUIRE(type == dns_rdatatype_sig); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_sig(ARGS_FREESTRUCT) { + dns_rdata_sig_t *sig = (dns_rdata_sig_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_sig(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +digest_sig(ARGS_DIGEST) { + + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + return (ISC_R_NOTIMPLEMENTED); +} + +static inline 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 inline bool +checkowner_sig(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_sig); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_sig(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_sig); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..a3882d9 --- /dev/null +++ b/lib/dns/rdata/generic/sig_24.h @@ -0,0 +1,35 @@ +/* + * 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 http://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..1289414 --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.c @@ -0,0 +1,278 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_sink(ARGS_FROMSTRUCT) { + dns_rdata_sink_t *sink = source; + + REQUIRE(type == dns_rdatatype_sink); + REQUIRE(source != 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 inline isc_result_t +tostruct_sink(ARGS_TOSTRUCT) { + dns_rdata_sink_t *sink = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_sink); + REQUIRE(target != 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 inline void +freestruct_sink(ARGS_FREESTRUCT) { + dns_rdata_sink_t *sink = (dns_rdata_sink_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_sink(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sink); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_sink(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_sink); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_sink(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_sink); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..4e6e01e --- /dev/null +++ b/lib/dns/rdata/generic/sink_40.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..efd7f01 --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.c @@ -0,0 +1,156 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_smimea(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromtext_tlsa(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_smimea(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_smimea); + + return (generic_totext_tlsa(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_smimea(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromwire_tlsa(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_smimea(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_smimea); + + return (generic_fromstruct_tlsa(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_smimea(ARGS_TOSTRUCT) { + dns_rdata_smimea_t *smimea = target; + + REQUIRE(rdata->type == dns_rdatatype_smimea); + REQUIRE(target != NULL); + + smimea->common.rdclass = rdata->rdclass; + smimea->common.rdtype = rdata->type; + ISC_LINK_INIT(&smimea->common, link); + + return (generic_tostruct_tlsa(rdata, target, mctx)); +} + +static inline void +freestruct_smimea(ARGS_FREESTRUCT) { + dns_rdata_smimea_t *smimea = source; + + REQUIRE(source != NULL); + REQUIRE(smimea->common.rdtype == dns_rdatatype_smimea); + + generic_freestruct_tlsa(source); +} + +static inline isc_result_t +additionaldata_smimea(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_smimea); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_smimea(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_smimea); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_smimea(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_smimea); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..cabec2b --- /dev/null +++ b/lib/dns/rdata/generic/smimea_53.h @@ -0,0 +1,17 @@ +/* + * 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 http://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..c21d64b --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.c @@ -0,0 +1,438 @@ +/* + * 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 http://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 inline 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 inline 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); + comm = (multiline) ? + (tctx->flags & DNS_STYLEFLAG_RRCOMMENT) : + 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, 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_soa(ARGS_FROMSTRUCT) { + dns_rdata_soa_t *soa = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_soa); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_soa(ARGS_FREESTRUCT) { + dns_rdata_soa_t *soa = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_soa(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_soa); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_soa(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_soa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline 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 inline 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..c9be985 --- /dev/null +++ b/lib/dns/rdata/generic/soa_6.h @@ -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 http://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..c41edf6 --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.c @@ -0,0 +1,163 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_spf(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_spf); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + return (generic_fromtext_txt(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_spf(ARGS_TOTEXT) { + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_spf); + + return (generic_totext_txt(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_spf(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_spf); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + return (generic_fromwire_txt(rdclass, type, source, dctx, options, + target)); +} + +static inline isc_result_t +towire_spf(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline isc_result_t +fromstruct_spf(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_spf); + + return (generic_fromstruct_txt(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_spf(ARGS_TOSTRUCT) { + dns_rdata_spf_t *spf = target; + + REQUIRE(rdata->type == dns_rdatatype_spf); + REQUIRE(target != NULL); + + spf->common.rdclass = rdata->rdclass; + spf->common.rdtype = rdata->type; + ISC_LINK_INIT(&spf->common, link); + + return (generic_tostruct_txt(rdata, target, mctx)); +} + +static inline void +freestruct_spf(ARGS_FREESTRUCT) { + dns_rdata_spf_t *txt = source; + + REQUIRE(source != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_spf); + + generic_freestruct_txt(source); +} + +static inline isc_result_t +additionaldata_spf(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_spf(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_spf); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_spf(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_spf); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..f10b790 --- /dev/null +++ b/lib/dns/rdata/generic/spf_99.h @@ -0,0 +1,34 @@ +/* + * 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 http://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..1b5d7ca --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.c @@ -0,0 +1,263 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_sshfp(ARGS_FROMTEXT) { + isc_token_t token; + + 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)); + + /* + * Digest. + */ + return (isc_hex_tobuffer(lexer, target, -1)); +} + +static inline 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)); + + /* + * 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 inline 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 < 4) + return (ISC_R_UNEXPECTEDEND); + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_sshfp(ARGS_FROMSTRUCT) { + dns_rdata_sshfp_t *sshfp = source; + + REQUIRE(type == dns_rdatatype_sshfp); + REQUIRE(source != 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 inline isc_result_t +tostruct_sshfp(ARGS_TOSTRUCT) { + dns_rdata_sshfp_t *sshfp = target; + isc_region_t region; + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + REQUIRE(target != 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 inline 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 inline isc_result_t +additionaldata_sshfp(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_sshfp); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_sshfp(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_sshfp); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_sshfp(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_sshfp); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..ba1129a --- /dev/null +++ b/lib/dns/rdata/generic/sshfp_44.h @@ -0,0 +1,28 @@ +/* + * 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 http://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..12ec6e7 --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.c @@ -0,0 +1,165 @@ +/* + * 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 http://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 inline isc_result_t +fromtext_ta(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromtext_ds(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_ta(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_ta); + + return (generic_totext_ds(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_ta(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromwire_ds(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_ta(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_ta); + + return (generic_fromstruct_ds(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_ta(ARGS_TOSTRUCT) { + dns_rdata_ds_t *ds = target; + + REQUIRE(rdata->type == dns_rdatatype_ta); + + /* + * 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(rdata, target, mctx)); +} + +static inline 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 inline isc_result_t +additionaldata_ta(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_ta); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_ta(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_ta); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_ta(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_ta); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..1bd7a28 --- /dev/null +++ b/lib/dns/rdata/generic/ta_32768.h @@ -0,0 +1,20 @@ +/* + * 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 http://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..7f6bacb --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.c @@ -0,0 +1,264 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_talink(ARGS_FROMSTRUCT) { + dns_rdata_talink_t *talink = source; + isc_region_t region; + + REQUIRE(type == dns_rdatatype_talink); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_talink(ARGS_FREESTRUCT) { + dns_rdata_talink_t *talink = source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_talink(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_talink); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_talink(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_talink); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_talink(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_talink); + + UNUSED(bad); + UNUSED(owner); + + return (true); +} + +static inline 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..03166a2 --- /dev/null +++ b/lib/dns/rdata/generic/talink_58.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..7bbd1d3 --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.c @@ -0,0 +1,556 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_tkey(ARGS_FROMSTRUCT) { + dns_rdata_tkey_t *tkey = source; + + REQUIRE(type == dns_rdatatype_tkey); + REQUIRE(source != 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 inline 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(target != 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 inline void +freestruct_tkey(ARGS_FREESTRUCT) { + dns_rdata_tkey_t *tkey = (dns_rdata_tkey_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_tkey(ARGS_ADDLDATA) { + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_tkey); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +digest_tkey(ARGS_DIGEST) { + UNUSED(rdata); + UNUSED(digest); + UNUSED(arg); + + REQUIRE(rdata->type == dns_rdatatype_tkey); + + return (ISC_R_NOTIMPLEMENTED); +} + +static inline bool +checkowner_tkey(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_tkey); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_tkey(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_tkey); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..2fab087 --- /dev/null +++ b/lib/dns/rdata/generic/tkey_249.h @@ -0,0 +1,34 @@ +/* + * 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 http://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..1224dc1 --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.c @@ -0,0 +1,334 @@ +/* + * 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 http://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 inline 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, -1)); +} + +static inline 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 inline 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); + + if (sr.length < 3) + return (ISC_R_UNEXPECTEDEND); + + isc_buffer_forward(source, sr.length); + return (mem_tobuffer(target, sr.base, sr.length)); +} + +static inline isc_result_t +fromtext_tlsa(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromtext_tlsa(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_tlsa(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + return (generic_totext_tlsa(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_tlsa(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromwire_tlsa(rdclass, type, source, dctx, options, + target)); +} + +static inline 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 inline 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 inline isc_result_t +generic_fromstruct_tlsa(ARGS_FROMSTRUCT) { + dns_rdata_tlsa_t *tlsa = source; + + REQUIRE(source != 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 inline isc_result_t +generic_tostruct_tlsa(ARGS_TOSTRUCT) { + dns_rdata_tlsa_t *tlsa = target; + isc_region_t region; + + REQUIRE(rdata != 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 inline 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 inline isc_result_t +fromstruct_tlsa(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_tlsa); + + return (generic_fromstruct_tlsa(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_tlsa(ARGS_TOSTRUCT) { + dns_rdata_tlsa_t *tlsa = target; + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + REQUIRE(target != NULL); + + tlsa->common.rdclass = rdata->rdclass; + tlsa->common.rdtype = rdata->type; + ISC_LINK_INIT(&tlsa->common, link); + + return (generic_tostruct_tlsa(rdata, target, mctx)); +} + +static inline void +freestruct_tlsa(ARGS_FREESTRUCT) { + dns_rdata_tlsa_t *tlsa = source; + + REQUIRE(source != NULL); + REQUIRE(tlsa->common.rdtype == dns_rdatatype_tlsa); + + generic_freestruct_tlsa(source); +} + +static inline isc_result_t +additionaldata_tlsa(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_tlsa(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_tlsa); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_tlsa(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_tlsa); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..422fd8b --- /dev/null +++ b/lib/dns/rdata/generic/tlsa_52.h @@ -0,0 +1,29 @@ +/* + * 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 http://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..3f3842e --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.c @@ -0,0 +1,360 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline isc_result_t +fromtext_txt(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromtext_txt(rdclass, type, lexer, origin, options, + target, callbacks)); +} + +static inline isc_result_t +totext_txt(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_txt); + + return (generic_totext_txt(rdata, tctx, target)); +} + +static inline isc_result_t +fromwire_txt(ARGS_FROMWIRE) { + + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromwire_txt(rdclass, type, source, dctx, options, + target)); +} + +static inline isc_result_t +towire_txt(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline 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 inline isc_result_t +generic_fromstruct_txt(ARGS_FROMSTRUCT) { + dns_rdata_txt_t *txt = source; + isc_region_t region; + uint8_t length; + + REQUIRE(source != 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 inline isc_result_t +generic_tostruct_txt(ARGS_TOSTRUCT) { + dns_rdata_txt_t *txt = target; + isc_region_t r; + + REQUIRE(target != 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 inline void +generic_freestruct_txt(ARGS_FREESTRUCT) { + dns_rdata_txt_t *txt = source; + + REQUIRE(source != NULL); + + if (txt->mctx == NULL) + return; + + if (txt->txt != NULL) + isc_mem_free(txt->mctx, txt->txt); + txt->mctx = NULL; +} + +static inline isc_result_t +fromstruct_txt(ARGS_FROMSTRUCT) { + + REQUIRE(type == dns_rdatatype_txt); + + return (generic_fromstruct_txt(rdclass, type, source, target)); +} + +static inline isc_result_t +tostruct_txt(ARGS_TOSTRUCT) { + dns_rdata_txt_t *txt = target; + + REQUIRE(rdata->type == dns_rdatatype_txt); + REQUIRE(target != NULL); + + txt->common.rdclass = rdata->rdclass; + txt->common.rdtype = rdata->type; + ISC_LINK_INIT(&txt->common, link); + + return (generic_tostruct_txt(rdata, target, mctx)); +} + +static inline void +freestruct_txt(ARGS_FREESTRUCT) { + dns_rdata_txt_t *txt = source; + + REQUIRE(source != NULL); + REQUIRE(txt->common.rdtype == dns_rdatatype_txt); + + generic_freestruct_txt(source); +} + +static inline isc_result_t +additionaldata_txt(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_txt(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_txt); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_txt(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_txt); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..aaf94bf --- /dev/null +++ b/lib/dns/rdata/generic/txt_16.h @@ -0,0 +1,45 @@ +/* + * 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 http://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/unspec_103.c b/lib/dns/rdata/generic/unspec_103.c new file mode 100644 index 0000000..1c4654f --- /dev/null +++ b/lib/dns/rdata/generic/unspec_103.c @@ -0,0 +1,187 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#ifndef RDATA_GENERIC_UNSPEC_103_C +#define RDATA_GENERIC_UNSPEC_103_C + +#define RRTYPE_UNSPEC_ATTRIBUTES (0) + +static inline isc_result_t +fromtext_unspec(ARGS_FROMTEXT) { + + REQUIRE(type == dns_rdatatype_unspec); + + UNUSED(type); + UNUSED(rdclass); + UNUSED(origin); + UNUSED(options); + UNUSED(callbacks); + + return (atob_tobuffer(lexer, target)); +} + +static inline isc_result_t +totext_unspec(ARGS_TOTEXT) { + + REQUIRE(rdata->type == dns_rdatatype_unspec); + + UNUSED(tctx); + + return (btoa_totext(rdata->data, rdata->length, target)); +} + +static inline isc_result_t +fromwire_unspec(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_unspec); + + 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 inline isc_result_t +towire_unspec(ARGS_TOWIRE) { + + REQUIRE(rdata->type == dns_rdatatype_unspec); + + UNUSED(cctx); + + return (mem_tobuffer(target, rdata->data, rdata->length)); +} + +static inline int +compare_unspec(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_unspec); + + dns_rdata_toregion(rdata1, &r1); + dns_rdata_toregion(rdata2, &r2); + return (isc_region_compare(&r1, &r2)); +} + +static inline isc_result_t +fromstruct_unspec(ARGS_FROMSTRUCT) { + dns_rdata_unspec_t *unspec = source; + + REQUIRE(type == dns_rdatatype_unspec); + REQUIRE(source != NULL); + REQUIRE(unspec->common.rdtype == type); + REQUIRE(unspec->common.rdclass == rdclass); + REQUIRE(unspec->data != NULL || unspec->datalen == 0); + + UNUSED(type); + UNUSED(rdclass); + + return (mem_tobuffer(target, unspec->data, unspec->datalen)); +} + +static inline isc_result_t +tostruct_unspec(ARGS_TOSTRUCT) { + dns_rdata_unspec_t *unspec = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_unspec); + REQUIRE(target != NULL); + + unspec->common.rdclass = rdata->rdclass; + unspec->common.rdtype = rdata->type; + ISC_LINK_INIT(&unspec->common, link); + + dns_rdata_toregion(rdata, &r); + unspec->datalen = r.length; + unspec->data = mem_maybedup(mctx, r.base, r.length); + if (unspec->data == NULL) + return (ISC_R_NOMEMORY); + + unspec->mctx = mctx; + return (ISC_R_SUCCESS); +} + +static inline void +freestruct_unspec(ARGS_FREESTRUCT) { + dns_rdata_unspec_t *unspec = source; + + REQUIRE(source != NULL); + REQUIRE(unspec->common.rdtype == dns_rdatatype_unspec); + + if (unspec->mctx == NULL) + return; + + if (unspec->data != NULL) + isc_mem_free(unspec->mctx, unspec->data); + unspec->mctx = NULL; +} + +static inline isc_result_t +additionaldata_unspec(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_unspec); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +digest_unspec(ARGS_DIGEST) { + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_unspec); + + dns_rdata_toregion(rdata, &r); + + return ((digest)(arg, &r)); +} + +static inline bool +checkowner_unspec(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_unspec); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_unspec(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_unspec); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline int +casecompare_unspec(ARGS_COMPARE) { + return (compare_unspec(rdata1, rdata2)); +} + +#endif /* RDATA_GENERIC_UNSPEC_103_C */ diff --git a/lib/dns/rdata/generic/unspec_103.h b/lib/dns/rdata/generic/unspec_103.h new file mode 100644 index 0000000..9afb1bd --- /dev/null +++ b/lib/dns/rdata/generic/unspec_103.h @@ -0,0 +1,24 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* */ +#ifndef GENERIC_UNSPEC_103_H +#define GENERIC_UNSPEC_103_H 1 + + +typedef struct dns_rdata_unspec_t { + dns_rdatacommon_t common; + isc_mem_t *mctx; + unsigned char *data; + uint16_t datalen; +} dns_rdata_unspec_t; + +#endif /* GENERIC_UNSPEC_103_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..8fb0307 --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.c @@ -0,0 +1,308 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline isc_result_t +fromstruct_uri(ARGS_FROMSTRUCT) { + dns_rdata_uri_t *uri = source; + + REQUIRE(type == dns_rdatatype_uri); + REQUIRE(source != 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 inline isc_result_t +tostruct_uri(ARGS_TOSTRUCT) { + dns_rdata_uri_t *uri = target; + isc_region_t sr; + + REQUIRE(rdata->type == dns_rdatatype_uri); + REQUIRE(target != 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 inline void +freestruct_uri(ARGS_FREESTRUCT) { + dns_rdata_uri_t *uri = (dns_rdata_uri_t *) source; + + REQUIRE(source != 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 inline isc_result_t +additionaldata_uri(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_uri); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_uri(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_uri); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_uri(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_uri); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..cea1d22 --- /dev/null +++ b/lib/dns/rdata/generic/uri_256.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..d267f65 --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.c @@ -0,0 +1,214 @@ +/* + * 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 http://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 inline 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(token.value.as_textregion.base[i] & 0xff)) + RETTOK(ISC_R_RANGE); + RETTOK(txt_fromtext(&token.value.as_textregion, target)); + return (ISC_R_SUCCESS); +} + +static inline 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 inline isc_result_t +fromwire_x25(ARGS_FROMWIRE) { + isc_region_t sr; + + REQUIRE(type == dns_rdatatype_x25); + + UNUSED(type); + UNUSED(dctx); + UNUSED(rdclass); + UNUSED(options); + + isc_buffer_activeregion(source, &sr); + if (sr.length < 5) + return (DNS_R_FORMERR); + return (txt_fromwire(source, target)); +} + +static inline 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 inline 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 inline isc_result_t +fromstruct_x25(ARGS_FROMSTRUCT) { + dns_rdata_x25_t *x25 = source; + uint8_t i; + + REQUIRE(type == dns_rdatatype_x25); + REQUIRE(source != 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(x25->x25[i] & 0xff)) + return (ISC_R_RANGE); + + RETERR(uint8_tobuffer(x25->x25_len, target)); + return (mem_tobuffer(target, x25->x25, x25->x25_len)); +} + +static inline isc_result_t +tostruct_x25(ARGS_TOSTRUCT) { + dns_rdata_x25_t *x25 = target; + isc_region_t r; + + REQUIRE(rdata->type == dns_rdatatype_x25); + REQUIRE(target != 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 inline void +freestruct_x25(ARGS_FREESTRUCT) { + dns_rdata_x25_t *x25 = source; + REQUIRE(source != 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 inline isc_result_t +additionaldata_x25(ARGS_ADDLDATA) { + REQUIRE(rdata->type == dns_rdatatype_x25); + + UNUSED(rdata); + UNUSED(add); + UNUSED(arg); + + return (ISC_R_SUCCESS); +} + +static inline 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 inline bool +checkowner_x25(ARGS_CHECKOWNER) { + + REQUIRE(type == dns_rdatatype_x25); + + UNUSED(name); + UNUSED(type); + UNUSED(rdclass); + UNUSED(wildcard); + + return (true); +} + +static inline bool +checknames_x25(ARGS_CHECKNAMES) { + + REQUIRE(rdata->type == dns_rdatatype_x25); + + UNUSED(rdata); + UNUSED(owner); + UNUSED(bad); + + return (true); +} + +static inline 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..b10063c --- /dev/null +++ b/lib/dns/rdata/generic/x25_19.h @@ -0,0 +1,26 @@ +/* + * 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 http://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/hs_4/a_1.c b/lib/dns/rdata/hs_4/a_1.c new file mode 100644 index 0000000..973465e --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.c @@ -0,0 +1,227 @@ +/* + * 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 http://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 inline 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); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (getquad(DNS_AS_STR(token), &addr, lexer, callbacks) != 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 inline 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, ®ion, target)); +} + +static inline 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 inline 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 inline 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 inline 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(source != 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 inline 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); + + 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 inline void +freestruct_hs_a(ARGS_FREESTRUCT) { + UNUSED(source); + + REQUIRE(source != NULL); +} + +static inline 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 inline 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 inline 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 inline 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 inline 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..c5f6996 --- /dev/null +++ b/lib/dns/rdata/hs_4/a_1.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..9d7d431 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.c @@ -0,0 +1,460 @@ +/* + * 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 http://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 inline 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 inline 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, &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 inline 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); + sr.base[0] &= mask; /* Ensure pad bits are zero. */ + 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_a6(ARGS_FREESTRUCT) { + dns_rdata_in_a6_t *a6 = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..9811640 --- /dev/null +++ b/lib/dns/rdata/in_1/a6_38.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..d277699 --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.c @@ -0,0 +1,245 @@ +/* + * 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 http://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 inline 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); + + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + if (getquad(DNS_AS_STR(token), &addr, lexer, callbacks) != 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 inline 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, ®ion, target)); +} + +static inline 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 inline 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 inline 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 inline 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(source != 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 inline isc_result_t +tostruct_in_a(ARGS_TOSTRUCT) { + dns_rdata_in_a_t *a = target; + uint32_t n; + isc_region_t region; + + 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 inline void +freestruct_in_a(ARGS_FREESTRUCT) { + dns_rdata_in_a_t *a = source; + + REQUIRE(source != NULL); + REQUIRE(a->common.rdtype == dns_rdatatype_a); + REQUIRE(a->common.rdclass == dns_rdataclass_in); + + UNUSED(a); +} + +static inline 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 inline 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 inline bool +checkowner_in_a(ARGS_CHECKOWNER) { + dns_name_t prefix, suffix; + + REQUIRE(type == dns_rdatatype_a); + REQUIRE(rdclass == dns_rdataclass_in); + + UNUSED(type); + UNUSED(rdclass); + + /* + * Handle Active Diretory 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 inline 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 inline 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..f44809d --- /dev/null +++ b/lib/dns/rdata/in_1/a_1.h @@ -0,0 +1,22 @@ +/* + * 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 http://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..655c5ed --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.c @@ -0,0 +1,241 @@ +/* + * 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 http://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 inline 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 inline isc_result_t +totext_in_aaaa(ARGS_TOTEXT) { + isc_region_t region; + + UNUSED(tctx); + + REQUIRE(rdata->type == dns_rdatatype_aaaa); + REQUIRE(rdata->rdclass == dns_rdataclass_in); + REQUIRE(rdata->length == 16); + + dns_rdata_toregion(rdata, ®ion); + return (inet_totext(AF_INET6, ®ion, target)); +} + +static inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_aaaa(ARGS_FREESTRUCT) { + dns_rdata_in_aaaa_t *aaaa = source; + + REQUIRE(source != NULL); + REQUIRE(aaaa->common.rdclass == dns_rdataclass_in); + REQUIRE(aaaa->common.rdtype == dns_rdatatype_aaaa); + + UNUSED(aaaa); +} + +static inline 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 inline 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 inline 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 Diretory 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 inline 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 inline 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..caabf7e --- /dev/null +++ b/lib/dns/rdata/in_1/aaaa_28.h @@ -0,0 +1,24 @@ +/* + * 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 http://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..ae35db9 --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.c @@ -0,0 +1,456 @@ +/* + * 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 http://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 inline 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 inline 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, &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, &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 inline 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 inline 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 inline 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 inline 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(source != 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 inline isc_result_t +tostruct_in_apl(ARGS_TOSTRUCT) { + dns_rdata_in_apl_t *apl = target; + isc_region_t r; + + 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 inline void +freestruct_in_apl(ARGS_FREESTRUCT) { + dns_rdata_in_apl_t *apl = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..8c17a53 --- /dev/null +++ b/lib/dns/rdata/in_1/apl_42.h @@ -0,0 +1,52 @@ +/* + * 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 http://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/dhcid_49.c b/lib/dns/rdata/in_1/dhcid_49.c new file mode 100644 index 0000000..90971b7 --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.c @@ -0,0 +1,232 @@ +/* + * 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 http://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 inline 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, -1)); +} + +static inline isc_result_t +totext_in_dhcid(ARGS_TOTEXT) { + isc_region_t sr, sr2; + char buf[sizeof(" ; 64000 255 64000")]; + size_t n; + + 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) { + n = snprintf(buf, sizeof(buf), " ; %u %u %u", + sr2.base[0] * 256U + sr2.base[1], + sr2.base[2], rdata->length - 3U); + INSIST(n < sizeof(buf)); + RETERR(str_totext(buf, target)); + } + } + return (ISC_R_SUCCESS); +} + +static inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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..455eaed --- /dev/null +++ b/lib/dns/rdata/in_1/dhcid_49.h @@ -0,0 +1,24 @@ +/* + * 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 http://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/kx_36.c b/lib/dns/rdata/in_1/kx_36.c new file mode 100644 index 0000000..2786951 --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.c @@ -0,0 +1,284 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_kx(ARGS_FREESTRUCT) { + dns_rdata_in_kx_t *kx = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..577a4c4 --- /dev/null +++ b/lib/dns/rdata/in_1/kx_36.h @@ -0,0 +1,26 @@ +/* + * 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 http://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/nsap-ptr_23.c b/lib/dns/rdata/in_1/nsap-ptr_23.c new file mode 100644 index 0000000..3217300 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.c @@ -0,0 +1,241 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_nsap_ptr(ARGS_FREESTRUCT) { + dns_rdata_in_nsap_ptr_t *nsap_ptr = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..4880a09 --- /dev/null +++ b/lib/dns/rdata/in_1/nsap-ptr_23.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..ba3e69c --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.c @@ -0,0 +1,251 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_nsap(ARGS_FREESTRUCT) { + dns_rdata_in_nsap_t *nsap = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..79599ef --- /dev/null +++ b/lib/dns/rdata/in_1/nsap_22.h @@ -0,0 +1,26 @@ +/* + * 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 http://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..a94b9df --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.c @@ -0,0 +1,370 @@ +/* + * 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 http://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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_px(ARGS_FREESTRUCT) { + dns_rdata_in_px_t *px = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..3cab1f1 --- /dev/null +++ b/lib/dns/rdata/in_1/px_26.h @@ -0,0 +1,27 @@ +/* + * 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 http://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..63a4e80 --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.c @@ -0,0 +1,394 @@ +/* + * 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 http://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 (0) + +static inline 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 inline 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 inline 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 inline 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 inline 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 inline 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(source != 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 inline 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(target != 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 inline void +freestruct_in_srv(ARGS_FREESTRUCT) { + dns_rdata_in_srv_t *srv = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..78d5ba9 --- /dev/null +++ b/lib/dns/rdata/in_1/srv_33.h @@ -0,0 +1,27 @@ +/* + * 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 http://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/wks_11.c b/lib/dns/rdata/in_1/wks_11.c new file mode 100644 index 0000000..0dc5804 --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.c @@ -0,0 +1,412 @@ +/* + * 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 http://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 + +#define RRTYPE_WKS_ATTRIBUTES (0) + +static isc_mutex_t wks_lock; + +static void init_lock(void) { + RUNTIME_CHECK(isc_mutex_init(&wks_lock) == ISC_R_SUCCESS); +} + +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 + +static inline 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; + 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); + + 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 + + /* + * IPv4 dotted quad. + */ + CHECK(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); + + isc_buffer_availableregion(target, ®ion); + if (getquad(DNS_AS_STR(token), &addr, lexer, callbacks) != 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) + ; + else if (!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. + */ + strncpy(service, DNS_AS_STR(token), sizeof(service)); + service[sizeof(service)-1] = '\0'; + 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) + ; + else if (!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 + + return (result); +} + +static inline 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, &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 inline 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 (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 inline 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 inline 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 inline 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(source != 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 inline isc_result_t +tostruct_in_wks(ARGS_TOSTRUCT) { + dns_rdata_in_wks_t *wks = target; + uint32_t n; + isc_region_t region; + + 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 inline void +freestruct_in_wks(ARGS_FREESTRUCT) { + dns_rdata_in_wks_t *wks = source; + + REQUIRE(source != 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 inline 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 inline 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 inline 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 inline 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 inline 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..d7883dd --- /dev/null +++ b/lib/dns/rdata/in_1/wks_11.h @@ -0,0 +1,25 @@ +/* + * 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 http://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..d09cb27 --- /dev/null +++ b/lib/dns/rdata/rdatastructpre.h @@ -0,0 +1,35 @@ +/* + * 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 http://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..d3a8e46 --- /dev/null +++ b/lib/dns/rdata/rdatastructsuf.h @@ -0,0 +1,15 @@ +/* + * 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 http://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..59b3a74 --- /dev/null +++ b/lib/dns/rdatalist.c @@ -0,0 +1,415 @@ +/* + * 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 http://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 "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, + NULL, + NULL, + NULL, + NULL, + NULL, + isc__rdatalist_setownercase, + isc__rdatalist_getownercase +}; + +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, 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 = rdataset->rdclass; + dns_rdataset_t *tneg = NULL; + dns_rdataset_t *tnegsig = NULL; + dns_name_t *noqname = rdataset->private6; + + REQUIRE(rdataset != NULL); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0); + + (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, 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 = rdataset->rdclass; + dns_rdataset_t *tneg = NULL; + dns_rdataset_t *tnegsig = NULL; + dns_name_t *closest = rdataset->private7; + + REQUIRE(rdataset != NULL); + REQUIRE((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0); + + (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. + */ + 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..5a599fe --- /dev/null +++ b/lib/dns/rdatalist_p.h @@ -0,0 +1,63 @@ +/* + * 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 http://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, 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, 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..a2ac36f --- /dev/null +++ b/lib/dns/rdataset.c @@ -0,0 +1,834 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static const char *trustnames[] = { + "none", + "pending-additional", + "pending-answer", + "additional", + "glue", + "answer", + "authauthority", + "authanswer", + "secure", + "local" /* aka ultimate */ +}; + +const char * +dns_trust_totext(dns_trust_t trust) { + if (trust >= sizeof(trustnames)/sizeof(*trustnames)) + return ("bad"); + return (trustnames[trust]); +} + +void +dns_rdataset_init(dns_rdataset_t *rdataset) { + + /* + * Make 'rdataset' a valid, disassociated rdataset. + */ + + REQUIRE(rdataset != NULL); + + rdataset->magic = DNS_RDATASET_MAGIC; + rdataset->methods = NULL; + ISC_LINK_INIT(rdataset, link); + rdataset->rdclass = 0; + rdataset->type = 0; + rdataset->ttl = 0; + rdataset->trust = 0; + rdataset->covers = 0; + rdataset->attributes = 0; + rdataset->count = UINT32_MAX; + 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 = UINT32_MAX; + 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 = UINT32_MAX; + 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, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +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) + +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 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) +{ + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r; + isc_result_t result; + unsigned int i, count = 0, added, choice; + isc_buffer_t savedbuffer, rdlen, rrbuffer; + unsigned int headlen; + bool question = false; + bool shuffle = false; + dns_rdata_t *in = NULL, in_fixed[MAX_SHUFFLE]; + struct towire_sort *out = NULL, out_fixed[MAX_SHUFFLE]; + dns_fixedname_t fixed; + dns_name_t *name; + + 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((order == NULL) == (order_arg == NULL)); + REQUIRE(cctx != NULL && cctx->mctx != NULL); + + 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 shuffle this answer? + */ + if (!question && count > 1 && + (!WANT_FIXED(rdataset) || order != NULL) && + rdataset->type != dns_rdatatype_rrsig) + shuffle = true; + + if (shuffle && 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 = false; + } else { + in = in_fixed; + out = out_fixed; + } + + if (shuffle) { + /* + * 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); + + /* + * Now we shuffle. + */ + if (WANT_FIXED(rdataset)) { + /* + * 'Fixed' order. + */ + INSIST(order != NULL); + for (i = 0; i < count; i++) { + out[i].key = (*order)(&in[i], order_arg); + out[i].rdata = &in[i]; + } + } else if (WANT_RANDOM(rdataset)) { + /* + * 'Random' order. + */ + for (i = 0; i < count; i++) { + uint32_t val; + + isc_random_get(&val); + choice = i + (val % (count - i)); + rdata = in[i]; + in[i] = in[choice]; + in[choice] = rdata; + if (order != NULL) + out[i].key = (*order)(&in[i], + order_arg); + else + out[i].key = 0; /* Unused */ + out[i].rdata = &in[i]; + } + } else { + /* + * "Cyclic" order. + */ + uint32_t val; + unsigned int j; + + val = rdataset->count; + if (val == UINT32_MAX) + isc_random_get(&val); + j = val % count; + for (i = 0; i < count; i++) { + if (order != NULL) + out[i].key = (*order)(&in[j], + order_arg); + else + out[i].key = 0; /* Unused */ + out[i].rdata = &in[j]; + j++; + if (j == count) + j = 0; /* Wrap around. */ + } + } + + /* + * Sorted order. + */ + if (order != NULL) + qsort(out, count, sizeof(out[0]), towire_compare); + } + + savedbuffer = *target; + i = 0; + added = 0; + + name = dns_fixedname_initname(&fixed); + dns_name_copy(owner_name, name, NULL); + dns_rdataset_getownercase(rdataset, name); + + 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_towire(name, cctx, target); + 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) { + isc_buffer_putuint32(target, rdataset->ttl); + + /* + * Save space for rdlen. + */ + rdlen = *target; + isc_buffer_add(target, 2); + + /* + * Copy out the rdata + */ + if (shuffle) + 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) { + 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, + 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, 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)); +} + +/* + * Additional cache stuff + */ +isc_result_t +dns_rdataset_getadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t **zonep, + dns_db_t **dbp, + dns_dbversion_t **versionp, + dns_dbnode_t **nodep, + dns_name_t *fname, + dns_message_t *msg, + isc_stdtime_t now) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + REQUIRE(zonep == NULL || *zonep == NULL); + REQUIRE(dbp != NULL && *dbp == NULL); + REQUIRE(versionp != NULL && *versionp == NULL); + REQUIRE(nodep != NULL && *nodep == NULL); + REQUIRE(fname != NULL); + REQUIRE(msg != NULL); + + if (acache != NULL && rdataset->methods->getadditional != NULL) { + return ((rdataset->methods->getadditional)(rdataset, type, + qtype, acache, + zonep, dbp, + versionp, nodep, + fname, msg, now)); + } + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_rdataset_setadditional(dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype, + dns_acache_t *acache, + dns_zone_t *zone, + dns_db_t *db, + dns_dbversion_t *version, + dns_dbnode_t *node, + dns_name_t *fname) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (acache != NULL && rdataset->methods->setadditional != NULL) { + return ((rdataset->methods->setadditional)(rdataset, type, + qtype, acache, zone, + db, version, + node, fname)); + } + + return (ISC_R_FAILURE); +} + +isc_result_t +dns_rdataset_putadditional(dns_acache_t *acache, + dns_rdataset_t *rdataset, + dns_rdatasetadditional_t type, + dns_rdatatype_t qtype) +{ + REQUIRE(DNS_RDATASET_VALID(rdataset)); + REQUIRE(rdataset->methods != NULL); + + if (acache != NULL && rdataset->methods->putadditional != NULL) { + return ((rdataset->methods->putadditional)(acache, rdataset, + type, qtype)); + } + + return (ISC_R_FAILURE); +} + +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; +} diff --git a/lib/dns/rdatasetiter.c b/lib/dns/rdatasetiter.c new file mode 100644 index 0000000..c292ef2 --- /dev/null +++ b/lib/dns/rdatasetiter.c @@ -0,0 +1,73 @@ +/* + * 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 http://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 + +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..930a822 --- /dev/null +++ b/lib/dns/rdataslab.c @@ -0,0 +1,1134 @@ +/* + * 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 http://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 here currently only support DNSSEC order iteration. + * + * 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 + +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 sentinal 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 + unsigned int buflen; + isc_result_t result; + unsigned int nitems; + unsigned int nalloc; + unsigned int i; +#if DNS_RDATASET_FIXED + unsigned int *offsettable; +#endif + 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); + if (rawbuf == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (x == NULL) + return (ISC_R_NOMEMORY); + + /* + * 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 + 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 + nitems--; + } else { +#if DNS_RDATASET_FIXED + buflen += (8 + x[i-1].rdata.length); +#else + buflen += (2 + x[i-1].rdata.length); +#endif + /* + * 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 + buflen += (2 + x[i-1].rdata.length); +#endif + /* + * 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 (rawbuf == NULL) { + result = ISC_R_NOMEMORY; + goto free_rdatas; + } + +#if DNS_RDATASET_FIXED + /* Allocate temporary offset table. */ + offsettable = isc_mem_get(mctx, nalloc * sizeof(unsigned int)); + if (offsettable == NULL) { + isc_mem_put(mctx, rawbuf, buflen); + result = ISC_R_NOMEMORY; + goto free_rdatas; + } + memset(offsettable, 0, nalloc * sizeof(unsigned int)); +#endif + + region->base = rawbuf; + region->length = buflen; + + memset(rawbuf, 0, buflen); + rawbuf += reservelen; + +#if DNS_RDATASET_FIXED + offsetbase = rawbuf; +#endif + + *rawbuf++ = (nitems & 0xff00) >> 8; + *rawbuf++ = (nitems & 0x00ff); + +#if DNS_RDATASET_FIXED + /* Skip load order table. Filled in later. */ + rawbuf += nitems * 4; +#endif + + for (i = 0; i < nalloc; i++) { + if (x[i].rdata.data == &removed) + continue; +#if DNS_RDATASET_FIXED + offsettable[x[i].order] = rawbuf - offsetbase; +#endif + 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 + /* + * 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 + + result = ISC_R_SUCCESS; + + free_rdatas: + isc_mem_put(mctx, x, nalloc * sizeof(struct xrdata)); + 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); + } +#if DNS_RDATASET_FIXED + raw += 2 + (4 * count); +#else + raw += 2; +#endif + /* + * 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 + raw += length + 2; +#endif + 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 + raw += 2; +#endif + 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) { + *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 dns_rdatasetmethods_t rdataset_methods = { + rdataset_disassociate, + rdataset_first, + rdataset_next, + rdataset_current, + rdataset_clone, + rdataset_count, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +void +dns_rdataslab_tordataset(unsigned char *slab, unsigned int reservelen, + dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, + dns_rdatatype_t covers, dns_ttl_t ttl, + dns_rdataset_t *rdataset) +{ + REQUIRE(slab != NULL); + REQUIRE(!dns_rdataset_isassociated(rdataset)); + + rdataset->methods = &rdataset_methods; + rdataset->rdclass = rdclass; + rdataset->type = rdtype; + rdataset->covers = covers; + rdataset->ttl = ttl; + rdataset->trust = 0; + rdataset->private1 = NULL; + rdataset->private2 = NULL; + rdataset->private3 = slab + reservelen; + + /* + * Reset iterator state. + */ + rdataset->privateuint4 = 0; + rdataset->private5 = NULL; +} + +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 + while (count > 0) { + count--; + length = *current++ * 256; + length += *current++; +#if DNS_RDATASET_FIXED + current += length + 2; +#else + current += length; +#endif + } + + return ((unsigned int)(current - slab)); +} + +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 inline 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 + 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 inline 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 + + 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 + + /* + * 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 + ostart = ocurrent; + ncurrent = nslab + reservelen; + ncount = *ncurrent++ * 256; + ncount += *ncurrent++; +#if DNS_RDATASET_FIXED + ncurrent += (4 * ncount); +#endif + INSIST(ocount > 0 && ncount > 0); + +#if DNS_RDATASET_FIXED + oncount = ncount; +#endif + + /* + * 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 + olength += length + 2; + ocurrent += length; +#endif + } + + /* + * 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 + tlength += nrdata.length + 2; +#endif + 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); + if (tstart == NULL) + return (ISC_R_NOMEMORY); + memmove(tstart, nslab, reservelen); + tcurrent = tstart + reservelen; +#if DNS_RDATASET_FIXED + offsetbase = tcurrent; +#endif + + /* + * 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)); + if (offsettable == NULL) { + isc_mem_put(mctx, tstart, tlength); + return (ISC_R_NOMEMORY); + } + memset(offsettable, 0, (ocount + oncount) * sizeof(unsigned int)); +#endif + + /* + * Merge the two slabs. + */ + ocurrent = ostart; + INSIST(ocount != 0); +#if DNS_RDATASET_FIXED + oorder = ocurrent[2] * 256 + ocurrent[3]; + INSIST(oorder < ocount); +#endif + rdata_from_slab(&ocurrent, rdclass, type, &ordata); + + ncurrent = nslab + reservelen + 2; +#if DNS_RDATASET_FIXED + ncurrent += (4 * oncount); +#endif + + if (ncount > 0) { + do { + dns_rdata_reset(&nrdata); +#if DNS_RDATASET_FIXED + norder = ncurrent[2] * 256 + ncurrent[3]; + + INSIST(norder < oncount); +#endif + 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 + 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 + 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 + rdata_from_slab(&ocurrent, rdclass, type, + &ordata); + } + } else { +#if DNS_RDATASET_FIXED + offsettable[ocount + norder] = tcurrent - offsetbase; +#endif + 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 + 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 + 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 + + 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 + + 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 + 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 + + /* + * 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); + if (tstart == NULL) + return (ISC_R_NOMEMORY); + memmove(tstart, mslab, reservelen); + tcurrent = tstart + reservelen; +#if DNS_RDATASET_FIXED + offsetbase = tcurrent; + + offsettable = isc_mem_get(mctx, mcount * sizeof(unsigned int)); + if (offsettable == NULL) { + isc_mem_put(mctx, tstart, tlength); + return (ISC_R_NOMEMORY); + } + memset(offsettable, 0, mcount * sizeof(unsigned int)); +#endif + + /* + * Write the new count. + */ + *tcurrent++ = (tcount & 0xff00) >> 8; + *tcurrent++ = (tcount & 0x00ff); + +#if DNS_RDATASET_FIXED + tcurrent += (4 * tcount); +#endif + + /* + * Copy the parts of mslab not in sslab. + */ + mcurrent = mslab + reservelen; + mcount = *mcurrent++ * 256; + mcount += *mcurrent++; +#if DNS_RDATASET_FIXED + mcurrent += (4 * mcount); +#endif + for (i = 0; i < mcount; i++) { + unsigned char *mrdatabegin = mcurrent; +#if DNS_RDATASET_FIXED + order = mcurrent[2] * 256 + mcurrent[3]; + INSIST(order < mcount); +#endif + 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 + 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 + + 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 + + while (count1 > 0) { + length1 = *current1++ * 256; + length1 += *current1++; + + length2 = *current2++ * 256; + length2 += *current2++; + +#if DNS_RDATASET_FIXED + current1 += 2; + current2 += 2; +#endif + + 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 + + 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..a235387 --- /dev/null +++ b/lib/dns/request.c @@ -0,0 +1,1604 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REQUESTMGR_MAGIC ISC_MAGIC('R', 'q', 'u', 'M') +#define VALID_REQUESTMGR(mgr) ISC_MAGIC_VALID(mgr, REQUESTMGR_MAGIC) + +#define REQUEST_MAGIC ISC_MAGIC('R', 'q', 'u', '!') +#define VALID_REQUEST(request) ISC_MAGIC_VALID(request, REQUEST_MAGIC) + +typedef ISC_LIST(dns_request_t) dns_requestlist_t; + +#define DNS_REQUEST_NLOCKS 7 + +struct dns_requestmgr { + unsigned int magic; + isc_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; + isc_socket_t *sock; + isc_result_t result; + 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); + UNUSED(sock); + 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)); + if (requestmgr == NULL) + return (ISC_R_NOMEMORY); + + result = isc_mutex_init(&requestmgr->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, requestmgr, sizeof(*requestmgr)); + return (result); + } + for (i = 0; i < DNS_REQUEST_NLOCKS; i++) { + result = isc_mutex_init(&requestmgr->locks[i]); + if (result != ISC_R_SUCCESS) { + while (--i >= 0) + DESTROYLOCK(&requestmgr->locks[i]); + DESTROYLOCK(&requestmgr->lock); + isc_mem_put(mctx, requestmgr, sizeof(*requestmgr)); + return (result); + } + } + 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; + REQUIRE(VALID_REQUESTMGR(requestmgr)); + + *requestmgrp = NULL; + 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; + 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); + + *requestmgrp = NULL; +} + +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; + isc_mem_t *mctx; + + req_log(ISC_LOG_DEBUG(3), "mgr_destroy"); + + REQUIRE(requestmgr->eref == 0); + REQUIRE(requestmgr->iref == 0); + + DESTROYLOCK(&requestmgr->lock); + for (i = 0; i < DNS_REQUEST_NLOCKS; i++) + DESTROYLOCK(&requestmgr->locks[i]); + if (requestmgr->dispatchv4 != NULL) + dns_dispatch_detach(&requestmgr->dispatchv4); + if (requestmgr->dispatchv6 != NULL) + dns_dispatch_detach(&requestmgr->dispatchv6); + requestmgr->magic = 0; + mctx = requestmgr->mctx; + isc_mem_put(mctx, requestmgr, sizeof(*requestmgr)); + isc_mem_detach(&mctx); +} + +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 inline isc_result_t +req_send(dns_request_t *request, isc_task_t *task, 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); + if (result != ISC_R_SUCCESS) + request->flags &= ~DNS_REQUEST_F_SENDING; + 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)); + if (request == NULL) + return (ISC_R_NOMEMORY); + + /* + * 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, 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, + isc_sockaddr_t *srcaddr, + 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_gettcp2(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, 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 + + 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_createtcp2(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, isc_sockaddr_t *srcaddr, + 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, + isc_sockaddr_t *srcaddr, 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, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp) +{ + return(dns_request_createraw4(requestmgr, msgbuf, srcaddr, destaddr, + -1, options, timeout, 0, 0, task, action, + arg, requestp)); +} + +isc_result_t +dns_request_createraw2(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + unsigned int udptimeout, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp) +{ + unsigned int udpretries = 0; + + if (udptimeout != 0) + udpretries = timeout / udptimeout; + + return (dns_request_createraw4(requestmgr, msgbuf, srcaddr, destaddr, + -1, options, timeout, udptimeout, + udpretries, task, action, arg, + requestp)); +} + +isc_result_t +dns_request_createraw3(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, unsigned int timeout, + unsigned int udptimeout, unsigned int udpretries, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp) +{ + return (dns_request_createraw4(requestmgr, msgbuf, srcaddr, destaddr, + -1, options, timeout, udptimeout, + udpretries, task, action, arg, + requestp)); +} + +isc_result_t +dns_request_createraw4(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf, + isc_sockaddr_t *srcaddr, 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)); + if (request->event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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_addresponse3(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); + + result = isc_buffer_allocate(mctx, &request->query, + r.length + (tcp ? 2 : 0)); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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, + 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_createvia4(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, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + 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_createvia4(requestmgr, message, srcaddr, destaddr, + -1, options, key, timeout, 0, 0, task, + action, arg, requestp)); +} + +isc_result_t +dns_request_createvia2(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, dns_tsigkey_t *key, + unsigned int timeout, unsigned int udptimeout, + isc_task_t *task, isc_taskaction_t action, void *arg, + dns_request_t **requestp) +{ + unsigned int udpretries = 0; + + if (udptimeout != 0) + udpretries = timeout / udptimeout; + return (dns_request_createvia4(requestmgr, message, srcaddr, destaddr, + -1, options, key, timeout, udptimeout, + udpretries, task, action, arg, + requestp)); +} + +isc_result_t +dns_request_createvia3(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr, + unsigned int options, dns_tsigkey_t *key, + unsigned int timeout, unsigned int udptimeout, + unsigned int udpretries, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_request_t **requestp) +{ + return (dns_request_createvia4(requestmgr, message, srcaddr, destaddr, + -1, options, key, timeout, udptimeout, + udpretries, task, action, arg, + requestp)); +} + +isc_result_t +dns_request_createvia4(dns_requestmgr_t *requestmgr, dns_message_t *message, + isc_sockaddr_t *srcaddr, 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)); + if (request->event == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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); + share = (options & DNS_REQUESTOPT_SHARE); + result = get_dispatch(tcp, false, share, + requestmgr, srcaddr, destaddr, + dscp, &connected, &request->dispatch); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_dispatch_addresponse2(request->dispatch, 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. + */ + result = isc_buffer_allocate(mctx, &buf1, 65535); + if (result != ISC_R_SUCCESS) + return (result); + + 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; + } + result = isc_buffer_allocate(mctx, &buf2, r.length + (tcp ? 2 : 0)); + if (result != ISC_R_SUCCESS) + goto cleanup; + 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); +} + +bool +dns_request_usedtcp(dns_request_t *request) { + REQUIRE(VALID_REQUEST(request)); + + return (request->flags & DNS_REQUEST_F_TCP); +} + +void +dns_request_destroy(dns_request_t **requestp) { + dns_request_t *request; + + REQUIRE(requestp != NULL && VALID_REQUEST(*requestp)); + + request = *requestp; + + 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); + + *requestp = NULL; +} + +/*** + *** 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); + + 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); + result = sevent->result; + 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]); + isc_event_free(&event); +} + +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; + + 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); + + 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 (sevent->result != ISC_R_SUCCESS) { + req_cancel(request); + send_if_done(request, ISC_R_CANCELED); + } + UNLOCK(&request->requestmgr->locks[request->hash]); + + isc_event_free(&event); +} + +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); + result = isc_buffer_allocate(request->mctx, &request->answer, + r.length); + if (result != ISC_R_SUCCESS) + goto done; + 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; + + REQUIRE(VALID_REQUEST(request)); + + req_log(ISC_LOG_DEBUG(3), "req_timeout: request %p", request); + + UNUSED(task); + LOCK(&request->requestmgr->locks[request->hash]); + if (event->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]); + isc_event_free(&event); +} + +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) { + isc_mem_t *mctx; + + 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_detach(&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); + mctx = request->mctx; + isc_mem_put(mctx, request, sizeof(*request)); + isc_mem_detach(&mctx); +} + +/* + * 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_detach(&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..0abf4de --- /dev/null +++ b/lib/dns/resolver.c @@ -0,0 +1,10327 @@ +/* + * 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 http://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 + +#ifdef AES_CC +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +/* + * The maximum time we will wait for a single query. + */ +#define MAX_SINGLE_QUERY_TIMEOUT 9U +#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_SEC) + +/* + * We need to allow a individual query time to complete / timeout. + */ +#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1U) + +/* The default time in seconds for the whole query to live. */ +#ifndef DEFAULT_QUERY_TIMEOUT +#define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT +#endif + +/* The maximum time in seconds for the whole query to live. */ +#ifndef MAXIMUM_QUERY_TIMEOUT +#define MAXIMUM_QUERY_TIMEOUT 30 +#endif + +/* The default maximum number of recursions to follow before giving up. */ +#ifndef DEFAULT_RECURSION_DEPTH +#define DEFAULT_RECURSION_DEPTH 7 +#endif + +/* The default maximum number of iterative queries to allow before giving up. */ +#ifndef DEFAULT_MAX_QUERIES +#define DEFAULT_MAX_QUERIES 75 +#endif + +/* Number of hash buckets for zone counters */ +#ifndef RES_DOMAIN_BUCKETS +#define RES_DOMAIN_BUCKETS 523 +#endif +#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; + 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; + unsigned int 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 +} 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; + + /*% Locked by appropriate bucket lock. */ + fetchstate state; + bool want_shutdown; + bool cloned; + bool spilled; + unsigned int references; + 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; + unsigned int attributes; + isc_timer_t * timer; + isc_time_t expires; + isc_interval_t interval; + dns_message_t * qmessage; + dns_message_t * rmessage; + ISC_LIST(resquery_t) queries; + dns_adbfindlist_t finds; + dns_adbfind_t * find; + 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; + + /*% + * The number of events we're waiting for. + */ + unsigned int pending; + + /*% + * 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; + + /*% + * 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; + isc_sockaddr_t *client; + unsigned int depth; +}; + +#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 +#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) (((f)->attributes & FCTX_ATTR_HAVEANSWER) != \ + 0) +#define GLUING(f) (((f)->attributes & FCTX_ATTR_GLUING) != \ + 0) +#define ADDRWAIT(f) (((f)->attributes & FCTX_ATTR_ADDRWAIT) != \ + 0) +#define SHUTTINGDOWN(f) (((f)->attributes & FCTX_ATTR_SHUTTINGDOWN) \ + != 0) +#define WANTCACHE(f) (((f)->attributes & FCTX_ATTR_WANTCACHE) != 0) +#define WANTNCACHE(f) (((f)->attributes & FCTX_ATTR_WANTNCACHE) != 0) +#define NEEDEDNS0(f) (((f)->attributes & FCTX_ATTR_NEEDEDNS0) != 0) +#define TRIEDFIND(f) (((f)->attributes & FCTX_ATTR_TRIEDFIND) != 0) +#define TRIEDALT(f) (((f)->attributes & FCTX_ATTR_TRIEDALT) != 0) + +typedef struct { + dns_adbaddrinfo_t * addrinfo; + fetchctx_t * fctx; +} 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; + 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 nlock; + 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 + dns_rbt_t * algorithms; + dns_rbt_t * digests; +#if USE_MBSLOCK + isc_rwlock_t mbslock; +#endif + 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]; + + /* Locked by lock. */ + unsigned int references; + bool exiting; + isc_eventlist_t whenshutdown; + unsigned int activebuckets; + bool priming; + unsigned int spillat; /* clients-per-query */ + unsigned int zspill; /* fetches-per-zone */ + + dns_badcache_t * badcache; /* Bad cache. */ + + /* Locked by primelock. */ + dns_fetch_t * primefetch; + /* Locked by nlock. */ + unsigned int 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 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 NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) +#define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) + +#ifdef ENABLE_AFL +bool dns_fuzzing_resolver = false; +void dns_resolver_setfuzzing() { + dns_fuzzing_resolver = true; +} +#endif + +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 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 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 bool maybe_destroy(fetchctx_t *fctx, bool locked); +static void add_bad(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, + isc_result_t reason, badnstype_t badtype); +static inline isc_result_t findnoqname(fetchctx_t *fctx, 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); + +/*% + * Increment resolver-related statistics counters. + */ +static inline void +inc_stats(dns_resolver_t *res, isc_statscounter_t counter) { + if (res->view->resstats != NULL) + isc_stats_increment(res->view->resstats, counter); +} + +static inline 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_adbaddrinfo_t *addrinfo, dns_name_t *name, + dns_rdatatype_t type, dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, unsigned int valoptions, + isc_task_t *task) +{ + dns_validator_t *validator = NULL; + dns_valarg_t *valarg; + isc_result_t result; + + valarg = isc_mem_get(fctx->mctx, sizeof(*valarg)); + if (valarg == NULL) + return (ISC_R_NOMEMORY); + + valarg->fctx = fctx; + valarg->addrinfo = addrinfo; + + 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, fctx->rmessage, + 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 + 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 postive + * 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 inline 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 inline 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 inline 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 inline 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; + REQUIRE(!ISC_LINK_LINKED(query, link)); + + INSIST(query->tcpsocket == NULL); + + fctx = query->fctx; + res = fctx->res; + bucket = fctx->bucketnum; + + fctx->nqueries--; + + LOCK(&res->buckets[bucket].lock); + empty = fctx_decreference(query->fctx); + UNLOCK(&res->buckets[bucket].lock); + + query->magic = 0; + isc_mem_put(query->mctx, query, sizeof(*query)); + *queryp = NULL; + + if (empty) + empty_bucket(res); +} + +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; + + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) + dns_adb_ednsto(fctx->adb, query->addrinfo, + query->udpsize); + else + dns_adb_timeout(fctx->adb, query->addrinfo); + + /* + * 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); + isc_random_get(&value); + 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); + } + 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); + } + } else 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 inline void +fctx_stopqueries(fetchctx_t *fctx, bool no_response, + bool age_untried) +{ + FCTXTRACE("stopqueries"); + fctx_cancelqueries(fctx, no_response, age_untried); + fctx_stoptimer(fctx); +} + +static inline 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) { + char dbuf[DNS_NAME_FORMATSIZE]; + isc_stdtime_t now; + + if (! isc_log_wouldlog(dns_lctx, ISC_LOG_INFO)) + return; + + isc_stdtime_get(&now); + if (counter->logged > now - 60) + return; + + dns_name_format(&fctx->domain, dbuf, sizeof(dbuf)); + + 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); + + 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, spill; + + REQUIRE(fctx != NULL); + REQUIRE(fctx->res != NULL); + + INSIST(fctx->dbucketnum == RES_NOBUCKET); + bucketnum = dns_name_fullhash(&fctx->domain, false) + % RES_DOMAIN_BUCKETS; + + LOCK(&fctx->res->lock); + spill = fctx->res->zspill; + UNLOCK(&fctx->res->lock); + + 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)); + if (counter == NULL) + result = ISC_R_NOMEMORY; + else { + 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_copy(&fctx->domain, counter->domain, NULL); + ISC_LIST_APPEND(dbucket->list, counter, link); + } + } else { + if (!force && spill != 0 && counter->count >= spill) { + counter->dropped++; + fcount_logspill(fctx, counter); + 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) { + ISC_LIST_UNLINK(dbucket->list, counter, link); + isc_mem_put(dbucket->mctx, counter, sizeof(*counter)); + } + } + + UNLOCK(&dbucket->lock); +} + +static inline 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); + 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 ((fctx->attributes & FCTX_ATTR_HAVEANSWER) != 0 && + fctx->spilled && + (count < fctx->res->spillatmax || fctx->res->spillatmax == 0)) { + LOCK(&fctx->res->lock); + if (count == fctx->res->spillat && !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 inline 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); + + fctx->reason = NULL; +} + +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; + } else if (result == ISC_R_TIMEDOUT) + age_untried = true; + + fctx->reason = NULL; + + fctx_stopqueries(fctx, no_response, age_untried); + + LOCK(&res->buckets[fctx->bucketnum].lock); + + fctx->state = fetchstate_done; + fctx->attributes &= ~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->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->attributes &= ~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 inline 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 inline void +fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) { + unsigned int seconds; + unsigned int us; + + /* + * We retry every .8 seconds the first two times through the address + * list, and then we do exponential back-off. + */ + if (fctx->restarts < 3) + us = 800000; + else + us = (800000 << (fctx->restarts - 2)); + + /* + * 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; + + FCTXTRACE("query"); + + res = fctx->res; + task = res->buckets[fctx->bucketnum].task; + + srtt = addrinfo->srtt; + + /* + * 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)); + + dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE); + + query = isc_mem_get(fctx->mctx, sizeof(*query)); + if (query == NULL) { + result = ISC_R_NOMEMORY; + goto stop_idle_timer; + } + 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 + /* + * 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 fetch */ + dns_adb_beginudpfetch(fctx->adb, addrinfo); + + result = resquery_send(query); + if (result != ISC_R_SUCCESS) + goto cleanup_dispatch; + } + + fctx->querysent++; + + ISC_LIST_APPEND(fctx->queries, query, link); + query->fctx->nqueries++; + 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_dispatch: + if (query->dispatch != NULL) + dns_dispatch_detach(&query->dispatch); + + cleanup_query: + if (query->connects == 0) { + query->magic = 0; + isc_mem_put(fctx->mctx, query, sizeof(*query)); + } + + stop_idle_timer: + 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 + if (bad_edns(fctx, address)) + return; + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + if (sa == NULL) + return; + + *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)); + if (tried == NULL) + return; + + 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)); + if (tried == NULL) + return; + + tried->addr = *address; + tried->count = 1; + ISC_LIST_INITANDAPPEND(fctx->edns512, tried, link); +} + +static void +compute_cc(resquery_t *query, unsigned char *cookie, size_t len) { +#ifdef AES_CC + unsigned char digest[ISC_AES_BLOCK_LENGTH]; + unsigned char input[16]; + isc_netaddr_t netaddr; + unsigned int i; + + INSIST(len >= 8U); + + isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr); + switch (netaddr.family) { + case AF_INET: + memmove(input, (unsigned char *)&netaddr.type.in, 4); + memset(input + 4, 0, 12); + break; + case AF_INET6: + memmove(input, (unsigned char *)&netaddr.type.in6, 16); + break; + } + isc_aes128_crypt(query->fctx->res->view->secret, input, digest); + for (i = 0; i < 8; i++) + digest[i] ^= digest[i + 8]; + memmove(cookie, digest, 8); +#endif +#ifdef HMAC_SHA1_CC + unsigned char digest[ISC_SHA1_DIGESTLENGTH]; + isc_netaddr_t netaddr; + isc_hmacsha1_t hmacsha1; + + INSIST(len >= 8U); + + isc_hmacsha1_init(&hmacsha1, query->fctx->res->view->secret, + ISC_SHA1_DIGESTLENGTH); + isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr); + switch (netaddr.family) { + case AF_INET: + isc_hmacsha1_update(&hmacsha1, + (unsigned char *)&netaddr.type.in, 4); + break; + case AF_INET6: + isc_hmacsha1_update(&hmacsha1, + (unsigned char *)&netaddr.type.in6, 16); + break; + } + isc_hmacsha1_sign(&hmacsha1, digest, sizeof(digest)); + memmove(cookie, digest, 8); + isc_hmacsha1_invalidate(&hmacsha1); +#endif +#ifdef HMAC_SHA256_CC + unsigned char digest[ISC_SHA256_DIGESTLENGTH]; + isc_netaddr_t netaddr; + isc_hmacsha256_t hmacsha256; + + INSIST(len >= 8U); + + isc_hmacsha256_init(&hmacsha256, query->fctx->res->view->secret, + ISC_SHA256_DIGESTLENGTH); + isc_netaddr_fromsockaddr(&netaddr, &query->addrinfo->sockaddr); + switch (netaddr.family) { + case AF_INET: + isc_hmacsha256_update(&hmacsha256, + (unsigned char *)&netaddr.type.in, 4); + break; + case AF_INET6: + isc_hmacsha256_update(&hmacsha256, + (unsigned char *)&netaddr.type.in6, 16); + break; + } + isc_hmacsha256_sign(&hmacsha256, digest, sizeof(digest)); + memmove(cookie, digest, 8); + isc_hmacsha256_invalidate(&hmacsha256); +#endif +} + +static isc_result_t +issecuredomain(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, + isc_stdtime_t now, bool checknta, + 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, issecure)); +} + +static bool +wouldvalidate(fetchctx_t *fctx) { + bool secure_domain; + isc_result_t result; + isc_stdtime_t now; + + if (!fctx->res->view->enablevalidation) + return (false); + + if (fctx->res->view->dlv != NULL) + return (true); + + isc_stdtime_get(&now); + result = dns_view_issecuredomain(fctx->res->view, &fctx->name, + now, true, &secure_domain); + if (result != ISC_R_SUCCESS) + return (false); + return (secure_domain); +} + +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 connecting = false; + bool tcp = (query->options & DNS_FETCHOPT_TCP); + 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_addresponse2(query->dispatch, + &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_init(qname, NULL); + 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); + result = issecuredomain(res->view, &fctx->name, fctx->type, + isc_time_seconds(&query->start), + checknta, &secure_domain); + if (result != ISC_R_SUCCESS) + secure_domain = false; + if (res->view->dlv != NULL) + secure_domain = true; + if (secure_domain) + 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; + + /* See if response history indicates that EDNS is not supported. */ + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0 && + dns_adb_noedns(fctx->adb, query->addrinfo)) + 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 (fctx->timeouts > (MAX_EDNS0_TIMEOUTS * 2) && + (!EDNSOK(query->addrinfo) || !wouldvalidate(fctx))) { + query->options |= DNS_FETCHOPT_NOEDNS0; + fctx->reason = "disabling EDNS"; + } else if ((tried = triededns512(fctx, sockaddr)) != NULL && + tried->count >= 2U && + (!EDNSOK(query->addrinfo) || !wouldvalidate(fctx))) { + query->options |= DNS_FETCHOPT_NOEDNS0; + fctx->reason = "disabling EDNS"; + } else 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; + unsigned char cookie[64]; + + if ((flags & FCTX_ADDRINFO_EDNSOK) != 0 && + (query->options & DNS_FETCHOPT_EDNS512) == 0) { + udpsize = dns_adb_probesize2(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 DNS_EDNS_VERSION > 0 + /* + * Some EDNS(0) servers don't ignore unknown options + * as it was not a explict requirement of RFC 2671. + * Only send COOKIE to EDNS(1) servers. + */ + if (version < 1) + sendcookie = false; +#endif + 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, 8); + ednsopts[ednsopt].value = cookie; + ednsopts[ednsopt].length = 8; + inc_stats(fctx->res, + dns_resstatscounter_cookienew); + } + ednsopt++; + } + 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 choosen. + */ + 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_logfmtpacket2(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); + + if (query->exclusivesocket) + sock = dns_dispatch_getentrysocket(query->dispentry); + else + sock = dns_dispatch_getsocket(query->dispatch); + /* + * 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; + connecting = true; + 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); + if (result != ISC_R_SUCCESS) { + if (connecting) { + /* + * This query is still connecting. + * Mark it as canceled so that it will just be + * cleaned up when the connected event is received. + * Keep fctx around until the event is processed. + */ + query->fctx->nqueries++; + query->attributes |= RESQUERY_ATTR_CANCELED; + } + goto cleanup_message; + } + + 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. 20 seconds + * should 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, 20, 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, + 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); + 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->attributes &= ~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->attributes &= ~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->attributes &= ~FCTX_ATTR_ADDRWAIT; + want_done = true; + } + } + } else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 && + fctx->nqueries == 0 && ISC_LIST_EMPTY(fctx->validators)) { + + if (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 inline 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 inline 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 + + /* + * 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_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 + + 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' */ + } + } + + if (bad_server(fctx, address)) { + /* + * We already know this server is bad. + */ + return; + } + + FCTXTRACE("add_bad"); + + sa = isc_mem_get(fctx->mctx, sizeof(*sa)); + if (sa == NULL) + return; + *sa = *address; + ISC_LIST_INITANDAPPEND(fctx->bad, sa, link); + + if (reason == DNS_R_LAME) /* already logged */ + return; + + if (reason == DNS_R_UNEXPECTEDRCODE && + fctx->rmessage->rcode == dns_rcode_servfail && + ISFORWARDER(addrinfo)) + return; + + if (reason == DNS_R_UNEXPECTEDRCODE) { + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(fctx->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)fctx->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, dns_name_t *name, in_port_t port, + unsigned int options, unsigned int flags, isc_stdtime_t now, + bool *overquota, bool *need_alternate) +{ + dns_adbaddrinfo_t *ai; + dns_adbfind_t *find; + dns_resolver_t *res; + bool unshared; + isc_result_t result; + + res = fctx->res; + unshared = (fctx->options & DNS_FETCHOPT_UNSHARED); + /* + * 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_createfind2(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); + 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_FORWARDER) != 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; + } 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(dns_name_t *name1, 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; + + 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 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_find2(res->view->fwdtable, name, + domain, &forwarders); + if (result == ISC_R_SUCCESS) { + fwd = ISC_LIST_HEAD(forwarders->fwdrs); + fctx->fwdpolicy = forwarders->fwdpolicy; + 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); + result = dns_name_dup(domain, fctx->mctx, + &fctx->domain); + if (result != ISC_R_SUCCESS) + return (result); + 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. + */ + + 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; + + findname(fctx, &ns.name, 0, stdoptions, 0, now, + &overquota, &need_alternate); + + if (!overquota) + all_spilled = false; + + dns_rdata_reset(&rdata); + dns_rdata_freestruct(&ns); + } + 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_FORWARDER, + now, 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; + 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_dlv || + 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 inline 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 inline 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; + return (addrinfo); + } + } + + /* + * No forwarders. Move to the next find. + */ + + fctx->attributes |= 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->attributes |= 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; + unsigned int bucketnum; + bool bucket_empty; + + FCTXTRACE5("try", "fctx->qc=", isc_counter_used(fctx->qc)); + + REQUIRE(!ADDRWAIT(fctx)); + + res = fctx->res; + + /* 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->attributes |= 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; + } + } + + if (dns_name_countlabels(&fctx->domain) > 2) { + 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; + } + } + + bucketnum = fctx->bucketnum; + 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 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(fctx->references == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + + FCTXTRACE("unlink"); + + res = fctx->res; + bucketnum = fctx->bucketnum; + + ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link); + + LOCK(&res->nlock); + res->nfctx--; + UNLOCK(&res->nlock); + dec_stats(res, dns_resstatscounter_nfetch); + + if (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(fctx->references == 0); + REQUIRE(ISC_LIST_EMPTY(fctx->validators)); + REQUIRE(!ISC_LINK_LINKED(fctx, link)); + + FCTXTRACE("destroy"); + + /* + * 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_detach(&fctx->timer); + dns_message_destroy(&fctx->rmessage); + dns_message_destroy(&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_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->attributes &= ~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); +} + +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_send(fctx->res->buckets[fctx->bucketnum].task, + &cevent); + } +} + +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->attributes &= ~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); + + /* + * 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->attributes |= 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 (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->attributes |= 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 (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_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 Cancelation. + */ + +static inline isc_result_t +fctx_join(fetchctx_t *fctx, isc_task_t *task, 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)); + if (event == NULL) { + isc_task_detach(&tclone); + return (ISC_R_NOMEMORY); + } + 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->references++; + fctx->client = client; + + fetch->magic = DNS_FETCH_MAGIC; + fetch->private = fctx; + + return (ISC_R_SUCCESS); +} + +static inline 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, dns_name_t *name, dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + 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; + dns_fixedname_t fixed; + unsigned int findoptions = 0; + char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + isc_mem_t *mctx; + + /* + * 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)); + if (fctx == NULL) + return (ISC_R_NOMEMORY); + + 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)); + dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + strlcat(buf, "/", sizeof(buf)); + strlcat(buf, typebuf, sizeof(buf)); + fctx->info = isc_mem_strdup(mctx, buf); + if (fctx->info == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_counter; + } + + FCTXTRACE("create"); + dns_name_init(&fctx->name, NULL); + result = dns_name_dup(name, mctx, &fctx->name); + if (result != ISC_R_SUCCESS) + goto cleanup_info; + dns_name_init(&fctx->domain, NULL); + dns_rdataset_init(&fctx->nameservers); + + fctx->type = 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; + fctx->references = 0; + fctx->bucketnum = bucketnum; + fctx->dbucketnum = RES_NOBUCKET; + fctx->state = fetchstate_init; + fctx->want_shutdown = false; + fctx->cloned = false; + fctx->depth = depth; + 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; + 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; + 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; + fctx->client = NULL; + 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; + unsigned int labels; + dns_name_t *fwdname = name; + dns_name_t suffix; + + /* + * DS records are found in the parent server. Strip one + * leading label from the name (to be used in finding + * the forwarder). + */ + if (dns_rdatatype_atparent(fctx->type) && + dns_name_countlabels(name) > 1) { + dns_name_init(&suffix, NULL); + labels = dns_name_countlabels(name); + dns_name_getlabelsequence(name, 1, labels - 1, &suffix); + fwdname = &suffix; + } + + /* Find the forwarder for this name. */ + domain = dns_fixedname_initname(&fixed); + result = dns_fwdtable_find2(fctx->res->view->fwdtable, fwdname, + domain, &forwarders); + if (result == ISC_R_SUCCESS) + fctx->fwdpolicy = forwarders->fwdpolicy; + + if (fctx->fwdpolicy != dns_fwdpolicy_only) { + /* + * 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, + domain, 0, findoptions, + true, + &fctx->nameservers, + NULL); + if (result != ISC_R_SUCCESS) + goto cleanup_nameservers; + + result = dns_name_dup(domain, mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) + goto cleanup_nameservers; + + fctx->ns_ttl = fctx->nameservers.ttl; + fctx->ns_ttl_ok = true; + } else { + /* + * We're in forward-only mode. Set the query domain. + */ + result = dns_name_dup(domain, mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) + goto cleanup_name; + } + } else { + result = dns_name_dup(domain, mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) + goto cleanup_name; + 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"); + + INSIST(dns_name_issubdomain(&fctx->name, &fctx->domain)); + + fctx->qmessage = NULL; + result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, + &fctx->qmessage); + + if (result != ISC_R_SUCCESS) + goto cleanup_fcount; + + fctx->rmessage = NULL; + result = dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, + &fctx->rmessage); + + if (result != ISC_R_SUCCESS) + goto cleanup_qmessage; + + /* + * Compute an expiration time for the entire fetch. + */ + isc_interval_set(&interval, res->query_timeout, 0); + 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_rmessage; + } + + /* + * 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. 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_rmessage; + } + + /* + * 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; + + ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link); + + LOCK(&res->nlock); + res->nfctx++; + UNLOCK(&res->nlock); + inc_stats(res, dns_resstatscounter_nfetch); + + *fctxp = fctx; + + return (ISC_R_SUCCESS); + + cleanup_rmessage: + dns_message_destroy(&fctx->rmessage); + + cleanup_qmessage: + dns_message_destroy(&fctx->qmessage); + + cleanup_fcount: + fcount_decr(fctx); + + cleanup_domain: + if (dns_name_countlabels(&fctx->domain) > 0) + dns_name_free(&fctx->domain, mctx); + + cleanup_nameservers: + if (dns_rdataset_isassociated(&fctx->nameservers)) + dns_rdataset_disassociate(&fctx->nameservers); + + cleanup_name: + dns_name_free(&fctx->name, mctx); + + cleanup_info: + isc_mem_free(mctx, fctx->info); + + cleanup_counter: + isc_counter_detach(&fctx->qc); + + cleanup_fetch: + isc_mem_put(mctx, fctx, sizeof(*fctx)); + + return (result); +} + +/* + * Handle Responses + */ +static inline bool +is_lame(fetchctx_t *fctx) { + dns_message_t *message = fctx->rmessage; + 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 inline 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 inline void +log_formerr(fetchctx_t *fctx, const char *format, ...) { + char nsbuf[ISC_SOCKADDR_FORMATSIZE]; + char clbuf[ISC_SOCKADDR_FORMATSIZE]; + const char *clmsg = ""; + 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)); + + if (fctx->client != NULL) { + clmsg = " for client "; + isc_sockaddr_format(fctx->client, clbuf, sizeof(clbuf)); + } else { + clbuf[0] = '\0'; + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE, + "DNS format error from %s resolving %s%s%s: %s", + nsbuf, fctx->info, clmsg, clbuf, msgbuf); +} + +static isc_result_t +same_question(fetchctx_t *fctx) { + isc_result_t result; + dns_message_t *message = fctx->rmessage; + 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; + isc_result_t result; + 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)) { + name = dns_fixedname_name(&event->foundname); + result = dns_name_copy(hname, name, NULL); + if (result != ISC_R_SUCCESS) + event->result = result; + else + 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) + +/* + * Destroy '*fctx' if it is ready to be destroyed (i.e., if it has + * no references and is no longer waiting for any events). + * + * Requires: + * '*fctx' is shutting down. + * + * Returns: + * true if the resolver is exiting and this is the last fctx in the bucket. + */ +static bool +maybe_destroy(fetchctx_t *fctx, bool locked) { + unsigned int bucketnum; + bool bucket_empty = false; + dns_resolver_t *res = fctx->res; + dns_validator_t *validator, *next_validator; + bool dodestroy = false; + + REQUIRE(SHUTTINGDOWN(fctx)); + + bucketnum = fctx->bucketnum; + if (!locked) + LOCK(&res->buckets[bucketnum].lock); + if (fctx->pending != 0 || fctx->nqueries != 0) + goto unlock; + + for (validator = ISC_LIST_HEAD(fctx->validators); + validator != NULL; validator = next_validator) { + next_validator = ISC_LIST_NEXT(validator, link); + dns_validator_cancel(validator); + } + + if (fctx->references == 0 && ISC_LIST_EMPTY(fctx->validators)) { + bucket_empty = fctx_unlink(fctx); + dodestroy = true; + } + unlock: + if (!locked) + UNLOCK(&res->buckets[bucketnum].lock); + if (dodestroy) + fctx_destroy(fctx); + return (bucket_empty); +} + +/* + * 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; + dns_validatorevent_t *vevent; + fetchctx_t *fctx; + bool chaining; + bool negative; + bool sentresponse; + isc_result_t eresult = ISC_R_SUCCESS; + isc_result_t result = ISC_R_SUCCESS; + isc_stdtime_t now; + uint32_t ttl; + unsigned options; + uint32_t bucketnum; + + UNUSED(task); /* for now */ + + REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE); + valarg = event->ev_arg; + fctx = valarg->fctx; + res = fctx->res; + addrinfo = valarg->addrinfo; + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(!ISC_LIST_EMPTY(fctx->validators)); + + 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; + + /* + * Destroy the validator early so that we can + * destroy the fctx if necessary. + */ + dns_validator_destroy(&vevent->validator); + isc_mem_put(fctx->mctx, valarg, sizeof(*valarg)); + + negative = (vevent->rdataset == NULL); + + sentresponse = (fctx->options & DNS_FETCHOPT_NOVALIDATE); + + /* + * If shutting down, ignore the results. Check to see if we're + * done waiting for validator completions and ADB pending events; if + * so, destroy the fctx. + */ + if (SHUTTINGDOWN(fctx) && !sentresponse) { + bool bucket_empty; + bucket_empty = maybe_destroy(fctx, true); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) + empty_bucket(res); + goto cleanup_event; + } + + isc_stdtime_get(&now); + + /* + * If chaining, we need to make sure that the right result code is + * returned, and that the rdatasets are bound. + */ + if (vevent->result == ISC_R_SUCCESS && + !negative && + vevent->rdataset != NULL && + CHAINING(vevent->rdataset)) + { + if (vevent->rdataset->type == dns_rdatatype_cname) + eresult = DNS_R_CNAME; + else { + INSIST(vevent->rdataset->type == dns_rdatatype_dname); + eresult = DNS_R_DNAME; + } + chaining = true; + } else + chaining = false; + + /* + * Either we're not shutting down, or we are shutting down but want + * to cache the result anyway (if this was a validation started by + * a query with cd set) + */ + + hevent = ISC_LIST_HEAD(fctx->events); + if (hevent != NULL) { + if (!negative && !chaining && + (fctx->type == dns_rdatatype_any || + fctx->type == dns_rdatatype_rrsig || + fctx->type == dns_rdatatype_sig)) { + /* + * Don't bind rdatasets; the caller + * will iterate the node. + */ + } else { + ardataset = hevent->rdataset; + asigrdataset = hevent->sigrdataset; + } + } + + if (vevent->result != ISC_R_SUCCESS) { + FCTXTRACE("validation failed"); + inc_stats(res, dns_resstatscounter_valfail); + fctx->valfail++; + fctx->vresult = vevent->result; + if (fctx->vresult != DNS_R_BROKENCHAIN) { + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) + result = dns_db_findnode(fctx->cache, + vevent->name, + true, &node); + if (result == ISC_R_SUCCESS) + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, + vevent->type, 0); + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + (void)dns_db_deleterdataset(fctx->cache, node, + NULL, + dns_rdatatype_rrsig, + vevent->type); + if (result == ISC_R_SUCCESS) + dns_db_detachnode(fctx->cache, &node); + } + if (fctx->vresult == DNS_R_BROKENCHAIN && !negative) { + /* + * Cache the data as pending for later validation. + */ + result = ISC_R_NOTFOUND; + if (vevent->rdataset != NULL) + result = dns_db_findnode(fctx->cache, + vevent->name, + true, &node); + if (result == ISC_R_SUCCESS) { + (void)dns_db_addrdataset(fctx->cache, node, + NULL, now, + vevent->rdataset, 0, + NULL); + } + if (result == ISC_R_SUCCESS && + vevent->sigrdataset != NULL) + (void)dns_db_addrdataset(fctx->cache, node, + NULL, now, + vevent->sigrdataset, + 0, NULL); + if (result == ISC_R_SUCCESS) + dns_db_detachnode(fctx->cache, &node); + } + result = fctx->vresult; + add_bad(fctx, addrinfo, result, badns_validation); + isc_event_free(&event); + UNLOCK(&res->buckets[bucketnum].lock); + INSIST(fctx->validator == NULL); + fctx->validator = ISC_LIST_HEAD(fctx->validators); + if (fctx->validator != NULL) + dns_validator_send(fctx->validator); + 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_dlv || + 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. */ + return; + } + + + if (negative) { + dns_rdatatype_t covers; + FCTXTRACE("nonexistence validation OK"); + + inc_stats(res, dns_resstatscounter_valnegsuccess); + + /* + * Cache DS NXDOMAIN seperately to other types. + */ + if (fctx->rmessage->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(fctx->rmessage, fctx->cache, node, + covers, now, 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, 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) { + bool bucket_empty = false; + /* + * If we only deferred the destroy because we wanted to cache + * the data, destroy now. + */ + dns_db_detachnode(fctx->cache, &node); + if (SHUTTINGDOWN(fctx)) + bucket_empty = maybe_destroy(fctx, true); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) + empty_bucket(res); + 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 NS/NSEC records that happened to be validated. + */ + result = dns_message_firstname(fctx->rmessage, DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(fctx->rmessage, 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_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(fctx->rmessage, + DNS_SECTION_AUTHORITY); + } + + result = ISC_R_SUCCESS; + + /* + * Respond with an answer, positive or negative, + * as opposed to an error. 'node' must be non-NULL. + */ + + fctx->attributes |= FCTX_ATTR_HAVEANSWER; + + if (hevent != NULL) { + /* + * Negative results must be indicated in event->result. + */ + if (dns_rdataset_isassociated(hevent->rdataset) && + NEGATIVE(hevent->rdataset)) { + INSIST(eresult == DNS_R_NCACHENXDOMAIN || + eresult == DNS_R_NCACHENXRRSET); + } + hevent->result = eresult; + RUNTIME_CHECK(dns_name_copy(vevent->name, + dns_fixedname_name(&hevent->foundname), NULL) + == ISC_R_SUCCESS); + 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); + isc_event_free(&event); +} + +static void +fctx_log(void *arg, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list args; + fetchctx_t *fctx = arg; + + va_start(args, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); + va_end(args); + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, + "fctx %p(%s): %s", fctx, fctx->info, msgbuf); +} + +static inline isc_result_t +findnoqname(fetchctx_t *fctx, 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(fctx->rmessage, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(fctx->rmessage, section)) { + dns_name_t *nsec = NULL; + dns_message_currentname(fctx->rmessage, 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 inline isc_result_t +cache_name(fetchctx_t *fctx, dns_name_t *name, 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; + + /* + * 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, &secure_domain); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (!secure_domain && res->view->dlv != NULL) { + valoptions |= DNS_VALIDATOR_DLV; + secure_domain = true; + } + } + + 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); + result = dns_name_copy(name, aname, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + 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); + 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; + } + + /* + * 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, 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, 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, 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, addrinfo, name, vtype, valrdataset, + valsigrdataset, valoptions, task); + } + + if (result == ISC_R_SUCCESS && have_answer) { + fctx->attributes |= 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 inline isc_result_t +cache_message(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo, isc_stdtime_t now) +{ + isc_result_t result; + dns_section_t section; + dns_name_t *name; + + FCTXTRACE("cache_message"); + + fctx->attributes &= ~FCTX_ATTR_WANTCACHE; + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + + for (section = DNS_SECTION_ANSWER; + section <= DNS_SECTION_ADDITIONAL; + section++) { + result = dns_message_firstname(fctx->rmessage, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(fctx->rmessage, section, + &name); + if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) { + result = cache_name(fctx, name, addrinfo, now); + if (result != ISC_R_SUCCESS) + break; + } + result = dns_message_nextname(fctx->rmessage, 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 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, maxttl, optout, ardataset); + else + result = dns_ncache_add(message, cache, node, covers, now, + 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 inline isc_result_t +ncache_message(fetchctx_t *fctx, 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->attributes &= ~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 noanswer_response(). + */ + INSIST(fctx->rmessage->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, &secure_domain); + if (result != ISC_R_SUCCESS) + return (result); + + if (!secure_domain && res->view->dlv != NULL) { + valoptions |= DNS_VALIDATOR_DLV; + secure_domain = true; + } + } + + 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(fctx->rmessage, + DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + tname = NULL; + dns_message_currentname(fctx->rmessage, + 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(fctx->rmessage, + DNS_SECTION_AUTHORITY); + } + if (result != ISC_R_NOMORE) + return (result); + + } + + if (need_validation) { + /* + * Do negative response validation. + */ + result = valcreate(fctx, 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); + result = dns_name_copy(name, aname, NULL); + if (result != ISC_R_SUCCESS) + goto unlock; + 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(fctx->rmessage, fctx->cache, node, + covers, now, ttl, false, + false, ardataset, &eresult); + if (result != ISC_R_SUCCESS) + goto unlock; + + if (!HAVE_ANSWER(fctx)) { + fctx->attributes |= 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 inline 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; +} + +static isc_result_t +check_section(void *arg, dns_name_t *addname, dns_rdatatype_t type, + dns_section_t section) +{ + fetchctx_t *fctx = arg; + 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 + + gluing = (GLUING(fctx) || + (fctx->type == dns_rdatatype_ns && + dns_name_equal(&fctx->name, dns_rootname))); + + result = dns_message_findname(fctx->rmessage, section, addname, + dns_rdatatype_any, 0, &name, NULL); + if (result == ISC_R_SUCCESS) { + external = !dns_name_issubdomain(name, &fctx->domain); + 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, 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 +#if CHECK_FOR_GLUE_IN_ANSWER +static isc_result_t +check_answer(void *arg, dns_name_t *addname, dns_rdatatype_t type) { + return (check_section(arg, addname, type, DNS_SECTION_ANSWER)); +} +#endif + +static void +chase_additional(fetchctx_t *fctx) { + bool rescan; + dns_section_t section = DNS_SECTION_ADDITIONAL; + isc_result_t result; + + again: + rescan = false; + + for (result = dns_message_firstname(fctx->rmessage, section); + result == ISC_R_SUCCESS; + result = dns_message_nextname(fctx->rmessage, section)) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset; + dns_message_currentname(fctx->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, + fctx); + rescan = true; + } + } + } + if (rescan) + goto again; +} + +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: + INSIST(0); + } + + 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. + */ + if (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; + } +} + +/* + * Handle a no-answer response (NXDOMAIN, NXRRSET, or referral). + * If look_in_options has LOOK_FOR_NS_IN_ANSWER then we look in the answer + * section for the NS RRset if the query type is NS; if it has + * LOOK_FOR_GLUE_IN_ANSWER we look for glue incorrectly returned in the answer + * section for A and AAAA queries. + */ +#define LOOK_FOR_NS_IN_ANSWER 0x1 +#define LOOK_FOR_GLUE_IN_ANSWER 0x2 + +static isc_result_t +noanswer_response(fetchctx_t *fctx, dns_name_t *oqname, + unsigned int look_in_options) +{ + isc_result_t result; + dns_message_t *message; + dns_name_t *name, *qname, *ns_name, *soa_name, *ds_name, *save_name; + dns_rdataset_t *rdataset, *ns_rdataset; + bool aa, negative_response; + dns_rdatatype_t type, save_type; + dns_section_t section; + + FCTXTRACE("noanswer_response"); + + if ((look_in_options & LOOK_FOR_NS_IN_ANSWER) != 0) { + INSIST(fctx->type == dns_rdatatype_ns); + section = DNS_SECTION_ANSWER; + } else + section = DNS_SECTION_AUTHORITY; + + message = fctx->rmessage; + + /* + * Setup qname. + */ + if (oqname == NULL) { + /* + * We have a normal, non-chained negative response or + * referral. + */ + if ((message->flags & DNS_MESSAGEFLAG_AA) != 0) + aa = true; + else + aa = false; + qname = &fctx->name; + } else { + /* + * We're being invoked by answer_response() after it has + * followed a CNAME/DNAME chain. + */ + qname = oqname; + aa = false; + /* + * If the current qname is not a subdomain of the query + * domain, there's no point in looking at the authority + * section without doing DNSSEC validation. + * + * Until we do that validation, we'll just return success + * in this case. + */ + if (!dns_name_issubdomain(qname, &fctx->domain)) + return (ISC_R_SUCCESS); + } + + /* + * We have to figure out if this is a negative response, or a + * referral. + */ + + /* + * Sometimes we can tell if its a negative response by looking at + * the message header. + */ + negative_response = false; + if (message->rcode == dns_rcode_nxdomain || + (message->counts[DNS_SECTION_ANSWER] == 0 && + message->counts[DNS_SECTION_AUTHORITY] == 0)) + negative_response = true; + + /* + * Process the authority section. + */ + ns_name = NULL; + ns_rdataset = NULL; + soa_name = NULL; + ds_name = NULL; + save_name = NULL; + save_type = dns_rdatatype_none; + result = dns_message_firstname(message, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, section, &name); + if (dns_name_issubdomain(name, &fctx->domain)) { + /* + * Look for NS/SOA RRsets first. + */ + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + type = rdataset->type; + if (type == dns_rdatatype_rrsig) + type = rdataset->covers; + if (((type == dns_rdatatype_ns || + type == dns_rdatatype_soa) && + !dns_name_issubdomain(qname, 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(qname, qbuf, + sizeof(qbuf)); + log_formerr(fctx, + "unrelated %s %s in " + "%s authority section", + tbuf, nbuf, qbuf); + goto nextname; + } + if (type == dns_rdatatype_ns) { + /* + * NS or RRSIG NS. + * + * Only one set of NS RRs is allowed. + */ + if (rdataset->type == + dns_rdatatype_ns) { + if (ns_name != NULL && + name != ns_name) { + log_formerr(fctx, + "multiple NS " + "RRsets in " + "authority " + "section"); + return (DNS_R_FORMERR); + } + ns_name = name; + ns_rdataset = rdataset; + } + name->attributes |= + DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + rdataset->trust = dns_trust_glue; + } + if (type == dns_rdatatype_soa) { + /* + * SOA, or RRSIG SOA. + * + * Only one SOA is allowed. + */ + if (rdataset->type == + dns_rdatatype_soa) { + if (soa_name != NULL && + name != soa_name) { + log_formerr(fctx, + "multiple SOA " + "RRs in " + "authority " + "section"); + return (DNS_R_FORMERR); + } + soa_name = name; + } + name->attributes |= + DNS_NAMEATTR_NCACHE; + rdataset->attributes |= + DNS_RDATASETATTR_NCACHE; + if (aa) + rdataset->trust = + dns_trust_authauthority; + else if (ISFORWARDER(fctx->addrinfo)) + rdataset->trust = + dns_trust_answer; + else + rdataset->trust = + dns_trust_additional; + } + } + } + nextname: + result = dns_message_nextname(message, section); + if (result == ISC_R_NOMORE) + break; + else if (result != ISC_R_SUCCESS) + return (result); + } + + log_ns_ttl(fctx, "noanswer_response"); + + if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && + !dns_name_equal(ns_name, dns_rootname)) + trim_ns_ttl(fctx, ns_name, 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 (soa_name != NULL) + negative_response = true; + + result = dns_message_firstname(message, section); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(message, section, &name); + if (dns_name_issubdomain(name, &fctx->domain)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + type = rdataset->type; + if (type == dns_rdatatype_rrsig) + type = rdataset->covers; + if (type == dns_rdatatype_nsec || + type == dns_rdatatype_nsec3) { + /* + * NSEC or RRSIG NSEC. + */ + if (negative_response) { + 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 (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. + */ + } else if (type == 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 (ns_name == NULL) { + log_formerr(fctx, + "DS with no " + "referral"); + return (DNS_R_FORMERR); + } + if (rdataset->type == + dns_rdatatype_ds) { + if (ds_name != NULL && + name != ds_name) { + log_formerr(fctx, + "DS doesn't " + "match " + "referral " + "(NS)"); + return (DNS_R_FORMERR); + } + ds_name = name; + } + name->attributes |= + DNS_NAMEATTR_CACHE; + rdataset->attributes |= + DNS_RDATASETATTR_CACHE; + if (aa) + rdataset->trust = + dns_trust_authauthority; + else if (ISFORWARDER(fctx->addrinfo)) + rdataset->trust = + dns_trust_answer; + else + rdataset->trust = + dns_trust_additional; + } + } + } else { + save_name = name; + save_type = ISC_LIST_HEAD(name->list)->type; + } + result = dns_message_nextname(message, section); + if (result == ISC_R_NOMORE) + break; + else if (result != ISC_R_SUCCESS) + return (result); + } + + /* + * Trigger lookups for DNS nameservers. + */ + if (negative_response && message->rcode == dns_rcode_noerror && + fctx->type == dns_rdatatype_ds && soa_name != NULL && + dns_name_equal(soa_name, qname) && + !dns_name_equal(qname, dns_rootname)) + return (DNS_R_CHASEDSSERVERS); + + /* + * Did we find anything? + */ + if (!negative_response && ns_name == NULL) { + /* + * Nope. + */ + if (oqname != NULL) { + /* + * We've already got a partial CNAME/DNAME chain, + * and haven't found else anything useful here, but + * no error has occurred since we have an answer. + */ + return (ISC_R_SUCCESS); + } else { + /* + * The responder is insane. + */ + if (save_name == NULL) { + log_formerr(fctx, "invalid response"); + return (DNS_R_FORMERR); + } + if (!dns_name_issubdomain(save_name, &fctx->domain)) { + char nbuf[DNS_NAME_FORMATSIZE]; + char dbuf[DNS_NAME_FORMATSIZE]; + char tbuf[DNS_RDATATYPE_FORMATSIZE]; + + dns_rdatatype_format(save_type, tbuf, + sizeof(tbuf)); + dns_name_format(save_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 (ns_name != NULL && soa_name != NULL && ns_name != soa_name) { + log_formerr(fctx, "NS/SOA mismatch"); + return (DNS_R_FORMERR); + } + + /* + * Do we have a referral? (We only want to follow a referral if + * we're not following a chain.) + */ + if (!negative_response && ns_name != NULL && oqname == NULL) { + /* + * 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(ns_name, &fctx->domain)) { + log_formerr(fctx, "non-improving referral"); + return (DNS_R_FORMERR); + } + + /* + * If the referral name is not a parent of the query + * name, consider the responder insane. + */ + if (! dns_name_issubdomain(&fctx->name, ns_name)) { + /* Logged twice */ + log_formerr(fctx, "referral to non-parent"); + FCTXTRACE("referral to non-parent"); + return (DNS_R_FORMERR); + } + + /* + * Mark any additional data related to this rdataset. + * It's important that we do this before we change the + * query domain. + */ + INSIST(ns_rdataset != NULL); + fctx->attributes |= FCTX_ATTR_GLUING; + (void)dns_rdataset_additionaldata(ns_rdataset, check_related, + fctx); +#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 ((look_in_options & LOOK_FOR_GLUE_IN_ANSWER) != 0 && + (fctx->type == dns_rdatatype_aaaa || + fctx->type == dns_rdatatype_a)) + (void)dns_rdataset_additionaldata(ns_rdataset, + check_answer, fctx); +#endif + fctx->attributes &= ~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 (ns_rdataset->ttl == 0) + 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); + result = dns_name_dup(ns_name, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) + return (result); + result = fcount_incr(fctx, true); + if (result != ISC_R_SUCCESS) + return (result); + fctx->attributes |= FCTX_ATTR_WANTCACHE; + fctx->ns_ttl_ok = false; + log_ns_ttl(fctx, "DELEGATION"); + return (DNS_R_DELEGATION); + } + + /* + * Since we're not doing a referral, we don't want to cache any + * NS RRs we may have found. + */ + if (ns_name != NULL) + ns_name->attributes &= ~DNS_NAMEATTR_CACHE; + + if (negative_response && oqname == NULL) + fctx->attributes |= FCTX_ATTR_WANTNCACHE; + + return (ISC_R_SUCCESS); +} + +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 isc_result_t +answer_response(fetchctx_t *fctx) { + isc_result_t result; + dns_message_t *message = NULL; + dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL; + dns_name_t *aname = NULL, *cname = NULL, *dname = NULL; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t *ardataset = NULL, *crdataset = NULL; + dns_rdataset_t *drdataset = NULL, *ns_rdataset = NULL; + bool done = false, aa; + unsigned int dname_labels, domain_labels; + bool chaining = false; + dns_rdatatype_t type; + dns_view_t *view = NULL; + dns_trust_t trust; + + REQUIRE(VALID_FCTX(fctx)); + + FCTXTRACE("answer_response"); + + message = fctx->rmessage; + qname = &fctx->name; + view = fctx->res->view; + type = fctx->type; + + /* + * There can be multiple RRSIG and SIG records at a name so + * we treat these types as a subset of ANY. + */ + if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) { + type = dns_rdatatype_any; + } + + /* + * Bigger than any valid DNAME label count. + */ + dname_labels = dns_name_countlabels(qname); + domain_labels = dns_name_countlabels(&fctx->domain); + + /* + * Perform a single pass looking for the answer, cname or covering + * dname. + */ + for (result = dns_message_firstname(message, DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_ANSWER)) + { + int order; + unsigned int nlabels; + dns_namereln_t namereln; + + name = NULL; + dns_message_currentname(message, DNS_SECTION_ANSWER, &name); + namereln = dns_name_fullcompare(qname, 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 == type || + type == dns_rdatatype_any) + { + aname = name; + if (type != dns_rdatatype_any) { + ardataset = rdataset; + } + break; + } + if (rdataset->type == dns_rdatatype_cname) { + cname = name; + crdataset = rdataset; + break; + } + } + break; + + case dns_namereln_subdomain: + /* + * 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 >= dname_labels || nlabels < 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; + } + dname = name; + drdataset = rdataset; + dname_labels = nlabels; + break; + } + break; + default: + break; + } + } + + if (dname != NULL) { + aname = NULL; + ardataset = NULL; + cname = NULL; + crdataset = NULL; + } else if (aname != NULL) { + cname = NULL; + crdataset = NULL; + } + + aa = (message->flags & DNS_MESSAGEFLAG_AA); + trust = aa ? dns_trust_authanswer : dns_trust_answer; + + if (aname != NULL && type == dns_rdatatype_any) { + for (rdataset = ISC_LIST_HEAD(aname->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!validinanswer(rdataset, fctx)) { + return (DNS_R_FORMERR); + } + 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(view, aname, rdataset)) + { + return (DNS_R_SERVFAIL); + } + if ((rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, qname, aname, + rdataset, NULL)) + { + return (DNS_R_SERVFAIL); + } + aname->attributes |= DNS_NAMEATTR_CACHE; + aname->attributes |= DNS_NAMEATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = trust; + (void)dns_rdataset_additionaldata(rdataset, + check_related, + fctx); + } + } else if (aname != NULL) { + if (!validinanswer(ardataset, fctx)) + return (DNS_R_FORMERR); + if ((ardataset->type == dns_rdatatype_a || + ardataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(view, aname, ardataset)) { + return (DNS_R_SERVFAIL); + } + if ((ardataset->type == dns_rdatatype_cname || + ardataset->type == dns_rdatatype_dname) && + type != ardataset->type && + type != dns_rdatatype_any && + !is_answertarget_allowed(fctx, qname, aname, ardataset, + NULL)) + { + return (DNS_R_SERVFAIL); + } + aname->attributes |= DNS_NAMEATTR_CACHE; + aname->attributes |= DNS_NAMEATTR_ANSWER; + ardataset->attributes |= DNS_RDATASETATTR_ANSWER; + ardataset->attributes |= DNS_RDATASETATTR_CACHE; + ardataset->trust = trust; + (void)dns_rdataset_additionaldata(ardataset, check_related, + fctx); + for (sigrdataset = ISC_LIST_HEAD(aname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { + if (!validinanswer(sigrdataset, fctx)) + return (DNS_R_FORMERR); + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != type) + continue; + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + } else if (cname != NULL) { + if (!validinanswer(crdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key || + type == dns_rdatatype_nsec) + { + char buf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(type, buf, sizeof(buf)); + log_formerr(fctx, "CNAME response for %s RR", buf); + return (DNS_R_FORMERR); + } + if (!is_answertarget_allowed(fctx, qname, cname, crdataset, + NULL)) + { + return (DNS_R_SERVFAIL); + } + cname->attributes |= DNS_NAMEATTR_CACHE; + cname->attributes |= DNS_NAMEATTR_ANSWER; + cname->attributes |= DNS_NAMEATTR_CHAINING; + crdataset->attributes |= DNS_RDATASETATTR_ANSWER; + crdataset->attributes |= DNS_RDATASETATTR_CACHE; + crdataset->attributes |= DNS_RDATASETATTR_CHAINING; + crdataset->trust = trust; + for (sigrdataset = ISC_LIST_HEAD(cname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_cname) + { + continue; + } + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + chaining = true; + } else if (dname != NULL) { + if (!validinanswer(drdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (!is_answertarget_allowed(fctx, qname, dname, drdataset, + &chaining)) { + return (DNS_R_SERVFAIL); + } + dname->attributes |= DNS_NAMEATTR_CACHE; + dname->attributes |= DNS_NAMEATTR_ANSWER; + dname->attributes |= DNS_NAMEATTR_CHAINING; + drdataset->attributes |= DNS_RDATASETATTR_ANSWER; + drdataset->attributes |= DNS_RDATASETATTR_CACHE; + drdataset->attributes |= DNS_RDATASETATTR_CHAINING; + drdataset->trust = trust; + for (sigrdataset = ISC_LIST_HEAD(dname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_dname) + { + continue; + } + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + } else { + log_formerr(fctx, "reply has no answer"); + return (DNS_R_FORMERR); + } + + /* + * This response is now potentially cacheable. + */ + fctx->attributes |= FCTX_ATTR_WANTCACHE; + + /* + * Did chaining end before we got the final answer? + */ + if (chaining) { + return (ISC_R_SUCCESS); + } + + /* + * We didn't end with an incomplete chain, so the rcode should be + * "no error". + */ + if (message->rcode != dns_rcode_noerror) { + log_formerr(fctx, "CNAME/DNAME chain complete, but RCODE " + "indicates error"); + return (DNS_R_FORMERR); + } + + /* + * Examine the authority section (if there is one). + * + * We expect there to be only one owner name for all the rdatasets + * in this section, and we expect that it is not external. + */ + result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + while (!done && result == ISC_R_SUCCESS) { + bool external; + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + external = !dns_name_issubdomain(name, &fctx->domain); + if (!external) { + /* + * 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 (aa && !chaining) { + rdataset->trust = + dns_trust_authauthority; + } else { + rdataset->trust = + dns_trust_additional; + } + + if (rdataset->type == dns_rdatatype_ns) + { + ns_name = name; + ns_rdataset = rdataset; + } + /* + * Mark any additional data related + * to this rdataset. + */ + (void)dns_rdataset_additionaldata( + rdataset, + check_related, + fctx); + done = true; + } + } + } + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY); + } + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + log_ns_ttl(fctx, "answer_response"); + + if (ns_rdataset != NULL && dns_name_equal(&fctx->domain, ns_name) && + !dns_name_equal(ns_name, dns_rootname)) + trim_ns_ttl(fctx, ns_name, ns_rdataset); + + return (result); +} + +static void +fctx_increference(fetchctx_t *fctx) { + REQUIRE(VALID_FCTX(fctx)); + + LOCK(&fctx->res->buckets[fctx->bucketnum].lock); + fctx->references++; + UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock); +} + +static bool +fctx_decreference(fetchctx_t *fctx) { + bool bucket_empty = false; + + REQUIRE(VALID_FCTX(fctx)); + + INSIST(fctx->references > 0); + fctx->references--; + if (fctx->references == 0) { + /* + * 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; + bool bucket_empty; + bool locked = false; + unsigned int bucketnum; + 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; + + 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); + + bucketnum = fctx->bucketnum; + + /* + * 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); + result = dns_name_dup(&fctx->nsname, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + goto cleanup; + } + 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. + */ + dns_fixedname_init(&fixed); + domain = dns_fixedname_name(&fixed); + dns_name_copy(&fctx->nsfetch->private->domain, domain, NULL); + 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, + fctx->options, 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) { + fctx_done(fctx, result, __LINE__); + } else { + LOCK(&res->buckets[bucketnum].lock); + locked = true; + fctx->references++; + } + } + + cleanup: + INSIST(event == NULL); + INSIST(fevent == NULL); + if (dns_rdataset_isassociated(&nameservers)) + dns_rdataset_disassociate(&nameservers); + if (!locked) + LOCK(&res->buckets[bucketnum].lock); + bucket_empty = fctx_decreference(fctx); + UNLOCK(&res->buckets[bucketnum].lock); + if (bucket_empty) + empty_bucket(res); +} + +static inline 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]; + uint16_t buflen, i; + unsigned char *p, *nsid; + unsigned char *buf = NULL, *pbuf = NULL; + + /* Allocate buffer for storing hex version of the NSID */ + buflen = (uint16_t)nsid_len * 2 + 1; + buf = isc_mem_get(mctx, buflen); + if (buf == NULL) + goto cleanup; + pbuf = isc_mem_get(mctx, nsid_len + 1); + if (pbuf == NULL) + goto cleanup; + + /* Convert to hex */ + p = buf; + nsid = isc_buffer_current(opt); + for (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 (i = 0; i < nsid_len; i++) { + if (isprint(nsid[i])) + *p++ = nsid[i]; + else + *p++ = '.'; + } + *p = '\0'; + + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, level, + "received NSID %s (\"%s\") from %s", buf, pbuf, addrbuf); + cleanup: + if (pbuf != NULL) + isc_mem_put(mctx, pbuf, nsid_len + 1); + if (buf != NULL) + isc_mem_put(mctx, buf, buflen); +} + +static bool +iscname(fetchctx_t *fctx) { + isc_result_t result; + + result = dns_message_findname(fctx->rmessage, DNS_SECTION_ANSWER, + &fctx->name, dns_rdatatype_cname, 0, + NULL, NULL); + return (result == ISC_R_SUCCESS ? true : false); +} + +static bool +betterreferral(fetchctx_t *fctx) { + isc_result_t result; + dns_name_t *name; + dns_rdataset_t *rdataset; + dns_message_t *message = fctx->rmessage; + + for (result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_AUTHORITY)) { + name = NULL; + dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); + if (!isstrictsubdomain(name, &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); +} + +static void +process_opt(resquery_t *query, dns_rdataset_t *opt) { + 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[8]; + bool seen_cookie = false; + bool seen_nsid = false; + + result = dns_rdataset_first(opt); + if (result == ISC_R_SUCCESS) { + dns_rdata_init(&rdata); + dns_rdataset_current(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) + log_nsid(&optbuf, optlen, query, + ISC_LOG_DEBUG(3), + query->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->fctx->rmessage->cc_bad == 0 && + query->fctx->rmessage->cc_ok == 0); + if (optlen >= 8U && + memcmp(cookie, optvalue, 8) == 0) { + query->fctx->rmessage->cc_ok = 1; + inc_stats(query->fctx->res, + dns_resstatscounter_cookieok); + addrinfo = query->addrinfo; + dns_adb_setcookie(query->fctx->adb, + addrinfo, optvalue, + optlen); + } else + query->fctx->rmessage->cc_bad = 1; + isc_buffer_forward(&optbuf, optlen); + inc_stats(query->fctx->res, + dns_resstatscounter_cookiein); + seen_cookie = true; + break; + default: + isc_buffer_forward(&optbuf, optlen); + break; + } + } + INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + } +} + +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; + bool keep_trying, get_nameservers, resend, nextitem; + bool truncated; + dns_message_t *message; + dns_rdataset_t *opt; + fetchctx_t *fctx; + dns_name_t *fname; + dns_fixedname_t foundname; + isc_stdtime_t now; + isc_time_t tnow, *finish; + dns_adbaddrinfo_t *addrinfo; + unsigned int options; + unsigned int findoptions; + isc_result_t broken_server; + badnstype_t broken_type = badns_response; + bool no_response; + unsigned int bucketnum; + dns_resolver_t *res; + bool bucket_empty; +#ifdef HAVE_DNSTAP + 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 */ + + REQUIRE(VALID_QUERY(query)); + fctx = query->fctx; + options = query->options; + REQUIRE(VALID_FCTX(fctx)); + REQUIRE(event->ev_type == DNS_EVENT_DISPATCH); + + QTRACE("response"); + + res = fctx->res; + if (isc_sockaddr_pf(&query->addrinfo->sockaddr) == PF_INET) + inc_stats(res, dns_resstatscounter_responsev4); + else + inc_stats(res, dns_resstatscounter_responsev6); + + (void)isc_timer_touch(fctx->timer); + + keep_trying = false; + broken_server = ISC_R_SUCCESS; + get_nameservers = false; + resend = false; + nextitem = false; + truncated = false; + finish = NULL; + no_response = false; + + if (res->exiting) { + result = ISC_R_SHUTTINGDOWN; + FCTXTRACE("resolver shutting down"); + goto done; + } + + fctx->timeouts = 0; + fctx->timeout = false; + fctx->addrinfo = query->addrinfo; + + /* + * 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(&tnow); + finish = &tnow; + isc_stdtime_get(&now); + + /* + * Did the dispatcher have a problem? + */ + if (devent->result != ISC_R_SUCCESS) { + if (devent->result == ISC_R_EOF && + (query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + options |= DNS_FETCHOPT_NOEDNS0; + resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + } else { + /* + * There's no hope for this query. + */ + keep_trying = 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)) { + broken_server = devent->result; + broken_type = badns_unreachable; + finish = NULL; + no_response = true; + } + } + FCTXTRACE3("dispatcher failure", devent->result); + goto done; + } + + message = fctx->rmessage; + + if (query->tsig != NULL) { + result = dns_message_setquerytsig(message, query->tsig); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set query tsig", result); + goto done; + } + } + + if (query->tsigkey) { + result = dns_message_settsigkey(message, query->tsigkey); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("unable to set tsig key", result); + goto done; + } + } + + dns_message_setclass(message, res->rdclass); + + if ((options & DNS_FETCHOPT_TCP) == 0) { + if ((options & DNS_FETCHOPT_NOEDNS0) == 0) + dns_adb_setudpsize(fctx->adb, query->addrinfo, + isc_buffer_usedlength(&devent->buffer)); + else + dns_adb_plainresponse(fctx->adb, query->addrinfo); + } + result = dns_message_parse(message, &devent->buffer, 0); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("message failed to parse", result); + switch (result) { + case ISC_R_UNEXPECTEDEND: + if (!message->question_ok || + (message->flags & DNS_MESSAGEFLAG_TC) == 0 || + (options & DNS_FETCHOPT_TCP) != 0) { + /* + * 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 ((query->options & DNS_FETCHOPT_NOEDNS0) + == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + options |= DNS_FETCHOPT_NOEDNS0; + resend = true; + add_bad_edns(fctx, + &query->addrinfo->sockaddr); + inc_stats(res, + dns_resstatscounter_edns0fail); + } else { + broken_server = result; + keep_trying = true; + } + goto done; + } + /* + * We defer retrying via TCP for a bit so we can + * check out this message further. + */ + truncated = true; + break; + case DNS_R_FORMERR: + if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) { + /* + * The problem might be that they + * don't understand EDNS0. Turn it + * off and try again. + */ + options |= DNS_FETCHOPT_NOEDNS0; + resend = true; + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(res, dns_resstatscounter_edns0fail); + } else { + broken_server = DNS_R_UNEXPECTEDRCODE; + keep_trying = true; + } + goto done; + default: + /* + * Something bad has happened. + */ + goto done; + } + } + + /* + * Log the incoming packet. + */ + dns_message_logfmtpacket2(message, "received packet from", + &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_PACKETS, + &dns_master_style_comment, + ISC_LOG_DEBUG(10), res->mctx); + +#ifdef HAVE_DNSTAP + /* + * Log the response via dnstap. + */ + memset(&zr, 0, sizeof(zr)); + result = dns_compress_init(&cctx, -1, 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; + } + + if (query->exclusivesocket) { + sock = dns_dispatch_getentrysocket(query->dispentry); + } else { + sock = dns_dispatch_getsocket(query->dispatch); + } + + if (sock != NULL) { + result = isc_socket_getsockname(sock, &localaddr); + if (result == ISC_R_SUCCESS) { + la = &localaddr; + } + } + + dns_dt_send(res->view, dtmsgtype, la, &query->addrinfo->sockaddr, + (query->options & DNS_FETCHOPT_TCP), + &zr, &query->start, NULL, &devent->buffer); +#endif /* HAVE_DNSTAP */ + + if (message->rdclass != res->rdclass) { + resend = true; + FCTXTRACE("bad class"); + goto done; + } + + /* + * Process receive opt record. + */ + opt = dns_message_getopt(message); + if (opt != NULL) + process_opt(query, opt); + + if (message->cc_bad && (options & DNS_FETCHOPT_TCP) == 0) { + /* + * If the COOKIE is bad, assume it is an attack and + * keep listening for a good answer. + */ + 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); + } + goto done; + } + + /* + * 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 (message->rcode) { + case dns_rcode_notimp: + case dns_rcode_formerr: + if (message->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); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("response did not match question", result); + nextitem = true; + goto done; + } + break; + } + + /* + * If the message is signed, check the signature. If not, this + * returns success anyway. + */ + result = dns_message_checksig(message, res->view); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("signature check failed", result); + goto done; + } + + /* + * The dispatcher should ensure we only get responses with QR set. + */ + INSIST((message->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). + */ + + /* + * 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 (opt == NULL && !EDNSOK(query->addrinfo) && + (message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_nxdomain || + message->rcode == dns_rcode_refused || + message->rcode == dns_rcode_yxdomain) && + bad_edns(fctx, &query->addrinfo->sockaddr)) { + dns_message_logpacket2(message, + "received packet (bad edns) from", + &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, + ISC_LOG_DEBUG(3), + res->mctx); + dns_adb_changeflags(fctx->adb, query->addrinfo, + DNS_FETCHOPT_NOEDNS0, + DNS_FETCHOPT_NOEDNS0); + } else if (opt == NULL && (message->flags & DNS_MESSAGEFLAG_TC) == 0 && + !EDNSOK(query->addrinfo) && + (message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_nxdomain) && + (query->options & 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_logpacket2(message, "received packet (no opt) from", + &query->addrinfo->sockaddr, + DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, + ISC_LOG_DEBUG(3), 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 (opt != NULL && !EDNSOK(query->addrinfo) && + (query->options & DNS_FETCHOPT_NOEDNS0) == 0 && + (message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_nxdomain || + message->rcode == dns_rcode_refused || + message->rcode == dns_rcode_yxdomain)) { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_EDNSOK, + FCTX_ADDRINFO_EDNSOK); + } + + /* + * Deal with truncated responses by retrying using TCP. + */ + if ((message->flags & DNS_MESSAGEFLAG_TC) != 0) + truncated = true; + + if (truncated) { + inc_stats(res, dns_resstatscounter_truncated); + if ((options & DNS_FETCHOPT_TCP) != 0) { + broken_server = DNS_R_TRUNCATEDTCP; + keep_trying = true; + } else { + options |= DNS_FETCHOPT_TCP; + resend = true; + } + FCTXTRACE3("message truncated", result); + goto done; + } + + /* + * Is it a query response? + */ + if (message->opcode != dns_opcode_query) { + /* XXXRTH Log */ + broken_server = DNS_R_UNEXPECTEDOPCODE; + keep_trying = true; + FCTXTRACE("invalid message opcode"); + goto done; + } + + /* + * Update statistics about erroneous responses. + */ + if (message->rcode != dns_rcode_noerror) { + switch (message->rcode) { + case dns_rcode_nxdomain: + inc_stats(res, dns_resstatscounter_nxdomain); + break; + case dns_rcode_servfail: + inc_stats(res, dns_resstatscounter_servfail); + break; + case dns_rcode_formerr: + inc_stats(res, dns_resstatscounter_formerr); + break; + case dns_rcode_refused: + inc_stats(res, dns_resstatscounter_refused); + break; + case dns_rcode_badvers: + inc_stats(res, dns_resstatscounter_badvers); + break; + case dns_rcode_badcookie: + inc_stats(res, dns_resstatscounter_badcookie); + break; + default: + inc_stats(res, dns_resstatscounter_othererror); + break; + } + } + + /* + * Is the remote server broken, or does it dislike us? + */ + if (message->rcode != dns_rcode_noerror && + message->rcode != dns_rcode_yxdomain && + message->rcode != dns_rcode_nxdomain) { + isc_buffer_t b; + char code[64]; + unsigned char cookie[64]; + + /* + * Some servers do not ignore unknown EDNS options. + */ + if (!NOCOOKIE(query->addrinfo) && + (message->rcode == dns_rcode_formerr || + message->rcode == dns_rcode_notimp || + message->rcode == dns_rcode_refused) && + dns_adb_getcookie(fctx->adb, query->addrinfo, + cookie, sizeof(cookie)) == 0U) { + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOCOOKIE, + FCTX_ADDRINFO_NOCOOKIE); + resend = true; + } else if ((message->rcode == dns_rcode_formerr || + message->rcode == dns_rcode_notimp || + (message->rcode == dns_rcode_servfail && + dns_message_getopt(message) == NULL)) && + (query->options & 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. + */ + options |= DNS_FETCHOPT_NOEDNS0; + resend = true; + /* + * Remember that they may not like EDNS0. + */ + add_bad_edns(fctx, &query->addrinfo->sockaddr); + inc_stats(res, dns_resstatscounter_edns0fail); + } else if (message->rcode == dns_rcode_formerr) { + if (ISFORWARDER(query->addrinfo)) { + /* + * This forwarder doesn't understand us, + * but other forwarders might. Keep trying. + */ + broken_server = DNS_R_REMOTEFORMERR; + keep_trying = 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 (message->rcode == dns_rcode_badvers) { + unsigned int version; + bool setnocookie = false; +#if DNS_EDNS_VERSION > 0 + unsigned int flags, mask; +#endif + + /* + * Some servers return BADVERS to unknown + * EDNS options. This cannot be long term + * strategy. Do not disable COOKIE if we have + * already have received a COOKIE from this + * server. + */ + if (dns_adb_getcookie(fctx->adb, query->addrinfo, + cookie, sizeof(cookie)) == 0U) { + if (!NOCOOKIE(query->addrinfo)) + setnocookie = true; + dns_adb_changeflags(fctx->adb, query->addrinfo, + FCTX_ADDRINFO_NOCOOKIE, + FCTX_ADDRINFO_NOCOOKIE); + } + + INSIST(opt != NULL); + version = (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 + + /* + * 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); + resend = true; + } else { + broken_server = DNS_R_BADVERS; + keep_trying = true; + } +#else + if (version == 0U && setnocookie) { + resend = true; + } else { + broken_server = DNS_R_BADVERS; + keep_trying = true; + } +#endif + } else if (message->rcode == dns_rcode_badcookie && + message->cc_ok) { + /* + * We have recorded the new cookie. + */ + if (BADCOOKIE(query->addrinfo)) + query->options |= DNS_FETCHOPT_TCP; + query->addrinfo->flags |= FCTX_ADDRINFO_BADCOOKIE; + resend = true; + } else { + /* + * XXXRTH log. + */ + broken_server = DNS_R_UNEXPECTEDRCODE; + INSIST(broken_server != ISC_R_SUCCESS); + keep_trying = true; + } + + isc_buffer_init(&b, code, sizeof(code) - 1); + dns_rcode_totext(fctx->rmessage->rcode, &b); + code[isc_buffer_usedlength(&b)] = '\0'; + FCTXTRACE2("remote server broken: returned ", code); + goto done; + } + + /* + * Is the server lame? + */ + if (res->lame_ttl != 0 && !ISFORWARDER(query->addrinfo) && + is_lame(fctx)) { + inc_stats(res, dns_resstatscounter_lame); + log_lame(fctx, query->addrinfo); + result = dns_adb_marklame(fctx->adb, query->addrinfo, + &fctx->name, fctx->type, + now + 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)); + broken_server = DNS_R_LAME; + keep_trying = true; + FCTXTRACE("lame server"); + goto done; + } + + /* + * Enforce delegations only zones like NET and COM. + */ + if (!ISFORWARDER(query->addrinfo) && + dns_view_isdelegationonly(res->view, &fctx->domain) && + !dns_name_equal(&fctx->domain, &fctx->name) && + fix_mustbedelegationornxdomain(message, fctx)) { + char namebuf[DNS_NAME_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char classbuf[64]; + char typebuf[64]; + + 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(res->rdclass, classbuf, + sizeof(classbuf)); + isc_sockaddr_format(&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); + } + + if ((res->options & DNS_RESOLVER_CHECKNAMES) != 0) + checknames(message); + + /* + * Clear cache bits. + */ + fctx->attributes &= ~(FCTX_ATTR_WANTNCACHE | FCTX_ATTR_WANTCACHE); + + /* + * Did we get any answers? + */ + if (message->counts[DNS_SECTION_ANSWER] > 0 && + (message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_yxdomain || + message->rcode == dns_rcode_nxdomain)) { + /* + * [normal case] + * We've got answers. If it has an authoritative answer or an + * answer from a forwarder, we're done. + */ + if ((message->flags & DNS_MESSAGEFLAG_AA) != 0 || + ISFORWARDER(query->addrinfo)) + { + result = answer_response(fctx); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("answer_response (AA/fwd)", result); + } else if (iscname(fctx) && + 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 = answer_response(fctx); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("answer_response (!ANY/!CNAME)", + result); + } else if (fctx->type != dns_rdatatype_ns && + !betterreferral(fctx)) { + /* + * Lame response !!!. + */ + result = answer_response(fctx); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("answer_response (!NS)", result); + } else { + 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. + */ + result = noanswer_response(fctx, NULL, + LOOK_FOR_NS_IN_ANSWER); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("noanswer_response (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. LOOK_FOR_GLUE_IN_ANSWER will handle + * such a corner case. + */ + result = noanswer_response(fctx, NULL, + LOOK_FOR_GLUE_IN_ANSWER); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("noanswer_response", result); + } + if (result != DNS_R_DELEGATION) { + /* + * 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. + */ + broken_server = DNS_R_LAME; + keep_trying = true; + goto done; + } + goto force_referral; + } + if (result != ISC_R_SUCCESS) { + if (result == DNS_R_FORMERR) + keep_trying = true; + goto done; + } + } else if (message->counts[DNS_SECTION_AUTHORITY] > 0 || + message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_nxdomain) { + /* + * NXDOMAIN, NXRDATASET, or referral. + */ + result = noanswer_response(fctx, NULL, 0); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_CHASEDSSERVERS: + break; + case DNS_R_DELEGATION: + force_referral: + /* + * We don't have the answer, but we know a better + * place to look. + */ + get_nameservers = true; + keep_trying = true; + /* + * We have a new set of name servers, and it + * has not experienced any restarts yet. + */ + fctx->restarts = 0; + + /* + * Update local statistics counters collected for each + * new zone. + */ + fctx->referrals++; + fctx->querysent = 0; + fctx->lamecount = 0; + fctx->quotacount = 0; + fctx->neterr = 0; + fctx->badresp = 0; + fctx->adberr = 0; + + result = ISC_R_SUCCESS; + break; + default: + /* + * Something has gone wrong. + */ + if (result == DNS_R_FORMERR) + keep_trying = true; + FCTXTRACE3("noanswer_response", result); + goto done; + } + } else { + /* + * The server is insane. + */ + /* XXXRTH Log */ + broken_server = DNS_R_UNEXPECTEDRCODE; + keep_trying = true; + FCTXTRACE("broken server: unexpected rcode"); + goto done; + } + + /* + * Follow additional section data chains. + */ + chase_additional(fctx); + + /* + * Cache the cacheable parts of the message. This may also cause + * work to be queued to the DNSSEC validator. + */ + if (WANTCACHE(fctx)) { + result = cache_message(fctx, query->addrinfo, now); + if (result != ISC_R_SUCCESS) { + FCTXTRACE3("cache_message complete", result); + goto done; + } + } + + /* + * Ncache the negatively cacheable parts of the message. This may + * also cause work to be queued to the DNSSEC validator. + */ + if (WANTNCACHE(fctx)) { + dns_rdatatype_t covers; + + /* + * Cache DS NXDOMAIN seperately to other types. + */ + if (message->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, query->addrinfo, covers, now); + if (result != ISC_R_SUCCESS) + FCTXTRACE3("ncache_message complete", result); + } + + done: + /* + * Remember the query's addrinfo, in case we need to mark the + * server as broken. + */ + addrinfo = query->addrinfo; + + FCTXTRACE4("query canceled in response(); ", + no_response ? "no response" : "responding", + result); + + /* + * Cancel the query. + * + * XXXRTH Don't cancel the query if waiting for validation? + */ + if (!nextitem) + fctx_cancelquery(&query, &devent, finish, + no_response, false); + +#ifdef ENABLE_AFL + if (dns_fuzzing_resolver && (keep_trying || resend)) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } else +#endif + if (keep_trying) { + if (result == DNS_R_FORMERR) + broken_server = DNS_R_FORMERR; + if (broken_server != ISC_R_SUCCESS) { + /* + * Add this server to the list of bad servers for + * this fctx. + */ + add_bad(fctx, addrinfo, broken_server, broken_type); + } + + if (get_nameservers) { + dns_name_t *name; + fname = dns_fixedname_initname(&foundname); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + findoptions = 0; + if (dns_rdatatype_atparent(fctx->type)) + findoptions |= DNS_DBFIND_NOEXACT; + if ((options & DNS_FETCHOPT_UNSHARED) == 0) + name = &fctx->name; + else + name = &fctx->domain; + result = dns_view_findzonecut(res->view, + name, fname, + now, findoptions, + 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); + result = dns_name_dup(fname, fctx->mctx, &fctx->domain); + if (result != ISC_R_SUCCESS) { + fctx_done(fctx, DNS_R_SERVFAIL, __LINE__); + return; + } + 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_cleanupfinds(fctx); + fctx_cleanupaltfinds(fctx); + fctx_cleanupforwaddrs(fctx); + fctx_cleanupaltaddrs(fctx); + } + /* + * Try again. + */ + fctx_try(fctx, !get_nameservers, false); + } else if (resend) { + /* + * Resend (probably with changed options). + */ + FCTXTRACE("resend"); + inc_stats(res, dns_resstatscounter_retry); + bucketnum = fctx->bucketnum; + fctx_increference(fctx); + result = fctx_query(fctx, addrinfo, 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 (nextitem) { + /* + * Wait for next item. + */ + FCTXTRACE("nextitem"); + inc_stats(fctx->res, dns_resstatscounter_nextitem); + INSIST(query->dispentry != NULL); + dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE); + result = dns_dispatch_getnext(query->dispentry, &devent); + if (result != ISC_R_SUCCESS) + fctx_done(fctx, result, __LINE__); + } 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 if (result == DNS_R_CHASEDSSERVERS) { + unsigned int n; + add_bad(fctx, addrinfo, result, 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(res, &fctx->nsname, + dns_rdatatype_ns, + NULL, NULL, NULL, + fctx->options, task, + resume_dslookup, fctx, + &fctx->nsrrset, NULL, + &fctx->nsfetch); + if (result != ISC_R_SUCCESS) + fctx_done(fctx, result, __LINE__); + else { + fctx_increference(fctx); + result = fctx_stopidletimer(fctx); + if (result != ISC_R_SUCCESS) + fctx_done(fctx, result, __LINE__); + } + } else { + /* + * We're done. + */ + fctx_done(fctx, result, __LINE__); + } +} + +/*** + *** Resolver Methods + ***/ +static void +destroy(dns_resolver_t *res) { + unsigned int i; + alternate_t *a; + + REQUIRE(res->references == 0); + REQUIRE(!res->priming); + REQUIRE(res->primefetch == NULL); + + RTRACE("destroy"); + + INSIST(res->nfctx == 0); + + DESTROYLOCK(&res->primelock); + DESTROYLOCK(&res->nlock); + DESTROYLOCK(&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); + DESTROYLOCK(&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); + DESTROYLOCK(&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_MBSLOCK + isc_rwlock_destroy(&res->mbslock); +#endif + isc_timer_detach(&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(!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)); + if (res == NULL) + return (ISC_R_NOMEMORY); + 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; + dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE, + &res->badcache); + res->mustbesecure = NULL; + res->spillatmin = res->spillat = 10; + res->spillatmax = 100; + res->spillattimer = NULL; + res->zspill = 0; + res->zero_no_soa_ttl = false; + 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)); + if (res->buckets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_res; + } + for (i = 0; i < ntasks; i++) { + result = isc_mutex_init(&res->buckets[i].lock); + if (result != ISC_R_SUCCESS) + goto cleanup_buckets; + res->buckets[i].task = NULL; + result = isc_task_create(taskmgr, 0, &res->buckets[i].task); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&res->buckets[i].lock); + goto cleanup_buckets; + } + res->buckets[i].mctx = NULL; + snprintf(name, sizeof(name), "res%u", i); +#ifdef ISC_PLATFORM_USETHREADS + /* + * 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. + */ + result = isc_mem_create(0, 0, &res->buckets[i].mctx); + if (result != ISC_R_SUCCESS) { + isc_task_detach(&res->buckets[i].task); + DESTROYLOCK(&res->buckets[i].lock); + goto cleanup_buckets; + } + isc_mem_setname(res->buckets[i].mctx, name, NULL); +#else + isc_mem_attach(view->mctx, &res->buckets[i].mctx); +#endif + isc_task_setname(res->buckets[i].task, name, res); + ISC_LIST_INIT(res->buckets[i].fctxs); + res->buckets[i].exiting = false; + buckets_created++; + } + + res->dbuckets = isc_mem_get(view->mctx, + RES_DOMAIN_BUCKETS * sizeof(zonebucket_t)); + if (res->dbuckets == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_buckets; + } + 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); + result = isc_mutex_init(&res->dbuckets[i].lock); + if (result != ISC_R_SUCCESS) { + isc_mem_detach(&res->dbuckets[i].mctx); + goto cleanup_dbuckets; + } + 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; + res->references = 1; + res->exiting = false; + res->frozen = false; + ISC_LIST_INIT(res->whenshutdown); + res->priming = false; + res->primefetch = NULL; + res->nfctx = 0; + + result = isc_mutex_init(&res->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_dispatches; + + result = isc_mutex_init(&res->nlock); + if (result != ISC_R_SUCCESS) + goto cleanup_lock; + + result = isc_mutex_init(&res->primelock); + if (result != ISC_R_SUCCESS) + goto cleanup_nlock; + + 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 + result = isc_rwlock_init(&res->alglock, 0, 0); + if (result != ISC_R_SUCCESS) + goto cleanup_spillattimer; +#endif +#if USE_MBSLOCK + result = isc_rwlock_init(&res->mbslock, 0, 0); + if (result != ISC_R_SUCCESS) +#if USE_ALGLOCK + goto cleanup_alglock; +#else + goto cleanup_spillattimer; +#endif +#endif + + res->magic = RES_MAGIC; + + *resp = res; + + return (ISC_R_SUCCESS); + +#if USE_ALGLOCK && USE_MBSLOCK + cleanup_alglock: + isc_rwlock_destroy(&res->alglock); +#endif + +#if USE_ALGLOCK || USE_MBSLOCK + cleanup_spillattimer: + isc_timer_detach(&res->spillattimer); +#endif + + cleanup_primelock: + DESTROYLOCK(&res->primelock); + + cleanup_nlock: + DESTROYLOCK(&res->nlock); + + cleanup_lock: + DESTROYLOCK(&res->lock); + + cleanup_dispatches: + if (res->dispatches6 != NULL) + dns_dispatchset_destroy(&res->dispatches6); + if (res->dispatches4 != NULL) + dns_dispatchset_destroy(&res->dispatches4); + + cleanup_dbuckets: + for (i = 0; i < dbuckets_created; i++) { + DESTROYLOCK(&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); + DESTROYLOCK(&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)); + + 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->lock); + + INSIST(res->priming); + res->priming = false; + LOCK(&res->primelock); + fetch = res->primefetch; + res->primefetch = NULL; + UNLOCK(&res->primelock); + + UNLOCK(&res->lock); + + 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"); + + LOCK(&res->lock); + + if (!res->exiting && !res->priming) { + INSIST(res->primefetch == NULL); + res->priming = true; + want_priming = true; + } + + UNLOCK(&res->lock); + + 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)); + if (rdataset == NULL) { + LOCK(&res->lock); + INSIST(res->priming); + INSIST(res->primefetch == NULL); + res->priming = false; + UNLOCK(&res->lock); + return; + } + dns_rdataset_init(rdataset); + LOCK(&res->primelock); + result = dns_resolver_createfetch(res, dns_rootname, + dns_rdatatype_ns, + NULL, NULL, NULL, 0, + 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)); + LOCK(&res->lock); + INSIST(res->priming); + res->priming = false; + UNLOCK(&res->lock); + } + } +} + +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(!source->exiting); + + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); + 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 (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; + + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("shutdown"); + + LOCK(&res->lock); + + if (!res->exiting) { + RTRACE("exiting"); + res->exiting = true; + + 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); + } + 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; + bool need_destroy = false; + + REQUIRE(resp != NULL); + res = *resp; + REQUIRE(VALID_RESOLVER(res)); + + RTRACE("detach"); + + LOCK(&res->lock); + + INSIST(res->references > 0); + res->references--; + if (res->references == 0) { + INSIST(res->exiting && res->activebuckets == 0); + need_destroy = true; + } + + UNLOCK(&res->lock); + + if (need_destroy) + destroy(res); + + *resp = NULL; +} + +static inline bool +fctx_match(fetchctx_t *fctx, 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 inline void +log_fetch(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); +} + +isc_result_t +dns_resolver_createfetch(dns_resolver_t *res, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp) +{ + return (dns_resolver_createfetch3(res, name, type, domain, + nameservers, forwarders, NULL, 0, + options, 0, NULL, task, action, arg, + rdataset, sigrdataset, fetchp)); +} + +isc_result_t +dns_resolver_createfetch2(dns_resolver_t *res, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + isc_sockaddr_t *client, dns_messageid_t id, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_rdataset_t *rdataset, + dns_rdataset_t *sigrdataset, + dns_fetch_t **fetchp) +{ + return (dns_resolver_createfetch3(res, name, type, domain, + nameservers, forwarders, client, id, + options, 0, NULL, task, action, arg, + rdataset, sigrdataset, fetchp)); +} + +isc_result_t +dns_resolver_createfetch3(dns_resolver_t *res, dns_name_t *name, + dns_rdatatype_t type, + dns_name_t *domain, dns_rdataset_t *nameservers, + dns_forwarders_t *forwarders, + 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)); + if (fetch == NULL) + return (ISC_R_NOMEMORY); + 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 (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, + 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 (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, *next_event; + isc_task_t *etask; + + 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 completion event for this fetch (as opposed + * to those for other fetches that have joined the same + * fctx) and send it with result = ISC_R_CANCELED. + */ + 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); + if (event->fetch == fetch) { + ISC_LIST_UNLINK(fctx->events, event, ev_link); + break; + } + } + } + if (event != NULL) { + etask = event->ev_sender; + event->ev_sender = fctx; + event->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event)); + } + /* + * 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; + 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)); + *fetchp = NULL; + + 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; +} + +unsigned int +dns_resolver_nrunning(dns_resolver_t *resolver) { + unsigned int n; + LOCK(&resolver->nlock); + n = resolver->nfctx; + UNLOCK(&resolver->nlock); + return (n); +} + +isc_result_t +dns_resolver_addalternate(dns_resolver_t *resolver, isc_sockaddr_t *alt, + dns_name_t *name, in_port_t port) { + alternate_t *a; + isc_result_t result; + + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(!resolver->frozen); + REQUIRE((alt == NULL) ^ (name == NULL)); + + a = isc_mem_get(resolver->mctx, sizeof(*a)); + if (a == NULL) + return (ISC_R_NOMEMORY); + 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); + result = dns_name_dup(name, resolver->mctx, &a->_u._n.name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(resolver->mctx, a, sizeof(*a)); + return (result); + } + } + 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, 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, dns_name_t *name) { + dns_badcache_flushtree(resolver->badcache, name); +} + +void +dns_resolver_addbadcache(dns_resolver_t *resolver, dns_name_t *name, + dns_rdatatype_t type, isc_time_t *expire) +{ +#ifdef ENABLE_AFL + if (!dns_fuzzing_resolver) +#endif + { + (void) dns_badcache_add(resolver->badcache, name, type, + false, 0, expire); + } +} + +bool +dns_resolver_getbadcache(dns_resolver_t *resolver, 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 (resolver->algorithms != NULL) + dns_rbt_destroy(&resolver->algorithms); +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif +} + +isc_result_t +dns_resolver_disable_algorithm(dns_resolver_t *resolver, 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 (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); + if (tmp == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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 + return (result); +} + +bool +dns_resolver_algorithm_supported(dns_resolver_t *resolver, 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 (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 (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 (resolver->digests != NULL) + dns_rbt_destroy(&resolver->digests); +#if USE_ALGLOCK + RWUNLOCK(&resolver->alglock, isc_rwlocktype_write); +#endif +} + +isc_result_t +dns_resolver_disable_ds_digest(dns_resolver_t *resolver, 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 (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); + if (tmp == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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 + return (result); +} + +bool +dns_resolver_ds_digest_supported(dns_resolver_t *resolver, 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 (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 (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 (resolver->mustbesecure != NULL) + dns_rbt_destroy(&resolver->mustbesecure); +#if USE_MBSLOCK + RWUNLOCK(&resolver->mbslock, isc_rwlocktype_write); +#endif +} + +static bool yes = true, no = false; + +isc_result_t +dns_resolver_setmustbesecure(dns_resolver_t *resolver, 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 (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 + return (result); +} + +bool +dns_resolver_getmustbesecure(dns_resolver_t *resolver, 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 (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 + 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)); + + LOCK(&resolver->lock); + resolver->zspill = clients; + UNLOCK(&resolver->lock); +} + + +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 seconds) { + REQUIRE(VALID_RESOLVER(resolver)); + + if (seconds == 0) + seconds = DEFAULT_QUERY_TIMEOUT; + if (seconds > MAXIMUM_QUERY_TIMEOUT) + seconds = MAXIMUM_QUERY_TIMEOUT; + if (seconds < MINIMUM_QUERY_TIMEOUT) + seconds = MINIMUM_QUERY_TIMEOUT; + + resolver->query_timeout = seconds; +} + +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]); +} diff --git a/lib/dns/result.c b/lib/dns/result.c new file mode 100644 index 0000000..e7507c5 --- /dev/null +++ b/lib/dns/result.c @@ -0,0 +1,443 @@ +/* + * 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 http://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 + +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 */ +}; + +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", +}; + +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_msgcat, DNS_RESULT_RESULTSET); + if (result == ISC_R_SUCCESS) + result = isc_result_register(ISC_RESULTCLASS_DNSRCODE, + DNS_R_NRCODERESULTS, + rcode_text, dns_msgcat, + 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_msgcat, DNS_RESULT_RESULTSET); + if (result == ISC_R_SUCCESS) + result = isc_result_registerids(ISC_RESULTCLASS_DNSRCODE, + DNS_R_NRCODERESULTS, + rcode_ids, dns_msgcat, + DNS_RESULT_RCODERESULTSET); + if (result != ISC_R_SUCCESS) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_registerids() failed: %u", result); +} + +static void +initialize(void) { + dns_lib_initmsgcat(); + 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_NOSPACE: + 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..70b2b7d --- /dev/null +++ b/lib/dns/rootns.c @@ -0,0 +1,528 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include + +#include + +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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, 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, + &callbacks, db->mctx); + } 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..f0993be --- /dev/null +++ b/lib/dns/rpz.c @@ -0,0 +1,2413 @@ +/* + * 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 http://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 + + +/* + * 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. + */ + + +/* + * 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; +}; + +#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 + +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; + default: + str = ""; + POST(str); + INSIST(0); + } + 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 DNS_RPZ_MAX_ZONES > 32 + if ((zbit & 0xffffffff00000000L) != 0) { + zbit >>= 32; + rpz_num += 32; + } +#endif + 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: + INSIST(0); + break; + } +} + +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: + INSIST(0); + break; + } +} + +/* + * 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 bitfield 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; +#if DNS_RPZ_MAX_ZONES > 32 + req_mask |= req_mask >> 32; +#endif + + /* + * 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; + dns_rpz_zbits_t *have; + + 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: + INSIST(0); + } + + 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)); + if (node == NULL) + return (NULL); + 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, 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, + dns_name_t *base_name, dns_name_t *ip_name) +{ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + 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 || len > (int)sizeof(str)) { + return (ISC_R_FAILURE); + } + } else { + len = snprintf(str, sizeof(str), "%d", tgt_prefix); + if (len == -1) + 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 < (int)sizeof(str)); + if (n == best_first) { + len += snprintf(str + len, sizeof(str) - len, + ".zz"); + n += best_len - 1; + } else { + len += snprintf(str + len, sizeof(str) - len, + ".%x", w[n]); + } + } + } + + 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(dns_rpz_zone_t *rpz, 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); + +#ifdef ENABLE_RPZ_NSIP + if (dns_name_issubdomain(name, &rpz->nsip)) + return (DNS_RPZ_TYPE_NSIP); +#endif + +#ifdef ENABLE_RPZ_NSDNAME + if (dns_name_issubdomain(name, &rpz->nsdname)) + return (DNS_RPZ_TYPE_NSDNAME); +#endif + + 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, 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]; + char 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); + } + + 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 inline 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 + +/* + * 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 + bit += clz(delta); +#endif + break; + } + } + return (ISC_MIN(bit, maxbit)); +} + +/* + * Given a hit while searching the radix trees, + * clear all bits for higher numbered zones. + */ +static inline 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; + return (zbits &= x); +} + +/* + * 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, 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)); + if (nm_data == NULL) + return (ISC_R_NOMEMORY); + *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, 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, isc_mem_t *mctx) { + dns_rpz_zones_t *new; + isc_result_t result; + + REQUIRE(rpzsp != NULL && *rpzsp == NULL); + + *rpzsp = NULL; + + new = isc_mem_get(mctx, sizeof(*new)); + if (new == NULL) + return (ISC_R_NOMEMORY); + memset(new, 0, sizeof(*new)); + + result = isc_rwlock_init(&new->search_lock, 0, 0); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = isc_mutex_init(&new->maint_lock); + if (result != ISC_R_SUCCESS) { + isc_rwlock_destroy(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = isc_refcount_init(&new->refs, 1); + if (result != ISC_R_SUCCESS) { + DESTROYLOCK(&new->maint_lock); + isc_rwlock_destroy(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + result = dns_rbt_create(mctx, rpz_node_deleter, mctx, &new->rbt); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement(&new->refs, NULL); + isc_refcount_destroy(&new->refs); + DESTROYLOCK(&new->maint_lock); + isc_rwlock_destroy(&new->search_lock); + isc_mem_put(mctx, new, sizeof(*new)); + return (result); + } + + isc_mem_attach(mctx, &new->mctx); + + *rpzsp = new; + return (ISC_R_SUCCESS); +} + +/* + * 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_zones_t *rpzs) { + dns_rpz_zone_t *rpz; + unsigned int refs; + + rpz = *rpzp; + *rpzp = NULL; + isc_refcount_decrement(&rpz->refs, &refs); + if (refs != 0) + return; + isc_refcount_destroy(&rpz->refs); + + 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); + + isc_mem_put(rpzs->mctx, rpz, sizeof(*rpz)); +} + +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, NULL); + *rpzsp = rpzs; +} + +/* + * Forget a view's policy zones. + */ +void +dns_rpz_detach_rpzs(dns_rpz_zones_t **rpzsp) { + dns_rpz_zones_t *rpzs; + dns_rpz_zone_t *rpz; + dns_rpz_num_t rpz_num; + unsigned int refs; + + REQUIRE(rpzsp != NULL); + rpzs = *rpzsp; + REQUIRE(rpzs != NULL); + + *rpzsp = NULL; + isc_refcount_decrement(&rpzs->refs, &refs); + if (refs > 0) + return; + + /* + * Forget the last of view's rpz machinery after the last + * reference. + */ + for (rpz_num = 0; rpz_num < DNS_RPZ_MAX_ZONES; ++rpz_num) { + rpz = rpzs->zones[rpz_num]; + rpzs->zones[rpz_num] = NULL; + if (rpz != NULL) + rpz_detach(&rpz, rpzs); + } + + cidr_free(rpzs); + dns_rbt_destroy(&rpzs->rbt); + DESTROYLOCK(&rpzs->maint_lock); + isc_rwlock_destroy(&rpzs->search_lock); + isc_refcount_destroy(&rpzs->refs); + isc_mem_putanddetach(&rpzs->mctx, rpzs, sizeof(*rpzs)); +} + +/* + * Create empty summary database to load one zone. + * The RBTDB write tree lock must be held. + */ +isc_result_t +dns_rpz_beginload(dns_rpz_zones_t **load_rpzsp, + dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) +{ + dns_rpz_zones_t *load_rpzs; + dns_rpz_zone_t *rpz; + dns_rpz_zbits_t tgt; + isc_result_t result; + + REQUIRE(rpz_num < rpzs->p.num_zones); + rpz = rpzs->zones[rpz_num]; + REQUIRE(rpz != NULL); + + /* + * When reloading a zone, there are usually records among the summary + * data for the zone. Some of those records might be deleted by the + * reloaded zone data. To deal with that case: + * reload the new zone data into a new blank summary database + * if the reload fails, discard the new summary database + * if the new zone data is acceptable, copy the records for the + * other zones into the new summary CIDR and RBT databases + * and replace the old summary databases with the new, and + * correct the triggers and have values for the updated + * zone. + * + * At the first attempt to load a zone, there is no summary data + * for the zone and so no records that need to be deleted. + * This is also the most common case of policy zone loading. + * Most policy zone maintenance should be by incremental changes + * and so by the addition and deletion of individual records. + * Detect that case and load records the first time into the + * operational summary database + */ + tgt = DNS_RPZ_ZBIT(rpz_num); + LOCK(&rpzs->maint_lock); + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + if ((rpzs->load_begun & tgt) == 0) { + /* + * There is no existing version of the target zone. + */ + rpzs->load_begun |= tgt; + dns_rpz_attach_rpzs(rpzs, load_rpzsp); + } else { + /* + * Setup the new RPZ struct with empty summary trees. + */ + result = dns_rpz_new_zones(load_rpzsp, rpzs->mctx); + if (result != ISC_R_SUCCESS) + return (result); + load_rpzs = *load_rpzsp; + /* + * Initialize some members so that dns_rpz_add() works. + */ + load_rpzs->p.num_zones = rpzs->p.num_zones; + memset(&load_rpzs->triggers, 0, sizeof(load_rpzs->triggers)); + load_rpzs->zones[rpz_num] = rpz; + isc_refcount_increment(&rpz->refs, NULL); + } + + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write); + UNLOCK(&rpzs->maint_lock); + + return (ISC_R_SUCCESS); +} + +/* + * This function updates "have" bits and also the qname_skip_recurse + * mask. It must be called when holding a write lock on rpzs->search_lock. + */ +static void +fix_triggers(dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { + dns_rpz_num_t n; + dns_rpz_triggers_t old_totals; + dns_rpz_zbits_t zbit; + char namebuf[DNS_NAME_FORMATSIZE]; + + /* + * rpzs->total_triggers is only used to log a message below. + */ + + memmove(&old_totals, &rpzs->total_triggers, sizeof(old_totals)); + memset(&rpzs->total_triggers, 0, sizeof(rpzs->total_triggers)); + +#define SET_TRIG(n, zbit, type) \ + if (rpzs->triggers[n].type == 0U) { \ + rpzs->have.type &= ~zbit; \ + } else { \ + rpzs->total_triggers.type += rpzs->triggers[n].type; \ + rpzs->have.type |= zbit; \ + } + + for (n = 0; n < rpzs->p.num_zones; ++n) { + zbit = DNS_RPZ_ZBIT(n); + SET_TRIG(n, zbit, client_ipv4); + SET_TRIG(n, zbit, client_ipv6); + SET_TRIG(n, zbit, qname); + SET_TRIG(n, zbit, ipv4); + SET_TRIG(n, zbit, ipv6); + SET_TRIG(n, zbit, nsdname); + SET_TRIG(n, zbit, nsipv4); + SET_TRIG(n, zbit, nsipv6); + } + +#undef SET_TRIG + + fix_qname_skip_recurse(rpzs); + + dns_name_format(&rpzs->zones[rpz_num]->origin, + namebuf, sizeof(namebuf)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_INFO_LEVEL, + "(re)loading policy zone '%s' changed from" + " %lu to %lu qname, %lu to %lu nsdname," + " %lu to %lu IP, %lu to %lu NSIP," + " %lu to %lu CLIENTIP entries", + namebuf, + (unsigned long) old_totals.qname, + (unsigned long) rpzs->total_triggers.qname, + (unsigned long) old_totals.nsdname, + (unsigned long) rpzs->total_triggers.nsdname, + (unsigned long) old_totals.ipv4 + old_totals.ipv6, + (unsigned long) (rpzs->total_triggers.ipv4 + + rpzs->total_triggers.ipv6), + (unsigned long) old_totals.nsipv4 + old_totals.nsipv6, + (unsigned long) (rpzs->total_triggers.nsipv4 + + rpzs->total_triggers.nsipv6), + (unsigned long) old_totals.client_ipv4 + + old_totals.client_ipv6, + (unsigned long) (rpzs->total_triggers.client_ipv4 + + rpzs->total_triggers.client_ipv6)); +} + +/* + * Finish loading one zone. This function is called during a commit when + * a RPZ zone loading is complete. The RBTDB write tree lock must be + * held. + * + * Here, rpzs is a pointer to the view's common rpzs + * structure. *load_rpzsp is a rpzs structure that is local to the + * RBTDB, which is used during a single zone's load. + * + * During the zone load, i.e., between dns_rpz_beginload() and + * dns_rpz_ready(), only the zone that is being loaded updates + * *load_rpzsp. These updates in the summary databases inside load_rpzsp + * are made only for the rpz_num (and corresponding bit) of that + * zone. Nothing else reads or writes *load_rpzsp. The view's common + * rpzs is used during this time for queries. + * + * When zone loading is complete and we arrive here, the parts of the + * summary databases (CIDR and nsdname+qname RBT trees) from the view's + * common rpzs struct have to be merged into the summary databases of + * *load_rpzsp, as the summary databases of the view's common rpzs + * struct may have changed during the time the zone was being loaded. + * + * The function below carries out the merge. During the merge, it holds + * the maint_lock of the view's common rpzs struct so that it is not + * updated while the merging is taking place. + * + * After the merging is carried out, *load_rpzsp contains the most + * current state of the rpzs structure, i.e., the summary trees contain + * data for the new zone that was just loaded, as well as all other + * zones. + * + * Pointers to the summary databases of *load_rpzsp (CIDR and + * nsdname+qname RBT trees) are then swapped into the view's common rpz + * struct, so that the query path can continue using it. During the + * swap, the search_lock of the view's common rpz struct is acquired so + * that queries are paused while this swap occurs. + * + * The trigger counts for the new zone are also copied into the view's + * common rpz struct, and some other summary counts and masks are + * updated. + */ +isc_result_t +dns_rpz_ready(dns_rpz_zones_t *rpzs, + dns_rpz_zones_t **load_rpzsp, dns_rpz_num_t rpz_num) +{ + dns_rpz_zones_t *load_rpzs; + const dns_rpz_cidr_node_t *cnode, *next_cnode, *parent_cnode; + dns_rpz_cidr_node_t *found; + dns_rpz_zbits_t new_bit; + dns_rpz_addr_zbits_t new_ip; + dns_rbt_t *rbt; + dns_rbtnodechain_t chain; + dns_rbtnode_t *nmnode; + dns_rpz_nm_data_t *nm_data, new_data; + dns_fixedname_t labelf, originf, namef; + dns_name_t *label, *origin, *name; + isc_result_t result; + + INSIST(rpzs != NULL); + LOCK(&rpzs->maint_lock); + load_rpzs = *load_rpzsp; + INSIST(load_rpzs != NULL); + + if (load_rpzs == rpzs) { + /* + * This is a successful initial zone loading, perhaps + * for a new instance of a view. + */ + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + fix_triggers(rpzs, rpz_num); + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write); + UNLOCK(&rpzs->maint_lock); + dns_rpz_detach_rpzs(load_rpzsp); + return (ISC_R_SUCCESS); + } + + LOCK(&load_rpzs->maint_lock); + RWLOCK(&load_rpzs->search_lock, isc_rwlocktype_write); + + /* + * Unless there is only one policy zone, copy the other policy zones + * from the old policy structure to the new summary databases. + */ + if (rpzs->p.num_zones > 1) { + new_bit = ~DNS_RPZ_ZBIT(rpz_num); + + /* + * Copy to the radix tree. + */ + for (cnode = rpzs->cidr; cnode != NULL; cnode = next_cnode) { + new_ip.ip = cnode->set.ip & new_bit; + new_ip.client_ip = cnode->set.client_ip & new_bit; + new_ip.nsip = cnode->set.nsip & new_bit; + if (new_ip.client_ip != 0 || + new_ip.ip != 0 || + new_ip.nsip != 0) { + result = search(load_rpzs, + &cnode->ip, cnode->prefix, + &new_ip, true, &found); + if (result == ISC_R_NOMEMORY) + goto unlock_and_detach; + INSIST(result == ISC_R_SUCCESS); + } + /* + * Do down and to the left as far as possible. + */ + next_cnode = cnode->child[0]; + if (next_cnode != NULL) + continue; + /* + * Go up until we find a branch to the right where + * we previously took the branch to the left. + */ + for (;;) { + parent_cnode = cnode->parent; + if (parent_cnode == NULL) + break; + if (parent_cnode->child[0] == cnode) { + next_cnode = parent_cnode->child[1]; + if (next_cnode != NULL) + break; + } + cnode = parent_cnode; + } + } + + /* + * Copy to the summary RBT. + */ + dns_fixedname_init(&namef); + name = dns_fixedname_name(&namef); + dns_fixedname_init(&labelf); + label = dns_fixedname_name(&labelf); + dns_fixedname_init(&originf); + origin = dns_fixedname_name(&originf); + dns_rbtnodechain_init(&chain, NULL); + result = dns_rbtnodechain_first(&chain, rpzs->rbt, NULL, NULL); + while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) { + result = dns_rbtnodechain_current(&chain, label, origin, + &nmnode); + INSIST(result == ISC_R_SUCCESS); + nm_data = nmnode->data; + if (nm_data != NULL) { + new_data.set.qname = (nm_data->set.qname & + new_bit); + new_data.set.ns = nm_data->set.ns & new_bit; + new_data.wild.qname = (nm_data->wild.qname & + new_bit); + new_data.wild.ns = nm_data->wild.ns & new_bit; + if (new_data.set.qname != 0 || + new_data.set.ns != 0 || + new_data.wild.qname != 0 || + new_data.wild.ns != 0) { + result = dns_name_concatenate(label, + origin, name, NULL); + INSIST(result == ISC_R_SUCCESS); + result = add_nm(load_rpzs, name, + &new_data); + if (result != ISC_R_SUCCESS) + goto unlock_and_detach; + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + if (result != ISC_R_NOMORE && result != ISC_R_NOTFOUND) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, + DNS_LOGMODULE_RBTDB, DNS_RPZ_ERROR_LEVEL, + "dns_rpz_ready(): unexpected %s", + isc_result_totext(result)); + goto unlock_and_detach; + } + } + + /* + * Exchange the summary databases. + */ + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + rpzs->triggers[rpz_num] = load_rpzs->triggers[rpz_num]; + fix_triggers(rpzs, rpz_num); + + found = rpzs->cidr; + rpzs->cidr = load_rpzs->cidr; + load_rpzs->cidr = found; + + rbt = rpzs->rbt; + rpzs->rbt = load_rpzs->rbt; + load_rpzs->rbt = rbt; + + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + result = ISC_R_SUCCESS; + + unlock_and_detach: + UNLOCK(&rpzs->maint_lock); + RWUNLOCK(&load_rpzs->search_lock, isc_rwlocktype_write); + UNLOCK(&load_rpzs->maint_lock); + dns_rpz_detach_rpzs(load_rpzsp); + return (result); +} + +/* + * 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, 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); + + rpz_type = type_from_name(rpz, src_name); + + LOCK(&rpzs->maint_lock); + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + 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); + UNLOCK(&rpzs->maint_lock); + 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, 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, 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, + 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); + + rpz_type = type_from_name(rpz, src_name); + + LOCK(&rpzs->maint_lock); + RWLOCK(&rpzs->search_lock, isc_rwlocktype_write); + + 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); + UNLOCK(&rpzs->maint_lock); +} + +/* + * 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; + dns_rpz_have_t have; + int i; + + LOCK(&rpzs->maint_lock); + have = rpzs->have; + UNLOCK(&rpzs->maint_lock); + + /* + * 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: + INSIST(0); + break; + } + } else if (netaddr->family == AF_INET6) { + dns_rpz_cidr_key_t src_ip6; + + /* + * Given the int aligned struct in_addr member of netaddr->type + * one could cast netaddr->type.in6 to dns_rpz_cidr_key_t *, + * but some people object. + */ + memmove(src_ip6.w, &netaddr->type.in6, sizeof(src_ip6.w)); + for (i = 0; i < 4; i++) { + tgt_ip.w[i] = ntohl(src_ip6.w[i]); + } + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + zbits &= have.client_ipv6; + break; + case DNS_RPZ_TYPE_IP: + zbits &= have.ipv6; + break; + case DNS_RPZ_TYPE_NSIP: + zbits &= have.nsipv6; + break; + default: + INSIST(0); + break; + } + } else { + return (DNS_RPZ_INVALID_NUM); + } + + if (zbits == 0) + return (DNS_RPZ_INVALID_NUM); + make_addr_set(&tgt_set, zbits, rpz_type); + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + result = search(rpzs, &tgt_ip, 128, &tgt_set, false, &found); + if (result == ISC_R_NOTFOUND) { + /* + * There are no eligible zones for this IP address. + */ + RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read); + return (DNS_RPZ_INVALID_NUM); + } + + /* + * Construct the trigger name for the longest matching trigger + * in the first eligible zone with a match. + */ + *prefixp = found->prefix; + switch (rpz_type) { + case DNS_RPZ_TYPE_CLIENT_IP: + rpz_num = zbit_to_num(found->set.client_ip & tgt_set.client_ip); + break; + case DNS_RPZ_TYPE_IP: + rpz_num = zbit_to_num(found->set.ip & tgt_set.ip); + break; + case DNS_RPZ_TYPE_NSIP: + rpz_num = zbit_to_num(found->set.nsip & tgt_set.nsip); + break; + default: + INSIST(0); + break; + } + 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; + isc_result_t result; + + if (zbits == 0) + return (0); + + found_zbits = 0; + + RWLOCK(&rpzs->search_lock, isc_rwlocktype_read); + + nmnode = NULL; + result = dns_rbt_findnode(rpzs->rbt, trig_name, NULL, &nmnode, NULL, + 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; + } + nmnode = nmnode->parent; + /* fall thru */ + case DNS_R_PARTIALMATCH: + 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; + } + nmnode = nmnode->parent; + } + 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); + 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..3a6b9a1 --- /dev/null +++ b/lib/dns/rriterator.c @@ -0,0 +1,204 @@ +/* + * 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 http://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 + +/*** + *** 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, + 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, + 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..cb7e7b6 --- /dev/null +++ b/lib/dns/rrl.c @@ -0,0 +1,1320 @@ +/* + * 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 http://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 + +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 + }; + 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 inline 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 inline 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 inline 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); + if (b == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL, + "isc_mem_get(%d) failed for RRL entries", + bsize); + return (ISC_R_NOMEMORY); + } + 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 inline 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); + if (hash == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL, + "isc_mem_get(%d) failed for" + " RRL hash table", + hsize); + return (ISC_R_NOMEMORY); + } + 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 inline 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 inline 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_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass, + dns_rrl_rtype_t rtype) +{ + dns_name_t base; + dns_offsets_t base_offsets; + int labels, 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) { + /* + * Ignore the first label of wildcards. + */ + if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 && + (labels = dns_name_countlabels(qname)) > 1) + { + dns_name_init(&base, base_offsets); + dns_name_getlabelsequence(qname, 1, labels-1, &base); + key->s.qname_hash = + dns_name_fullhash(&base, 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 inline 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: + INSIST(0); + } + return (NULL); +} + +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_rdataclass_t qclass, dns_rdatatype_t qtype, 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, 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 prevous 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=12345678")]; + 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 inline 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, + 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 inline 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 inline 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, + 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: + INSIST(0); + break; + } + + 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: + INSIST(0); + } + + 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)); + if (qbuf != NULL) { + memset(qbuf, 0, sizeof(*qbuf)); + ISC_LINK_INIT(qbuf, link); + qbuf->index = rrl->num_qnames; + rrl->qnames[rrl->num_qnames++] = qbuf; + } else { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_RRL, + DNS_LOGMODULE_REQUEST, + DNS_RRL_LOG_FAIL, + "isc_mem_get(%d)" + " failed for RRL qname", + (int)sizeof(*qbuf)); + } + } + if (qbuf != NULL) { + e->log_qname = qbuf->index; + qbuf->e = e; + dns_fixedname_init(&qbuf->qname); + dns_name_copy(qname, + dns_fixedname_name(&qbuf->qname), + NULL); + } + } + 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), " (%08x)", + 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, + const isc_sockaddr_t *client_addr, bool is_tcp, + dns_rdataclass_t qclass, dns_rdatatype_t qtype, + 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, + 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, 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, + 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 occassionally 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); + + DESTROYLOCK(&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)); + if (rrl == NULL) + return (ISC_R_NOMEMORY); + memset(rrl, 0, sizeof(*rrl)); + isc_mem_attach(view->mctx, &rrl->mctx); + result = isc_mutex_init(&rrl->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl)); + return (result); + } + 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..cabda7f --- /dev/null +++ b/lib/dns/sdb.c @@ -0,0 +1,1593 @@ +/* + * 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 http://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; + isc_mutex_t lock; + /* Locked */ + unsigned int 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; + isc_mutex_t lock; + dns_rdatacallbacks_t callbacks; + /* Locked */ + unsigned int 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 +#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 + +static int dummy; + +static isc_result_t dns_sdb_create(isc_mem_t *mctx, 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, + 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)); + if (imp == NULL) + return (ISC_R_NOMEMORY); + imp->methods = methods; + imp->driverdata = driverdata; + imp->flags = flags; + imp->mctx = NULL; + isc_mem_attach(mctx, &imp->mctx); + result = isc_mutex_init(&imp->driverlock); + if (result != ISC_R_SUCCESS) + goto cleanup_mctx; + + 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: + DESTROYLOCK(&imp->driverlock); + cleanup_mctx: + isc_mem_put(mctx, imp, sizeof(dns_sdbimplementation_t)); + return (result); +} + +void +dns_sdb_unregister(dns_sdbimplementation_t **sdbimp) { + dns_sdbimplementation_t *imp; + isc_mem_t *mctx; + + REQUIRE(sdbimp != NULL && *sdbimp != NULL); + + imp = *sdbimp; + dns_db_unregister(&imp->dbimp); + DESTROYLOCK(&imp->driverlock); + + mctx = imp->mctx; + isc_mem_put(mctx, imp, sizeof(dns_sdbimplementation_t)); + isc_mem_detach(&mctx); + + *sdbimp = NULL; +} + +static inline 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_result_t result; + 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)); + if (rdatalist == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (rdata == NULL) + return (ISC_R_NOMEMORY); + + result = isc_buffer_allocate(mctx, &rdatabuf, rdlen); + if (result != ISC_R_SUCCESS) + goto failure; + 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); + rdata = NULL; + + failure: + if (rdata != NULL) + isc_mem_put(mctx, rdata, sizeof(dns_rdata_t)); + return (result); +} + +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; + 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); + if (p == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + 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, *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)); + if (sdbnode->name == NULL) { + destroynode(sdbnode); + return (ISC_R_NOMEMORY); + } + dns_name_init(sdbnode->name, NULL); + result = dns_name_dup(newname, mctx, sdbnode->name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, sdbnode->name, sizeof(dns_name_t)); + destroynode(sdbnode); + return (result); + } + 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)); + + LOCK(&sdb->lock); + REQUIRE(sdb->references > 0); + sdb->references++; + UNLOCK(&sdb->lock); + + *targetp = source; +} + +static void +destroy(dns_sdb_t *sdb) { + isc_mem_t *mctx; + dns_sdbimplementation_t *imp = sdb->implementation; + + mctx = sdb->common.mctx; + + if (imp->methods->destroy != NULL) { + MAYBE_LOCK(sdb); + imp->methods->destroy(sdb->zone, imp->driverdata, + &sdb->dbdata); + MAYBE_UNLOCK(sdb); + } + + isc_mem_free(mctx, sdb->zone); + DESTROYLOCK(&sdb->lock); + + sdb->common.magic = 0; + sdb->common.impmagic = 0; + + dns_name_free(&sdb->common.origin, mctx); + + isc_mem_put(mctx, sdb, sizeof(dns_sdb_t)); + isc_mem_detach(&mctx); +} + +static void +detach(dns_db_t **dbp) { + dns_sdb_t *sdb = (dns_sdb_t *)(*dbp); + bool need_destroy = false; + + REQUIRE(VALID_SDB(sdb)); + LOCK(&sdb->lock); + REQUIRE(sdb->references > 0); + sdb->references--; + if (sdb->references == 0) + need_destroy = true; + UNLOCK(&sdb->lock); + + if (need_destroy) + destroy(sdb); + + *dbp = NULL; +} + +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 == false); + + UNUSED(db); + UNUSED(commit); + + *versionp = NULL; +} + +static isc_result_t +createnode(dns_sdb_t *sdb, dns_sdbnode_t **nodep) { + dns_sdbnode_t *node; + isc_result_t result; + + node = isc_mem_get(sdb->common.mctx, sizeof(dns_sdbnode_t)); + if (node == NULL) + return (ISC_R_NOMEMORY); + + 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; + result = isc_mutex_init(&node->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(sdb->common.mctx, node, sizeof(dns_sdbnode_t)); + return (result); + } + dns_rdatacallbacks_init(&node->callbacks); + 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)); + } + DESTROYLOCK(&node->lock); + node->magic = 0; + isc_mem_put(mctx, node, sizeof(dns_sdbnode_t)); + detach((dns_db_t **) (void *)&sdb); +} + +static isc_result_t +findnodeext(dns_db_t *db, 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(create == false); + 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, 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) { + isc_result_t xresult; + + xresult = dns_name_copy(xname, foundname, NULL); + if (xresult != ISC_R_SUCCESS) { + if (node != NULL) + destroynode(node); + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + return (DNS_R_BADDB); + } + } + + if (nodep != NULL) + *nodep = node; + else if (node != NULL) + detachnode(db, &node); + + return (result); +} + +static isc_result_t +findzonecut(dns_db_t *db, dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) +{ + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + 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); + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references++; + INSIST(node->references != 0); /* Catch overflow. */ + UNLOCK(&node->lock); + + *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; + bool need_destroy = false; + + REQUIRE(VALID_SDB(sdb)); + REQUIRE(targetp != NULL && *targetp != NULL); + + UNUSED(sdb); + + node = (dns_sdbnode_t *)(*targetp); + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references--; + if (node->references == 0) + need_destroy = true; + UNLOCK(&node->lock); + + if (need_destroy) + destroynode(node); + + *targetp = NULL; +} + +static isc_result_t +expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + UNUSED(db); + UNUSED(node); + UNUSED(now); + INSIST(0); + return (ISC_R_UNEXPECTED); +} + +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; + sdb_dbiterator_t *sdbiter; + dns_sdbimplementation_t *imp = sdb->implementation; + isc_result_t result; + + REQUIRE(VALID_SDB(sdb)); + + 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)); + if (sdbiter == NULL) + return (ISC_R_NOMEMORY); + + sdbiter->common.methods = &dbiterator_methods; + sdbiter->common.db = NULL; + dns_db_attach(db, &sdbiter->common.db); + sdbiter->common.relative_names = (options & DNS_DB_RELATIVENAMES); + 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) +{ + dns_rdatalist_t *list; + dns_sdbnode_t *sdbnode = (dns_sdbnode_t *)node; + + REQUIRE(VALID_SDBNODE(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, + 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)); + if (iterator == NULL) + return (ISC_R_NOMEMORY); + + 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.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, + dump, + currentversion, + newversion, + attachversion, + closeversion, + NULL, + NULL, + findzonecut, + attachnode, + detachnode, + expirenode, + printnode, + createiterator, + findrdataset, + allrdatasets, + addrdataset, + subtractrdataset, + deleterdataset, + issecure, + nodecount, + ispersistent, + overmem, + 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 */ + findnodeext, + findext, + NULL, /* setcachestats */ + NULL, /* hashsize */ + NULL, /* nodefullname */ + NULL /* getsize */ +}; + +static isc_result_t +dns_sdb_create(isc_mem_t *mctx, 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)); + if (sdb == NULL) + return (ISC_R_NOMEMORY); + 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 = isc_mutex_init(&sdb->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_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); + if (sdb->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_origin; + } + + 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; + } + + 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: + (void)isc_mutex_destroy(&sdb->lock); + cleanup_mctx: + isc_mem_put(mctx, sdb, sizeof(dns_sdb_t)); + isc_mem_detach(&mctx); + + 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, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +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, 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) + return (dns_name_copy(sdbiter->current->name, name, NULL)); + 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); + return (dns_name_copy(dns_rootname, name, NULL)); +} + +/* + * 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..11c4c73 --- /dev/null +++ b/lib/dns/sdlz.c @@ -0,0 +1,2172 @@ +/* + * Portions 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 http://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 + +#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; + isc_mutex_t refcnt_lock; + /* Locked */ + unsigned int references; + 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; + isc_mutex_t lock; + dns_rdatacallbacks_t callbacks; + /* Locked */ + unsigned int 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 +#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 + +/* + * Forward references. + */ +static isc_result_t getnodedata(dns_db_t *db, 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, + 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 inline 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)); + + LOCK(&sdlz->refcnt_lock); + REQUIRE(sdlz->references > 0); + sdlz->references++; + UNLOCK(&sdlz->refcnt_lock); + + *targetp = source; +} + +static void +destroy(dns_sdlz_db_t *sdlz) { + isc_mem_t *mctx; + mctx = sdlz->common.mctx; + + sdlz->common.magic = 0; + sdlz->common.impmagic = 0; + + (void)isc_mutex_destroy(&sdlz->refcnt_lock); + + dns_name_free(&sdlz->common.origin, mctx); + + isc_mem_put(mctx, sdlz, sizeof(dns_sdlz_db_t)); + isc_mem_detach(&mctx); +} + +static void +detach(dns_db_t **dbp) { + dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)(*dbp); + bool need_destroy = false; + + REQUIRE(VALID_SDLZDB(sdlz)); + LOCK(&sdlz->refcnt_lock); + REQUIRE(sdlz->references > 0); + sdlz->references--; + if (sdlz->references == 0) + need_destroy = true; + UNLOCK(&sdlz->refcnt_lock); + + if (need_destroy) + destroy(sdlz); + + *dbp = NULL; +} + +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; + isc_result_t result; + + node = isc_mem_get(sdlz->common.mctx, sizeof(dns_sdlznode_t)); + if (node == NULL) + return (ISC_R_NOMEMORY); + + 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; + result = isc_mutex_init(&node->lock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + isc_mem_put(sdlz->common.mctx, node, sizeof(dns_sdlznode_t)); + return (ISC_R_UNEXPECTED); + } + dns_rdatacallbacks_init(&node->callbacks); + 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; + + 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)); + } + DESTROYLOCK(&node->lock); + 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, 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 == false); + } + + 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; + dns_name_t *wild; + + dns_fixedname_init(&fixed); + if (i == dlabels) + wild = dns_wildcardname; + else { + wild = dns_fixedname_name(&fixed); + dns_name_getlabelsequence(name, i + 1, + dlabels - i - 1, + wild); + result = dns_name_concatenate(dns_wildcardname, + wild, wild, NULL); + if (result != ISC_R_SUCCESS) + return (result); + } + + isc_buffer_init(&b, wildstr, sizeof(wildstr)); + result = dns_name_totext(wild, true, &b); + if (result != ISC_R_SUCCESS) + 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) { + 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) + { + destroynode(node); + return (result); + } + } + + if (node->name == NULL) { + node->name = isc_mem_get(sdlz->common.mctx, + sizeof(dns_name_t)); + if (node->name == NULL) { + destroynode(node); + return (ISC_R_NOMEMORY); + } + dns_name_init(node->name, NULL); + result = dns_name_dup(name, sdlz->common.mctx, node->name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(sdlz->common.mctx, node->name, + sizeof(dns_name_t)); + destroynode(node); + return (result); + } + } + + *nodep = node; + return (ISC_R_SUCCESS); +} + +static isc_result_t +findnodeext(dns_db_t *db, 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, 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, dns_name_t *name, unsigned int options, + isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) +{ + UNUSED(db); + UNUSED(name); + UNUSED(options); + UNUSED(now); + UNUSED(nodep); + UNUSED(foundname); + 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); + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references++; + INSIST(node->references != 0); /* Catch overflow. */ + UNLOCK(&node->lock); + + *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; + bool need_destroy = false; + + REQUIRE(VALID_SDLZDB(sdlz)); + REQUIRE(targetp != NULL && *targetp != NULL); + + UNUSED(sdlz); + + node = (dns_sdlznode_t *)(*targetp); + + LOCK(&node->lock); + INSIST(node->references > 0); + node->references--; + if (node->references == 0) + need_destroy = true; + UNLOCK(&node->lock); + + if (need_destroy) + destroynode(node); + + *targetp = NULL; +} + +static isc_result_t +expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { + UNUSED(db); + UNUSED(node); + UNUSED(now); + INSIST(0); + return (ISC_R_UNEXPECTED); +} + +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)); + if (sdlziter == NULL) + return (ISC_R_NOMEMORY); + + sdlziter->common.methods = &dbiterator_methods; + sdlziter->common.db = NULL; + dns_db_attach(db, &sdlziter->common.db); + sdlziter->common.relative_names = (options & DNS_DB_RELATIVENAMES); + 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) +{ + dns_rdatalist_t *list; + dns_sdlznode_t *sdlznode = (dns_sdlznode_t *)node; + + REQUIRE(VALID_SDLZNODE(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, 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) { + destroynode(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) { + isc_result_t xresult; + + xresult = dns_name_copy(xname, foundname, NULL); + if (xresult != ISC_R_SUCCESS) { + if (node != NULL) + destroynode(node); + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + return (DNS_R_BADDB); + } + } + + if (nodep != NULL) + *nodep = node; + else if (node != NULL) + detachnode(db, &node); + + return (result); +} + +static isc_result_t +find(dns_db_t *db, 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, + 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)); + if (iterator == NULL) + return (ISC_R_NOMEMORY); + + 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.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; + + result = isc_buffer_allocate(mctx, &buffer, 1024); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_master_stylecreate(&style, 0, 0, 0, 0, 0, 0, 1, mctx); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_master_rdatasettotext(sdlznode->name, rdataset, + style, 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, + 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 */ +}; + +/* + * 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); + 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, 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) + return (dns_name_copy(sdlziter->current->name, name, NULL)); + 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); + return (dns_name_copy(dns_rootname, name, NULL)); +} + +/* + * 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, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +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, + 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)); + if (sdlzdb == NULL) + return (ISC_R_NOMEMORY); + 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; + + /* initialize the reference count mutex */ + result = isc_mutex_init(&sdlzdb->refcnt_lock); + if (result != ISC_R_SUCCESS) + goto name_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; + 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); + + /* + * reference count mutex could not be initialized, clean up + * name memory + */ + name_cleanup: + dns_name_free(&sdlzdb->common.origin, mctx); + 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, dns_name_t *name, + 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) { + MAYBE_LOCK(imp); + result = imp->methods->allowzonexfr(imp->driverarg, dbdata, + namestr, clientstr); + MAYBE_UNLOCK(imp); + /* + * if zone is supported and transfers allowed build a 'bind' + * database driver + */ + if (result == ISC_R_SUCCESS) + result = dns_sdlzcreateDBP(mctx, driverarg, dbdata, + name, rdclass, dbp); + 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, 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(dns_name_t *signer, dns_name_t *name, 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; + 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)); + if (rdatalist == NULL) + return (ISC_R_NOMEMORY); + 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)); + if (rdata == NULL) + return (ISC_R_NOMEMORY); + 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; + result = isc_buffer_allocate(mctx, &rdatabuf, size); + if (result != ISC_R_SUCCESS) + goto failure; + + 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) + 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, *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)); + if (sdlznode->name == NULL) { + destroynode(sdlznode); + return (ISC_R_NOMEMORY); + } + dns_name_init(sdlznode->name, NULL); + result = dns_name_dup(newname, mctx, sdlznode->name); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, sdlznode->name, sizeof(dns_name_t)); + destroynode(sdlznode); + return (result); + } + 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)); + if (imp == NULL) + return (ISC_R_NOMEMORY); + + /* 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) + */ + result = isc_mutex_init(&imp->driverlock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + goto cleanup_mctx; + } + + 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 */ + DESTROYLOCK(&imp->driverlock); + + cleanup_mctx: + /* + * return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_put(mctx, imp, sizeof(dns_sdlzimplementation_t)); + isc_mem_detach(&mctx); + return (result); +} + +void +dns_sdlzunregister(dns_sdlzimplementation_t **sdlzimp) { + dns_sdlzimplementation_t *imp; + isc_mem_t *mctx; + + /* 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; + + /* Unregister the DLZ driver implementation */ + dns_dlzunregister(&imp->dlz_imp); + + /* destroy the driver lock, we don't need it anymore */ + DESTROYLOCK(&imp->driverlock); + + mctx = imp->mctx; + + /* + * return the memory back to the available memory pool and + * remove it from the memory context. + */ + isc_mem_put(mctx, imp, sizeof(dns_sdlzimplementation_t)); + isc_mem_detach(&mctx); + + *sdlzimp = NULL; +} + + +isc_result_t +dns_sdlz_setdb(dns_dlzdb_t *dlzdatabase, dns_rdataclass_t rdclass, + 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..9be3467 --- /dev/null +++ b/lib/dns/soa.c @@ -0,0 +1,142 @@ +/* + * 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 http://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 + +static inline uint32_t +decode_uint32(unsigned char *p) { + return ((p[0] << 24) + + (p[1] << 16) + + (p[2] << 8) + + (p[3] << 0)); +} + +static inline 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(dns_name_t *origin, 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/spnego.asn1 b/lib/dns/spnego.asn1 new file mode 100644 index 0000000..abf9b76 --- /dev/null +++ b/lib/dns/spnego.asn1 @@ -0,0 +1,50 @@ +-- Copyright (C) The Internet Society 2005. This version of +-- this module is part of RFC 4178; see the RFC itself for +-- full legal notices. + +-- (The above copyright notice is per RFC 3978 5.6 (a), q.v.) + +-- This is the SPNEGO ASN.1 module from RFC 4178, tweaked +-- to get the Heimdal ASN.1 compiler to accept it. + +SPNEGOASNOneSpec DEFINITIONS ::= BEGIN + +MechType ::= OBJECT IDENTIFIER + +MechTypeList ::= SEQUENCE OF MechType + +ContextFlags ::= BIT STRING { + delegFlag (0), + mutualFlag (1), + replayFlag (2), + sequenceFlag (3), + anonFlag (4), + confFlag (5), + integFlag (6) +} + +NegTokenInit ::= SEQUENCE { + mechTypes [0] MechTypeList, + reqFlags [1] ContextFlags OPTIONAL, + mechToken [2] OCTET STRING OPTIONAL, + mechListMIC [3] OCTET STRING OPTIONAL +} + +NegTokenResp ::= SEQUENCE { + negState [0] ENUMERATED { + accept-completed (0), + accept-incomplete (1), + reject (2), + request-mic (3) + } OPTIONAL, + supportedMech [1] MechType OPTIONAL, + responseToken [2] OCTET STRING OPTIONAL, + mechListMIC [3] OCTET STRING OPTIONAL +} + +NegotiationToken ::= CHOICE { + negTokenInit [0] NegTokenInit, + negTokenResp [1] NegTokenResp +} + +END diff --git a/lib/dns/spnego.c b/lib/dns/spnego.c new file mode 100644 index 0000000..ad77f24 --- /dev/null +++ b/lib/dns/spnego.c @@ -0,0 +1,1825 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file + * \brief + * Portable SPNEGO implementation. + * + * This is part of a portable implementation of the SPNEGO protocol + * (RFCs 2478 and 4178). This implementation uses the RFC 4178 ASN.1 + * module but is not a full implementation of the RFC 4178 protocol; + * at the moment, we only support GSS-TSIG with Kerberos + * authentication, so we only need enough of the SPNEGO protocol to + * support that. + * + * The files that make up this portable SPNEGO implementation are: + * \li spnego.c (this file) + * \li spnego.h (API SPNEGO exports to the rest of lib/dns) + * \li spnego.asn1 (SPNEGO ASN.1 module) + * \li spnego_asn1.c (routines generated from spngo.asn1) + * \li spnego_asn1.pl (perl script to generate spnego_asn1.c) + * + * Everything but the functions exported in spnego.h is static, to + * avoid possible conflicts with other libraries (particularly Heimdal, + * since much of this code comes from Heimdal by way of mod_auth_kerb). + * + * spnego_asn1.c is shipped as part of lib/dns because generating it + * requires both Perl and the Heimdal ASN.1 compiler. See + * spnego_asn1.pl for further details. We've tried to eliminate all + * compiler warnings from the generated code, but you may see a few + * when using a compiler version we haven't tested yet. + */ + +/* + * Portions of this code were derived from mod_auth_kerb and Heimdal. + * These packages are available from: + * + * http://modauthkerb.sourceforge.net/ + * http://www.pdc.kth.se/heimdal/ + * + * and were released under the following licenses: + * + * ---------------------------------------------------------------- + * + * Copyright (c) 2004 Masarykova universita + * (Masaryk University, Brno, Czech Republic) + * 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 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 OWNER 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. + * + * ---------------------------------------------------------------- + * + * Copyright (c) 1997 - 2003 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * 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 Institute 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 INSTITUTE 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 INSTITUTE 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. + */ + +/* + * XXXSRA We should omit this file entirely in Makefile.in via autoconf, + * but this will keep it from generating errors until that's written. + */ + +#ifdef GSSAPI + +/* + * XXXSRA Some of the following files are almost certainly unnecessary, + * but using this list (borrowed from gssapictx.c) gets rid of some + * whacky compilation errors when building with MSVC and should be + * harmless in any case. + */ + +#include + +#include +#include +#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" + +/* + * The API we export + */ +#include "spnego.h" + +/* asn1_err.h */ +/* Generated from ../../../lib/asn1/asn1_err.et */ + +#ifndef ERROR_TABLE_BASE_asn1 +/* these may be brought in already via gssapi_krb5.h */ +typedef enum asn1_error_number { + ASN1_BAD_TIMEFORMAT = 1859794432, + ASN1_MISSING_FIELD = 1859794433, + ASN1_MISPLACED_FIELD = 1859794434, + ASN1_TYPE_MISMATCH = 1859794435, + ASN1_OVERFLOW = 1859794436, + ASN1_OVERRUN = 1859794437, + ASN1_BAD_ID = 1859794438, + ASN1_BAD_LENGTH = 1859794439, + ASN1_BAD_FORMAT = 1859794440, + ASN1_PARSE_ERROR = 1859794441 +} asn1_error_number; + +#define ERROR_TABLE_BASE_asn1 1859794432 +#endif + +#define __asn1_common_definitions__ + +typedef struct octet_string { + size_t length; + void *data; +} octet_string; + +typedef char *general_string; + +typedef char *utf8_string; + +typedef struct oid { + size_t length; + unsigned *components; +} oid; + +/* der.h */ + +typedef enum { + ASN1_C_UNIV = 0, ASN1_C_APPL = 1, + ASN1_C_CONTEXT = 2, ASN1_C_PRIVATE = 3 +} Der_class; + +typedef enum { + PRIM = 0, CONS = 1 +} Der_type; + +/* Universal tags */ + +enum { + UT_Boolean = 1, + UT_Integer = 2, + UT_BitString = 3, + UT_OctetString = 4, + UT_Null = 5, + UT_OID = 6, + UT_Enumerated = 10, + UT_Sequence = 16, + UT_Set = 17, + UT_PrintableString = 19, + UT_IA5String = 22, + UT_UTCTime = 23, + UT_GeneralizedTime = 24, + UT_VisibleString = 26, + UT_GeneralString = 27 +}; + +#define ASN1_INDEFINITE 0xdce0deed + +static int +der_get_length(const unsigned char *p, size_t len, + size_t * val, size_t * size); + +static int +der_get_octet_string(const unsigned char *p, size_t len, + octet_string * data, size_t * size); +static int +der_get_oid(const unsigned char *p, size_t len, + oid * data, size_t * size); +static int +der_get_tag(const unsigned char *p, size_t len, + Der_class * xclass, Der_type * type, + int *tag, size_t * size); + +static int +der_match_tag(const unsigned char *p, size_t len, + Der_class xclass, Der_type type, + int tag, size_t * size); +static int +der_match_tag_and_length(const unsigned char *p, size_t len, + Der_class xclass, Der_type type, int tag, + size_t * length_ret, size_t * size); + +static int +decode_oid(const unsigned char *p, size_t len, + oid * k, size_t * size); + +static int +decode_enumerated(const unsigned char *p, size_t len, void *num, size_t *size); + +static int +decode_octet_string(const unsigned char *, size_t, octet_string *, size_t *); + +static int +der_put_int(unsigned char *p, size_t len, int val, size_t *); + +static int +der_put_length(unsigned char *p, size_t len, size_t val, size_t *); + +static int +der_put_octet_string(unsigned char *p, size_t len, + const octet_string * data, size_t *); +static int +der_put_oid(unsigned char *p, size_t len, + const oid * data, size_t * size); +static int +der_put_tag(unsigned char *p, size_t len, Der_class xclass, Der_type type, + int tag, size_t *); +static int +der_put_length_and_tag(unsigned char *, size_t, size_t, + Der_class, Der_type, int, size_t *); + +static int +encode_enumerated(unsigned char *p, size_t len, const void *data, size_t *); + +static int +encode_octet_string(unsigned char *p, size_t len, + const octet_string * k, size_t *); +static int +encode_oid(unsigned char *p, size_t len, + const oid * k, size_t *); + +static void +free_octet_string(octet_string * k); + +static void +free_oid (oid * k); + +static size_t +length_len(size_t len); + +static int +fix_dce(size_t reallen, size_t * len); + +/* + * Include stuff generated by the ASN.1 compiler. + */ + +#include "spnego_asn1.c" + +/* + * Force the oid arrays to be uint64_t aligned to silence warnings + * about the arrays not being properly aligned for (void *). + */ +typedef union { unsigned char b[8]; uint64_t _align; } aligned8; +typedef union { unsigned char b[16]; uint64_t _align[2]; } aligned16; + +static aligned16 gss_krb5_mech_oid_bytes = { + { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02 } +}; + +static gss_OID_desc gss_krb5_mech_oid_desc = { + 9, gss_krb5_mech_oid_bytes.b +}; + +static gss_OID GSS_KRB5_MECH = &gss_krb5_mech_oid_desc; + +static aligned16 gss_mskrb5_mech_oid_bytes = { + { 0x2a, 0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02 } +}; + +static gss_OID_desc gss_mskrb5_mech_oid_desc = { + 9, gss_mskrb5_mech_oid_bytes.b +}; + +static gss_OID GSS_MSKRB5_MECH = &gss_mskrb5_mech_oid_desc; + +static aligned8 gss_spnego_mech_oid_bytes = { + { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 } +}; + +static gss_OID_desc gss_spnego_mech_oid_desc = { + 6, gss_spnego_mech_oid_bytes.b +}; + +static gss_OID GSS_SPNEGO_MECH = &gss_spnego_mech_oid_desc; + +/* spnegokrb5_locl.h */ + +static OM_uint32 +gssapi_spnego_encapsulate(OM_uint32 *, + unsigned char *, + size_t, + gss_buffer_t, + const gss_OID); + +static OM_uint32 +gssapi_spnego_decapsulate(OM_uint32 *, + gss_buffer_t, + unsigned char **, + size_t *, + const gss_OID); + +/* mod_auth_kerb.c */ + +static int +cmp_gss_type(gss_buffer_t token, gss_OID gssoid) +{ + unsigned char *p; + size_t len; + + if (token->length == 0U) + return (GSS_S_DEFECTIVE_TOKEN); + + p = token->value; + if (*p++ != 0x60) + return (GSS_S_DEFECTIVE_TOKEN); + len = *p++; + if (len & 0x80) { + if ((len & 0x7f) > 4U) + return (GSS_S_DEFECTIVE_TOKEN); + p += len & 0x7f; + } + if (*p++ != 0x06) + return (GSS_S_DEFECTIVE_TOKEN); + + if (((OM_uint32) *p++) != gssoid->length) + return (GSS_S_DEFECTIVE_TOKEN); + + return (isc_safe_memcompare(p, gssoid->elements, gssoid->length)); +} + +/* accept_sec_context.c */ +/* + * SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly + * based on Heimdal code) + */ + +static OM_uint32 +code_NegTokenArg(OM_uint32 * minor_status, + const NegTokenResp * resp, + unsigned char **outbuf, + size_t * outbuf_size) +{ + OM_uint32 ret; + u_char *buf; + size_t buf_size, buf_len = 0; + + buf_size = 1024; + buf = malloc(buf_size); + if (buf == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + do { + ret = encode_NegTokenResp(buf + buf_size - 1, + buf_size, + resp, &buf_len); + if (ret == 0) { + size_t tmp; + + ret = der_put_length_and_tag(buf + buf_size - buf_len - 1, + buf_size - buf_len, + buf_len, + ASN1_C_CONTEXT, + CONS, + 1, + &tmp); + if (ret == 0) + buf_len += tmp; + } + if (ret) { + if (ret == ASN1_OVERFLOW) { + u_char *tmp; + + buf_size *= 2; + tmp = realloc(buf, buf_size); + if (tmp == NULL) { + *minor_status = ENOMEM; + free(buf); + return (GSS_S_FAILURE); + } + buf = tmp; + } else { + *minor_status = ret; + free(buf); + return (GSS_S_FAILURE); + } + } + } while (ret == ASN1_OVERFLOW); + + *outbuf = malloc(buf_len); + if (*outbuf == NULL) { + *minor_status = ENOMEM; + free(buf); + return (GSS_S_FAILURE); + } + memmove(*outbuf, buf + buf_size - buf_len, buf_len); + *outbuf_size = buf_len; + + free(buf); + + return (GSS_S_COMPLETE); +} + +static OM_uint32 +send_reject(OM_uint32 * minor_status, + gss_buffer_t output_token) +{ + NegTokenResp resp; + OM_uint32 ret; + + resp.negState = malloc(sizeof(*resp.negState)); + if (resp.negState == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + *(resp.negState) = reject; + + resp.supportedMech = NULL; + resp.responseToken = NULL; + resp.mechListMIC = NULL; + + ret = code_NegTokenArg(minor_status, &resp, + (unsigned char **)&output_token->value, + &output_token->length); + free_NegTokenResp(&resp); + if (ret) + return (ret); + + return (GSS_S_BAD_MECH); +} + +static OM_uint32 +send_accept(OM_uint32 * minor_status, + gss_buffer_t output_token, + gss_buffer_t mech_token, + const gss_OID pref) +{ + NegTokenResp resp; + OM_uint32 ret; + + memset(&resp, 0, sizeof(resp)); + resp.negState = malloc(sizeof(*resp.negState)); + if (resp.negState == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + *(resp.negState) = accept_completed; + + resp.supportedMech = malloc(sizeof(*resp.supportedMech)); + if (resp.supportedMech == NULL) { + free_NegTokenResp(&resp); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + ret = der_get_oid(pref->elements, + pref->length, + resp.supportedMech, + NULL); + if (ret) { + free_NegTokenResp(&resp); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + if (mech_token != NULL && mech_token->length != 0U) { + resp.responseToken = malloc(sizeof(*resp.responseToken)); + if (resp.responseToken == NULL) { + free_NegTokenResp(&resp); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + resp.responseToken->length = mech_token->length; + resp.responseToken->data = mech_token->value; + } + + ret = code_NegTokenArg(minor_status, &resp, + (unsigned char **)&output_token->value, + &output_token->length); + if (resp.responseToken != NULL) { + free(resp.responseToken); + resp.responseToken = NULL; + } + free_NegTokenResp(&resp); + if (ret) + return (ret); + + return (GSS_S_COMPLETE); +} + +OM_uint32 +gss_accept_sec_context_spnego(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_cred_id_t acceptor_cred_handle, + const gss_buffer_t input_token_buffer, + const gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + NegTokenInit init_token; + OM_uint32 major_status = GSS_S_COMPLETE; + OM_uint32 minor_status2; + gss_buffer_desc ibuf, obuf; + gss_buffer_t ot = NULL; + gss_OID pref = GSS_KRB5_MECH; + unsigned char *buf; + size_t buf_size; + size_t len, taglen, ni_len; + int found = 0; + int ret; + unsigned i; + + /* + * Before doing anything else, see whether this is a SPNEGO + * PDU. If not, dispatch to the GSSAPI library and get out. + */ + + if (cmp_gss_type(input_token_buffer, GSS_SPNEGO_MECH)) + return (gss_accept_sec_context(minor_status, + context_handle, + acceptor_cred_handle, + input_token_buffer, + input_chan_bindings, + src_name, + mech_type, + output_token, + ret_flags, + time_rec, + delegated_cred_handle)); + + /* + * If we get here, it's SPNEGO. + */ + + memset(&init_token, 0, sizeof(init_token)); + + ret = gssapi_spnego_decapsulate(minor_status, input_token_buffer, + &buf, &buf_size, GSS_SPNEGO_MECH); + if (ret) + return (ret); + + ret = der_match_tag_and_length(buf, buf_size, ASN1_C_CONTEXT, CONS, + 0, &len, &taglen); + if (ret) + return (ret); + + ret = decode_NegTokenInit(buf + taglen, len, &init_token, &ni_len); + if (ret) { + *minor_status = EINVAL; /* XXX */ + return (GSS_S_DEFECTIVE_TOKEN); + } + + for (i = 0; !found && i < init_token.mechTypes.len; ++i) { + unsigned char mechbuf[17]; + size_t mech_len; + + ret = der_put_oid(mechbuf + sizeof(mechbuf) - 1, + sizeof(mechbuf), + &init_token.mechTypes.val[i], + &mech_len); + if (ret) { + free_NegTokenInit(&init_token); + return (GSS_S_DEFECTIVE_TOKEN); + } + if (mech_len == GSS_KRB5_MECH->length && + isc_safe_memequal(GSS_KRB5_MECH->elements, + mechbuf + sizeof(mechbuf) - mech_len, + mech_len)) + { + found = 1; + break; + } + if (mech_len == GSS_MSKRB5_MECH->length && + isc_safe_memequal(GSS_MSKRB5_MECH->elements, + mechbuf + sizeof(mechbuf) - mech_len, + mech_len)) + { + found = 1; + if (i == 0) + pref = GSS_MSKRB5_MECH; + break; + } + } + + if (!found) { + free_NegTokenInit(&init_token); + return (send_reject(minor_status, output_token)); + } + + if (i == 0 && init_token.mechToken != NULL) { + ibuf.length = init_token.mechToken->length; + ibuf.value = init_token.mechToken->data; + + major_status = gss_accept_sec_context(minor_status, + context_handle, + acceptor_cred_handle, + &ibuf, + input_chan_bindings, + src_name, + mech_type, + &obuf, + ret_flags, + time_rec, + delegated_cred_handle); + if (GSS_ERROR(major_status)) { + free_NegTokenInit(&init_token); + send_reject(&minor_status2, output_token); + return (major_status); + } + ot = &obuf; + } + ret = send_accept(&minor_status2, output_token, ot, pref); + free_NegTokenInit(&init_token); + if (ot != NULL && ot->length != 0U) + gss_release_buffer(&minor_status2, ot); + + return (ret != GSS_S_COMPLETE ? (OM_uint32) ret : major_status); +} + +/* decapsulate.c */ + +static OM_uint32 +gssapi_verify_mech_header(u_char ** str, + size_t total_len, + const gss_OID mech) +{ + size_t len, len_len, mech_len, foo; + int e; + u_char *p = *str; + + if (total_len < 1U) + return (GSS_S_DEFECTIVE_TOKEN); + if (*p++ != 0x60) + return (GSS_S_DEFECTIVE_TOKEN); + e = der_get_length(p, total_len - 1, &len, &len_len); + if (e || 1 + len_len + len != total_len) + return (GSS_S_DEFECTIVE_TOKEN); + p += len_len; + if (*p++ != 0x06) + return (GSS_S_DEFECTIVE_TOKEN); + e = der_get_length(p, total_len - 1 - len_len - 1, + &mech_len, &foo); + if (e) + return (GSS_S_DEFECTIVE_TOKEN); + p += foo; + if (mech_len != mech->length) + return (GSS_S_BAD_MECH); + if (!isc_safe_memequal(p, mech->elements, mech->length)) + return (GSS_S_BAD_MECH); + p += mech_len; + *str = p; + return (GSS_S_COMPLETE); +} + +/* + * Remove the GSS-API wrapping from `in_token' giving `buf and buf_size' Does + * not copy data, so just free `in_token'. + */ + +static OM_uint32 +gssapi_spnego_decapsulate(OM_uint32 *minor_status, + gss_buffer_t input_token_buffer, + unsigned char **buf, + size_t *buf_len, + const gss_OID mech) +{ + u_char *p; + OM_uint32 ret; + + p = input_token_buffer->value; + ret = gssapi_verify_mech_header(&p, + input_token_buffer->length, + mech); + if (ret) { + *minor_status = ret; + return (GSS_S_FAILURE); + } + *buf_len = input_token_buffer->length - + (p - (u_char *) input_token_buffer->value); + *buf = p; + return (GSS_S_COMPLETE); +} + +/* der_free.c */ + +static void +free_octet_string(octet_string *k) +{ + free(k->data); + k->data = NULL; +} + +static void +free_oid(oid *k) +{ + free(k->components); + k->components = NULL; +} + +/* der_get.c */ + +/* + * All decoding functions take a pointer `p' to first position in which to + * read, from the left, `len' which means the maximum number of characters we + * are able to read, `ret' were the value will be returned and `size' where + * the number of used bytes is stored. Either 0 or an error code is returned. + */ + +static int +der_get_unsigned(const unsigned char *p, size_t len, + unsigned *ret, size_t *size) +{ + unsigned val = 0; + size_t oldlen = len; + + while (len--) + val = val * 256 + *p++; + *ret = val; + if (size) + *size = oldlen; + return (0); +} + +static int +der_get_int(const unsigned char *p, size_t len, + int *ret, size_t *size) +{ + int val = 0; + size_t oldlen = len; + + if (len > 0U) { + val = (signed char)*p++; + while (--len) + val = val * 256 + *p++; + } + *ret = val; + if (size) + *size = oldlen; + return (0); +} + +static int +der_get_length(const unsigned char *p, size_t len, + size_t *val, size_t *size) +{ + size_t v; + + if (len <= 0U) + return (ASN1_OVERRUN); + --len; + v = *p++; + if (v < 128U) { + *val = v; + if (size) + *size = 1; + } else { + int e; + size_t l; + unsigned tmp; + + if (v == 0x80U) { + *val = ASN1_INDEFINITE; + if (size) + *size = 1; + return (0); + } + v &= 0x7F; + if (len < v) + return (ASN1_OVERRUN); + e = der_get_unsigned(p, v, &tmp, &l); + if (e) + return (e); + *val = tmp; + if (size) + *size = l + 1; + } + return (0); +} + +static int +der_get_octet_string(const unsigned char *p, size_t len, + octet_string *data, size_t *size) +{ + data->length = len; + if (len != 0U) { + data->data = malloc(len); + if (data->data == NULL) + return (ENOMEM); + memmove(data->data, p, len); + } else + data->data = NULL; + if (size) + *size = len; + return (0); +} + +static int +der_get_oid(const unsigned char *p, size_t len, oid *data, size_t *size) { + int n; + size_t oldlen = len; + + data->components = NULL; + data->length = 0; + if (len < 1U) { + return (ASN1_OVERRUN); + } + + data->components = malloc(len * sizeof(*data->components)); + if (data->components == NULL) { + return (ENOMEM); + } + data->components[0] = (*p) / 40; + data->components[1] = (*p) % 40; + --len; + ++p; + for (n = 2; len > 0U; ++n) { + unsigned u = 0; + + do { + --len; + u = u * 128 + (*p++ % 128); + } while (len > 0U && p[-1] & 0x80); + data->components[n] = u; + } + if (p[-1] & 0x80) { + free_oid(data); + return (ASN1_OVERRUN); + } + data->length = n; + if (size) { + *size = oldlen; + } + return (0); +} + +static int +der_get_tag(const unsigned char *p, size_t len, + Der_class *xclass, Der_type *type, + int *tag, size_t *size) +{ + if (len < 1U) + return (ASN1_OVERRUN); + *xclass = (Der_class) (((*p) >> 6) & 0x03); + *type = (Der_type) (((*p) >> 5) & 0x01); + *tag = (*p) & 0x1F; + if (size) + *size = 1; + return (0); +} + +static int +der_match_tag(const unsigned char *p, size_t len, + Der_class xclass, Der_type type, + int tag, size_t *size) +{ + size_t l; + Der_class thisclass; + Der_type thistype; + int thistag; + int e; + + e = der_get_tag(p, len, &thisclass, &thistype, &thistag, &l); + if (e) + return (e); + if (xclass != thisclass || type != thistype) + return (ASN1_BAD_ID); + if (tag > thistag) + return (ASN1_MISPLACED_FIELD); + if (tag < thistag) + return (ASN1_MISSING_FIELD); + if (size) + *size = l; + return (0); +} + +static int +der_match_tag_and_length(const unsigned char *p, size_t len, + Der_class xclass, Der_type type, int tag, + size_t *length_ret, size_t *size) +{ + size_t l, ret = 0; + int e; + + e = der_match_tag(p, len, xclass, type, tag, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, length_ret, &l); + if (e) + return (e); + /* p += l; */ + len -= l; + POST(len); + ret += l; + if (size) + *size = ret; + return (0); +} + +static int +decode_enumerated(const unsigned char *p, size_t len, void *num, size_t *size) +{ + size_t ret = 0; + size_t l, reallen; + int e; + + e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_Enumerated, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &reallen, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + e = der_get_int(p, reallen, num, &l); + if (e) + return (e); + p += l; + len -= l; + POST(p); POST(len); + ret += l; + if (size) + *size = ret; + return (0); +} + +static int +decode_octet_string(const unsigned char *p, size_t len, + octet_string *k, size_t *size) +{ + size_t ret = 0; + size_t l; + int e; + size_t slen; + + k->data = NULL; + k->length = 0; + + e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_OctetString, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + + e = der_get_length(p, len, &slen, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + if (len < slen) + return (ASN1_OVERRUN); + + e = der_get_octet_string(p, slen, k, &l); + if (e) + return (e); + p += l; + len -= l; + POST(p); POST(len); + ret += l; + if (size) + *size = ret; + return (0); +} + +static int +decode_oid(const unsigned char *p, size_t len, + oid *k, size_t *size) +{ + size_t ret = 0; + size_t l; + int e; + size_t slen; + + e = der_match_tag(p, len, ASN1_C_UNIV, PRIM, UT_OID, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + + e = der_get_length(p, len, &slen, &l); + if (e) + return (e); + p += l; + len -= l; + ret += l; + if (len < slen) + return (ASN1_OVERRUN); + + e = der_get_oid(p, slen, k, &l); + if (e) + return (e); + p += l; + len -= l; + POST(p); POST(len); + ret += l; + if (size) + *size = ret; + return (0); +} + +static int +fix_dce(size_t reallen, size_t *len) +{ + if (reallen == ASN1_INDEFINITE) + return (1); + if (*len < reallen) + return (-1); + *len = reallen; + return (0); +} + +/* der_length.c */ + +static size_t +len_unsigned(unsigned val) +{ + size_t ret = 0; + + do { + ++ret; + val /= 256; + } while (val); + return (ret); +} + +static size_t +length_len(size_t len) +{ + if (len < 128U) + return (1); + else + return (len_unsigned((unsigned int)len) + 1); +} + + +/* der_put.c */ + +/* + * All encoding functions take a pointer `p' to first position in which to + * write, from the right, `len' which means the maximum number of characters + * we are able to write. The function returns the number of characters + * written in `size' (if non-NULL). The return value is 0 or an error. + */ + +static int +der_put_unsigned(unsigned char *p, size_t len, unsigned val, size_t *size) +{ + unsigned char *base = p; + + if (val) { + while (len > 0U && val) { + *p-- = val % 256; + val /= 256; + --len; + } + if (val != 0) + return (ASN1_OVERFLOW); + else { + *size = base - p; + return (0); + } + } else if (len < 1U) + return (ASN1_OVERFLOW); + else { + *p = 0; + *size = 1; + return (0); + } +} + +static int +der_put_int(unsigned char *p, size_t len, int val, size_t *size) +{ + unsigned char *base = p; + + if (val >= 0) { + do { + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = val % 256; + len--; + val /= 256; + } while (val); + if (p[1] >= 128) { + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = 0; + len--; + POST(len); + } + } else { + val = ~val; + do { + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = ~(val % 256); + len--; + val /= 256; + } while (val); + if (p[1] < 128) { + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = 0xff; + len--; + POST(len); + } + } + *size = base - p; + return (0); +} + +static int +der_put_length(unsigned char *p, size_t len, size_t val, size_t *size) +{ + if (len < 1U) + return (ASN1_OVERFLOW); + if (val < 128U) { + *p = (unsigned char)val; + *size = 1; + return (0); + } else { + size_t l; + int e; + + e = der_put_unsigned(p, len - 1, (unsigned int)val, &l); + if (e) + return (e); + p -= l; + *p = 0x80 | (unsigned char)l; + *size = l + 1; + return (0); + } +} + +static int +der_put_octet_string(unsigned char *p, size_t len, + const octet_string *data, size_t *size) +{ + if (len < data->length) + return (ASN1_OVERFLOW); + p -= data->length; + len -= data->length; + POST(len); + memmove(p + 1, data->data, data->length); + *size = data->length; + return (0); +} + +static int +der_put_oid(unsigned char *p, size_t len, + const oid *data, size_t *size) +{ + unsigned char *base = p; + size_t n; + + for (n = data->length; n >= 3u; --n) { + unsigned u = data->components[n - 1]; + + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = u % 128; + u /= 128; + --len; + while (u > 0) { + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = 128 + u % 128; + u /= 128; + --len; + } + } + if (len < 1U) + return (ASN1_OVERFLOW); + *p-- = 40 * data->components[0] + data->components[1]; + *size = base - p; + return (0); +} + +static int +der_put_tag(unsigned char *p, size_t len, Der_class xclass, Der_type type, + int tag, size_t *size) +{ + if (len < 1U) + return (ASN1_OVERFLOW); + *p = (xclass << 6) | (type << 5) | tag; /* XXX */ + *size = 1; + return (0); +} + +static int +der_put_length_and_tag(unsigned char *p, size_t len, size_t len_val, + Der_class xclass, Der_type type, int tag, size_t *size) +{ + size_t ret = 0; + size_t l; + int e; + + e = der_put_length(p, len, len_val, &l); + if (e) + return (e); + p -= l; + len -= l; + ret += l; + e = der_put_tag(p, len, xclass, type, tag, &l); + if (e) + return (e); + p -= l; + len -= l; + POST(p); POST(len); + ret += l; + *size = ret; + return (0); +} + +static int +encode_enumerated(unsigned char *p, size_t len, const void *data, size_t *size) +{ + unsigned num = *(const unsigned *)data; + size_t ret = 0; + size_t l; + int e; + + e = der_put_int(p, len, num, &l); + if (e) + return (e); + p -= l; + len -= l; + ret += l; + e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_Enumerated, &l); + if (e) + return (e); + p -= l; + len -= l; + POST(p); POST(len); + ret += l; + *size = ret; + return (0); +} + +static int +encode_octet_string(unsigned char *p, size_t len, + const octet_string *k, size_t *size) +{ + size_t ret = 0; + size_t l; + int e; + + e = der_put_octet_string(p, len, k, &l); + if (e) + return (e); + p -= l; + len -= l; + ret += l; + e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_OctetString, &l); + if (e) + return (e); + p -= l; + len -= l; + POST(p); POST(len); + ret += l; + *size = ret; + return (0); +} + +static int +encode_oid(unsigned char *p, size_t len, + const oid *k, size_t *size) +{ + size_t ret = 0; + size_t l; + int e; + + e = der_put_oid(p, len, k, &l); + if (e) + return (e); + p -= l; + len -= l; + ret += l; + e = der_put_length_and_tag(p, len, l, ASN1_C_UNIV, PRIM, UT_OID, &l); + if (e) + return (e); + p -= l; + len -= l; + POST(p); POST(len); + ret += l; + *size = ret; + return (0); +} + + +/* encapsulate.c */ + +static void +gssapi_encap_length(size_t data_len, + size_t *len, + size_t *total_len, + const gss_OID mech) +{ + size_t len_len; + + *len = 1 + 1 + mech->length + data_len; + + len_len = length_len(*len); + + *total_len = 1 + len_len + *len; +} + +static u_char * +gssapi_mech_make_header(u_char *p, + size_t len, + const gss_OID mech) +{ + int e; + size_t len_len, foo; + + *p++ = 0x60; + len_len = length_len(len); + e = der_put_length(p + len_len - 1, len_len, len, &foo); + if (e || foo != len_len) + return (NULL); + p += len_len; + *p++ = 0x06; + *p++ = mech->length; + memmove(p, mech->elements, mech->length); + p += mech->length; + return (p); +} + +/* + * Give it a krb5_data and it will encapsulate with extra GSS-API wrappings. + */ + +static OM_uint32 +gssapi_spnego_encapsulate(OM_uint32 * minor_status, + unsigned char *buf, + size_t buf_size, + gss_buffer_t output_token, + const gss_OID mech) +{ + size_t len, outer_len; + u_char *p; + + gssapi_encap_length(buf_size, &len, &outer_len, mech); + + output_token->length = outer_len; + output_token->value = malloc(outer_len); + if (output_token->value == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + p = gssapi_mech_make_header(output_token->value, len, mech); + if (p == NULL) { + if (output_token->length != 0U) + gss_release_buffer(minor_status, output_token); + return (GSS_S_FAILURE); + } + memmove(p, buf, buf_size); + return (GSS_S_COMPLETE); +} + +/* init_sec_context.c */ +/* + * SPNEGO wrapper for Kerberos5 GSS-API kouril@ics.muni.cz, 2003 (mostly + * based on Heimdal code) + */ + +static int +add_mech(MechTypeList * mech_list, gss_OID mech) +{ + MechType *tmp; + int ret; + + tmp = realloc(mech_list->val, (mech_list->len + 1) * sizeof(*tmp)); + if (tmp == NULL) + return (ENOMEM); + mech_list->val = tmp; + + ret = der_get_oid(mech->elements, mech->length, + &mech_list->val[mech_list->len], NULL); + if (ret) + return (ret); + + mech_list->len++; + return (0); +} + +/* + * return the length of the mechanism in token or -1 + * (which implies that the token was bad - GSS_S_DEFECTIVE_TOKEN + */ + +static ssize_t +gssapi_krb5_get_mech(const u_char *ptr, + size_t total_len, + const u_char **mech_ret) +{ + size_t len, len_len, mech_len, foo; + const u_char *p = ptr; + int e; + + if (total_len < 1U) + return (-1); + if (*p++ != 0x60) + return (-1); + e = der_get_length (p, total_len - 1, &len, &len_len); + if (e || 1 + len_len + len != total_len) + return (-1); + p += len_len; + if (*p++ != 0x06) + return (-1); + e = der_get_length (p, total_len - 1 - len_len - 1, + &mech_len, &foo); + if (e) + return (-1); + p += foo; + *mech_ret = p; + return (mech_len); +} + +static OM_uint32 +spnego_initial(OM_uint32 *minor_status, + const gss_cred_id_t initiator_cred_handle, + gss_ctx_id_t *context_handle, + const gss_name_t target_name, + const gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID *actual_mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + NegTokenInit token_init; + OM_uint32 major_status, minor_status2; + gss_buffer_desc krb5_output_token = GSS_C_EMPTY_BUFFER; + unsigned char *buf = NULL; + size_t buf_size; + size_t len; + int ret; + + (void)mech_type; + + memset(&token_init, 0, sizeof(token_init)); + + ret = add_mech(&token_init.mechTypes, GSS_KRB5_MECH); + if (ret) { + *minor_status = ret; + ret = GSS_S_FAILURE; + goto end; + } + + major_status = gss_init_sec_context(minor_status, + initiator_cred_handle, + context_handle, + target_name, + GSS_KRB5_MECH, + req_flags, + time_req, + input_chan_bindings, + input_token, + actual_mech_type, + &krb5_output_token, + ret_flags, + time_rec); + if (GSS_ERROR(major_status)) { + ret = major_status; + goto end; + } + if (krb5_output_token.length > 0U) { + token_init.mechToken = malloc(sizeof(*token_init.mechToken)); + if (token_init.mechToken == NULL) { + *minor_status = ENOMEM; + ret = GSS_S_FAILURE; + goto end; + } + token_init.mechToken->data = krb5_output_token.value; + token_init.mechToken->length = krb5_output_token.length; + } + /* + * The MS implementation of SPNEGO seems to not like the mechListMIC + * field, so we omit it (it's optional anyway) + */ + + buf_size = 1024; + buf = malloc(buf_size); + if (buf == NULL) { + *minor_status = ENOMEM; + ret = GSS_S_FAILURE; + goto end; + } + + do { + ret = encode_NegTokenInit(buf + buf_size - 1, + buf_size, + &token_init, &len); + if (ret == 0) { + size_t tmp; + + ret = der_put_length_and_tag(buf + buf_size - len - 1, + buf_size - len, + len, + ASN1_C_CONTEXT, + CONS, + 0, + &tmp); + if (ret == 0) + len += tmp; + } + if (ret) { + if (ret == ASN1_OVERFLOW) { + u_char *tmp; + + buf_size *= 2; + tmp = realloc(buf, buf_size); + if (tmp == NULL) { + *minor_status = ENOMEM; + ret = GSS_S_FAILURE; + goto end; + } + buf = tmp; + } else { + *minor_status = ret; + ret = GSS_S_FAILURE; + goto end; + } + } + } while (ret == ASN1_OVERFLOW); + + ret = gssapi_spnego_encapsulate(minor_status, + buf + buf_size - len, len, + output_token, GSS_SPNEGO_MECH); + if (ret == GSS_S_COMPLETE) + ret = major_status; + +end: + if (token_init.mechToken != NULL) { + free(token_init.mechToken); + token_init.mechToken = NULL; + } + free_NegTokenInit(&token_init); + if (krb5_output_token.length != 0U) + gss_release_buffer(&minor_status2, &krb5_output_token); + if (buf) + free(buf); + + return (ret); +} + +static OM_uint32 +spnego_reply(OM_uint32 *minor_status, + const gss_cred_id_t initiator_cred_handle, + gss_ctx_id_t *context_handle, + const gss_name_t target_name, + const gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID *actual_mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + OM_uint32 ret; + NegTokenResp resp; + unsigned char *buf; + size_t buf_size; + u_char oidbuf[17]; + size_t oidlen; + gss_buffer_desc sub_token; + ssize_t mech_len; + const u_char *p; + size_t len, taglen; + + (void)mech_type; + + output_token->length = 0; + output_token->value = NULL; + + /* + * SPNEGO doesn't include gss wrapping on SubsequentContextToken + * like the Kerberos 5 mech does. But lets check for it anyway. + */ + + mech_len = gssapi_krb5_get_mech(input_token->value, + input_token->length, + &p); + + if (mech_len < 0) { + buf = input_token->value; + buf_size = input_token->length; + } else if ((size_t)mech_len == GSS_KRB5_MECH->length && + isc_safe_memequal(GSS_KRB5_MECH->elements, p, mech_len)) + return (gss_init_sec_context(minor_status, + initiator_cred_handle, + context_handle, + target_name, + GSS_KRB5_MECH, + req_flags, + time_req, + input_chan_bindings, + input_token, + actual_mech_type, + output_token, + ret_flags, + time_rec)); + else if ((size_t)mech_len == GSS_SPNEGO_MECH->length && + isc_safe_memequal(GSS_SPNEGO_MECH->elements, p, mech_len)) { + ret = gssapi_spnego_decapsulate(minor_status, + input_token, + &buf, + &buf_size, + GSS_SPNEGO_MECH); + if (ret) + return (ret); + } else + return (GSS_S_BAD_MECH); + + ret = der_match_tag_and_length(buf, buf_size, + ASN1_C_CONTEXT, CONS, 1, &len, &taglen); + if (ret) + return (ret); + + if(len > buf_size - taglen) + return (ASN1_OVERRUN); + + ret = decode_NegTokenResp(buf + taglen, len, &resp, NULL); + if (ret) { + free_NegTokenResp(&resp); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + + if (resp.negState == NULL || + *(resp.negState) == reject || + resp.supportedMech == NULL) { + free_NegTokenResp(&resp); + return (GSS_S_BAD_MECH); + } + + ret = der_put_oid(oidbuf + sizeof(oidbuf) - 1, + sizeof(oidbuf), + resp.supportedMech, + &oidlen); + if (ret || oidlen != GSS_KRB5_MECH->length || + !isc_safe_memequal(oidbuf + sizeof(oidbuf) - oidlen, + GSS_KRB5_MECH->elements, oidlen)) + { + free_NegTokenResp(&resp); + return GSS_S_BAD_MECH; + } + + if (resp.responseToken != NULL) { + sub_token.length = resp.responseToken->length; + sub_token.value = resp.responseToken->data; + } else { + sub_token.length = 0; + sub_token.value = NULL; + } + + ret = gss_init_sec_context(minor_status, + initiator_cred_handle, + context_handle, + target_name, + GSS_KRB5_MECH, + req_flags, + time_req, + input_chan_bindings, + &sub_token, + actual_mech_type, + output_token, + ret_flags, + time_rec); + if (ret) { + free_NegTokenResp(&resp); + return (ret); + } + + /* + * XXXSRA I don't think this limited implementation ever needs + * to check the MIC -- our preferred mechanism (Kerberos) + * authenticates its own messages and is the only mechanism + * we'll accept, so if the mechanism negotiation completes + * successfully, we don't need the MIC. See RFC 4178. + */ + + free_NegTokenResp(&resp); + return (ret); +} + + + +OM_uint32 +gss_init_sec_context_spnego(OM_uint32 *minor_status, + const gss_cred_id_t initiator_cred_handle, + gss_ctx_id_t *context_handle, + const gss_name_t target_name, + const gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID *actual_mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + /* Dirty trick to suppress compiler warnings */ + + /* Figure out whether we're starting over or processing a reply */ + + if (input_token == GSS_C_NO_BUFFER || input_token->length == 0U) + return (spnego_initial(minor_status, + initiator_cred_handle, + context_handle, + target_name, + mech_type, + req_flags, + time_req, + input_chan_bindings, + input_token, + actual_mech_type, + output_token, + ret_flags, + time_rec)); + else + return (spnego_reply(minor_status, + initiator_cred_handle, + context_handle, + target_name, + mech_type, + req_flags, + time_req, + input_chan_bindings, + input_token, + actual_mech_type, + output_token, + ret_flags, + time_rec)); +} + +#endif /* GSSAPI */ diff --git a/lib/dns/spnego.h b/lib/dns/spnego.h new file mode 100644 index 0000000..3118720 --- /dev/null +++ b/lib/dns/spnego.h @@ -0,0 +1,65 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file + * \brief + * Entry points into portable SPNEGO implementation. + * See spnego.c for information on the SPNEGO implementation itself. + */ + +#ifndef _SPNEGO_H_ +#define _SPNEGO_H_ + +/*% + * Wrapper for GSSAPI gss_init_sec_context(), using portable SPNEGO + * implementation instead of the one that's part of the GSSAPI + * library. Takes arguments identical to the standard GSSAPI + * function, uses standard gss_init_sec_context() to handle + * everything inside the SPNEGO wrapper. + */ +OM_uint32 +gss_init_sec_context_spnego(OM_uint32 *, + const gss_cred_id_t, + gss_ctx_id_t *, + const gss_name_t, + const gss_OID, + OM_uint32, + OM_uint32, + const gss_channel_bindings_t, + const gss_buffer_t, + gss_OID *, + gss_buffer_t, + OM_uint32 *, + OM_uint32 *); + +/*% + * Wrapper for GSSAPI gss_accept_sec_context(), using portable SPNEGO + * implementation instead of the one that's part of the GSSAPI + * library. Takes arguments identical to the standard GSSAPI + * function. Checks the OID of the input token to see if it's SPNEGO; + * if so, processes it, otherwise hands the call off to the standard + * gss_accept_sec_context() function. + */ +OM_uint32 gss_accept_sec_context_spnego(OM_uint32 *, + gss_ctx_id_t *, + const gss_cred_id_t, + const gss_buffer_t, + const gss_channel_bindings_t, + gss_name_t *, + gss_OID *, + gss_buffer_t, + OM_uint32 *, + OM_uint32 *, + gss_cred_id_t *); + + +#endif diff --git a/lib/dns/spnego_asn1.c b/lib/dns/spnego_asn1.c new file mode 100644 index 0000000..6fbce55 --- /dev/null +++ b/lib/dns/spnego_asn1.c @@ -0,0 +1,862 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file + * \brief Method routines generated from SPNEGO ASN.1 module. + * See spnego_asn1.pl for details. Do not edit. + */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + +#ifndef __asn1_h__ +#define __asn1_h__ + + +#ifndef __asn1_common_definitions__ +#define __asn1_common_definitions__ + +typedef struct octet_string { + size_t length; + void *data; +} octet_string; + +typedef char *general_string; + +typedef char *utf8_string; + +typedef struct oid { + size_t length; + unsigned *components; +} oid; + +#define ASN1_MALLOC_ENCODE(T, B, BL, S, L, R) \ + do { \ + (BL) = length_##T((S)); \ + (B) = malloc((BL)); \ + if((B) == NULL) { \ + (R) = ENOMEM; \ + } else { \ + (R) = encode_##T(((unsigned char*)(B)) + (BL) - 1, (BL), \ + (S), (L)); \ + if((R) != 0) { \ + free((B)); \ + (B) = NULL; \ + } \ + } \ + } while (0) + +#endif + +/* + * MechType ::= OBJECT IDENTIFIER + */ + +typedef oid MechType; + +static int encode_MechType(unsigned char *, size_t, const MechType *, size_t *); +static int decode_MechType(const unsigned char *, size_t, MechType *, size_t *); +static void free_MechType(MechType *); +/* unused declaration: length_MechType */ +/* unused declaration: copy_MechType */ + + +/* + * MechTypeList ::= SEQUENCE OF MechType + */ + +typedef struct MechTypeList { + unsigned int len; + MechType *val; +} MechTypeList; + +static int encode_MechTypeList(unsigned char *, size_t, const MechTypeList *, size_t *); +static int decode_MechTypeList(const unsigned char *, size_t, MechTypeList *, size_t *); +static void free_MechTypeList(MechTypeList *); +/* unused declaration: length_MechTypeList */ +/* unused declaration: copy_MechTypeList */ + + +/* + * ContextFlags ::= BIT STRING { delegFlag(0), mutualFlag(1), replayFlag(2), + * sequenceFlag(3), anonFlag(4), confFlag(5), integFlag(6) } + */ + +typedef struct ContextFlags { + unsigned int delegFlag:1; + unsigned int mutualFlag:1; + unsigned int replayFlag:1; + unsigned int sequenceFlag:1; + unsigned int anonFlag:1; + unsigned int confFlag:1; + unsigned int integFlag:1; +} ContextFlags; + + +static int encode_ContextFlags(unsigned char *, size_t, const ContextFlags *, size_t *); +static int decode_ContextFlags(const unsigned char *, size_t, ContextFlags *, size_t *); +static void free_ContextFlags(ContextFlags *); +/* unused declaration: length_ContextFlags */ +/* unused declaration: copy_ContextFlags */ +/* unused declaration: ContextFlags2int */ +/* unused declaration: int2ContextFlags */ +/* unused declaration: asn1_ContextFlags_units */ + +/* + * NegTokenInit ::= SEQUENCE { mechTypes[0] MechTypeList, reqFlags[1] + * ContextFlags OPTIONAL, mechToken[2] OCTET STRING OPTIONAL, + * mechListMIC[3] OCTET STRING OPTIONAL } + */ + +typedef struct NegTokenInit { + MechTypeList mechTypes; + ContextFlags *reqFlags; + octet_string *mechToken; + octet_string *mechListMIC; +} NegTokenInit; + +static int encode_NegTokenInit(unsigned char *, size_t, const NegTokenInit *, size_t *); +static int decode_NegTokenInit(const unsigned char *, size_t, NegTokenInit *, size_t *); +static void free_NegTokenInit(NegTokenInit *); +/* unused declaration: length_NegTokenInit */ +/* unused declaration: copy_NegTokenInit */ + + +/* + * NegTokenResp ::= SEQUENCE { negState[0] ENUMERATED { + * accept-completed(0), accept-incomplete(1), reject(2), request-mic(3) } + * OPTIONAL, supportedMech[1] MechType OPTIONAL, responseToken[2] OCTET + * STRING OPTIONAL, mechListMIC[3] OCTET STRING OPTIONAL } + */ + +typedef struct NegTokenResp { + enum { + accept_completed = 0, + accept_incomplete = 1, + reject = 2, + request_mic = 3 + } *negState; + + MechType *supportedMech; + octet_string *responseToken; + octet_string *mechListMIC; +} NegTokenResp; + +static int encode_NegTokenResp(unsigned char *, size_t, const NegTokenResp *, size_t *); +static int decode_NegTokenResp(const unsigned char *, size_t, NegTokenResp *, size_t *); +static void free_NegTokenResp(NegTokenResp *); +/* unused declaration: length_NegTokenResp */ +/* unused declaration: copy_NegTokenResp */ + + + + +#endif /* __asn1_h__ */ +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +#define BACK if (e) return e; p -= l; len -= l; ret += l; POST(p); POST(len); POST(ret) + +static int +encode_MechType(unsigned char *p, size_t len, const MechType * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int e; + + e = encode_oid(p, len, data, &l); + BACK; + *size = ret; + return 0; +} + +#define FORW if(e) goto fail; p += l; len -= l; ret += l; POST(p); POST(len); POST(ret) + +static int +decode_MechType(const unsigned char *p, size_t len, MechType * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int e; + + memset(data, 0, sizeof(*data)); + e = decode_oid(p, len, data, &l); + FORW; + if (size) + *size = ret; + return 0; +fail: + free_MechType(data); + return e; +} + +static void +free_MechType(MechType * data) +{ + free_oid(data); +} + +/* unused function: length_MechType */ + + +/* unused function: copy_MechType */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +static int +encode_MechTypeList(unsigned char *p, size_t len, const MechTypeList * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int i, e; + + for (i = (data)->len - 1; i >= 0; --i) { + size_t oldret = ret; + ret = 0; + e = encode_MechType(p, len, &(data)->val[i], &l); + BACK; + ret += oldret; + } + e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence, &l); + BACK; + *size = ret; + return 0; +} + +static int +decode_MechTypeList(const unsigned char *p, size_t len, MechTypeList * data, size_t * size) +{ + size_t ret = 0, reallen; + size_t l; + int e; + + memset(data, 0, sizeof(*data)); + reallen = 0; + e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence, &reallen, &l); + FORW; + if (len < reallen) + return ASN1_OVERRUN; + len = reallen; + { + size_t origlen = len; + size_t oldret = ret; + ret = 0; + (data)->len = 0; + (data)->val = NULL; + while (ret < origlen) { + void *old = (data)->val; + (data)->len++; + (data)->val = realloc((data)->val, sizeof(*((data)->val)) * (data)->len); + if ((data)->val == NULL) { + (data)->val = old; + (data)->len--; + return ENOMEM; + } + e = decode_MechType(p, len, &(data)->val[(data)->len - 1], &l); + FORW; + len = origlen - ret; + } + ret += oldret; + } + if (size) + *size = ret; + return 0; +fail: + free_MechTypeList(data); + return e; +} + +static void +free_MechTypeList(MechTypeList * data) +{ + while ((data)->len) { + free_MechType(&(data)->val[(data)->len - 1]); + (data)->len--; + } + free((data)->val); + (data)->val = NULL; +} + +/* unused function: length_MechTypeList */ + + +/* unused function: copy_MechTypeList */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +static int +encode_ContextFlags(unsigned char *p, size_t len, const ContextFlags * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int e; + + { + unsigned char c = 0; + *p-- = c; + len--; + ret++; + c = 0; + *p-- = c; + len--; + ret++; + c = 0; + *p-- = c; + len--; + ret++; + c = 0; + if (data->integFlag) + c |= 1 << 1; + if (data->confFlag) + c |= 1 << 2; + if (data->anonFlag) + c |= 1 << 3; + if (data->sequenceFlag) + c |= 1 << 4; + if (data->replayFlag) + c |= 1 << 5; + if (data->mutualFlag) + c |= 1 << 6; + if (data->delegFlag) + c |= 1 << 7; + *p-- = c; + *p-- = 0; + len -= 2; + ret += 2; + } + + e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, PRIM, UT_BitString, &l); + BACK; + *size = ret; + return 0; +} + +static int +decode_ContextFlags(const unsigned char *p, size_t len, ContextFlags * data, size_t * size) +{ + size_t ret = 0, reallen; + size_t l; + int e; + + memset(data, 0, sizeof(*data)); + reallen = 0; + e = der_match_tag_and_length(p, len, ASN1_C_UNIV, PRIM, UT_BitString, &reallen, &l); + FORW; + if (len < reallen) + return ASN1_OVERRUN; + p++; + len--; + POST(len); + reallen--; + ret++; + data->delegFlag = (*p >> 7) & 1; + data->mutualFlag = (*p >> 6) & 1; + data->replayFlag = (*p >> 5) & 1; + data->sequenceFlag = (*p >> 4) & 1; + data->anonFlag = (*p >> 3) & 1; + data->confFlag = (*p >> 2) & 1; + data->integFlag = (*p >> 1) & 1; + ret += reallen; + if (size) + *size = ret; + return 0; +fail: + free_ContextFlags(data); + return e; +} + +static void +free_ContextFlags(ContextFlags * data) +{ + (void)data; +} + +/* unused function: length_ContextFlags */ + + +/* unused function: copy_ContextFlags */ + + +/* unused function: ContextFlags2int */ + + +/* unused function: int2ContextFlags */ + + +/* unused variable: ContextFlags_units */ + +/* unused function: asn1_ContextFlags_units */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +static int +encode_NegTokenInit(unsigned char *p, size_t len, const NegTokenInit * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int e; + + if ((data)->mechListMIC) { + size_t oldret = ret; + ret = 0; + e = encode_octet_string(p, len, (data)->mechListMIC, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 3, &l); + BACK; + ret += oldret; + } + if ((data)->mechToken) { + size_t oldret = ret; + ret = 0; + e = encode_octet_string(p, len, (data)->mechToken, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 2, &l); + BACK; + ret += oldret; + } + if ((data)->reqFlags) { + size_t oldret = ret; + ret = 0; + e = encode_ContextFlags(p, len, (data)->reqFlags, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 1, &l); + BACK; + ret += oldret; + } { + size_t oldret = ret; + ret = 0; + e = encode_MechTypeList(p, len, &(data)->mechTypes, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 0, &l); + BACK; + ret += oldret; + } + e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence, &l); + BACK; + *size = ret; + return 0; +} + +static int +decode_NegTokenInit(const unsigned char *p, size_t len, NegTokenInit * data, size_t * size) +{ + size_t ret = 0, reallen; + size_t l; + int e; + + memset(data, 0, sizeof(*data)); + reallen = 0; + e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence, &reallen, &l); + FORW; + { + int dce_fix; + if ((dce_fix = fix_dce(reallen, &len)) < 0) + return ASN1_BAD_FORMAT; + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 0, &l); + if (e) + return e; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + e = decode_MechTypeList(p, len, &(data)->mechTypes, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 1, &l); + if (e) + (data)->reqFlags = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->reqFlags = malloc(sizeof(*(data)->reqFlags)); + if ((data)->reqFlags == NULL) + return ENOMEM; + e = decode_ContextFlags(p, len, (data)->reqFlags, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 2, &l); + if (e) + (data)->mechToken = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->mechToken = malloc(sizeof(*(data)->mechToken)); + if ((data)->mechToken == NULL) + return ENOMEM; + e = decode_octet_string(p, len, (data)->mechToken, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 3, &l); + if (e) + (data)->mechListMIC = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->mechListMIC = malloc(sizeof(*(data)->mechListMIC)); + if ((data)->mechListMIC == NULL) + return ENOMEM; + e = decode_octet_string(p, len, (data)->mechListMIC, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + if (dce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } + } + if (size) + *size = ret; + return 0; +fail: + free_NegTokenInit(data); + return e; +} + +static void +free_NegTokenInit(NegTokenInit * data) +{ + free_MechTypeList(&(data)->mechTypes); + if ((data)->reqFlags) { + free_ContextFlags((data)->reqFlags); + free((data)->reqFlags); + (data)->reqFlags = NULL; + } + if ((data)->mechToken) { + free_octet_string((data)->mechToken); + free((data)->mechToken); + (data)->mechToken = NULL; + } + if ((data)->mechListMIC) { + free_octet_string((data)->mechListMIC); + free((data)->mechListMIC); + (data)->mechListMIC = NULL; + } +} + +/* unused function: length_NegTokenInit */ + + +/* unused function: copy_NegTokenInit */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +static int +encode_NegTokenResp(unsigned char *p, size_t len, const NegTokenResp * data, size_t * size) +{ + size_t ret = 0; + size_t l; + int e; + + if ((data)->mechListMIC) { + size_t oldret = ret; + ret = 0; + e = encode_octet_string(p, len, (data)->mechListMIC, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 3, &l); + BACK; + ret += oldret; + } + if ((data)->responseToken) { + size_t oldret = ret; + ret = 0; + e = encode_octet_string(p, len, (data)->responseToken, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 2, &l); + BACK; + ret += oldret; + } + if ((data)->supportedMech) { + size_t oldret = ret; + ret = 0; + e = encode_MechType(p, len, (data)->supportedMech, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 1, &l); + BACK; + ret += oldret; + } + if ((data)->negState) { + size_t oldret = ret; + ret = 0; + e = encode_enumerated(p, len, (data)->negState, &l); + BACK; + e = der_put_length_and_tag(p, len, ret, ASN1_C_CONTEXT, CONS, 0, &l); + BACK; + ret += oldret; + } + e = der_put_length_and_tag(p, len, ret, ASN1_C_UNIV, CONS, UT_Sequence, &l); + BACK; + *size = ret; + return 0; +} + +static int +decode_NegTokenResp(const unsigned char *p, size_t len, NegTokenResp * data, size_t * size) +{ + size_t ret = 0, reallen; + size_t l; + int e; + + memset(data, 0, sizeof(*data)); + reallen = 0; + e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence, &reallen, &l); + FORW; + { + int dce_fix; + if ((dce_fix = fix_dce(reallen, &len)) < 0) + return ASN1_BAD_FORMAT; + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 0, &l); + if (e) + (data)->negState = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->negState = malloc(sizeof(*(data)->negState)); + if ((data)->negState == NULL) + return ENOMEM; + e = decode_enumerated(p, len, (data)->negState, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 1, &l); + if (e) + (data)->supportedMech = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->supportedMech = malloc(sizeof(*(data)->supportedMech)); + if ((data)->supportedMech == NULL) + return ENOMEM; + e = decode_MechType(p, len, (data)->supportedMech, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 2, &l); + if (e) + (data)->responseToken = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->responseToken = malloc(sizeof(*(data)->responseToken)); + if ((data)->responseToken == NULL) + return ENOMEM; + e = decode_octet_string(p, len, (data)->responseToken, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + { + size_t newlen, oldlen; + + e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 3, &l); + if (e) + (data)->mechListMIC = NULL; + else { + p += l; + len -= l; + ret += l; + e = der_get_length(p, len, &newlen, &l); + FORW; + { + int mydce_fix; + oldlen = len; + if ((mydce_fix = fix_dce(newlen, &len)) < 0) + return ASN1_BAD_FORMAT; + (data)->mechListMIC = malloc(sizeof(*(data)->mechListMIC)); + if ((data)->mechListMIC == NULL) + return ENOMEM; + e = decode_octet_string(p, len, (data)->mechListMIC, &l); + FORW; + if (mydce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } else + len = oldlen - newlen; + } + } + } + if (dce_fix) { + e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); + FORW; + } + } + if (size) + *size = ret; + return 0; +fail: + free_NegTokenResp(data); + return e; +} + +static void +free_NegTokenResp(NegTokenResp * data) +{ + if ((data)->negState) { + free((data)->negState); + (data)->negState = NULL; + } + if ((data)->supportedMech) { + free_MechType((data)->supportedMech); + free((data)->supportedMech); + (data)->supportedMech = NULL; + } + if ((data)->responseToken) { + free_octet_string((data)->responseToken); + free((data)->responseToken); + (data)->responseToken = NULL; + } + if ((data)->mechListMIC) { + free_octet_string((data)->mechListMIC); + free((data)->mechListMIC); + (data)->mechListMIC = NULL; + } +} + +/* unused function: length_NegTokenResp */ + + +/* unused function: copy_NegTokenResp */ + +/* Generated from spnego.asn1 */ +/* Do not edit */ + + +/* CHOICE */ +/* unused variable: asn1_NegotiationToken_dummy_holder */ diff --git a/lib/dns/spnego_asn1.pl b/lib/dns/spnego_asn1.pl new file mode 100644 index 0000000..66f7b73 --- /dev/null +++ b/lib/dns/spnego_asn1.pl @@ -0,0 +1,193 @@ +#!/bin/bin/perl -w +# +# 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 http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# Our SPNEGO implementation uses some functions generated by the +# Heimdal ASN.1 compiler, which this script then whacks a bit to make +# them work properly in this stripped down implementation. We don't +# want to require our users to have a copy of the compiler, so we ship +# the output of this script, but we need to keep the script around in +# any case to cope with future changes to the SPNEGO ASN.1 code, so we +# might as well supply the script for users who want it. + +# Overall plan: run the ASN.1 compiler, run each of its output files +# through indent, fix up symbols and whack everything to be static. +# We use indent for two reasons: (1) to whack the Heimdal compiler's +# output into something closer to ISC's coding standard, and (2) to +# make it easier for this script to parse the result. + +# Output from this script is C code which we expect to be #included +# into another C file, which is why everything generated by this +# script is marked "static". The intent is to minimize the number of +# extern symbols exported by the SPNEGO implementation, to avoid +# potential conflicts with the GSSAPI libraries. + +### + +# Filename of the ASN.1 specification. Hardcoded for the moment +# since this script is intended for compiling exactly one module. + +my $asn1_source = $ENV{ASN1_SOURCE} || "spnego.asn1"; + +# Heimdal ASN.1 compiler. This script was written using the version +# from Heimdal 0.7.1. To build this, download a copy of +# heimdal-0.7.1.tar.gz, configure and build with the default options, +# then look for the compiler in heimdal-0.7.1/lib/asn1/asn1_compile. + +my $asn1_compile = $ENV{ASN1_COMPILE} || "asn1_compile"; + +# BSD indent program. This script was written using the version of +# indent that comes with FreeBSD 4.11-STABLE. The GNU project, as +# usual, couldn't resist the temptation to monkey with indent's +# command line syntax, so this probably won't work with GNU indent. + +my $indent = $ENV{INDENT} || "indent"; + +### + +# Step 1: run the compiler. Input is the ASN.1 file. Outputs are a +# header file (name specified on command line without the .h suffix), +# a file called "asn1_files" listing the names of the other output +# files, and a set of files containing C code generated by the +# compiler for each data type that the compiler found. + +if (! -r $asn1_source || system($asn1_compile, $asn1_source, "asn1")) { + die("Couldn't compile ASN.1 source file $asn1_source\n"); +} + +my @files = ("asn1.h"); + +open(F, "asn1_files") + or die("Couldn't open asn1_files: $!\n"); +push(@files, split) + while (); +close(F); + +unlink("asn1_files"); + +### + +# Step 2: generate header block. + +print(q~/* + * Copyright (C) 2006 Internet Systems Consortium, Inc. ("ISC") + * + * 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 ISC 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. + */ + +/* $Id: spnego_asn1.pl,v 1.4 2007/06/19 23:47:16 tbox Exp $ */ + +/*! \file + * \brief Method routines generated from SPNEGO ASN.1 module. + * See spnego_asn1.pl for details. Do not edit. + */ + +~); + +### + +# Step 3: read and process each generated file, then delete it. + +my $output; + +for my $file (@files) { + + my $is_static = 0; + + system($indent, "-di1", "-ldi1", $file) == 0 + or die("Couldn't indent $file"); + + unlink("$file.BAK"); + + open(F, $file) + or die("Couldn't open $file: $!"); + + while () { + + # Symbol name fixups + + s/heim_general_string/general_string/g; + s/heim_octet_string/octet_string/g; + s/heim_oid/oid/g; + s/heim_utf8_string/utf8_string/g; + + # Convert all externs to statics + + if (/^static/) { + $is_static = 1; + } + + if (!/^typedef/ && + !$is_static && + /^[A-Za-z_][0-9A-Za-z_]*[ \t]*($|[^:0-9A-Za-z_])/) { + $_ = "static " . $_; + $is_static = 1; + } + + if (/[{};]/) { + $is_static = 0; + } + + # Suppress file inclusion, pass anything else through + + if (!/#include/) { + $output .= $_; + } + } + + close(F); + unlink($file); +} + +# Step 4: Delete unused stuff to avoid code bloat and compiler warnings. + +my @unused_functions = qw(ContextFlags2int + int2ContextFlags + asn1_ContextFlags_units + length_NegTokenInit + copy_NegTokenInit + length_NegTokenResp + copy_NegTokenResp + length_MechTypeList + length_MechType + copy_MechTypeList + length_ContextFlags + copy_ContextFlags + copy_MechType); + +$output =~ s<^static [^\n]+\n$_\(.+?^}>ms + foreach (@unused_functions); + +$output =~ s<^static .+$_\(.*\);$>m + foreach (@unused_functions); + +$output =~ s<^static struct units ContextFlags_units\[\].+?^};> + ms; + +$output =~ s<^static int asn1_NegotiationToken_dummy_holder = 1;> + ms; + +$output =~ s<^static void\nfree_ContextFlags\(ContextFlags \* data\)\n{\n> + <$&\t(void)data;\n>ms; + +# Step 5: Write the result. + +print($output); + diff --git a/lib/dns/ssu.c b/lib/dns/ssu.c new file mode 100644 index 0000000..71ded66 --- /dev/null +++ b/lib/dns/ssu.c @@ -0,0 +1,711 @@ +/* + * 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 http://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 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? */ + unsigned int 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, */ + /*%< defaults to all but SIG,SOA,NS if NULL */ + ISC_LINK(dns_ssurule_t) link; +}; + +struct dns_ssutable { + unsigned int magic; + isc_mem_t *mctx; + unsigned int references; + isc_mutex_t lock; + dns_dlzdb_t *dlzdatabase; + ISC_LIST(dns_ssurule_t) rules; +}; + +isc_result_t +dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) { + isc_result_t result; + dns_ssutable_t *table; + + REQUIRE(tablep != NULL && *tablep == NULL); + REQUIRE(mctx != NULL); + + table = isc_mem_get(mctx, sizeof(dns_ssutable_t)); + if (table == NULL) + return (ISC_R_NOMEMORY); + result = isc_mutex_init(&table->lock); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, table, sizeof(dns_ssutable_t)); + return (result); + } + 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 inline 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)); + } + DESTROYLOCK(&table->lock); + 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); + + LOCK(&source->lock); + + INSIST(source->references > 0); + source->references++; + INSIST(source->references != 0); + + UNLOCK(&source->lock); + + *targetp = source; +} + +void +dns_ssutable_detach(dns_ssutable_t **tablep) { + dns_ssutable_t *table; + bool done = false; + + REQUIRE(tablep != NULL); + table = *tablep; + REQUIRE(VALID_SSUTABLE(table)); + + LOCK(&table->lock); + + INSIST(table->references > 0); + if (--table->references == 0) + done = true; + UNLOCK(&table->lock); + + *tablep = NULL; + + if (done) + destroy(table); +} + +isc_result_t +dns_ssutable_addrule(dns_ssutable_t *table, bool grant, + dns_name_t *identity, unsigned int matchtype, + dns_name_t *name, unsigned int ntypes, + dns_rdatatype_t *types) +{ + dns_ssurule_t *rule; + isc_mem_t *mctx; + isc_result_t result; + + 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)); + if (rule == NULL) + return (ISC_R_NOMEMORY); + + rule->identity = NULL; + rule->name = NULL; + rule->types = NULL; + + rule->grant = grant; + + rule->identity = isc_mem_get(mctx, sizeof(dns_name_t)); + if (rule->identity == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + dns_name_init(rule->identity, NULL); + result = dns_name_dup(identity, mctx, rule->identity); + if (result != ISC_R_SUCCESS) + goto failure; + + rule->name = isc_mem_get(mctx, sizeof(dns_name_t)); + if (rule->name == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + dns_name_init(rule->name, NULL); + result = dns_name_dup(name, mctx, rule->name); + if (result != ISC_R_SUCCESS) + goto failure; + + rule->matchtype = matchtype; + + rule->ntypes = ntypes; + if (ntypes > 0) { + rule->types = isc_mem_get(mctx, + ntypes * sizeof(dns_rdatatype_t)); + if (rule->types == NULL) { + result = ISC_R_NOMEMORY; + goto failure; + } + 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); + + failure: + if (rule->identity != NULL) { + if (dns_name_dynamic(rule->identity)) + dns_name_free(rule->identity, mctx); + isc_mem_put(mctx, rule->identity, sizeof(dns_name_t)); + } + if (rule->name != NULL) { + if (dns_name_dynamic(rule->name)) + 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, + ntypes * sizeof(dns_rdatatype_t)); + isc_mem_put(mctx, rule, sizeof(dns_ssurule_t)); + + return (result); +} + +static inline 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, isc_netaddr_t *tcpaddr) { + char buf[16 * 4 + sizeof("IP6.ARPA.")]; + isc_result_t result; + unsigned char *ap; + isc_buffer_t b; + unsigned long l; + + switch (tcpaddr->family) { + case AF_INET: + l = ntohl(tcpaddr->type.in.s_addr); + result = isc_string_printf(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 == ISC_R_SUCCESS); + break; + case AF_INET6: + ap = tcpaddr->type.in6.s6_addr; + result = isc_string_printf(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 == ISC_R_SUCCESS); + break; + default: + INSIST(0); + } + 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, 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; + unsigned char *ap; + isc_buffer_t b; + unsigned long l; + + switch(tcpaddr->family) { + case AF_INET: + l = ntohl(tcpaddr->type.in.s_addr); + result = isc_string_printf(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 == ISC_R_SUCCESS); + break; + case AF_INET6: + ap = tcpaddr->type.in6.s6_addr; + result = isc_string_printf(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 == ISC_R_SUCCESS); + break; + default: + INSIST(0); + } + 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, dns_name_t *signer, + dns_name_t *name, isc_netaddr_t *addr, + dns_rdatatype_t type, const dst_key_t *key) +{ + return (dns_ssutable_checkrules2 + (table, signer, name, addr, + addr == NULL ? false : true, + NULL, type, key)); +} + +bool +dns_ssutable_checkrules2(dns_ssutable_t *table, dns_name_t *signer, + dns_name_t *name, 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; + } + + 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)); + if (rule == NULL) { + dns_ssutable_detach(&table); + return (ISC_R_NOMEMORY); + } + + 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..a1b638e --- /dev/null +++ b/lib/dns/ssu_external.c @@ -0,0 +1,262 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/* + * This implements external update-policy rules. This allows permission + * to update a zone to be checked by consulting an external daemon (e.g., + * kerberos). + */ + +#include + +#include +#include +#include +#include + +#ifdef ISC_PLATFORM_HAVESYSUNH +#include +#include +#endif + +#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 %u", + 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]; + isc__strerror(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]; + isc__strerror(errno, strbuf, sizeof(strbuf)); + ssu_e_log(3, "ssu_external: unable to connect to " + "socket '%s' - %s", + path, strbuf); + close(fd); + return (-1); + } +#endif + 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(dns_name_t *identity, + dns_name_t *signer, dns_name_t *name, + 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); + if (data == NULL) { + close(fd); + return (false); + } + + 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]; + isc__strerror(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]; + isc__strerror(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..617ed3b --- /dev/null +++ b/lib/dns/stats.c @@ -0,0 +1,475 @@ +/* + * 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 http://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 + +#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_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 and + * those explicitly supported in the rdata implementation. + * XXXJT: this introduces tight coupling with the rdata implementation. + * Ideally, we should have rdata handle this type of details. + */ +/* + * types, !types, nxdomain, stale types, stale !types, stale nxdomain + */ +enum { + /* For 0-255, we use the rdtype value as counter indices */ + rdtypecounter_dlv = 256, /* for dns_rdatatype_dlv */ + rdtypecounter_others = 257, /* anything else */ + rdtypecounter_max = 258, + /* The following are used for rdataset */ + rdtypenxcounter_max = rdtypecounter_max * 2, + rdtypecounter_nxdomain = rdtypenxcounter_max, + /* stale counters offset */ + rdtypecounter_stale = rdtypecounter_nxdomain + 1, + rdatasettypecounter_max = rdtypecounter_stale * 2 +}; + +struct dns_stats { + /*% Unlocked */ + unsigned int magic; + dns_statstype_t type; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_stats_t *counters; + + /*% Locked by lock */ + unsigned int 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; + +void +dns_stats_attach(dns_stats_t *stats, dns_stats_t **statsp) { + REQUIRE(DNS_STATS_VALID(stats)); + REQUIRE(statsp != NULL && *statsp == NULL); + + LOCK(&stats->lock); + stats->references++; + UNLOCK(&stats->lock); + + *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; + + LOCK(&stats->lock); + stats->references--; + UNLOCK(&stats->lock); + + if (stats->references == 0) { + isc_stats_detach(&stats->counters); + DESTROYLOCK(&stats->lock); + 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)); + if (stats == NULL) + return (ISC_R_NOMEMORY); + + stats->counters = NULL; + stats->references = 1; + + result = isc_mutex_init(&stats->lock); + if (result != ISC_R_SUCCESS) + goto clean_stats; + + 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: + DESTROYLOCK(&stats->lock); + clean_stats: + 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); + + return (create_stats(mctx, dns_statstype_rdtype, rdtypecounter_max, + 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, + rdatasettypecounter_max, 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)); +} + +/*% + * 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); +} + +void +dns_rdatatypestats_increment(dns_stats_t *stats, dns_rdatatype_t type) { + int counter; + + REQUIRE(DNS_STATS_VALID(stats) && stats->type == dns_statstype_rdtype); + + if (type == dns_rdatatype_dlv) + counter = rdtypecounter_dlv; + else if (type > dns_rdatatype_any) + counter = rdtypecounter_others; + else + counter = (int)type; + + isc_stats_increment(stats->counters, (isc_statscounter_t)counter); +} + +static inline void +update_rdatasetstats(dns_stats_t *stats, dns_rdatastatstype_t rrsettype, + bool increment) +{ + int counter; + dns_rdatatype_t rdtype; + + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) != 0) { + counter = rdtypecounter_nxdomain; + } else { + rdtype = DNS_RDATASTATSTYPE_BASE(rrsettype); + if (rdtype == dns_rdatatype_dlv) + counter = (int)rdtypecounter_dlv; + else if (rdtype > dns_rdatatype_any) + counter = (int)rdtypecounter_others; + else + counter = (int)rdtype; + + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_NXRRSET) != 0) + counter += rdtypecounter_max; + } + + if (increment) { + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_STALE) != 0) { + isc_stats_decrement(stats->counters, counter); + counter += rdtypecounter_stale; + } + isc_stats_increment(stats->counters, counter); + } else { + if ((DNS_RDATASTATSTYPE_ATTR(rrsettype) & + DNS_RDATASTATSTYPE_ATTR_STALE) != 0) + counter += rdtypecounter_stale; + 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); +} + +/*% + * 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_others) + attributes |= DNS_RDATASTATSTYPE_ATTR_OTHERTYPE; + else { + if (rdcounter == rdtypecounter_dlv) + rdtype = dns_rdatatype_dlv; + else + rdtype = (dns_rdatatype_t)rdcounter; + } + 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; + + if (counter < rdtypecounter_max) { + dump_rdentry(counter, value, 0, rdatadumparg->fn, + rdatadumparg->arg); + } else if (counter < rdtypecounter_nxdomain) { + counter -= rdtypecounter_max; + attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET; + dump_rdentry(counter, value, attributes, rdatadumparg->fn, + rdatadumparg->arg); + } else if (counter == rdtypecounter_nxdomain) { + dump_rdentry(0, value, DNS_RDATASTATSTYPE_ATTR_NXDOMAIN, + rdatadumparg->fn, rdatadumparg->arg); + } else if (counter < rdtypecounter_stale + rdtypecounter_max) { + counter -= rdtypecounter_stale; + attributes = DNS_RDATASTATSTYPE_ATTR_STALE; + dump_rdentry(counter, value, attributes, rdatadumparg->fn, + rdatadumparg->arg); + } else if (counter < rdtypecounter_stale + rdtypecounter_nxdomain) { + counter -= rdtypecounter_stale + rdtypecounter_max; + attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET | + DNS_RDATASTATSTYPE_ATTR_STALE; + dump_rdentry(counter, value, attributes, rdatadumparg->fn, + rdatadumparg->arg); + } else { + attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN | + DNS_RDATASTATSTYPE_ATTR_STALE; + dump_rdentry(0, 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 +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..bb2a5d9 --- /dev/null +++ b/lib/dns/tcpmsg.c @@ -0,0 +1,239 @@ +/* + * 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 http://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 + +#ifdef TCPMSG_DEBUG +#include /* Required for printf. */ +#define XDEBUG(x) printf x +#else +#define XDEBUG(x) +#endif + +#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 + +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/Atffile b/lib/dns/tests/Atffile new file mode 100644 index 0000000..953082d --- /dev/null +++ b/lib/dns/tests/Atffile @@ -0,0 +1,34 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = bind9 + +tp: acl_test +tp: db_test +tp: dbdiff_test +tp: dbiterator_test +tp: dbversion_test +tp: dh_test +tp: dispatch_test +tp: dnstap_test +tp: dst_test +tp: geoip_test +tp: gost_test +tp: keytable_test +tp: master_test +tp: name_test +tp: nsec3_test +tp: peer_test +tp: private_test +tp: rbt_serialize_test +tp: rbt_test +tp: rdata_test +tp: rdataset_test +tp: rdatasetstats_test +tp: resolver_test +tp: rsa_test +tp: sigs_test +tp: time_test +tp: tsig_test +tp: update_test +tp: zonemgr_test +tp: zt_test 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.+005+29235.key b/lib/dns/tests/Krsa.+005+29235.key new file mode 100644 index 0000000..e2d81e7 --- /dev/null +++ b/lib/dns/tests/Krsa.+005+29235.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 5 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..0353a73 --- /dev/null +++ b/lib/dns/tests/Kyuafile @@ -0,0 +1,33 @@ +syntax(2) +test_suite('bind9') + +atf_test_program{name='acl_test'} +atf_test_program{name='db_test'} +atf_test_program{name='dbdiff_test'} +atf_test_program{name='dbiterator_test'} +atf_test_program{name='dbversion_test'} +atf_test_program{name='dh_test'} +atf_test_program{name='dispatch_test'} +atf_test_program{name='dnstap_test'} +atf_test_program{name='dst_test'} +atf_test_program{name='geoip_test'} +atf_test_program{name='gost_test'} +atf_test_program{name='keytable_test'} +atf_test_program{name='master_test'} +atf_test_program{name='name_test'} +atf_test_program{name='nsec3_test'} +atf_test_program{name='peer_test'} +atf_test_program{name='private_test'} +atf_test_program{name='rbt_serialize_test', is_exclusive=true} +atf_test_program{name='rbt_test'} +atf_test_program{name='rdata_test'} +atf_test_program{name='rdataset_test'} +atf_test_program{name='rdatasetstats_test'} +atf_test_program{name='resolver_test'} +atf_test_program{name='rsa_test'} +atf_test_program{name='sigs_test'} +atf_test_program{name='time_test'} +atf_test_program{name='tsig_test'} +atf_test_program{name='update_test'} +atf_test_program{name='zonemgr_test'} +atf_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..58fa872 --- /dev/null +++ b/lib/dns/tests/Makefile.in @@ -0,0 +1,266 @@ +# 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 http://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_MAKE_INCLUDES@ + +CINCLUDES = -I. -Iinclude ${DNS_INCLUDES} ${ISC_INCLUDES} \ + @DST_OPENSSL_INC@ +CDEFINES = @CRYPTO@ -DTESTS="\"${top_builddir}/lib/dns/tests/\"" + +ISCLIBS = ../../isc/libisc.@A@ +ISCDEPLIBS = ../../isc/libisc.@A@ +DNSLIBS = ../libdns.@A@ @DNS_CRYPTO_LIBS@ +DNSDEPLIBS = ../libdns.@A@ + +LIBS = @LIBS@ @ATFLIBS@ + +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 \ + gost_test.c \ + keytable_test.c \ + master_test.c \ + name_test.c \ + nsec3_test.c \ + peer_test.c \ + private_test.c \ + rbt_test.c \ + rbt_serialize_test.c \ + rdata_test.c \ + rdataset_test.c \ + rdatasetstats_test.c \ + resolver_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@ \ + gost_test@EXEEXT@ \ + keytable_test@EXEEXT@ \ + master_test@EXEEXT@ \ + name_test@EXEEXT@ \ + nsec3_test@EXEEXT@ \ + peer_test@EXEEXT@ \ + private_test@EXEEXT@ \ + rbt_test@EXEEXT@ \ + rbt_serialize_test@EXEEXT@ \ + rdata_test@EXEEXT@ \ + rdataset_test@EXEEXT@ \ + rdatasetstats_test@EXEEXT@ \ + resolver_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@ + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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} + +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@ ${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} + +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} + +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} + +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} + +rdata_test@EXEEXT@: rdata_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + rdata_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + +geoip_test@EXEEXT@: geoip_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + geoip_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} + +db_test@EXEEXT@: db_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + db_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + +gost_test@EXEEXT@: gost_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + gost_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} + +dst_test@EXEEXT@: dst_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + dst_test.@O@ dnstest.@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} + +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} + +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..285a990 --- /dev/null +++ b/lib/dns/tests/acl_test.c @@ -0,0 +1,374 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ + +#define BUFLEN 255 +#define BIGBUFLEN (70 * 1024) +#define TEST_ORIGIN "test" + +ATF_TC(dns_acl_isinsecure); +ATF_TC_HEAD(dns_acl_isinsecure, tc) { + atf_tc_set_md_var(tc, "descr", "test that dns_acl_isinsecure works"); +} +ATF_TC_BODY(dns_acl_isinsecure, tc) { + isc_result_t result; + unsigned int pass; + struct { + bool first; + bool second; + } ecs[] = { + { false, false }, + { true, true }, + { true, false }, + { false, true } + }; + + dns_acl_t *any = NULL; + dns_acl_t *none = NULL; + dns_acl_t *notnone = NULL; + dns_acl_t *notany = NULL; +#ifdef HAVE_GEOIP + dns_acl_t *geoip = NULL; + dns_acl_t *notgeoip = NULL; + dns_aclelement_t *de; +#endif + + dns_acl_t *pos4pos6 = NULL; + dns_acl_t *notpos4pos6 = NULL; + dns_acl_t *neg4pos6 = NULL; + dns_acl_t *notneg4pos6 = NULL; + dns_acl_t *pos4neg6 = NULL; + dns_acl_t *notpos4neg6 = NULL; + dns_acl_t *neg4neg6 = NULL; + dns_acl_t *notneg4neg6 = NULL; + + dns_acl_t *loop4 = NULL; + dns_acl_t *notloop4 = NULL; + + dns_acl_t *loop6 = NULL; + dns_acl_t *notloop6 = NULL; + + dns_acl_t *loop4pos6 = NULL; + dns_acl_t *notloop4pos6 = NULL; + dns_acl_t *loop4neg6 = NULL; + dns_acl_t *notloop4neg6 = NULL; + + struct in_addr inaddr; + isc_netaddr_t addr; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_any(mctx, &any); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_none(mctx, &none); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬none); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬any); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notnone, none, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notany, any, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + +#ifdef HAVE_GEOIP + result = dns_acl_create(mctx, 1, &geoip); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + de = geoip->elements; + ATF_REQUIRE(de != NULL); + 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; + ATF_REQUIRE(geoip->length < geoip->alloc); + geoip->node_count++; + de->node_num = geoip->node_count; + geoip->length++; + + result = dns_acl_create(mctx, 1, ¬geoip); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notgeoip, geoip, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); +#endif + + ATF_CHECK(dns_acl_isinsecure(any)); /* any; */ + ATF_CHECK(!dns_acl_isinsecure(none)); /* none; */ + ATF_CHECK(!dns_acl_isinsecure(notany)); /* !any; */ + ATF_CHECK(!dns_acl_isinsecure(notnone)); /* !none; */ + +#ifdef HAVE_GEOIP + ATF_CHECK(dns_acl_isinsecure(geoip)); /* geoip; */ + ATF_CHECK(!dns_acl_isinsecure(notgeoip)); /* !geoip; */ +#endif + + dns_acl_detach(&any); + dns_acl_detach(&none); + dns_acl_detach(¬any); + dns_acl_detach(¬none); +#ifdef HAVE_GEOIP + dns_acl_detach(&geoip); + dns_acl_detach(¬geoip); +#endif + + for (pass = 0; pass < sizeof(ecs)/sizeof(ecs[0]); pass++) { + result = dns_acl_create(mctx, 1, &pos4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬pos4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, &neg4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬neg4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, &pos4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬pos4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, &neg4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬neg4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x0a000000); /* 10.0.0.0 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(pos4pos6->iptable, &addr, 8, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* 0a00:: */ + result = dns_iptable_addprefix2(pos4pos6->iptable, &addr, 8, + true, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notpos4pos6, pos4pos6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x0a000000); /* !10.0.0.0/8 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(neg4pos6->iptable, &addr, 8, + false, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* 0a00::/8 */ + result = dns_iptable_addprefix2(neg4pos6->iptable, &addr, 8, + true, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notneg4pos6, neg4pos6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x0a000000); /* 10.0.0.0/8 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(pos4neg6->iptable, &addr, 8, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* !0a00::/8 */ + result = dns_iptable_addprefix2(pos4neg6->iptable, &addr, 8, + false, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notpos4neg6, pos4neg6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x0a000000); /* !10.0.0.0/8 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(neg4neg6->iptable, &addr, 8, + false, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* !0a00::/8 */ + result = dns_iptable_addprefix2(neg4neg6->iptable, &addr, 8, + false, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notneg4neg6, neg4neg6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK(dns_acl_isinsecure(pos4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notpos4pos6)); + ATF_CHECK(dns_acl_isinsecure(neg4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notneg4pos6)); + ATF_CHECK(dns_acl_isinsecure(pos4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notpos4neg6)); + ATF_CHECK(!dns_acl_isinsecure(neg4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notneg4neg6)); + + dns_acl_detach(&pos4pos6); + dns_acl_detach(¬pos4pos6); + dns_acl_detach(&neg4pos6); + dns_acl_detach(¬neg4pos6); + dns_acl_detach(&pos4neg6); + dns_acl_detach(¬pos4neg6); + dns_acl_detach(&neg4neg6); + dns_acl_detach(¬neg4neg6); + + result = dns_acl_create(mctx, 1, &loop4); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬loop4); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, &loop6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬loop6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(loop4->iptable, &addr, 32, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notloop4, loop4, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_netaddr_fromin6(&addr, &in6addr_loopback); /* ::1 */ + result = dns_iptable_addprefix2(loop6->iptable, &addr, 128, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notloop6, loop6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + if (!ecs[pass].first) { + ATF_CHECK(!dns_acl_isinsecure(loop4)); + ATF_CHECK(!dns_acl_isinsecure(notloop4)); + ATF_CHECK(!dns_acl_isinsecure(loop6)); + ATF_CHECK(!dns_acl_isinsecure(notloop6)); + } else if (ecs[pass].first) { + ATF_CHECK(dns_acl_isinsecure(loop4)); + ATF_CHECK(!dns_acl_isinsecure(notloop4)); + ATF_CHECK(dns_acl_isinsecure(loop6)); + ATF_CHECK(!dns_acl_isinsecure(notloop6)); + } + + dns_acl_detach(&loop4); + dns_acl_detach(¬loop4); + dns_acl_detach(&loop6); + dns_acl_detach(¬loop6); + + result = dns_acl_create(mctx, 1, &loop4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬loop4pos6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, &loop4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_create(mctx, 1, ¬loop4neg6); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(loop4pos6->iptable, &addr, 32, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* f700:0001::/32 */ + result = dns_iptable_addprefix2(loop4pos6->iptable, &addr, 32, + true, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notloop4pos6, loop4pos6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + inaddr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + isc_netaddr_fromin(&addr, &inaddr); + result = dns_iptable_addprefix2(loop4neg6->iptable, &addr, 32, + true, ecs[pass].first); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + addr.family = AF_INET6; /* !f700:0001::/32 */ + result = dns_iptable_addprefix2(loop4neg6->iptable, &addr, 32, + false, ecs[pass].second); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_acl_merge(notloop4neg6, loop4neg6, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + if (!ecs[pass].first && !ecs[pass].second) { + ATF_CHECK(dns_acl_isinsecure(loop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(loop4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4neg6)); + } else if (ecs[pass].first && !ecs[pass].second) { + ATF_CHECK(dns_acl_isinsecure(loop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4pos6)); + ATF_CHECK(dns_acl_isinsecure(loop4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4neg6)); + } else if (!ecs[pass].first && ecs[pass].second) { + ATF_CHECK(dns_acl_isinsecure(loop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(loop4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4neg6)); + } else { + ATF_CHECK(dns_acl_isinsecure(loop4pos6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4pos6)); + ATF_CHECK(dns_acl_isinsecure(loop4neg6)); + ATF_CHECK(!dns_acl_isinsecure(notloop4neg6)); + } + + dns_acl_detach(&loop4pos6); + dns_acl_detach(¬loop4pos6); + dns_acl_detach(&loop4neg6); + dns_acl_detach(¬loop4neg6); + } + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, dns_acl_isinsecure); + return (atf_no_error()); +} diff --git a/lib/dns/tests/db_test.c b/lib/dns/tests/db_test.c new file mode 100644 index 0000000..d12a497 --- /dev/null +++ b/lib/dns/tests/db_test.c @@ -0,0 +1,212 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +/* + * Individual unit tests + */ + +ATF_TC(getoriginnode); +ATF_TC_HEAD(getoriginnode, tc) { + atf_tc_set_md_var(tc, "descr", + "test multiple calls to dns_db_getoriginnode"); +} +ATF_TC_BODY(getoriginnode, tc) { + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + isc_mem_t *mymctx = NULL; + isc_result_t result; + + result = isc_mem_create(0, 0, &mymctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_hash_create(mymctx, NULL, 256); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_getoriginnode(db, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + + result = dns_db_getoriginnode(db, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + + dns_db_detach(&db); + isc_mem_detach(&mymctx); +} + +ATF_TC(class); +ATF_TC_HEAD(class, tc) { + atf_tc_set_md_var(tc, "descr", "database class"); +} +ATF_TC_BODY(class, tc) { + isc_result_t result; + dns_db_t *db = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_load(db, "testdata/db/data.db"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK_EQ(dns_db_class(db), dns_rdataclass_in); + + dns_db_detach(&db); +} + +ATF_TC(dbtype); +ATF_TC_HEAD(dbtype, tc) { + atf_tc_set_md_var(tc, "descr", "database type"); +} +ATF_TC_BODY(dbtype, tc) { + isc_result_t result; + dns_db_t *db = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* DB has zone semantics */ + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_db_load(db, "testdata/db/data.db"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(dns_db_iszone(db)); + ATF_CHECK(!dns_db_iscache(db)); + dns_db_detach(&db); + + /* DB has cache semantics */ + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_db_load(db, "testdata/db/data.db"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(dns_db_iscache(db)); + ATF_CHECK(!dns_db_iszone(db)); + dns_db_detach(&db); + + dns_test_end(); +} + +ATF_TC(version); +ATF_TC_HEAD(version, tc) { + atf_tc_set_md_var(tc, "descr", "database versions"); +} +ATF_TC_BODY(version, tc) { + 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; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_zone, "test.test", + "testdata/db/data.db"); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_newversion(db, &new); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Delete the rdataset from the new verison */ + result = dns_db_deleterdataset(db, node, new, dns_rdatatype_a, 0); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, &ver, false); + + dns_db_detach(&db); + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, getoriginnode); + ATF_TP_ADD_TC(tp, class); + ATF_TP_ADD_TC(tp, dbtype); + ATF_TP_ADD_TC(tp, version); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/dbdiff_test.c b/lib/dns/tests/dbdiff_test.c new file mode 100644 index 0000000..2ddd243 --- /dev/null +++ b/lib/dns/tests/dbdiff_test.c @@ -0,0 +1,166 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +static void +test_create(const atf_tc_t *tc, dns_db_t **old, dns_db_t **newdb) { + isc_result_t result; + + result = dns_test_loaddb(old, dns_dbtype_zone, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-old")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(newdb, dns_dbtype_zone, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-new")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); +} + +/* + * Individual unit tests + */ + +ATF_TC(diffx_same); +ATF_TC_HEAD(diffx_same, tc) { + atf_tc_set_md_var(tc, "descr", "dns_db_diffx of identical content"); + atf_tc_set_md_var(tc, "X-old", "testdata/diff/zone1.data"); + atf_tc_set_md_var(tc, "X-new", "testdata/diff/zone1.data"); } +ATF_TC_BODY(diffx_same, tc) { + dns_db_t *newdb = NULL, *olddb = NULL; + isc_result_t result; + dns_diff_t diff; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + test_create(tc, &olddb, &newdb); + + dns_diff_init(mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(ISC_LIST_EMPTY(diff.tuples), true); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); + dns_test_end(); +} + +ATF_TC(diffx_add); +ATF_TC_HEAD(diffx_add, tc) { + atf_tc_set_md_var(tc, "descr", + "dns_db_diffx of zone with record added"); + atf_tc_set_md_var(tc, "X-old", "testdata/diff/zone1.data"); + atf_tc_set_md_var(tc, "X-new", "testdata/diff/zone2.data"); +} +ATF_TC_BODY(diffx_add, tc) { + dns_db_t *newdb = NULL, *olddb = NULL; + dns_difftuple_t *tuple; + isc_result_t result; + dns_diff_t diff; + int count = 0; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + test_create(tc, &olddb, &newdb); + + dns_diff_init(mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(ISC_LIST_EMPTY(diff.tuples), false); + for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) { + ATF_REQUIRE_EQ(tuple->op, DNS_DIFFOP_ADD); + count++; + } + ATF_REQUIRE_EQ(count, 1); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); + dns_test_end(); +} + +ATF_TC(diffx_remove); +ATF_TC_HEAD(diffx_remove, tc) { + atf_tc_set_md_var(tc, "descr", + "dns_db_diffx of zone with record removed"); + atf_tc_set_md_var(tc, "X-old", "testdata/diff/zone1.data"); + atf_tc_set_md_var(tc, "X-new", "testdata/diff/zone3.data"); +} +ATF_TC_BODY(diffx_remove, tc) { + dns_db_t *newdb = NULL, *olddb = NULL; + dns_difftuple_t *tuple; + isc_result_t result; + dns_diff_t diff; + int count = 0; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + test_create(tc, &olddb, &newdb); + + dns_diff_init(mctx, &diff); + + result = dns_db_diffx(&diff, newdb, NULL, olddb, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(ISC_LIST_EMPTY(diff.tuples), false); + for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL; + tuple = ISC_LIST_NEXT(tuple, link)) { + ATF_REQUIRE_EQ(tuple->op, DNS_DIFFOP_DEL); + count++; + } + ATF_REQUIRE_EQ(count, 1); + + dns_diff_clear(&diff); + dns_db_detach(&newdb); + dns_db_detach(&olddb); + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, diffx_same); + ATF_TP_ADD_TC(tp, diffx_add); + ATF_TP_ADD_TC(tp, diffx_remove); + return (atf_no_error()); +} diff --git a/lib/dns/tests/dbiterator_test.c b/lib/dns/tests/dbiterator_test.c new file mode 100644 index 0000000..643627b --- /dev/null +++ b/lib/dns/tests/dbiterator_test.c @@ -0,0 +1,425 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ + +#define BUFLEN 255 +#define BIGBUFLEN (64 * 1024) +#define TEST_ORIGIN "test" + +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)); +} + +/* + * Individual unit tests + */ + +/* create: make sure we can create a dbiterator */ +static void +test_create(const atf_tc_t *tc) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(create); +ATF_TC_HEAD(create, tc) { + atf_tc_set_md_var(tc, "descr", "create a database iterator"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); +} +ATF_TC_BODY(create, tc) { + test_create(tc); +} + +ATF_TC(create_nsec3); +ATF_TC_HEAD(create_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "create a database iterator (NSEC3)"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); +} +ATF_TC_BODY(create_nsec3, tc) { + test_create(tc); +} + +/* walk: walk a database */ +static void +test_walk(const atf_tc_t *tc) { + 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; + + UNUSED(tc); + + name = dns_fixedname_initname(&f); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(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; + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + i++; + } + + ATF_CHECK_EQ(i, atoi(atf_tc_get_md_var(tc, "X-nodes"))); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(walk); +ATF_TC_HEAD(walk, tc) { + atf_tc_set_md_var(tc, "descr", "walk database"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); + atf_tc_set_md_var(tc, "X-nodes", "12"); +} +ATF_TC_BODY(walk, tc) { + test_walk(tc); +} + +ATF_TC(walk_nsec3); +ATF_TC_HEAD(walk_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "walk database"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); + atf_tc_set_md_var(tc, "X-nodes", "33"); +} +ATF_TC_BODY(walk_nsec3, tc) { + test_walk(tc); +} + +/* reverse: walk database backwards */ +static void test_reverse(const atf_tc_t *tc) { + 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; + + UNUSED(tc); + + name = dns_fixedname_initname(&f); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(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; + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + i++; + } + + ATF_CHECK_EQ(i, 12); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(reverse); +ATF_TC_HEAD(reverse, tc) { + atf_tc_set_md_var(tc, "descr", "walk database backwards"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); +} +ATF_TC_BODY(reverse, tc) { + test_reverse(tc); +} + +ATF_TC(reverse_nsec3); +ATF_TC_HEAD(reverse_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "walk database backwards"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); +} +ATF_TC_BODY(reverse_nsec3, tc) { + test_reverse(tc); +} + +/* seek: walk database starting at a particular node */ +static void test_seek(const atf_tc_t *tc) { + 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; + + UNUSED(tc); + + name = dns_fixedname_initname(&f1); + seekname = dns_fixedname_initname(&f2); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = make_name("c." TEST_ORIGIN, seekname); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + ATF_CHECK_EQ(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; + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(iter); + i++; + } + + ATF_CHECK_EQ(i, atoi(atf_tc_get_md_var(tc, "X-nodes"))); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(seek); +ATF_TC_HEAD(seek, tc) { + atf_tc_set_md_var(tc, "descr", "walk database starting at " + "a particular node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); + atf_tc_set_md_var(tc, "X-nodes", "9"); +} +ATF_TC_BODY(seek, tc) { + test_seek(tc); +} + +ATF_TC(seek_nsec3); +ATF_TC_HEAD(seek_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "walk database starting at " + "a particular node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); + atf_tc_set_md_var(tc, "X-nodes", "30"); +} +ATF_TC_BODY(seek_nsec3, tc) { + test_seek(tc); +} + +/* + * seek_emty: walk database starting at an empty nonterminal node + * (should fail) + */ +static void test_seek_empty(const atf_tc_t *tc) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_name_t *seekname; + dns_fixedname_t f1; + + UNUSED(tc); + + seekname = dns_fixedname_initname(&f1); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = make_name("d." TEST_ORIGIN, seekname); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + ATF_CHECK_EQ(result, DNS_R_PARTIALMATCH); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(seek_empty); +ATF_TC_HEAD(seek_empty, tc) { + atf_tc_set_md_var(tc, "descr", "walk database starting at an " + "empty nonterminal node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); +} +ATF_TC_BODY(seek_empty, tc) { + test_seek_empty(tc); +} + +ATF_TC(seek_empty_nsec3); +ATF_TC_HEAD(seek_empty_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "walk database starting at an " + "empty nonterminal node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); +} +ATF_TC_BODY(seek_empty_nsec3, tc) { + test_seek_empty(tc); +} + +/* + * seek_emty: walk database starting at an empty nonterminal node + * (should fail) + */ +static void test_seek_nx(const atf_tc_t *tc) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbiterator_t *iter = NULL; + dns_name_t *seekname; + dns_fixedname_t f1; + + UNUSED(tc); + + seekname = dns_fixedname_initname(&f1); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_cache, TEST_ORIGIN, + atf_tc_get_md_var(tc, "X-filename")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_createiterator(db, 0, &iter); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = make_name("nonexistent." TEST_ORIGIN, seekname); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + ATF_CHECK_EQ(result, DNS_R_PARTIALMATCH); + + result = make_name("nonexistent.", seekname); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dbiterator_seek(iter, seekname); + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + + dns_dbiterator_destroy(&iter); + dns_db_detach(&db); + dns_test_end(); +} + +ATF_TC(seek_nx); +ATF_TC_HEAD(seek_nx, tc) { + atf_tc_set_md_var(tc, "descr", "attempt to walk database starting " + "at a nonexistent node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone1.data"); +} +ATF_TC_BODY(seek_nx, tc) { + test_seek_nx(tc); +} + +ATF_TC(seek_nx_nsec3); +ATF_TC_HEAD(seek_nx_nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "attempt to walk database starting " + "at a nonexistent node"); + atf_tc_set_md_var(tc, "X-filename", "testdata/dbiterator/zone2.data"); +} +ATF_TC_BODY(seek_nx_nsec3, tc) { + test_seek_nx(tc); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, create); + ATF_TP_ADD_TC(tp, create_nsec3); + ATF_TP_ADD_TC(tp, walk); + ATF_TP_ADD_TC(tp, walk_nsec3); + ATF_TP_ADD_TC(tp, reverse); + ATF_TP_ADD_TC(tp, reverse_nsec3); + ATF_TP_ADD_TC(tp, seek); + ATF_TP_ADD_TC(tp, seek_nsec3); + ATF_TP_ADD_TC(tp, seek_empty); + ATF_TP_ADD_TC(tp, seek_empty_nsec3); + ATF_TP_ADD_TC(tp, seek_nx); + ATF_TP_ADD_TC(tp, seek_nx_nsec3); + return (atf_no_error()); +} + +/* + * XXX: + * dns_dbiterator API calls that are not yet part of this unit test: + * + * dns_dbiterator_pause + * dns_dbiterator_origin + * dns_dbiterator_setcleanmode + */ diff --git a/lib/dns/tests/dbversion_test.c b/lib/dns/tests/dbversion_test.c new file mode 100644 index 0000000..275fc9b --- /dev/null +++ b/lib/dns/tests/dbversion_test.c @@ -0,0 +1,735 @@ +/* + * 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 http://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 "dnstest.h" + +static char tempname[11] = "dtXXXXXXXX"; + +static void +local_callback(const char *file, int line, isc_assertiontype_t type, + const char *cond) +{ + UNUSED(file); UNUSED(line); UNUSED(type); UNUSED(cond); + if (strcmp(tempname, "dtXXXXXXXX")) + unlink(tempname); + atf_tc_pass(); + exit(0); +} + +static dns_db_t *db1 = NULL, *db2 = NULL; +static dns_dbversion_t *v1 = NULL, *v2 = NULL; + +static void +setup_db(void) { + isc_result_t result; + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_db_newversion(db1, &v1); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db2); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_db_newversion(db2, &v2); +} + +static void +close_db(void) { + if (v1 != NULL) { + dns_db_closeversion(db1, &v1, false); + ATF_REQUIRE_EQ(v1, NULL); + } + if (db1 != NULL) { + dns_db_detach(&db1); + ATF_REQUIRE_EQ(db1, NULL); + } + + if (v2 != NULL) { + dns_db_closeversion(db2, &v2, false); + ATF_REQUIRE_EQ(v2, NULL); + } + if (db2 != NULL) { + dns_db_detach(&db2); + ATF_REQUIRE_EQ(db2, NULL); + } +} + +#define VERSION(callback) ((callback == NULL) ? v1 : v2) +#define VERSIONP(callback) ((callback == NULL) ? &v1 : &v2) +/* + * Individual unit tests + */ +static void +attachversion(isc_assertioncallback_t callback) { + isc_result_t result; + dns_dbversion_t *v = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + isc_assertion_setcallback(callback); + dns_db_attachversion(db1, VERSION(callback), &v); + if (callback != NULL) + atf_tc_fail("dns_db_attachversion did not assert"); + + ATF_REQUIRE_EQ(v, v1); + dns_db_closeversion(db1, &v, false); + ATF_REQUIRE_EQ(v, NULL); + + close_db(); + dns_test_end(); +} + +ATF_TC(attachversion); +ATF_TC_HEAD(attachversion, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_attachversion passes with matching db/verison"); +} +ATF_TC_BODY(attachversion, tc) { + + UNUSED(tc); + + attachversion(NULL); +} + +ATF_TC(attachversion_bad); +ATF_TC_HEAD(attachversion_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_attachversion aborts with mis-matching db/verison"); +} +ATF_TC_BODY(attachversion_bad, tc) { + + UNUSED(tc); + + attachversion(local_callback); +} + +static void +closeversion(isc_assertioncallback_t callback) { + isc_result_t result; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + isc_assertion_setcallback(callback); + dns_db_closeversion(db1, VERSIONP(callback), false); + if (callback != NULL) + atf_tc_fail("dns_db_closeversion did not assert"); + ATF_REQUIRE_EQ(v1, NULL); + + close_db(); + dns_test_end(); +} + +ATF_TC(closeversion); +ATF_TC_HEAD(closeversion, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_closeversion passes with matching db/verison"); +} +ATF_TC_BODY(closeversion, tc) { + + UNUSED(tc); + + closeversion(NULL); +} + +ATF_TC(closeversion_bad); +ATF_TC_HEAD(closeversion_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_closeversion asserts with mis-matching db/verison"); +} +ATF_TC_BODY(closeversion_bad, tc) { + + UNUSED(tc); + + closeversion(local_callback); +} + +static void +find(isc_assertioncallback_t callback) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + dns_rdataset_init(&rdataset); + dns_fixedname_init(&fixed); + + isc_assertion_setcallback(callback); + result = dns_db_find(db1, dns_rootname, VERSION(callback), + dns_rdatatype_soa, 0, 0, NULL, + dns_fixedname_name(&fixed), &rdataset, NULL); + if (callback != NULL) + atf_tc_fail("dns_db_find did not assert"); + ATF_REQUIRE_EQ(result, DNS_R_NXDOMAIN); + + close_db(); + + dns_test_end(); +} +ATF_TC(find); +ATF_TC_HEAD(find, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_find passes with matching db/version"); +} +ATF_TC_BODY(find, tc) { + + UNUSED(tc); + + find(NULL); +} + +ATF_TC(find_bad); +ATF_TC_HEAD(find_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_find asserts with mis-matching db/version"); +} +ATF_TC_BODY(find_bad, tc) { + + UNUSED(tc); + + find(local_callback); +} + +static void +allrdatasets(isc_assertioncallback_t callback) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *iterator = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_allrdatasets(db1, node, VERSION(callback), 0, + &iterator); + if (callback != NULL) + atf_tc_fail("dns_db_allrdatasets did not assert"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_rdatasetiter_destroy(&iterator); + ATF_REQUIRE_EQ(iterator, NULL); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + close_db(); + + dns_test_end(); +} + +ATF_TC(allrdatasets); +ATF_TC_HEAD(allrdatasets, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_allrdatasets passes with matching db/version"); +} +ATF_TC_BODY(allrdatasets, tc) { + + UNUSED(tc); + + allrdatasets(NULL); +} + +ATF_TC(allrdatasets_bad); +ATF_TC_HEAD(allrdatasets_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_allrdatasets aborts with mis-matching db/version"); +} +ATF_TC_BODY(allrdatasets_bad, tc) { + + UNUSED(tc); + + allrdatasets(local_callback); +} + +static void +findrdataset(isc_assertioncallback_t callback) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_dbnode_t *node = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + dns_rdataset_init(&rdataset); + dns_fixedname_init(&fixed); + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_findrdataset(db1, node, VERSION(callback), + dns_rdatatype_soa, 0, 0, &rdataset, NULL); + if (callback != NULL) + atf_tc_fail("dns_db_findrdataset did not assert"); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + close_db(); + + dns_test_end(); +} + +ATF_TC(findrdataset); +ATF_TC_HEAD(findrdataset, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_findrdataset passes with matching db/version"); +} +ATF_TC_BODY(findrdataset, tc) { + + UNUSED(tc); + + findrdataset(NULL); +} + +ATF_TC(findrdataset_bad); +ATF_TC_HEAD(findrdataset_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_findrdataset aborts with mis-matching db/version"); +} +ATF_TC_BODY(findrdataset_bad, tc) { + + UNUSED(tc); + + findrdataset(local_callback); +} + +static void +deleterdataset(isc_assertioncallback_t callback) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_dbnode_t *node = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + dns_rdataset_init(&rdataset); + dns_fixedname_init(&fixed); + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_deleterdataset(db1, node, VERSION(callback), + dns_rdatatype_soa, 0); + if (callback != NULL) + atf_tc_fail("dns_db_deleterdataset did not assert"); + ATF_REQUIRE_EQ(result, DNS_R_UNCHANGED); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + close_db(); + + dns_test_end(); +} + +ATF_TC(deleterdataset); +ATF_TC_HEAD(deleterdataset, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_deleterdataset passes with matching db/version"); +} +ATF_TC_BODY(deleterdataset, tc) { + + UNUSED(tc); + + deleterdataset(NULL); +} + +ATF_TC(deleterdataset_bad); +ATF_TC_HEAD(deleterdataset_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_deleterdataset aborts with mis-matching db/version"); +} +ATF_TC_BODY(deleterdataset_bad, tc) { + + UNUSED(tc); + + deleterdataset(local_callback); +} + +static void +subtract(isc_assertioncallback_t callback) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_dbnode_t *node = NULL; + dns_rdatalist_t rdatalist; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + dns_rdataset_init(&rdataset); + dns_rdatalist_init(&rdatalist); + dns_fixedname_init(&fixed); + + rdatalist.rdclass = dns_rdataclass_in; + + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_subtractrdataset(db1, node, VERSION(callback), + &rdataset, 0, NULL); + if (callback != NULL) + atf_tc_fail("dns_db_subtractrdataset did not assert"); + ATF_REQUIRE_EQ(result, DNS_R_UNCHANGED); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + close_db(); + + dns_test_end(); +} + +ATF_TC(subtractrdataset); +ATF_TC_HEAD(subtractrdataset, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_subtractrdataset passes with matching db/version"); +} +ATF_TC_BODY(subtractrdataset, tc) { + + UNUSED(tc); + + subtract(NULL); +} + +ATF_TC(subtractrdataset_bad); +ATF_TC_HEAD(subtractrdataset_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_subtractrdataset aborts with mis-matching db/version"); +} +ATF_TC_BODY(subtractrdataset_bad, tc) { + + UNUSED(tc); + + subtract(local_callback); +} + +static void +dump(isc_assertioncallback_t callback) { + isc_result_t result; + FILE *f = NULL; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + result = isc_file_openunique(tempname, &f); + fclose(f); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_dump(db1, VERSION(callback), tempname); + (void)unlink(tempname); + if (callback != NULL) + atf_tc_fail("dns_db_dump did not assert"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + close_db(); + + dns_test_end(); +} + +ATF_TC(dump); +ATF_TC_HEAD(dump, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_dump passes with matching db/version"); +} +ATF_TC_BODY(dump, tc) { + + UNUSED(tc); + + dump(NULL); +} + +ATF_TC(dump_bad); +ATF_TC_HEAD(dump_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_dump aborts with mis-matching db/version"); +} +ATF_TC_BODY(dump_bad, tc) { + + UNUSED(tc); + + dump(local_callback); +} + +static void +addrdataset(isc_assertioncallback_t callback) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_fixedname_t fixed; + dns_dbnode_t *node = NULL; + dns_rdatalist_t rdatalist; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + dns_rdataset_init(&rdataset); + dns_rdatalist_init(&rdatalist); + dns_fixedname_init(&fixed); + + rdatalist.rdclass = dns_rdataclass_in; + + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_assertion_setcallback(callback); + result = dns_db_addrdataset(db1, node, VERSION(callback), 0, &rdataset, + 0, NULL); + if (callback != NULL) + atf_tc_fail("dns_db_adddataset did not assert"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + close_db(); + + dns_test_end(); +} + +ATF_TC(addrdataset); +ATF_TC_HEAD(addrdataset, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_addrdataset passes with matching db/version"); +} +ATF_TC_BODY(addrdataset, tc) { + + UNUSED(tc); + + addrdataset(NULL); +} + +ATF_TC(addrdataset_bad); +ATF_TC_HEAD(addrdataset_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_addrdataset aborts with mis-matching db/version"); +} +ATF_TC_BODY(addrdataset_bad, tc) { + + UNUSED(tc); + + addrdataset(local_callback); +} + +static void +getnsec3parameters(isc_assertioncallback_t callback) { + isc_result_t result; + dns_hash_t hash; + uint8_t flags; + uint16_t iterations; + unsigned char salt[DNS_NSEC3_SALTSIZE]; + size_t salt_length = sizeof(salt); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + isc_assertion_setcallback(callback); + result = dns_db_getnsec3parameters(db1, VERSION(callback), &hash, + &flags, &iterations, salt, + &salt_length); + if (callback != NULL) + atf_tc_fail("dns_db_dump did not assert"); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + + close_db(); + + dns_test_end(); +} + +ATF_TC(getnsec3parameters); +ATF_TC_HEAD(getnsec3parameters, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_getnsec3parameters passes with matching db/version"); +} +ATF_TC_BODY(getnsec3parameters, tc) { + + UNUSED(tc); + + getnsec3parameters(NULL); +} + +ATF_TC(getnsec3parameters_bad); +ATF_TC_HEAD(getnsec3parameters_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_db_getnsec3parameters aborts with mis-matching db/version"); +} +ATF_TC_BODY(getnsec3parameters_bad, tc) { + + UNUSED(tc); + + getnsec3parameters(local_callback); +} + +static void +resigned(isc_assertioncallback_t callback) { + isc_result_t result; + 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]; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + setup_db(); + + /* + * 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; + + result = dns_rdata_fromstruct(&rdata, dns_rdataclass_in, + dns_rdatatype_rrsig, &rrsig, &b); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + rdatalist.rdclass = dns_rdataclass_in; + rdatalist.type = dns_rdatatype_rrsig; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + rdataset.attributes |= DNS_RDATASETATTR_RESIGN; + rdataset.resign = 7200; + + result = dns_db_findnode(db1, dns_rootname, false, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, &added); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_db_detachnode(db1, &node); + ATF_REQUIRE_EQ(node, NULL); + + isc_assertion_setcallback(callback); + dns_db_resigned(db1, &added, VERSION(callback)); + if (callback != NULL) + atf_tc_fail("dns_db_resigned did not assert"); + + dns_rdataset_disassociate(&added); + + close_db(); + + dns_test_end(); +} + +ATF_TC(resigned); +ATF_TC_HEAD(resigned, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_rdataset_resigned passes with matching db/version"); +} +ATF_TC_BODY(resigned, tc) { + + UNUSED(tc); + + resigned(NULL); +} + +ATF_TC(resigned_bad); +ATF_TC_HEAD(resigned_bad, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_rdataset_resigned aborts with mis-matching db/version"); +} +ATF_TC_BODY(resigned_bad, tc) { + + UNUSED(tc); + + resigned(local_callback); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, dump); + ATF_TP_ADD_TC(tp, dump_bad); + ATF_TP_ADD_TC(tp, find); + ATF_TP_ADD_TC(tp, find_bad); + ATF_TP_ADD_TC(tp, allrdatasets); + ATF_TP_ADD_TC(tp, allrdatasets_bad); + ATF_TP_ADD_TC(tp, findrdataset); + ATF_TP_ADD_TC(tp, findrdataset_bad); + ATF_TP_ADD_TC(tp, addrdataset); + ATF_TP_ADD_TC(tp, addrdataset_bad); + ATF_TP_ADD_TC(tp, deleterdataset); + ATF_TP_ADD_TC(tp, deleterdataset_bad); + ATF_TP_ADD_TC(tp, subtractrdataset); + ATF_TP_ADD_TC(tp, subtractrdataset_bad); + ATF_TP_ADD_TC(tp, attachversion); + ATF_TP_ADD_TC(tp, attachversion_bad); + ATF_TP_ADD_TC(tp, closeversion); + ATF_TP_ADD_TC(tp, closeversion_bad); + ATF_TP_ADD_TC(tp, getnsec3parameters); + ATF_TP_ADD_TC(tp, getnsec3parameters_bad); + ATF_TP_ADD_TC(tp, resigned); + ATF_TP_ADD_TC(tp, resigned_bad); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/dh_test.c b/lib/dns/tests/dh_test.c new file mode 100644 index 0000000..6216b4e --- /dev/null +++ b/lib/dns/tests/dh_test.c @@ -0,0 +1,92 @@ +/* + * 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 http://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 "../dst_internal.h" + +#include "dnstest.h" + +#if defined(OPENSSL) && !defined(PK11_DH_DISABLE) + +ATF_TC(isc_dh_computesecret); +ATF_TC_HEAD(isc_dh_computesecret, tc) { + atf_tc_set_md_var(tc, "descr", "OpenSSL DH_compute_key() failure"); +} +ATF_TC_BODY(isc_dh_computesecret, tc) { + dst_key_t *key = NULL; + isc_buffer_t buf; + unsigned char array[1024]; + isc_result_t ret; + dns_fixedname_t fname; + dns_name_t *name; + + UNUSED(tc); + + ret = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&buf, "dh.", 3); + isc_buffer_add(&buf, 3); + ret = dns_name_fromtext(name, &buf, NULL, 0, NULL); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + ret = dst_key_fromfile(name, 18602, DST_ALG_DH, + DST_TYPE_PUBLIC | DST_TYPE_KEY, + "./", mctx, &key); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + isc_buffer_init(&buf, array, sizeof(array)); + ret = dst_key_computesecret(key, key, &buf); + ATF_REQUIRE_EQ(ret, DST_R_NOTPRIVATEKEY); + ret = key->func->computesecret(key, key, &buf); + ATF_REQUIRE_EQ(ret, DST_R_COMPUTESECRETFAILURE); + + dst_key_free(&key); + dns_test_end(); +} +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping OpenSSL DH test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("OpenSSL DH not compiled in"); +} +#endif +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#if defined(OPENSSL) && !defined(PK11_DH_DISABLE) + ATF_TP_ADD_TC(tp, isc_dh_computesecret); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + return (atf_no_error()); +} diff --git a/lib/dns/tests/dispatch_test.c b/lib/dns/tests/dispatch_test.c new file mode 100644 index 0000000..156fe9f --- /dev/null +++ b/lib/dns/tests/dispatch_test.c @@ -0,0 +1,341 @@ +/* + * 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 http://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 "dnstest.h" + +dns_dispatchmgr_t *dispatchmgr = NULL; +dns_dispatchset_t *dset = NULL; + +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(mctx, NULL, &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(mctx, socketmgr, taskmgr, disp, + &dset, ndisps); + dns_dispatch_detach(&disp); + + return (result); +} + +static void +teardown(void) { + if (dset != NULL) + dns_dispatchset_destroy(&dset); + if (dispatchmgr != NULL) + dns_dispatchmgr_destroy(&dispatchmgr); +} + +/* + * Individual unit tests + */ +ATF_TC(dispatchset_create); +ATF_TC_HEAD(dispatchset_create, tc) { + atf_tc_set_md_var(tc, "descr", "create dispatch set"); +} +ATF_TC_BODY(dispatchset_create, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = make_dispatchset(1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + teardown(); + + result = make_dispatchset(10); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + teardown(); + + dns_test_end(); +} + +ATF_TC(dispatchset_get); +ATF_TC_HEAD(dispatchset_get, tc) { + atf_tc_set_md_var(tc, "descr", "test dispatch set round-robin"); +} +ATF_TC_BODY(dispatchset_get, tc) { + isc_result_t result; + dns_dispatch_t *d1, *d2, *d3, *d4, *d5; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = make_dispatchset(1); + ATF_REQUIRE_EQ(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); + + ATF_CHECK_EQ(d1, d2); + ATF_CHECK_EQ(d2, d3); + ATF_CHECK_EQ(d3, d4); + ATF_CHECK_EQ(d4, d5); + + teardown(); + + result = make_dispatchset(4); + ATF_REQUIRE_EQ(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); + + ATF_CHECK_EQ(d1, d5); + ATF_CHECK(d1 != d2); + ATF_CHECK(d2 != d3); + ATF_CHECK(d3 != d4); + ATF_CHECK(d4 != d5); + + teardown(); + dns_test_end(); +} + +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 bool first = true; +static isc_mutex_t lock; +static isc_sockaddr_t local; +static unsigned int responses = 0; + +static void +response(isc_task_t *task, isc_event_t *event) { + dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event; + isc_result_t result; + bool wasfirst; + + UNUSED(task); + + LOCK(&lock); + wasfirst = first; + first = false; + responses++; + UNLOCK(&lock); + + if (wasfirst) { + result = dns_dispatch_getnext(dispentry, &devent); + ATF_CHECK_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_event_free(&event); +} + +ATF_TC(dispatch_getnext); +ATF_TC_HEAD(dispatch_getnext, tc) { + atf_tc_set_md_var(tc, "descr", "test dispatch getnext"); +} +ATF_TC_BODY(dispatch_getnext, tc) { + 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(tc); + + result = isc_mutex_init(&lock); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &task); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dispatchmgr_create(mctx, NULL, &dispatchmgr); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Create a local udp nameserver on the loopback. + */ + result = isc_socket_create(socketmgr, AF_INET, isc_sockettype_udp, + &sock); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ina.s_addr = htonl(INADDR_LOOPBACK); + isc_sockaddr_fromin(&local, &ina, 0); + result = isc_socket_bind(sock, &local, 0); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_socket_getsockname(sock, &local); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + first = true; + region.base = rbuf; + region.length = sizeof(rbuf); + result = isc_socket_recv(sock, ®ion, 1, task, nameserver, sock); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dispatch_addresponse(dispatch, &local, task, response, + NULL, &id, &dispentry); + ATF_REQUIRE_EQ(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(mctx, task, startit, ®ion); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_app_run(); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK_EQ(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); + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, dispatchset_create); + ATF_TP_ADD_TC(tp, dispatchset_get); + ATF_TP_ADD_TC(tp, dispatch_getnext); + return (atf_no_error()); +} diff --git a/lib/dns/tests/dnstap_test.c b/lib/dns/tests/dnstap_test.c new file mode 100644 index 0000000..56e3da4 --- /dev/null +++ b/lib/dns/tests/dnstap_test.c @@ -0,0 +1,383 @@ +/* + * 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 http://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 "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" + +/* + * Helper functions + */ +static void +cleanup() { + (void) isc_file_remove(TAPFILE); + (void) isc_file_remove(TAPSOCK); +} + +/* + * Individual unit tests + */ + +ATF_TC(create); +ATF_TC_HEAD(create, tc) { + atf_tc_set_md_var(tc, "descr", "set up dnstap environment"); +} +ATF_TC_BODY(create, tc) { + isc_result_t result; + dns_dtenv_t *dtenv = NULL; + struct fstrm_iothr_options *fopt; + + UNUSED(tc); + + cleanup(); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + fopt = fstrm_iothr_options_init(); + ATF_REQUIRE(fopt != NULL); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(mctx, dns_dtmode_file, TAPFILE, &fopt, &dtenv); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + if (dtenv != NULL) + dns_dt_detach(&dtenv); + if (fopt != NULL) + fstrm_iothr_options_destroy(&fopt); + + ATF_CHECK(isc_file_exists(TAPFILE)); + + fopt = fstrm_iothr_options_init(); + ATF_REQUIRE(fopt != NULL); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(mctx, dns_dtmode_unix, TAPSOCK, &fopt, &dtenv); + ATF_CHECK_EQ(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 */ + ATF_CHECK(!isc_file_exists(TAPSOCK)); + + fopt = fstrm_iothr_options_init(); + ATF_REQUIRE(fopt != NULL); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(mctx, 33, TAPSOCK, &fopt, &dtenv); + ATF_CHECK_EQ(result, ISC_R_FAILURE); + ATF_CHECK_EQ(dtenv, NULL); + if (dtenv != NULL) + dns_dt_detach(&dtenv); + if (fopt != NULL) + fstrm_iothr_options_destroy(&fopt); + + cleanup(); + + dns_dt_shutdown(); + dns_test_end(); +} + +ATF_TC(send); +ATF_TC_HEAD(send, tc) { + atf_tc_set_md_var(tc, "descr", "send dnstap messages"); +} +ATF_TC_BODY(send, tc) { + 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(tc); + + cleanup(); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + result = dns_test_makeview("test", &view); + + fopt = fstrm_iothr_options_init(); + ATF_REQUIRE(fopt != NULL); + fstrm_iothr_options_set_num_input_queues(fopt, 1); + + result = dns_dt_create(mctx, dns_dtmode_file, TAPFILE, &fopt, &dtenv); + ATF_REQUIRE(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + memset(&zr, 0, sizeof(zr)); + isc_buffer_init(&zb, zone, sizeof(zone)); + result = dns_compress_init(&cctx, -1, mctx); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + result = dns_name_towire(zname, &cctx, &zb); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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_dt_shutdown(); + dns_view_detach(&view); + + /* + * XXX now read back and check content. + */ + + result = dns_dt_open(TAPFILE, dns_dtmode_file, mctx, &handle); + ATF_REQUIRE_EQ(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(mctx, &r, &dtdata); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) { + n++; + continue; + } + + ATF_CHECK_EQ(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(); + + dns_test_end(); +} + +ATF_TC(totext); +ATF_TC_HEAD(totext, tc) { + atf_tc_set_md_var(tc, "descr", "dnstap message to text"); +} +ATF_TC_BODY(totext, tc) { + isc_result_t result; + dns_dthandle_t *handle = NULL; + uint8_t *data; + size_t dsize; + FILE *fp = NULL; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + result = dns_dt_open(TAPSAVED, dns_dtmode_file, mctx, &handle); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_stdio_open(TAPTEXT, "r", &fp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* make sure text conversion gets the right local time */ + setenv("TZ", "PST8", 1); + + 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); + ATF_CHECK_EQ(p, s); + if (p == NULL) + break; + + p = strchr(p, '\n'); + if (p != NULL) + *p = '\0'; + + /* parse dnstap frame */ + result = dns_dt_parse(mctx, &r, &dtdata); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) + continue; + + isc_buffer_allocate(mctx, &b, 2048); + ATF_CHECK(b != NULL); + if (b == NULL) + break; + + /* convert to text and compare */ + result = dns_dt_datatotext(dtdata, &b); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK_STREQ((char *) isc_buffer_base(b), s); + + dns_dtdata_free(&dtdata); + isc_buffer_free(&b); + } + + if (handle != NULL) + dns_dt_close(&handle); + cleanup(); + + dns_test_end(); +} + +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping dnstap test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("dnstap not available"); +} +#endif + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#ifdef HAVE_DNSTAP + ATF_TP_ADD_TC(tp, create); + ATF_TP_ADD_TC(tp, send); + ATF_TP_ADD_TC(tp, totext); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + + return (atf_no_error()); +} diff --git a/lib/dns/tests/dnstest.c b/lib/dns/tests/dnstest.c new file mode 100644 index 0000000..51bb90b --- /dev/null +++ b/lib/dns/tests/dnstest.c @@ -0,0 +1,596 @@ +/* + * 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 http://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 "dnstest.h" + +isc_mem_t *mctx = NULL; +isc_entropy_t *ectx = NULL; +isc_log_t *lctx = 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 hash_active = false, dst_active = 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 (app_running) + isc_app_finish(); + if (socketmgr != NULL) + isc_socketmgr_destroy(&socketmgr); + if (maintask != NULL) + isc_task_destroy(&maintask); + if (taskmgr != NULL) + isc_taskmgr_destroy(&taskmgr); + if (timermgr != NULL) + isc_timermgr_destroy(&timermgr); +} + +static isc_result_t +create_managers(void) { + isc_result_t result; +#ifdef ISC_PLATFORM_USETHREADS + ncpus = isc_os_ncpus(); +#else + ncpus = 1; +#endif + + CHECK(isc_taskmgr_create(mctx, ncpus, 0, &taskmgr)); + CHECK(isc_timermgr_create(mctx, &timermgr)); + CHECK(isc_socketmgr_create(mctx, &socketmgr)); + CHECK(isc_task_create(taskmgr, 0, &maintask)); + return (ISC_R_SUCCESS); + + cleanup: + cleanup_managers(); + return (result); +} + +isc_result_t +dns_test_begin(FILE *logfile, bool start_managers) { + isc_result_t result; + + if (start_managers) + CHECK(isc_app_start()); + if (debug_mem_record) + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + CHECK(isc_mem_create(0, 0, &mctx)); + CHECK(isc_entropy_create(mctx, &ectx)); + + CHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)); + hash_active = true; + + CHECK(dst_lib_init(mctx, ectx, ISC_ENTROPY_BLOCKING)); + dst_active = true; + + if (logfile != NULL) { + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + CHECK(isc_log_create(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; + CHECK(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()); + + /* + * atf-run changes us to a /tmp 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) { + if (dst_active) { + dst_lib_destroy(); + dst_active = false; + } + if (hash_active) { + isc_hash_destroy(); + hash_active = false; + } + if (ectx != NULL) + isc_entropy_detach(&ectx); + + cleanup_managers(); + + if (lctx != NULL) + isc_log_destroy(&lctx); + + if (mctx != NULL) + isc_mem_destroy(&mctx); +} + +/* + * 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(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, mctx); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Set zone type and origin. + */ + dns_zone_settype(zone, dns_zone_master); + 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(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) { +#ifdef HAVE_NANOSLEEP + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +#elif HAVE_USLEEP + usleep(usec); +#else + /* + * No fractional-second sleep function is available, so we + * round up to the nearest second and sleep instead + */ + sleep((usec / 1000000) + 1); +#endif +} + +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(mctx, "rbt", name, dbtype, dns_rdataclass_in, + 0, NULL, db); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_db_load(*db, testfile); + 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); + /* NOTREACHED */ +} + +/* + * 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); + ATF_REQUIRE_EQ(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); +} + +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) +{ + isc_buffer_t source, target; + isc_lex_t *lex = NULL; + 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(mctx, 64, &lex); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * 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); + + /* + * Parse input string, determining result. + */ + result = dns_rdata_fromtext(rdata, rdclass, rdtype, lex, dns_rootname, + 0, NULL, &target, NULL); + + 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); + + result = isc_buffer_allocate(mctx, &b, length); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_buffer_putmem(b, (const unsigned char *) namestr, length); + + name = dns_fixedname_initname(fname); + ATF_REQUIRE(name != NULL); + result = dns_name_fromtext(name, b, dns_rootname, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_buffer_free(&b); +} + +isc_result_t +dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes) { + 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(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, 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); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Create a diff tuple for the parsed change and append it to + * the diff. + */ + result = dns_difftuple_create(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); +} diff --git a/lib/dns/tests/dnstest.h b/lib/dns/tests/dnstest.h new file mode 100644 index 0000000..ebbca6c --- /dev/null +++ b/lib/dns/tests/dnstest.h @@ -0,0 +1,135 @@ +/* + * 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 http://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 CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +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 *mctx; +extern isc_entropy_t *ectx; +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. + */ +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); + +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'. + */ +isc_result_t +dns_test_difffromchanges(dns_diff_t *diff, const zonechange_t *changes); diff --git a/lib/dns/tests/dst_test.c b/lib/dns/tests/dst_test.c new file mode 100644 index 0000000..421ab2f --- /dev/null +++ b/lib/dns/tests/dst_test.c @@ -0,0 +1,263 @@ +/* + * 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 http://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 "../dst_internal.h" + +#include "dnstest.h" + +ATF_TC(sig); +ATF_TC_HEAD(sig, tc) { + atf_tc_set_md_var(tc, "descr", "signature ineffability"); +} + +/* + * Read sig in file at path to buf. + */ +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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_file_getsizefd(fileno(fp), &size); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + data = isc_mem_get(mctx, (size + 1)); + ATF_REQUIRE(data != NULL); + + len = (size_t)size; + p = data; + while (len != 0U) { + result = isc_stdio_read(p, 1, len, fp, &rval); + ATF_REQUIRE_EQ(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(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_file_getsizefd(fileno(fp), &size); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + data = isc_mem_get(mctx, (size + 1)); + ATF_REQUIRE(data != NULL); + + p = data; + len = (size_t)size; + do { + result = isc_stdio_read(p, 1, len, fp, &rval); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dst_key_fromfile(name, id, alg, type, "testdata/dst", + mctx, &key); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Verify that the key signed the data. + */ + isc_buffer_remainingregion(&sigbuf, &sigreg); + + result = dst_context_create3(key, mctx, DNS_LOGCATEGORY_GENERAL, + false, &ctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dst_context_adddata(ctx, &datareg); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dst_context_verify(ctx, &sigreg); + + ATF_REQUIRE((expect && (result == ISC_R_SUCCESS)) || + (!expect && (result != ISC_R_SUCCESS))); + + + isc_mem_put(mctx, data, size + 1); + dst_context_destroy(&ctx); + dst_key_free(&key); + + return; +} + +ATF_TC_BODY(sig, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + 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.dsasig", + "test.", 23616, DST_ALG_DSA, true + }, + { + "testdata/dst/test1.data", + "testdata/dst/test1.rsasig", + "test.", 54622, DST_ALG_RSAMD5, true + }, + { + /* wrong sig */ + "testdata/dst/test1.data", + "testdata/dst/test1.dsasig", + "test.", 54622, DST_ALG_RSAMD5, false + }, + { + /* wrong data */ + "testdata/dst/test2.data", + "testdata/dst/test1.dsasig", + "test.", 23616, DST_ALG_DSA, 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); + } + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, sig); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/geoip_test.c b/lib/dns/tests/geoip_test.c new file mode 100644 index 0000000..4e8a5f0 --- /dev/null +++ b/lib/dns/tests/geoip_test.c @@ -0,0 +1,711 @@ +/* + * 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 http://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 "dnstest.h" + +#ifdef HAVE_GEOIP +#include + +/* We use GeoIP databases from the 'geoip' system test */ +#define TEST_GEOIP_DATA "../../../bin/tests/system/geoip/data" + +/* + * Helper functions + * (Mostly copied from bin/named/geoip.c) + */ +static dns_geoip_databases_t geoip = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static void +init_geoip_db(GeoIP **dbp, GeoIPDBTypes edition, GeoIPDBTypes fallback, + GeoIPOptions method, const char *name) +{ + char *info; + GeoIP *db; + + REQUIRE(dbp != NULL); + + db = *dbp; + + if (db != NULL) { + GeoIP_delete(db); + db = *dbp = NULL; + } + + if (! GeoIP_db_avail(edition)) { + fprintf(stderr, "GeoIP %s (type %d) DB not available\n", + name, edition); + goto fail; + } + + fprintf(stderr, "initializing GeoIP %s (type %d) DB\n", + name, edition); + + db = GeoIP_open_type(edition, method); + if (db == NULL) { + fprintf(stderr, + "failed to initialize GeoIP %s (type %d) DB%s\n", + name, edition, fallback == 0 + ? "; geoip matches using this database will fail" + : ""); + goto fail; + } + + info = GeoIP_database_info(db); + if (info != NULL) + fprintf(stderr, "%s\n", info); + + *dbp = db; + return; + + fail: + if (fallback != 0) + init_geoip_db(dbp, fallback, 0, method, name); +} + +static void +load_geoip(const char *dir) { + GeoIPOptions method; + +#ifdef _WIN32 + method = GEOIP_STANDARD; +#else + method = GEOIP_MMAP_CACHE; +#endif + + if (dir != NULL) { + char *p; + DE_CONST(dir, p); + GeoIP_setup_custom_directory(p); + } + + init_geoip_db(&geoip.country_v4, GEOIP_COUNTRY_EDITION, 0, + method, "Country (IPv4)"); +#ifdef HAVE_GEOIP_V6 + init_geoip_db(&geoip.country_v6, GEOIP_COUNTRY_EDITION_V6, 0, + method, "Country (IPv6)"); +#endif + + init_geoip_db(&geoip.city_v4, GEOIP_CITY_EDITION_REV1, + GEOIP_CITY_EDITION_REV0, method, "City (IPv4)"); +#if defined(HAVE_GEOIP_V6) && defined(HAVE_GEOIP_CITY_V6) + init_geoip_db(&geoip.city_v6, GEOIP_CITY_EDITION_REV1_V6, + GEOIP_CITY_EDITION_REV0_V6, method, "City (IPv6)"); +#endif + + init_geoip_db(&geoip.region, GEOIP_REGION_EDITION_REV1, + GEOIP_REGION_EDITION_REV0, method, "Region"); + init_geoip_db(&geoip.isp, GEOIP_ISP_EDITION, 0, + method, "ISP"); + init_geoip_db(&geoip.org, GEOIP_ORG_EDITION, 0, + method, "Org"); + init_geoip_db(&geoip.as, GEOIP_ASNUM_EDITION, 0, + method, "AS"); + init_geoip_db(&geoip.domain, GEOIP_DOMAIN_EDITION, 0, + method, "Domain"); + init_geoip_db(&geoip.netspeed, GEOIP_NETSPEED_EDITION, 0, + method, "NetSpeed"); +} + +static bool +do_lookup_string(const char *addr, uint8_t *scope, + dns_geoip_subtype_t subtype, const char *string) +{ + dns_geoip_elem_t elt; + struct in_addr in4; + isc_netaddr_t na; + + inet_pton(AF_INET, addr, &in4); + isc_netaddr_fromin(&na, &in4); + + elt.subtype = subtype; + strlcpy(elt.as_string, string, sizeof(elt.as_string)); + + return (dns_geoip_match(&na, scope, &geoip, &elt)); +} + +static bool +do_lookup_string_v6(const char *addr, uint8_t *scope, + dns_geoip_subtype_t subtype, const char *string) +{ + dns_geoip_elem_t elt; + struct in6_addr in6; + isc_netaddr_t na; + + inet_pton(AF_INET6, addr, &in6); + isc_netaddr_fromin6(&na, &in6); + + elt.subtype = subtype; + strlcpy(elt.as_string, string, sizeof(elt.as_string)); + + return (dns_geoip_match(&na, scope, &geoip, &elt)); +} + +static bool +do_lookup_int(const char *addr, uint8_t *scope, + dns_geoip_subtype_t subtype, int id) +{ + dns_geoip_elem_t elt; + struct in_addr in4; + isc_netaddr_t na; + + inet_pton(AF_INET, addr, &in4); + isc_netaddr_fromin(&na, &in4); + + elt.subtype = subtype; + elt.as_int = id; + + return (dns_geoip_match(&na, scope, &geoip, &elt)); +} + +/* + * Individual unit tests + */ + +/* GeoIP country matching */ +ATF_TC(country); +ATF_TC_HEAD(country, tc) { + atf_tc_set_md_var(tc, "descr", "test country database matching"); +} +ATF_TC_BODY(country, tc) { + isc_result_t result; + bool match; + uint8_t scope; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.country_v4 == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.1", &scope, + dns_geoip_country_code, "AU"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 32); + + match = do_lookup_string("10.53.0.1", &scope, + dns_geoip_country_code3, "AUS"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 32); + + match = do_lookup_string("10.53.0.1", &scope, + dns_geoip_country_name, "Australia"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 32); + + match = do_lookup_string("192.0.2.128", &scope, + dns_geoip_country_code, "O1"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 24); + + match = do_lookup_string("192.0.2.128", &scope, + dns_geoip_country_name, "Other"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 24); + + dns_test_end(); +} + +/* GeoIP country (ipv6) matching */ +ATF_TC(country_v6); +ATF_TC_HEAD(country_v6, tc) { + atf_tc_set_md_var(tc, "descr", "test country (ipv6) database matching"); +} +ATF_TC_BODY(country_v6, tc) { + isc_result_t result; + bool match; + uint8_t scope; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.country_v6 == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope, + dns_geoip_country_code, "AU"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 128); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope, + dns_geoip_country_code3, "AUS"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 128); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", &scope, + dns_geoip_country_name, "Australia"); + ATF_CHECK(match); + ATF_CHECK_EQ(scope, 128); + + dns_test_end(); +} + +/* GeoIP city (ipv4) matching */ +ATF_TC(city); +ATF_TC_HEAD(city, tc) { + atf_tc_set_md_var(tc, "descr", "test city database matching"); +} +ATF_TC_BODY(city, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.city_v4 == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_continentcode, "NA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_countrycode, "US"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_countrycode3, "USA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_countryname, "United States"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_region, "CA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_regionname, "California"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_name, "Redwood City"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_city_postalcode, "94063"); + ATF_CHECK(match); + + match = do_lookup_int("10.53.0.1", NULL, dns_geoip_city_areacode, 650); + ATF_CHECK(match); + + match = do_lookup_int("10.53.0.1", NULL, dns_geoip_city_metrocode, 807); + ATF_CHECK(match); + + dns_test_end(); +} + +/* GeoIP city (ipv6) matching */ +ATF_TC(city_v6); +ATF_TC_HEAD(city_v6, tc) { + atf_tc_set_md_var(tc, "descr", "test city (ipv6) database matching"); +} +ATF_TC_BODY(city_v6, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.city_v6 == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_continentcode, "NA"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_countrycode, "US"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_countrycode3, "USA"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_countryname, + "United States"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_region, "CA"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_regionname, "California"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_name, "Redwood City"); + ATF_CHECK(match); + + match = do_lookup_string_v6("fd92:7065:b8e:ffff::1", NULL, + dns_geoip_city_postalcode, "94063"); + ATF_CHECK(match); + + dns_test_end(); +} + + +/* GeoIP region matching */ +ATF_TC(region); +ATF_TC_HEAD(region, tc) { + atf_tc_set_md_var(tc, "descr", "test region database matching"); +} +ATF_TC_BODY(region, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.region == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_region_code, "CA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_region_name, "California"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.1", NULL, + dns_geoip_region_countrycode, "US"); + ATF_CHECK(match); + + dns_test_end(); +} + +/* + * GeoIP best-database matching + * (With no specified databse and a city database available, answers + * should come from city database. With city database unavailable, region + * database. Region database unavailable, country database.) + */ +ATF_TC(best); +ATF_TC_HEAD(best, tc) { + atf_tc_set_md_var(tc, "descr", "test best database matching"); +} +ATF_TC_BODY(best, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.region == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode, "US"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode3, "USA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countryname, "United States"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_regionname, "Virginia"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_region, "VA"); + ATF_CHECK(match); + + GeoIP_delete(geoip.city_v4); + geoip.city_v4 = NULL; + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode, "AU"); + ATF_CHECK(match); + + /* + * Note, region doesn't support code3 or countryname, so + * the next two would be answered from the country database instead + */ + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode3, "CAN"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countryname, "Canada"); + ATF_CHECK(match); + + GeoIP_delete(geoip.region); + geoip.region = NULL; + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode, "CA"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countrycode3, "CAN"); + ATF_CHECK(match); + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_countryname, "Canada"); + ATF_CHECK(match); + + dns_test_end(); +} + + +/* GeoIP asnum matching */ +ATF_TC(asnum); +ATF_TC_HEAD(asnum, tc) { + atf_tc_set_md_var(tc, "descr", "test asnum database matching"); +} +ATF_TC_BODY(asnum, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.as == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + + match = do_lookup_string("10.53.0.3", NULL, dns_geoip_as_asnum, + "AS100003 Three Network Labs"); + ATF_CHECK(match); + + dns_test_end(); +} + +/* GeoIP isp matching */ +ATF_TC(isp); +ATF_TC_HEAD(isp, tc) { + atf_tc_set_md_var(tc, "descr", "test isp database matching"); +} +ATF_TC_BODY(isp, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.isp == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.1", NULL, dns_geoip_isp_name, + "One Systems, Inc."); + ATF_CHECK(match); + + dns_test_end(); +} + +/* GeoIP org matching */ +ATF_TC(org); +ATF_TC_HEAD(org, tc) { + atf_tc_set_md_var(tc, "descr", "test org database matching"); +} +ATF_TC_BODY(org, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.org == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.2", NULL, dns_geoip_org_name, + "Two Technology Ltd."); + ATF_CHECK(match); + + dns_test_end(); +} + +/* GeoIP domain matching */ +ATF_TC(domain); +ATF_TC_HEAD(domain, tc) { + atf_tc_set_md_var(tc, "descr", "test domain database matching"); +} +ATF_TC_BODY(domain, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.domain == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_string("10.53.0.4", NULL, + dns_geoip_domain_name, "four.com"); + ATF_CHECK(match); + + dns_test_end(); +} + +/* GeoIP netspeed matching */ +ATF_TC(netspeed); +ATF_TC_HEAD(netspeed, tc) { + atf_tc_set_md_var(tc, "descr", "test netspeed database matching"); +} +ATF_TC_BODY(netspeed, tc) { + isc_result_t result; + bool match; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* Use databases from the geoip system test */ + load_geoip(TEST_GEOIP_DATA); + + if (geoip.netspeed == NULL) { + dns_test_end(); + atf_tc_skip("Database not available"); + } + + match = do_lookup_int("10.53.0.1", NULL, dns_geoip_netspeed_id, 0); + ATF_CHECK(match); + + match = do_lookup_int("10.53.0.2", NULL, dns_geoip_netspeed_id, 1); + ATF_CHECK(match); + + match = do_lookup_int("10.53.0.3", NULL, dns_geoip_netspeed_id, 2); + ATF_CHECK(match); + + match = do_lookup_int("10.53.0.4", NULL, dns_geoip_netspeed_id, 3); + ATF_CHECK(match); + + dns_test_end(); +} +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping geoip test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("GeoIP not available"); +} +#endif + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#ifdef HAVE_GEOIP + ATF_TP_ADD_TC(tp, country); + ATF_TP_ADD_TC(tp, country_v6); + ATF_TP_ADD_TC(tp, city); + ATF_TP_ADD_TC(tp, city_v6); + ATF_TP_ADD_TC(tp, region); + ATF_TP_ADD_TC(tp, best); + ATF_TP_ADD_TC(tp, asnum); + ATF_TP_ADD_TC(tp, isp); + ATF_TP_ADD_TC(tp, org); + ATF_TP_ADD_TC(tp, domain); + ATF_TP_ADD_TC(tp, netspeed); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + + return (atf_no_error()); +} diff --git a/lib/dns/tests/gost_test.c b/lib/dns/tests/gost_test.c new file mode 100644 index 0000000..204c6cc --- /dev/null +++ b/lib/dns/tests/gost_test.c @@ -0,0 +1,379 @@ +/* + * 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 http://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 "dnstest.h" + +#ifdef HAVE_OPENSSL_GOST +#include "../dst_gost.h" +#include +#include +#include +#include +#include +#endif + +#ifdef HAVE_PKCS11_GOST +#include "../dst_gost.h" +#include +#define WANT_GOST_PARAMS +#include +#include +#endif + +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) +/* + * Test data from Wikipedia GOST (hash function) + */ + +unsigned char digest[ISC_GOST_DIGESTLENGTH]; +unsigned char buffer[1024]; +const char *s; +char str[2 * ISC_GOST_DIGESTLENGTH + 3]; +int i = 0; + +/* + * Precondition: a hexadecimal number in *d, the length of that number in len, + * and a pointer to a character array to put the output (*out). + * Postcondition: A String representation of the given hexadecimal number is + * placed into the array *out + * + * 'out' MUST point to an array of at least len * 2 + 1 + * + * Return values: ISC_R_SUCCESS if the operation is sucessful + */ +static isc_result_t +tohexstr(unsigned char *d, unsigned int len, char *out, size_t out_size) { + char c_ret[] = "AA"; + unsigned int j; + + out[0] = '\0'; + strlcat(out, "0x", out_size); + for (j = 0; j < len; j++) { + snprintf(c_ret, sizeof(c_ret), "%02X", d[j]); + strlcat(out, c_ret, out_size); + } + return (ISC_R_SUCCESS); +} + + +#define TEST_INPUT(x) (x), sizeof(x)-1 + +typedef struct hash_testcase { + const char *input; + size_t input_len; + const char *result; + int repeats; +} hash_testcase_t; + +ATF_TC(isc_gost_md); +ATF_TC_HEAD(isc_gost_md, tc) { + atf_tc_set_md_var(tc, "descr", + "GOST R 34.11-94 examples from Wikipedia"); +} +ATF_TC_BODY(isc_gost_md, tc) { + isc_gost_t gost; + isc_result_t result; + + UNUSED(tc); + + /* + * These are the various test vectors. All of these are passed + * through the hash function and the results are compared to the + * result specified here. + */ + hash_testcase_t testcases[] = { + /* Test 1 */ + { + TEST_INPUT(""), + "0x981E5F3CA30C841487830F84FB433E1" + "3AC1101569B9C13584AC483234CD656C0", + 1 + }, + /* Test 2 */ + { + TEST_INPUT("a"), + "0xE74C52DD282183BF37AF0079C9F7805" + "5715A103F17E3133CEFF1AACF2F403011", + 1 + }, + /* Test 3 */ + { + TEST_INPUT("abc"), + "0xB285056DBF18D7392D7677369524DD1" + "4747459ED8143997E163B2986F92FD42C", + 1 + }, + /* Test 4 */ + { + TEST_INPUT("message digest"), + "0xBC6041DD2AA401EBFA6E9886734174F" + "EBDB4729AA972D60F549AC39B29721BA0", + 1 + }, + /* Test 5 */ + { + TEST_INPUT("The quick brown fox jumps " + "over the lazy dog"), + "0x9004294A361A508C586FE53D1F1B027" + "46765E71B765472786E4770D565830A76", + 1 + }, + + /* Test 6 */ + { + TEST_INPUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde" + "fghijklmnopqrstuvwxyz0123456789"), + "0x73B70A39497DE53A6E08C67B6D4DB85" + "3540F03E9389299D9B0156EF7E85D0F61", + 1 + }, + /* Test 7 */ + { + TEST_INPUT("1234567890123456789012345678901" + "2345678901234567890123456789012" + "345678901234567890"), + "0x6BC7B38989B28CF93AE8842BF9D7529" + "05910A7528A61E5BCE0782DE43E610C90", + 1 + }, + /* Test 8 */ + { + TEST_INPUT("This is message, length=32 bytes"), + "0x2CEFC2F7B7BDC514E18EA57FA74FF35" + "7E7FA17D652C75F69CB1BE7893EDE48EB", + 1 + }, + /* Test 9 */ + { + TEST_INPUT("Suppose the original message " + "has length = 50 bytes"), + "0xC3730C5CBCCACF915AC292676F21E8B" + "D4EF75331D9405E5F1A61DC3130A65011", + 1 + }, + /* Test 10 */ + { + TEST_INPUT("U") /* times 128 */, + "0x1C4AC7614691BBF427FA2316216BE8F" + "10D92EDFD37CD1027514C1008F649C4E8", + 128 + }, + /* Test 11 */ + { + TEST_INPUT("a") /* times 1000000 */, + "0x8693287AA62F9478F7CB312EC0866B6" + "C4E4A0F11160441E8F4FFCD2715DD554F", + 1000000 + }, + { NULL, 0, NULL, 1 } + }; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + hash_testcase_t *testcase = testcases; + + while (testcase->input != NULL && testcase->result != NULL) { + result = isc_gost_init(&gost); + ATF_REQUIRE(result == ISC_R_SUCCESS); + for(i = 0; i < testcase->repeats; i++) { + result = isc_gost_update(&gost, + (const uint8_t *) testcase->input, + testcase->input_len); + ATF_REQUIRE(result == ISC_R_SUCCESS); + } + result = isc_gost_final(&gost, digest); + ATF_REQUIRE(result == ISC_R_SUCCESS); + tohexstr(digest, ISC_GOST_DIGESTLENGTH, str, sizeof(str)); + ATF_CHECK_STREQ(str, testcase->result); + + testcase++; + } + + dns_test_end(); +} + +ATF_TC(isc_gost_private); +ATF_TC_HEAD(isc_gost_private, tc) { + atf_tc_set_md_var(tc, "descr", "GOST R 34.10-2001 private key"); +} +ATF_TC_BODY(isc_gost_private, tc) { + isc_result_t result; + unsigned char privraw[31] = { + 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 + }; +#ifdef HAVE_OPENSSL_GOST + unsigned char rbuf[32]; + unsigned char privasn1[70] = { + 0x30, 0x44, 0x02, 0x01, 0x00, 0x30, 0x1c, 0x06, + 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x30, + 0x12, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x23, 0x01, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x1e, 0x01, 0x04, 0x21, 0x02, 0x1f, 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 + }; + unsigned char abuf[71]; + unsigned char gost_dummy_key[71] = { + 0x30, 0x45, 0x02, 0x01, 0x00, 0x30, 0x1c, 0x06, + 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x30, + 0x12, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x23, 0x01, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x1e, 0x01, 0x04, 0x22, 0x02, 0x20, 0x1b, + 0x3f, 0x94, 0xf7, 0x1a, 0x5f, 0x2f, 0xe7, 0xe5, + 0x74, 0x0b, 0x8c, 0xd4, 0xb7, 0x18, 0xdd, 0x65, + 0x68, 0x26, 0xd1, 0x54, 0xfb, 0x77, 0xba, 0x63, + 0x72, 0xd9, 0xf0, 0x63, 0x87, 0xe0, 0xd6 + }; + EVP_PKEY *pkey; + EC_KEY *eckey; + BIGNUM *privkey; + const BIGNUM *privkey1; + const unsigned char *p; + int len; + unsigned char *q; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* raw parse */ + privkey = BN_bin2bn(privraw, (int) sizeof(privraw), NULL); + ATF_REQUIRE(privkey != NULL); + p = gost_dummy_key; + pkey = NULL; + ATF_REQUIRE(d2i_PrivateKey(NID_id_GostR3410_2001, &pkey, &p, + (long) sizeof(gost_dummy_key)) != NULL); + ATF_REQUIRE(pkey != NULL); + ATF_REQUIRE(EVP_PKEY_bits(pkey) == 256); + eckey = EVP_PKEY_get0(pkey); + ATF_REQUIRE(eckey != NULL); + ATF_REQUIRE(EC_KEY_set_private_key(eckey, privkey) == 1); + BN_clear_free(privkey); + + /* asn1 tofile */ + len = i2d_PrivateKey(pkey, NULL); + ATF_REQUIRE(len == 70); + q = abuf; + ATF_REQUIRE(i2d_PrivateKey(pkey, &q) == len); + ATF_REQUIRE(memcmp(abuf, privasn1, len) == 0); + EVP_PKEY_free(pkey); + + /* asn1 parse */ + p = privasn1; + pkey = NULL; + ATF_REQUIRE(d2i_PrivateKey(NID_id_GostR3410_2001, &pkey, &p, + (long) len) != NULL); + ATF_REQUIRE(pkey != NULL); + eckey = EVP_PKEY_get0(pkey); + ATF_REQUIRE(eckey != NULL); + privkey1 = EC_KEY_get0_private_key(eckey); + len = BN_num_bytes(privkey1); + ATF_REQUIRE(len == 31); + ATF_REQUIRE(BN_bn2bin(privkey1, rbuf) == len); + ATF_REQUIRE(memcmp(rbuf, privraw, len) == 0); + + dns_test_end(); +#else + CK_BBOOL truevalue = TRUE; + CK_BBOOL falsevalue = FALSE; + CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY; + CK_KEY_TYPE keyType = CKK_GOSTR3410; + 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_VALUE, privraw, sizeof(privraw) }, + { CKA_GOSTR3410_PARAMS, pk11_gost_a_paramset, + (CK_ULONG) sizeof(pk11_gost_a_paramset) }, + { CKA_GOSTR3411_PARAMS, pk11_gost_paramset, + (CK_ULONG) sizeof(pk11_gost_paramset) } + }; + CK_MECHANISM mech = { CKM_GOSTR3410_WITH_GOSTR3411, NULL, 0 }; + CK_BYTE sig[64]; + CK_ULONG siglen; + pk11_context_t pk11_ctx; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE(result == ISC_R_SUCCESS); + + /* create the private key */ + memset(&pk11_ctx, 0, sizeof(pk11_ctx)); + ATF_REQUIRE(pk11_get_session(&pk11_ctx, OP_GOST, true, + false, false, NULL, + pk11_get_best_token(OP_GOST)) == + ISC_R_SUCCESS); + pk11_ctx.object = CK_INVALID_HANDLE; + pk11_ctx.ontoken = false; + ATF_REQUIRE(pkcs_C_CreateObject(pk11_ctx.session, keyTemplate, + (CK_ULONG) 9, &pk11_ctx.object) == + CKR_OK); + ATF_REQUIRE(pk11_ctx.object != CK_INVALID_HANDLE); + + /* sign something */ + ATF_REQUIRE(pkcs_C_SignInit(pk11_ctx.session, &mech, + pk11_ctx.object) == CKR_OK); + siglen = 0; + ATF_REQUIRE(pkcs_C_Sign(pk11_ctx.session, sig, 64, + NULL, &siglen) == CKR_OK); + ATF_REQUIRE(siglen == 64); + ATF_REQUIRE(pkcs_C_Sign(pk11_ctx.session, sig, 64, + sig, &siglen) == CKR_OK); + ATF_REQUIRE(siglen == 64); + + dns_test_end(); +#endif +}; +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping gost test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("GOST not available"); +} +#endif +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#if defined(HAVE_OPENSSL_GOST) || defined(HAVE_PKCS11_GOST) + ATF_TP_ADD_TC(tp, isc_gost_md); + ATF_TP_ADD_TC(tp, isc_gost_private); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + return (atf_no_error()); +} + diff --git a/lib/dns/tests/keytable_test.c b/lib/dns/tests/keytable_test.c new file mode 100644 index 0000000..ed69fcc --- /dev/null +++ b/lib/dns/tests/keytable_test.c @@ -0,0 +1,622 @@ +/* + * 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 http://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 + +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dnstest.h" + +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 dns_keytag_t keytag1 = 30591; + +static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/inaJpYv7vH XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/ItBfh/hL2lm Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi79532Qeklxh x8pXWdeAaRU="; + +static dns_view_t *view = NULL; + +/* + * Test utilities. In general, these assume input parameters are valid + * (checking with ATF_REQUIRE_EQ, 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)); + ATF_REQUIRE_EQ(dns_name_fromtext(name, &namebuf, dns_rootname, 0, + NULL), ISC_R_SUCCESS); + + return (name); +} + +static void +create_key(uint16_t flags, uint8_t proto, uint8_t alg, + const char *keynamestr, const char *keystr, dst_key_t **target) +{ + dns_rdata_dnskey_t keystruct; + unsigned char keydata[4096]; + isc_buffer_t keydatabuf; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_region_t r; + const dns_rdataclass_t rdclass = dns_rdataclass_in; /* for brevity */ + + keystruct.common.rdclass = rdclass; + keystruct.common.rdtype = dns_rdatatype_dnskey; + keystruct.mctx = NULL; + ISC_LINK_INIT(&keystruct.common, link); + keystruct.flags = flags; + keystruct.protocol = proto; + keystruct.algorithm = alg; + + isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + ATF_REQUIRE_EQ(isc_base64_decodestring(keystr, &keydatabuf), + ISC_R_SUCCESS); + isc_buffer_usedregion(&keydatabuf, &r); + keystruct.datalen = r.length; + keystruct.data = r.base; + ATF_REQUIRE_EQ(dns_rdata_fromstruct(NULL, keystruct.common.rdclass, + keystruct.common.rdtype, + &keystruct, &rrdatabuf), + ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(dst_key_fromdns(str2name(keynamestr), rdclass, + &rrdatabuf, mctx, target), + ISC_R_SUCCESS); +} + +/* Common setup: create a keytable and ntatable to test with a few keys */ +static void +create_tables() { + isc_result_t result; + dst_key_t *key = NULL; + isc_stdtime_t now; + + result = dns_test_makeview("view", &view); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_REQUIRE_EQ(dns_keytable_create(mctx, &keytable), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_ntatable_create(view, taskmgr, timermgr, + &ntatable), ISC_R_SUCCESS); + + /* Add a normal key */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key), + ISC_R_SUCCESS); + + /* Add a null key */ + ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, + str2name("null.example")), + ISC_R_SUCCESS); + + /* Add a negative trust anchor, duration 1 hour */ + isc_stdtime_get(&now); + ATF_REQUIRE_EQ(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); +} + +/* + * Individual unit tests + */ + +ATF_TC(add); +ATF_TC_HEAD(add, tc) { + atf_tc_set_md_var(tc, "descr", "add keys to the keytable"); +} +ATF_TC_BODY(add, tc) { + dst_key_t *key = NULL; + dns_keynode_t *keynode = NULL; + dns_keynode_t *next_keynode = NULL; + dns_keynode_t *null_keynode = NULL; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + create_tables(); + + /* + * Get the keynode for the example.com key. There's no other key for + * the name, so nextkeynode() should return NOTFOUND. + */ + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + + /* + * Try to add the same key. This should have no effect, so + * nextkeynode() should still return NOTFOUND. + */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + + /* Add another key (different keydata) */ + dns_keytable_detachkeynode(keytable, &keynode); + create_key(257, 3, 5, "example.com", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &next_keynode); + + /* + * Add a normal key to a name that has a null key. The null key node + * will be updated with the normal key. + */ + dns_keytable_detachkeynode(keytable, &keynode); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), ISC_R_SUCCESS); + create_key(257, 3, 5, "null.example", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(keynode, null_keynode); /* should be the same node */ + ATF_REQUIRE(dns_keynode_key(keynode) != NULL); /* now have a key */ + 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, with no + * no next node. + * (Note: this and above checks confirm that if a name has a null key + * that's the only key for the name). + */ + ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable, + str2name("null.example")), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &null_keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(keynode, null_keynode); + ATF_REQUIRE(dns_keynode_key(keynode) != NULL); + ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode, + &next_keynode), ISC_R_NOTFOUND); + dns_keytable_detachkeynode(keytable, &null_keynode); + + dns_keytable_detachkeynode(keytable, &keynode); + destroy_tables(); + dns_test_end(); +} + +ATF_TC(delete); +ATF_TC_HEAD(delete, tc) { + atf_tc_set_md_var(tc, "descr", "delete keys from the keytable"); +} +ATF_TC_BODY(delete, tc) { + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + create_tables(); + + /* dns_keytable_delete requires exact match */ + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.org")), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("s.example.com")), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), + ISC_R_SUCCESS); + + /* works also for nodes with a null key */ + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("null.example")), + ISC_R_SUCCESS); + + /* or a negative trust anchor */ + ATF_REQUIRE_EQ(dns_ntatable_delete(ntatable, + str2name("insecure.example")), + ISC_R_SUCCESS); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(deletekeynode); +ATF_TC_HEAD(deletekeynode, tc) { + atf_tc_set_md_var(tc, "descr", "delete key nodes from the keytable"); +} +ATF_TC_BODY(deletekeynode, tc) { + dst_key_t *key = NULL; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + create_tables(); + + /* key name doesn't match */ + create_key(257, 3, 5, "example.org", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* subdomain match is the same as no match */ + create_key(257, 3, 5, "sub.example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* name matches but key doesn't match (resulting in PARTIALMATCH) */ + create_key(257, 3, 5, "example.com", keystr2, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + DNS_R_PARTIALMATCH); + dst_key_free(&key); + + /* + * exact match. after deleting the node the internal rbt node will be + * empty, and any delete or deletekeynode attempt should result in + * NOTFOUND. + */ + create_key(257, 3, 5, "example.com", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")), + ISC_R_NOTFOUND); + dst_key_free(&key); + + /* + * A null key node for a name is not deleted when searched by key; + * it must be deleted by dns_keytable_delete() + */ + create_key(257, 3, 5, "null.example", keystr1, &key); + ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key), + DNS_R_PARTIALMATCH); + ATF_REQUIRE_EQ(dns_keytable_delete(keytable, dst_key_name(key)), + ISC_R_SUCCESS); + dst_key_free(&key); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(find); +ATF_TC_HEAD(find, tc) { + atf_tc_set_md_var(tc, "descr", "check find-variant operations"); +} +ATF_TC_BODY(find, tc) { + dns_keynode_t *keynode = NULL; + dns_fixedname_t fname; + dns_name_t *name; + + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + create_tables(); + + /* + * dns_keytable_find() requires exact name match. It matches node + * that has a null key, too. + */ + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.org"), + &keynode), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("sub.example.com"), + &keynode), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"), + &keynode), ISC_R_SUCCESS); + dns_keytable_detachkeynode(keytable, &keynode); + ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"), + &keynode), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_keynode_key(keynode), NULL); + dns_keytable_detachkeynode(keytable, &keynode); + + /* + * dns_keytable_finddeepestmatch() allows partial match. Also match + * nodes with a null key. + */ + name = dns_fixedname_initname(&fname); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("example.com"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), true); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("s.example.com"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), true); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("example.org"), + name), ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable, + str2name("null.example"), + name), ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dns_name_equal(name, str2name("null.example")), + true); + + /* + * dns_keytable_findkeynode() requires exact name, algorithm, keytag + * match. If algorithm or keytag doesn't match, should result in + * PARTIALMATCH. Same for a node with a null key. + */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.org"), + 5, keytag1, &keynode), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("sub.example.com"), + 5, keytag1, &keynode), + ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 4, keytag1, &keynode), + DNS_R_PARTIALMATCH); /* different algorithm */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 5, keytag1 + 1, &keynode), + DNS_R_PARTIALMATCH); /* different keytag */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("null.example"), + 5, 0, &keynode), + DNS_R_PARTIALMATCH); /* null key */ + ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable, + str2name("example.com"), + 5, keytag1, &keynode), + ISC_R_SUCCESS); /* complete match */ + dns_keytable_detachkeynode(keytable, &keynode); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(issecuredomain); +ATF_TC_HEAD(issecuredomain, tc) { + atf_tc_set_md_var(tc, "descr", "check issecuredomain()"); +} +ATF_TC_BODY(issecuredomain, tc) { + bool issecure; + const char **n; + const char *names[] = {"example.com", "sub.example.com", + "null.example", "sub.null.example", NULL}; + + UNUSED(tc); + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + 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++) { + ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, + str2name(*n), + NULL, + &issecure), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(issecure, true); + } + + /* + * 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. + */ + ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable, + str2name("example.org"), + NULL, + &issecure), + ISC_R_SUCCESS); + ATF_REQUIRE_EQ(issecure, false); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(dump); +ATF_TC_HEAD(dump, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_keytable_dump()"); +} +ATF_TC_BODY(dump, tc) { + UNUSED(tc); + + ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS); + create_tables(); + + /* + * Right now, we only confirm the dump attempt doesn't cause disruption + * (so we don't check the dump content). + */ + ATF_REQUIRE_EQ(dns_keytable_dump(keytable, stdout), ISC_R_SUCCESS); + + destroy_tables(); + dns_test_end(); +} + +ATF_TC(nta); +ATF_TC_HEAD(nta, tc) { + atf_tc_set_md_var(tc, "descr", "check negative trust anchors"); +} +ATF_TC_BODY(nta, tc) { + isc_result_t result; + dst_key_t *key = NULL; + bool issecure, covered; + dns_view_t *myview = NULL; + isc_stdtime_t now; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makeview("view", &myview); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &myview->task); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_initsecroots(myview, mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_view_getsecroots(myview, &keytable); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_initntatable(myview, taskmgr, timermgr); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_view_getntatable(myview, &ntatable); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + create_key(257, 3, 5, "example", keystr1, &key); + result = dns_keytable_add(keytable, false, &key); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_stdtime_get(&now); + result = dns_ntatable_add(ntatable, + str2name("insecure.example"), + false, now, 1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Should be secure */ + result = dns_view_issecuredomain(myview, + str2name("test.secure.example"), + now, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Should not be secure */ + result = dns_view_issecuredomain(myview, + str2name("test.insecure.example"), + now, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(!issecure); + + /* NTA covered */ + covered = dns_view_ntacovers(myview, now, str2name("insecure.example"), + dns_rootname); + ATF_CHECK(covered); + + /* Not NTA covered */ + covered = dns_view_ntacovers(myview, now, str2name("secure.example"), + dns_rootname); + ATF_CHECK(!covered); + + /* As of now + 2, the NTA should be clear */ + result = dns_view_issecuredomain(myview, + str2name("test.insecure.example"), + now + 2, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Now check deletion */ + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + result = dns_ntatable_add(ntatable, str2name("new.example"), + false, now, 3600); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(!issecure); + + result = dns_ntatable_delete(ntatable, str2name("new.example")); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_view_issecuredomain(myview, str2name("test.new.example"), + now, true, &issecure); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK(issecure); + + /* Clean up */ + dns_ntatable_detach(&ntatable); + dns_keytable_detach(&keytable); + dns_view_detach(&myview); + + dns_test_end(); +} + +#else +#include + +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping keytable test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("DNSSEC not available"); +} +#endif + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + ATF_TP_ADD_TC(tp, add); + ATF_TP_ADD_TC(tp, delete); + ATF_TP_ADD_TC(tp, deletekeynode); + ATF_TP_ADD_TC(tp, find); + ATF_TP_ADD_TC(tp, issecuredomain); + ATF_TP_ADD_TC(tp, dump); + ATF_TP_ADD_TC(tp, nta); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + + return (atf_no_error()); +} diff --git a/lib/dns/tests/master_test.c b/lib/dns/tests/master_test.c new file mode 100644 index 0000000..5cce2f3 --- /dev/null +++ b/lib/dns/tests/master_test.c @@ -0,0 +1,684 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ + +#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 isc_result_t +add_callback(void *arg, dns_name_t *owner, dns_rdataset_t *dataset); + +static void +rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header); + +static isc_result_t +add_callback(void *arg, 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); + + result = dns_master_loadfile2(testfile, &dns_origin, &dns_origin, + dns_rdataclass_in, true, + &callbacks, mctx, format); + return (result); +} + +static void +include_callback(const char *filename, void *arg) { + char **argp = (char **) arg; + *argp = isc_mem_strdup(mctx, filename); +} + +/* + * Individual unit tests + */ + +/* Successful load test */ +ATF_TC(load); +ATF_TC_HEAD(load, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() loads a " + "valid master file and returns success"); +} +ATF_TC_BODY(load, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master1.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + + +/* Unepxected end of file test */ +ATF_TC(unexpected); +ATF_TC_HEAD(unexpected, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() returns " + "DNS_R_UNEXPECTED when file ends " + "too soon"); +} +ATF_TC_BODY(unexpected, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master2.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_UNEXPECTEDEND); + + dns_test_end(); +} + + +/* No owner test */ +ATF_TC(noowner); +ATF_TC_HEAD(noowner, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() accepts broken " + "zones with no TTL for first record " + "if it is an SOA"); +} +ATF_TC_BODY(noowner, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master3.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, DNS_R_NOOWNER); + + dns_test_end(); +} + + +/* No TTL test */ +ATF_TC(nottl); +ATF_TC_HEAD(nottl, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() returns " + "DNS_R_NOOWNER when no owner name " + "is specified"); +} + +ATF_TC_BODY(nottl, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master4.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + + +/* Bad class test */ +ATF_TC(badclass); +ATF_TC_HEAD(badclass, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() returns " + "DNS_R_BADCLASS when record class " + "doesn't match zone class"); +} +ATF_TC_BODY(badclass, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master5.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, DNS_R_BADCLASS); + + dns_test_end(); +} + +/* Too big rdata test */ +ATF_TC(toobig); +ATF_TC_HEAD(toobig, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() returns " + "ISC_R_NOSPACE when record is too big"); +} +ATF_TC_BODY(toobig, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master15.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_NOSPACE); + + dns_test_end(); +} + +/* Maximum rdata test */ +ATF_TC(maxrdata); +ATF_TC_HEAD(maxrdata, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() returns " + "ISC_R_SUCCESS when record is maximum " + "size"); +} +ATF_TC_BODY(maxrdata, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master16.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + +/* DNSKEY test */ +ATF_TC(dnskey); +ATF_TC_HEAD(dnskey, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() understands " + "DNSKEY with key material"); +} +ATF_TC_BODY(dnskey, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master6.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + + +/* DNSKEY with no key material test */ +ATF_TC(dnsnokey); +ATF_TC_HEAD(dnsnokey, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() understands " + "DNSKEY with no key material"); +} +ATF_TC_BODY(dnsnokey, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master7.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + +/* Include test */ +ATF_TC(include); +ATF_TC_HEAD(include, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() understands " + "$INCLUDE"); +} +ATF_TC_BODY(include, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master8.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, DNS_R_SEENINCLUDE); + + dns_test_end(); +} + +/* Include file list test */ +ATF_TC(master_includelist); +ATF_TC_HEAD(master_includelist, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile4() returns " + "names of included file"); +} +ATF_TC_BODY(master_includelist, tc) { + isc_result_t result; + char *filename = NULL; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = setup_master(NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_master_loadfile4("testdata/master/master8.data", + &dns_origin, &dns_origin, + dns_rdataclass_in, 0, true, + &callbacks, include_callback, + &filename, mctx, dns_masterformat_text); + ATF_CHECK_EQ(result, DNS_R_SEENINCLUDE); + ATF_CHECK(filename != NULL); + if (filename != NULL) { + ATF_CHECK_STREQ(filename, "testdata/master/master7.data"); + isc_mem_free(mctx, filename); + } + + dns_test_end(); +} + +/* Include failure test */ +ATF_TC(includefail); +ATF_TC_HEAD(includefail, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() understands " + "$INCLUDE failures"); +} +ATF_TC_BODY(includefail, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master9.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, DNS_R_BADCLASS); + + dns_test_end(); +} + + +/* Non-empty blank lines test */ +ATF_TC(blanklines); +ATF_TC_HEAD(blanklines, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() handles " + "non-empty blank lines"); +} +ATF_TC_BODY(blanklines, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master10.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + +/* SOA leading zeroes test */ +ATF_TC(leadingzero); +ATF_TC_HEAD(leadingzero, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() allows " + "leading zeroes in SOA"); +} +ATF_TC_BODY(leadingzero, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("testdata/master/master11.data", + dns_masterformat_text, NULL, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_test_end(); +} + +ATF_TC(totext); +ATF_TC_HEAD(totext, tc) { + atf_tc_set_md_var(tc, "descr", "masterfile totext tests"); +} +ATF_TC_BODY(totext, tc) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + isc_buffer_t target; + unsigned char buf[BIGBUFLEN]; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* 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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + isc_buffer_init(&target, buf, BIGBUFLEN); + result = dns_master_rdatasettotext(dns_rootname, + &rdataset, &dns_master_style_debug, + &target); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(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. + */ + + dns_test_end(); +} + +/* Raw load */ +ATF_TC(loadraw); +ATF_TC_HEAD(loadraw, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() loads a " + "valid raw file and returns success"); +} +ATF_TC_BODY(loadraw, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Raw format version 0 */ + result = test_master("testdata/master/master12.data", + dns_masterformat_raw, NULL, NULL); + ATF_CHECK_STREQ(isc_result_totext(result), "success"); + ATF_CHECK(headerset); + ATF_CHECK_EQ(header.flags, 0); + + /* Raw format version 1, no source serial */ + result = test_master("testdata/master/master13.data", + dns_masterformat_raw, NULL, NULL); + ATF_CHECK_STREQ(isc_result_totext(result), "success"); + ATF_CHECK(headerset); + ATF_CHECK_EQ(header.flags, 0); + + /* Raw format version 1, source serial == 2011120101 */ + result = test_master("testdata/master/master14.data", + dns_masterformat_raw, NULL, NULL); + ATF_CHECK_STREQ(isc_result_totext(result), "success"); + ATF_CHECK(headerset); + ATF_CHECK((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0); + ATF_CHECK_EQ(header.sourceserial, 2011120101); + + dns_test_end(); +} + +/* Raw dump*/ +ATF_TC(dumpraw); +ATF_TC_HEAD(dumpraw, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_dump*() functions " + "dump valid raw files"); +} +ATF_TC_BODY(dumpraw, tc) { + 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(tc); + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_create(mctx, "rbt", &dnsorigin, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_load(db, "testdata/master/master1.data"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_db_currentversion(db, &version); + + result = dns_master_dump2(mctx, db, version, + &dns_master_style_default, "test.dump", + dns_masterformat_raw); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("test.dump", dns_masterformat_raw, NULL, NULL); + ATF_CHECK_STREQ(isc_result_totext(result), "success"); + ATF_CHECK(headerset); + ATF_CHECK_EQ(header.flags, 0); + + dns_master_initrawheader(&header); + header.sourceserial = 12345; + header.flags |= DNS_MASTERRAW_SOURCESERIALSET; + + unlink("test.dump"); + result = dns_master_dump3(mctx, db, version, + &dns_master_style_default, "test.dump", + dns_masterformat_raw, &header); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = test_master("test.dump", dns_masterformat_raw, NULL, NULL); + ATF_CHECK_STREQ(isc_result_totext(result), "success"); + ATF_CHECK(headerset); + ATF_CHECK((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0); + ATF_CHECK_EQ(header.sourceserial, 12345); + + unlink("test.dump"); + dns_db_closeversion(db, &version, false); + dns_db_detach(&db); + dns_test_end(); +} + +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); + + 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 */ +ATF_TC(neworigin); +ATF_TC_HEAD(neworigin, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() rejects " + "zones with inherited name following " + "$ORIGIN"); +} +ATF_TC_BODY(neworigin, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + warn_expect_value = "record with inherited owner"; + warn_expect_result = false; + result = test_master("testdata/master/master17.data", + dns_masterformat_text, warn_expect, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_MSG(warn_expect_result, "'%s' warning not emitted", + warn_expect_value); + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, load); + ATF_TP_ADD_TC(tp, unexpected); + ATF_TP_ADD_TC(tp, noowner); + ATF_TP_ADD_TC(tp, nottl); + ATF_TP_ADD_TC(tp, badclass); + ATF_TP_ADD_TC(tp, dnskey); + ATF_TP_ADD_TC(tp, dnsnokey); + ATF_TP_ADD_TC(tp, include); + ATF_TP_ADD_TC(tp, master_includelist); + ATF_TP_ADD_TC(tp, includefail); + ATF_TP_ADD_TC(tp, blanklines); + ATF_TP_ADD_TC(tp, leadingzero); + ATF_TP_ADD_TC(tp, totext); + ATF_TP_ADD_TC(tp, loadraw); + ATF_TP_ADD_TC(tp, dumpraw); + ATF_TP_ADD_TC(tp, toobig); + ATF_TP_ADD_TC(tp, maxrdata); + ATF_TP_ADD_TC(tp, neworigin); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/mkraw.pl b/lib/dns/tests/mkraw.pl new file mode 100644 index 0000000..9791adc --- /dev/null +++ b/lib/dns/tests/mkraw.pl @@ -0,0 +1,24 @@ +#!/usr/bin/perl -w +# +# 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 http://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..1953b41 --- /dev/null +++ b/lib/dns/tests/name_test.c @@ -0,0 +1,772 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Individual unit tests + */ + +ATF_TC(fullcompare); +ATF_TC_HEAD(fullcompare, tc) { + atf_tc_set_md_var(tc, "descr", "dns_name_fullcompare test"); +} +ATF_TC_BODY(fullcompare, tc) { + 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(tc); + + 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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + relation = dns_name_fullcompare(name1, name1, &order, &nlabels); + ATF_REQUIRE_EQ(relation, dns_namereln_equal); + ATF_REQUIRE_EQ(order, 0); + ATF_REQUIRE_EQ(nlabels, name1->labels); + + /* Some random initializer */ + order = 3001; + nlabels = 3001; + + relation = dns_name_fullcompare(name1, name2, &order, &nlabels); + ATF_REQUIRE_EQ(relation, data[i].relation); + ATF_REQUIRE_EQ(order, data[i].order); + ATF_REQUIRE_EQ(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)); + + ATF_REQUIRE_EQ(dns_name_towire(name1, cctx, &source), ISC_R_SUCCESS); + + ATF_CHECK_EQ(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS); + ATF_CHECK_EQ(dns_name_towire(name2, cctx, &source), ISC_R_SUCCESS); + ATF_CHECK_EQ(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, false, + &target) == ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, false, + &target) == ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, false, + &target) == ISC_R_SUCCESS); + RUNTIME_CHECK(dns_name_fromwire(&name, &source, dctx, false, + &target) == ISC_R_SUCCESS); + dns_decompress_invalidate(dctx); + + ATF_CHECK_EQ(target.used, length); + ATF_CHECK(memcmp(target.base, expected, target.used) == 0); +} + +ATF_TC(compression); +ATF_TC_HEAD(compression, tc) { + atf_tc_set_md_var(tc, "descr", "name compression test"); +} +ATF_TC_BODY(compression, tc) { + 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"; + + ATF_REQUIRE_EQ(dns_test_begin(NULL, false), ISC_R_SUCCESS);; + + 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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; + ATF_REQUIRE_EQ(dns_compress_init(&cctx, -1, 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); + + dns_test_end(); +} + +ATF_TC(istat); +ATF_TC_HEAD(istat, tc) { + atf_tc_set_md_var(tc, "descr", "is trust-anchor-telemetry test"); +} +ATF_TC_BODY(istat, tc) { + 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 } + }; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ_MSG(dns_name_istat(name), data[i].istat, + "testing %s - expected %u", data[i].name, data[i].istat); + } +} + +ATF_TC(init); +ATF_TC_HEAD(init, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_init"); +} +ATF_TC_BODY(init, tc) { + dns_name_t name; + unsigned char offsets[1]; + + dns_name_init(&name, offsets); + + ATF_CHECK_EQ(name.ndata, NULL); + ATF_CHECK_EQ(name.length, 0); + ATF_CHECK_EQ(name.labels, 0); + ATF_CHECK_EQ(name.attributes, 0); + ATF_CHECK_EQ(name.offsets, offsets); + ATF_CHECK_EQ(name.buffer, NULL); +} + +ATF_TC(invalidate); +ATF_TC_HEAD(invalidate, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_invalidate"); +} +ATF_TC_BODY(invalidate, tc) { + dns_name_t name; + unsigned char offsets[1]; + + dns_name_init(&name, offsets); + dns_name_invalidate(&name); + + ATF_CHECK_EQ(name.ndata, NULL); + ATF_CHECK_EQ(name.length, 0); + ATF_CHECK_EQ(name.labels, 0); + ATF_CHECK_EQ(name.attributes, 0); + ATF_CHECK_EQ(name.offsets, NULL); + ATF_CHECK_EQ(name.buffer, NULL); +} + +ATF_TC(buffer); +ATF_TC_HEAD(buffer, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_setbuffer/hasbuffer"); +} +ATF_TC_BODY(buffer, tc) { + dns_name_t name; + unsigned char buf[BUFSIZ]; + isc_buffer_t b; + + isc_buffer_init(&b, buf, BUFSIZ); + dns_name_init(&name, NULL); + dns_name_setbuffer(&name, &b); + ATF_CHECK_EQ(name.buffer, &b); + ATF_CHECK(dns_name_hasbuffer(&name)); +} + +ATF_TC(isabsolute); +ATF_TC_HEAD(isabsolute, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_isabsolute"); +} +ATF_TC_BODY(isabsolute, tc) { + struct { + const char *namestr; + bool expect; + } testcases[] = { + { "x", false }, + { "a.b.c.d.", true }, + { "x.z", false} + }; + unsigned int i; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ATF_CHECK_EQ(dns_name_isabsolute(&name), testcases[i].expect); + } +} + +ATF_TC(hash); +ATF_TC_HEAD(hash, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_hash"); +} +ATF_TC_BODY(hash, tc) { + 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; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, + NULL, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Check case-insensitive hashing first */ + h1 = dns_name_hash(n1, false); + h2 = dns_name_hash(n2, false); + + printf("%s hashes to %u, %s to %u, case insensitive\n", + testcases[i].name1, h1, testcases[i].name2, h2); + + ATF_REQUIRE_EQ((h1 == h2), testcases[i].expect); + + /* Now case-sensitive */ + h1 = dns_name_hash(n1, false); + h2 = dns_name_hash(n2, false); + + printf("%s hashes to %u, %s to %u, case sensitive\n", + testcases[i].name1, h1, testcases[i].name2, h2); + + ATF_REQUIRE_EQ((h1 == h2), testcases[i].expect); + } +} + +ATF_TC(issubdomain); +ATF_TC_HEAD(issubdomain, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_issubdomain"); +} +ATF_TC_BODY(issubdomain, tc) { + 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; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, + NULL, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + printf("check: %s %s a subdomain of %s\n", + testcases[i].name1, + testcases[i].expect ? "is" : "is not", + testcases[i].name2); + + ATF_CHECK_EQ(dns_name_issubdomain(n1, n2), + testcases[i].expect); + } +} + +ATF_TC(countlabels); +ATF_TC_HEAD(countlabels, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_countlabels"); +} +ATF_TC_BODY(countlabels, tc) { + 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; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + printf("%s: expect %u labels\n", + testcases[i].namestr, testcases[i].expect); + + ATF_REQUIRE_EQ(dns_name_countlabels(name), + testcases[i].expect); + } +} + +ATF_TC(getlabel); +ATF_TC_HEAD(getlabel, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_getlabel"); +} +ATF_TC_BODY(getlabel, tc) { + 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; + + 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 char *p1, *p2; + unsigned int j; + + n1 = dns_fixedname_initname(&f1); + n2 = dns_fixedname_initname(&f2); + + result = dns_name_fromstring2(n1, testcases[i].name1, + NULL, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, + NULL, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_name_getlabel(n1, testcases[i].pos1, &l1); + dns_name_getlabel(n2, testcases[i].pos2, &l2); + ATF_CHECK_EQ(l1.length, l2.length); + + p1 = l1.base; + p2 = l2.base; + for (j = 0; j < l1.length; j++) { + ATF_REQUIRE_EQ(*p1++, *p2++); + } + } +} + +ATF_TC(getlabelsequence); +ATF_TC_HEAD(getlabelsequence, tc) { + atf_tc_set_md_var(tc, "descr", "dns_nane_getlabelsequence"); +} +ATF_TC_BODY(getlabelsequence, tc) { + 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; + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_name_fromstring2(n2, testcases[i].name2, + NULL, 0, NULL); + ATF_REQUIRE_EQ(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); + + ATF_REQUIRE(dns_name_equal(&t1, &t2)); + } +} + +#ifdef ISC_PLATFORM_USETHREADS +#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. + */ + +ATF_TC(benchmark); +ATF_TC_HEAD(benchmark, tc) { + atf_tc_set_md_var(tc, "descr", + "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); +} + +ATF_TC_BODY(benchmark, tc) { + isc_result_t result; + unsigned int i; + isc_time_t ts1, ts2; + double t; + unsigned int nthreads; + isc_thread_t threads[32]; + + UNUSED(tc); + + debug_mem_record = false; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_time_now(&ts1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + nthreads = ISC_MIN(isc_os_ncpus(), 32); + nthreads = ISC_MAX(nthreads, 1); + for (i = 0; i < nthreads; i++) { + result = isc_thread_create(fromwire_thread, NULL, &threads[i]); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + for (i = 0; i < nthreads; i++) { + result = isc_thread_join(threads[i], NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + result = isc_time_now(&ts2); + ATF_REQUIRE_EQ(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)); + + dns_test_end(); +} + +#endif /* DNS_BENCHMARK_TESTS */ +#endif /* ISC_PLATFORM_USETHREADS */ + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, fullcompare); + ATF_TP_ADD_TC(tp, compression); + ATF_TP_ADD_TC(tp, istat); + ATF_TP_ADD_TC(tp, init); + ATF_TP_ADD_TC(tp, invalidate); + ATF_TP_ADD_TC(tp, buffer); + ATF_TP_ADD_TC(tp, isabsolute); + ATF_TP_ADD_TC(tp, hash); + ATF_TP_ADD_TC(tp, issubdomain); + ATF_TP_ADD_TC(tp, countlabels); + ATF_TP_ADD_TC(tp, getlabel); + ATF_TP_ADD_TC(tp, getlabelsequence); +#ifdef ISC_PLATFORM_USETHREADS +#ifdef DNS_BENCHMARK_TESTS + ATF_TP_ADD_TC(tp, benchmark); +#endif /* DNS_BENCHMARK_TESTS */ +#endif /* ISC_PLATFORM_USETHREADS */ + + return (atf_no_error()); +} diff --git a/lib/dns/tests/nsec3_test.c b/lib/dns/tests/nsec3_test.c new file mode 100644 index 0000000..8a53344 --- /dev/null +++ b/lib/dns/tests/nsec3_test.c @@ -0,0 +1,207 @@ +/* + * 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 http://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 "dnstest.h" + +#if defined(OPENSSL) || defined(PKCS11CRYPTO) +/* + * Helper functions + */ + +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); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, "%s", file); + + result = dns_nsec3_maxiterations(db, NULL, mctx, &iterations); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, "%s", file); + + ATF_CHECK_EQ_MSG(iterations, expected, "%s", file); + + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Check typical use. + */ + result = dns_nsec3param_salttotext(&nsec3param, salt, sizeof(salt)); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, + "\"%s\": expected success, got %s\n", + params->nsec3param_text, isc_result_totext(result)); + ATF_CHECK_EQ_MSG(strcmp(salt, params->expected_salt), 0, + "\"%s\": expected salt \"%s\", got \"%s\"", + params->nsec3param_text, params->expected_salt, 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); + ATF_REQUIRE(length < sizeof(salt) - 1); /* prevent buffer overwrite */ + ATF_REQUIRE(length > 0U); /* prevent length underflow */ + + result = dns_nsec3param_salttotext(&nsec3param, salt, length - 1); + ATF_CHECK_EQ_MSG(result, ISC_R_NOSPACE, + "\"%s\": expected a %lu-byte target buffer to be " + "rejected, got %s\n", + params->nsec3param_text, (unsigned long)(length - 1), + isc_result_totext(result)); + result = dns_nsec3param_salttotext(&nsec3param, salt, length); + ATF_CHECK_EQ_MSG(result, ISC_R_NOSPACE, + "\"%s\": expected a %lu-byte target buffer to be " + "rejected, got %s\n", + params->nsec3param_text, (unsigned long)length, + isc_result_totext(result)); + result = dns_nsec3param_salttotext(&nsec3param, salt, length + 1); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, + "\"%s\": expected a %lu-byte target buffer to be " + "accepted, got %s\n", + params->nsec3param_text, (unsigned long)(length + 1), + isc_result_totext(result)); +} + +/* + * Individual unit tests + */ + +ATF_TC(max_iterations); +ATF_TC_HEAD(max_iterations, tc) { + atf_tc_set_md_var(tc, "descr", "check that appropriate max iterations " + " is returned for different key size mixes"); +} +ATF_TC_BODY(max_iterations, tc) { + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + iteration_test("testdata/nsec3/1024.db", 150); + iteration_test("testdata/nsec3/2048.db", 500); + iteration_test("testdata/nsec3/4096.db", 2500); + iteration_test("testdata/nsec3/min-1024.db", 150); + iteration_test("testdata/nsec3/min-2048.db", 500); + + dns_test_end(); +} + +ATF_TC(nsec3param_salttotext); +ATF_TC_HEAD(nsec3param_salttotext, tc) { + atf_tc_set_md_var(tc, "descr", "check dns_nsec3param_salttotext()"); +} +ATF_TC_BODY(nsec3param_salttotext, tc) { + isc_result_t result; + 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(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + nsec3param_salttotext_test(&tests[i]); + } + + dns_test_end(); +} +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping nsec3 test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("DNSSEC not available"); +} +#endif + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + ATF_TP_ADD_TC(tp, max_iterations); + ATF_TP_ADD_TC(tp, nsec3param_salttotext); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + + return (atf_no_error()); +} + diff --git a/lib/dns/tests/peer_test.c b/lib/dns/tests/peer_test.c new file mode 100644 index 0000000..1cff105 --- /dev/null +++ b/lib/dns/tests/peer_test.c @@ -0,0 +1,140 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Individual unit tests + */ +ATF_TC(dscp); +ATF_TC_HEAD(dscp, tc) { + atf_tc_set_md_var(tc, "descr", + "Test DSCP set/get functions"); +} +ATF_TC_BODY(dscp, tc) { + isc_result_t result; + isc_netaddr_t netaddr; + struct in_addr ina; + dns_peer_t *peer = NULL; + isc_dscp_t dscp; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Create peer structure for the loopback address. + */ + ina.s_addr = INADDR_LOOPBACK; + isc_netaddr_fromin(&netaddr, &ina); + result = dns_peer_new(mctx, &netaddr, &peer); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * All should be not set on creation. + * 'dscp' should remain unchanged. + */ + dscp = 100; + result = dns_peer_getquerydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dscp, 100); + + result = dns_peer_getnotifydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dscp, 100); + + result = dns_peer_gettransferdscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_peer_getnotifydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dscp, 100); + + result = dns_peer_gettransferdscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dscp, 100); + + result = dns_peer_getquerydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_peer_gettransferdscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + ATF_REQUIRE_EQ(dscp, 100); + + result = dns_peer_getquerydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dscp, 1); + + result = dns_peer_getnotifydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_peer_getquerydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dscp, 1); + + result = dns_peer_getnotifydscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dscp, 2); + + result = dns_peer_gettransferdscp(peer, &dscp); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(dscp, 3); + + dns_peer_detach(&peer); + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, dscp); + return (atf_no_error()); +} diff --git a/lib/dns/tests/private_test.c b/lib/dns/tests/private_test.c new file mode 100644 index 0000000..dcb859e --- /dev/null +++ b/lib/dns/tests/private_test.c @@ -0,0 +1,220 @@ +/* + * 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 http://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 "dnstest.h" + +static dns_rdatatype_t privatetype = 65534; + +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; + +/* + * Helper functions + */ +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 (*sp == '\0' && slen > 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); +} + +/* + * Individual unit tests + */ +ATF_TC(private_signing_totext); +ATF_TC_HEAD(private_signing_totext, tc) { + atf_tc_set_md_var(tc, "descr", + "convert private signing records to text"); +} +ATF_TC_BODY(private_signing_totext, tc) { + isc_result_t result; + 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(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + 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); + ATF_CHECK_STREQ(output, results[i]); + } + + dns_test_end(); +} + +ATF_TC(private_nsec3_totext); +ATF_TC_HEAD(private_nsec3_totext, tc) { + atf_tc_set_md_var(tc, "descr", "convert private chain records to text"); +} +ATF_TC_BODY(private_nsec3_totext, tc) { + isc_result_t result; + 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(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + 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); + ATF_CHECK_STREQ(output, results[i]); + } + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, private_signing_totext); + ATF_TP_ADD_TC(tp, private_nsec3_totext); + return (atf_no_error()); +} diff --git a/lib/dns/tests/rbt_serialize_test.c b/lib/dns/tests/rbt_serialize_test.c new file mode 100644 index 0000000..07d4e56 --- /dev/null +++ b/lib/dns/tests/rbt_serialize_test.c @@ -0,0 +1,456 @@ +/* + * 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 http://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 "dnstest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +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 *)datap; + data_holder_t temp; + off_t where; + + UNUSED(arg); + + REQUIRE(file != NULL); + REQUIRE(crc != NULL); + REQUIRE(data != NULL); + 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 = p->data; + size_t size; + + UNUSED(base); + UNUSED(max); + UNUSED(arg); + + REQUIRE(crc != NULL); + REQUIRE(p != NULL); + + + if (data == NULL) + printf("fixing data: data NULL\n"); + else + printf("fixing data: len %d, data %p\n", data->len, data->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) { + printf("data invalid\n"); + return (ISC_R_INVALIDFILE); + } + + isc_crc64_update(crc, (void *)data, sizeof(*data)); + + data->data = (data->len == 0) + ? NULL + : (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 *mymctx, 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, mymctx); + + 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); + ATF_CHECK_STREQ(dns_result_totext(result), "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); + ATF_CHECK_STREQ(dns_result_totext(result), "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); +} + +ATF_TC(serialize); +ATF_TC_HEAD(serialize, tc) { + atf_tc_set_md_var(tc, "descr", "Test writing an rbt to file"); +} +ATF_TC_BODY(serialize, tc) { + 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(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + result = dns_rbt_create(mctx, delete_data, NULL, &rbt); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + + add_test_data(mctx, rbt); + + dns_rbt_printtext(rbt, data_printer, stdout); + + /* + * Serialize the tree. + */ + printf("serialization begins.\n"); + rbtfile = fopen("./zone.bin", "w+b"); + ATF_REQUIRE(rbtfile != NULL); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + ATF_REQUIRE(result == ISC_R_SUCCESS); + dns_rbt_destroy(&rbt); + + /* + * Deserialize the tree + */ + printf("deserialization begins.\n"); + + /* + * Map in the whole file in one go + */ + fd = open("zone.bin", O_RDWR); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, + PROT_READ|PROT_WRITE, + MAP_FILE|MAP_PRIVATE, fd, 0); + ATF_REQUIRE(base != NULL && base != MAP_FAILED); + close(fd); + + result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, + delete_data, NULL, fix_data, NULL, + NULL, &rbt_deserialized); + + /* Test to make sure we have a valid tree */ + ATF_REQUIRE(result == ISC_R_SUCCESS); + if (rbt_deserialized == NULL) + atf_tc_fail("deserialized rbt is null!"); /* Abort execution. */ + + check_test_data(rbt_deserialized); + + dns_rbt_printtext(rbt_deserialized, data_printer, stdout); + + dns_rbt_destroy(&rbt_deserialized); + munmap(base, filesize); + unlink("zone.bin"); + dns_test_end(); +} + +ATF_TC(deserialize_corrupt); +ATF_TC_HEAD(deserialize_corrupt, tc) { + atf_tc_set_md_var(tc, "descr", "Test reading a corrupt map file"); +} +ATF_TC_BODY(deserialize_corrupt, tc) { + 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; + uint32_t r; + int i; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Set up map file */ + result = dns_rbt_create(mctx, delete_data, NULL, &rbt); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + add_test_data(mctx, rbt); + rbtfile = fopen("./zone.bin", "w+b"); + ATF_REQUIRE(rbtfile != NULL); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + ATF_REQUIRE(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); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, + PROT_READ|PROT_WRITE, + MAP_FILE|MAP_PRIVATE, fd, 0); + ATF_REQUIRE(base != NULL && base != MAP_FAILED); + close(fd); + + /* Randomly fuzz a portion of the memory */ + isc_random_get(&r); + p = base + (r % filesize); + q = base + filesize; + isc_random_get(&r); + q -= (r % (q - p)); + while (p++ < q) { + isc_random_get(&r); + *p = r & 0xff; + } + + result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, + delete_data, NULL, + fix_data, NULL, + NULL, &rbt_deserialized); + printf("%d: %s\n", i, isc_result_totext(result)); + + /* Test to make sure we have a valid tree */ + ATF_REQUIRE(result == ISC_R_SUCCESS || + result == ISC_R_INVALIDFILE); + if (result != ISC_R_SUCCESS) + ATF_REQUIRE(rbt_deserialized == NULL); + + if (rbt_deserialized != NULL) + dns_rbt_destroy(&rbt_deserialized); + + munmap(base, filesize); + } + + unlink("zone.bin"); + dns_test_end(); +} + + +ATF_TC(serialize_align); +ATF_TC_HEAD(serialize_align, tc) { + atf_tc_set_md_var(tc, "descr", + "Test the dns_rbt_serialize_align() function."); +} +ATF_TC_BODY(serialize_align, tc) { + UNUSED(tc); + + ATF_CHECK(dns_rbt_serialize_align(0) == 0); + ATF_CHECK(dns_rbt_serialize_align(1) == 8); + ATF_CHECK(dns_rbt_serialize_align(2) == 8); + ATF_CHECK(dns_rbt_serialize_align(3) == 8); + ATF_CHECK(dns_rbt_serialize_align(4) == 8); + ATF_CHECK(dns_rbt_serialize_align(5) == 8); + ATF_CHECK(dns_rbt_serialize_align(6) == 8); + ATF_CHECK(dns_rbt_serialize_align(7) == 8); + ATF_CHECK(dns_rbt_serialize_align(8) == 8); + ATF_CHECK(dns_rbt_serialize_align(9) == 16); + ATF_CHECK(dns_rbt_serialize_align(0xff) == 0x100); + ATF_CHECK(dns_rbt_serialize_align(0x301) == 0x308); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, serialize); + ATF_TP_ADD_TC(tp, deserialize_corrupt); + ATF_TP_ADD_TC(tp, serialize_align); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/rbt_test.c b/lib/dns/tests/rbt_test.c new file mode 100644 index 0000000..81d97d4 --- /dev/null +++ b/lib/dns/tests/rbt_test.c @@ -0,0 +1,1437 @@ +/* + * 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 http://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 /* uintptr_t */ +#include + +#include +#include +#include +#include +#include +#include "dnstest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +typedef struct { + 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 void +delete_data(void *data, void *arg) { + UNUSED(arg); + + isc_mem_put(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(mctx, sizeof(*ctx)); + ATF_REQUIRE(ctx != NULL); + + ctx->rbt = NULL; + result = dns_rbt_create(mctx, delete_data, NULL, &ctx->rbt); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ctx->rbt_distances = NULL; + result = dns_rbt_create(mctx, delete_data, NULL, &ctx->rbt_distances); + ATF_REQUIRE_EQ(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(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *n = i + 1; + result = dns_rbt_addname(ctx->rbt, name, n); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + n = isc_mem_get(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *n = node_distances[i]; + result = dns_rbt_addname(ctx->rbt_distances, name, n); + ATF_REQUIRE_EQ(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(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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(*n, i + 1); + } +} + +ATF_TC(rbt_create); +ATF_TC_HEAD(rbt_create, tc) { + atf_tc_set_md_var(tc, "descr", "Test the creation of an rbt"); +} +ATF_TC_BODY(rbt_create, tc) { + isc_result_t result; + test_context_t *ctx; + bool tree_ok; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + check_test_data(ctx->rbt); + + tree_ok = dns__rbt_checkproperties(ctx->rbt); + ATF_CHECK_EQ(tree_ok, true); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_nodecount); +ATF_TC_HEAD(rbt_nodecount, tc) { + atf_tc_set_md_var(tc, "descr", "Test dns_rbt_nodecount() on a tree"); +} +ATF_TC_BODY(rbt_nodecount, tc) { + isc_result_t result; + test_context_t *ctx; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + ATF_CHECK_EQ(15, dns_rbt_nodecount(ctx->rbt)); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbtnode_get_distance); +ATF_TC_HEAD(rbtnode_get_distance, tc) { + atf_tc_set_md_var(tc, "descr", + "Test dns_rbtnode_get_distance() on a tree"); +} +ATF_TC_BODY(rbtnode_get_distance, tc) { + 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(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + dns_test_namefromstring(name_str, &fname); + name = dns_fixedname_name(&fname); + + dns_rbtnodechain_init(&chain, mctx); + + result = dns_rbt_findnode(ctx->rbt_distances, name, NULL, + &node, &chain, 0, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + while (node != NULL) { + const size_t *distance = (const size_t *) node->data; + if (distance != NULL) + ATF_CHECK_EQ(*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); + } + + ATF_CHECK_EQ(result, ISC_R_NOMORE); + + dns_rbtnodechain_invalidate(&chain); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_check_distance_random); +ATF_TC_HEAD(rbt_check_distance_random, tc) { + atf_tc_set_md_var(tc, "descr", + "Test tree balance, inserting names in random order"); +} +ATF_TC_BODY(rbt_check_distance_random, tc) { + /* 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. + */ + dns_rbt_t *mytree = NULL; + const unsigned int log_num_nodes = 16; + + int i; + isc_result_t result; + bool tree_ok; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(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(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *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_get(&v); + namebuf[j] = 'a' + (v % 26); + } + 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) */ + ATF_CHECK_EQ(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). + */ + ATF_CHECK((2U * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, true); + + dns_rbt_destroy(&mytree); + + dns_test_end(); +} + +ATF_TC(rbt_check_distance_ordered); +ATF_TC_HEAD(rbt_check_distance_ordered, tc) { + atf_tc_set_md_var(tc, "descr", + "Test tree balance, inserting names in sorted order"); +} +ATF_TC_BODY(rbt_check_distance_ordered, tc) { + /* 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. + */ + dns_rbt_t *mytree = NULL; + const unsigned int log_num_nodes = 16; + + int i; + isc_result_t result; + bool tree_ok; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(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(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + /* 1 (root . node) + (1 << log_num_nodes) */ + ATF_CHECK_EQ(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). + */ + ATF_CHECK((2U * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, true); + + dns_rbt_destroy(&mytree); + + dns_test_end(); +} + +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, mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + is_equal = strcmp(labelstr, nodestr) == 0 ? true : false; + + isc_mem_free(mctx, nodestr); + + return (is_equal); +} + +ATF_TC(rbt_insert); +ATF_TC_HEAD(rbt_insert, tc) { + atf_tc_set_md_var(tc, "descr", "Test insertion into a tree"); +} +ATF_TC_BODY(rbt_insert, tc) { + isc_result_t result; + test_context_t *ctx; + dns_rbtnode_t *node; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + /* Check node count before beginning. */ + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + ATF_CHECK_EQ(15, dns_rbt_nodecount(ctx->rbt)); + + /* Try to insert a node that doesn't exist. */ + node = NULL; + result = insert_helper(ctx->rbt, "0", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "0"), true); + + /* Node count must have increased. */ + ATF_CHECK_EQ(16, dns_rbt_nodecount(ctx->rbt)); + + /* Another. */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE(node != NULL); + ATF_CHECK_EQ(node->data, NULL); + + /* Node count must have increased. */ + ATF_CHECK_EQ(17, dns_rbt_nodecount(ctx->rbt)); + + /* Re-adding it should return EXISTS */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + ATF_CHECK_EQ(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + ATF_CHECK_EQ(17, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node d.e.f */ + node = NULL; + result = insert_helper(ctx->rbt, "k.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "k"), true); + + /* Node count must have incremented twice ("d.e.f" fissioned to + * "d" and "e.f", and the newly added "k"). + */ + ATF_CHECK_EQ(19, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node "g.h" */ + node = NULL; + result = insert_helper(ctx->rbt, "h", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "h"), true); + + /* Node count must have incremented ("g.h" fissioned to "g" and + * "h"). + */ + ATF_CHECK_EQ(20, dns_rbt_nodecount(ctx->rbt)); + + /* Add child domains */ + + node = NULL; + result = insert_helper(ctx->rbt, "m.p.w.y.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "m"), true); + ATF_CHECK_EQ(21, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "n.p.w.y.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "n"), true); + ATF_CHECK_EQ(22, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "l.a", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "l"), true); + ATF_CHECK_EQ(23, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "r.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + node = NULL; + result = insert_helper(ctx->rbt, "s.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(25, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "h.w.y.d.e.f", &node); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "m", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "nm", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "om", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "k", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "l", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "fe", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "ge", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "i", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "ae", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "n", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_remove); +ATF_TC_HEAD(rbt_remove, tc) { + atf_tc_set_md_var(tc, "descr", "Test removal from a tree"); +} +ATF_TC_BODY(rbt_remove, tc) { + /* + * 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. + */ + isc_result_t result; + size_t j; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * 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(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + /* Add node data */ + ATF_REQUIRE(node != NULL); + ATF_REQUIRE_EQ(node->data, NULL); + + n = isc_mem_get(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + } + + /* Check RB tree properties. */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, true); + + dns_rbtnodechain_init(&chain, mctx); + + /* 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); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(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; + } + + ATF_REQUIRE(node != NULL); + + n = (size_t *) node->data; + if (n != NULL) { + /* printf("n=%zu, i=%zu\n", *n, i); */ + ATF_CHECK_EQ(*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. */ + ATF_REQUIRE_EQ(node, NULL); + + dns_rbt_destroy(&mytree); + } + + dns_test_end(); +} + +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(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + + *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_get(&v); + namebuf[j] = 'a' + (v % 26); + } + 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(mctx, + namebuf); + ATF_REQUIRE(names[*names_count] != NULL); + *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; + + isc_random_get(&node); + + node %= *names_count; + + dns_test_namefromstring(names[node], &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_deletename(mytree, name, false); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + isc_mem_free(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, + unsigned int line) +{ + bool tree_ok; + + UNUSED(names); + + ATF_CHECK_EQ_MSG(names_count + 1, dns_rbt_nodecount(mytree), + "line:%u: %lu != %u", line, + (unsigned long)(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). + */ + ATF_CHECK((2 * 10) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, true); +} + +ATF_TC(rbt_insert_and_remove); +ATF_TC_HEAD(rbt_insert_and_remove, tc) { + atf_tc_set_md_var(tc, "descr", + "Test insert and remove in a loop"); +} +ATF_TC_BODY(rbt_insert_and_remove, tc) { + /* + * 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. + */ + isc_result_t result; + dns_rbt_t *mytree = NULL; + size_t *n; + /* + * We use an array for storing names instead of a set + * structure. It's slow, but works and is good enough for tests. + */ + char *names[1024]; + size_t names_count; + int i; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + n = isc_mem_get(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + result = dns_rbt_addname(mytree, dns_rootname, n); + ATF_REQUIRE_EQ(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; + isc_random_get(&num_names); + + if (names_count < 1024) { + num_names %= 1024 - names_count; + num_names++; + } else { + num_names = 0; + } + + insert_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count, __LINE__); + + isc_random_get(&num_names); + if (names_count > 0) { + num_names %= names_count; + num_names++; + } else { + num_names = 0; + } + + remove_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count, __LINE__); + } + + /* Remove the rest of the nodes */ + remove_nodes(mytree, names, &names_count, names_count); + check_tree(mytree, names, names_count, __LINE__); + + for (i = 0; i < 1024; i++) { + if (names[i] != NULL) { + isc_mem_free(mctx, names[i]); + } + } + + result = dns_rbt_deletename(mytree, dns_rootname, false); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, + "result: %s", isc_result_totext(result)); + ATF_CHECK_EQ_MSG(dns_rbt_nodecount(mytree), 0, + "%u != 0", dns_rbt_nodecount(mytree)); + + dns_rbt_destroy(&mytree); + + dns_test_end(); +} + +ATF_TC(rbt_findname); +ATF_TC_HEAD(rbt_findname, tc) { + atf_tc_set_md_var(tc, "descr", "findname return values"); +} +ATF_TC_BODY(rbt_findname, tc) { + 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(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + 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); + ATF_CHECK(dns_name_equal(foundname, name)); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + /* Now without EMPTYDATA */ + result = dns_rbt_findname(ctx->rbt, name, 0, + foundname, (void *) &n); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(result, DNS_R_PARTIALMATCH); + ATF_CHECK(dns_name_equal(foundname, dns_rootname)); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_addname); +ATF_TC_HEAD(rbt_addname, tc) { + atf_tc_set_md_var(tc, "descr", "addname return values"); +} +ATF_TC_BODY(rbt_addname, tc) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_fixedname_t fname; + dns_name_t *name = NULL; + size_t *n; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + n = isc_mem_get(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Now add again, should get ISC_R_EXISTS */ + n = isc_mem_get(mctx, sizeof(size_t)); + ATF_REQUIRE(n != NULL); + *n = 2; + result = dns_rbt_addname(ctx->rbt, name, n); + ATF_REQUIRE_EQ(result, ISC_R_EXISTS); + isc_mem_put(mctx, n, sizeof(size_t)); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_deletename); +ATF_TC_HEAD(rbt_deletename, tc) { + atf_tc_set_md_var(tc, "descr", "deletename return values"); +} +ATF_TC_BODY(rbt_deletename, tc) { + isc_result_t result; + test_context_t *ctx = NULL; + dns_fixedname_t fname; + dns_name_t *name = NULL; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + 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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_NOTFOUND); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_nodechain); +ATF_TC_HEAD(rbt_nodechain, tc) { + atf_tc_set_md_var(tc, "descr", "nodechain"); +} +ATF_TC_BODY(rbt_nodechain, tc) { + 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(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, true); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + dns_rbtnodechain_init(&chain, mctx); + + dns_test_namefromstring("a", &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_findnode(ctx->rbt, name, NULL, + &node, &chain, 0, NULL, NULL); + ATF_CHECK_EQ(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); + ATF_CHECK_EQ(result, DNS_R_NEWORIGIN); + ATF_CHECK_EQ(dns_name_countlabels(foundname), 0); + + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_NOMORE); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL); + ATF_CHECK_EQ(result, DNS_R_NEWORIGIN); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_NOMORE); + + result = dns_rbtnodechain_last(&chain, ctx->rbt, NULL, NULL); + ATF_CHECK_EQ(result, DNS_R_NEWORIGIN); + + result = dns_rbtnodechain_prev(&chain, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + dns_rbtnodechain_invalidate(&chain); + + test_context_teardown(ctx); + + dns_test_end(); +} + +#ifdef ISC_PLATFORM_USETHREADS +#ifdef DNS_BENCHMARK_TESTS + +/* + * 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. + */ + +ATF_TC(benchmark); +ATF_TC_HEAD(benchmark, tc) { + atf_tc_set_md_var(tc, "descr", "Benchmark RBT implementation"); +} + +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); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE(node != NULL); + ATF_CHECK_EQ(values[i], (intptr_t) node->data); + } + } + + return (NULL); +} + +ATF_TC_BODY(benchmark, tc) { + 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(tc); + + srandom(time(NULL)); + + debug_mem_record = false; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + 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(mctx, NULL, NULL, &mytree); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + node->data = (void *) (intptr_t) i; + } + + result = isc_time_now(&ts1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + nthreads = ISC_MIN(isc_os_ncpus(), 32); + nthreads = ISC_MAX(nthreads, 1); + for (i = 0; i < nthreads; i++) { + result = isc_thread_create(find_thread, mytree, &threads[i]); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + for (i = 0; i < nthreads; i++) { + result = isc_thread_join(threads[i], NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + result = isc_time_now(&ts2); + ATF_REQUIRE_EQ(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); + + dns_test_end(); +} + +#endif /* DNS_BENCHMARK_TESTS */ +#endif /* ISC_PLATFORM_USETHREADS */ + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, rbt_create); + ATF_TP_ADD_TC(tp, rbt_nodecount); + ATF_TP_ADD_TC(tp, rbtnode_get_distance); + ATF_TP_ADD_TC(tp, rbt_check_distance_random); + ATF_TP_ADD_TC(tp, rbt_check_distance_ordered); + ATF_TP_ADD_TC(tp, rbt_insert); + ATF_TP_ADD_TC(tp, rbt_remove); + ATF_TP_ADD_TC(tp, rbt_insert_and_remove); + ATF_TP_ADD_TC(tp, rbt_findname); + ATF_TP_ADD_TC(tp, rbt_addname); + ATF_TP_ADD_TC(tp, rbt_deletename); + ATF_TP_ADD_TC(tp, rbt_nodechain); +#ifdef ISC_PLATFORM_USETHREADS +#ifdef DNS_BENCHMARK_TESTS + ATF_TP_ADD_TC(tp, benchmark); +#endif /* DNS_BENCHMARK_TESTS */ +#endif /* ISC_PLATFORM_USETHREADS */ + + return (atf_no_error()); +} diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c new file mode 100644 index 0000000..6837516 --- /dev/null +++ b/lib/dns/tests/rdata_test.c @@ -0,0 +1,1214 @@ +/* + * 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 http://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 "dnstest.h" + +/***** + ***** Commonly used structures + *****/ + +/* + * An array of these structures is passed to check_text_ok(). + */ +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 */ + int lineno; /* source line defining this RDATA */ +}; +typedef struct text_ok text_ok_t; + +/* + * An array of these structures is passed to check_wire_ok(). + */ +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? */ + int lineno; /* source line defining this RDATA */ +}; +typedef struct wire_ok wire_ok_t; + +/***** + ***** Convenience macros for creating the above structures + *****/ + +#define TEXT_VALID_CHANGED(data_in, data_out) \ + { data_in, data_out, __LINE__ } +#define TEXT_VALID(data) { data, data, __LINE__ } +#define TEXT_INVALID(data) { data, NULL, __LINE__ } +#define TEXT_SENTINEL() TEXT_INVALID(NULL) + +#define VARGC(...) (sizeof((unsigned char[]){ __VA_ARGS__ })) +#define WIRE_TEST(ok, ...) { \ + { __VA_ARGS__ }, VARGC(__VA_ARGS__), \ + ok, __LINE__ \ + } +#define WIRE_VALID(...) WIRE_TEST(true, __VA_ARGS__) +#define WIRE_INVALID(...) WIRE_TEST(false, __VA_ARGS__) +#define WIRE_SENTINEL() WIRE_TEST(false) + +/***** + ***** Checking functions used by test cases + *****/ + +/* + * 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, int lineno) { + 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], hex[BUFSIZ]; + + rdata_struct = isc_mem_allocate(mctx, structsize); + ATF_REQUIRE(rdata_struct != NULL); + + /* + * Convert from uncompressed wire form into type-specific struct. + */ + result = dns_rdata_tostruct(rdata, rdata_struct, NULL); + ATF_REQUIRE_EQ_MSG(result, ISC_R_SUCCESS, + "%s (%u): dns_rdata_tostruct() failed", + dns_test_tohex(rdata->data, rdata->length, + hex, sizeof(hex)), + rdata->length); + /* + * 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); + ATF_REQUIRE_EQ_MSG(result, ISC_R_SUCCESS, + "line %d: %s (%u): dns_rdata_fromstruct() failed", + lineno, dns_test_tohex(rdata->data, rdata->length, + hex, sizeof(hex)), + rdata->length); + /* + * Ensure results are consistent. + */ + ATF_REQUIRE_EQ_MSG(isc_buffer_usedlength(&target), rdata->length, + "line %d: %s (%u): wire form data length changed " + "after converting to type-specific struct and back", + lineno, dns_test_tohex(rdata->data, rdata->length, + hex, sizeof(hex)), + rdata->length); + ATF_REQUIRE_EQ_MSG(memcmp(buf, rdata->data, rdata->length), 0, + "line %d: %s (%u): wire form data different after " + "converting to type-specific struct and back", + lineno, dns_test_tohex(rdata->data, rdata->length, + hex, sizeof(hex)), + rdata->length); + + isc_mem_free(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) +{ + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char buf_fromtext[1024]; + char buf_totext[1024] = { 0 }; + isc_buffer_t target; + isc_result_t result; + + /* + * 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); + /* + * Check whether result is as expected. + */ + if (text_ok->text_out != NULL) { + ATF_REQUIRE_EQ_MSG(result, ISC_R_SUCCESS, + "line %d: '%s': " + "expected success, got failure", + text_ok->lineno, text_ok->text_in); + } else { + ATF_REQUIRE_MSG(result != ISC_R_SUCCESS, + "line %d: '%s': " + "expected failure, got success", + text_ok->lineno, text_ok->text_in); + } + /* + * 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); + ATF_REQUIRE_EQ_MSG(result, ISC_R_SUCCESS, + "line %d: '%s': " + "failed to convert rdata back to text form", + text_ok->lineno, text_ok->text_in); + ATF_REQUIRE_EQ_MSG(strcmp(buf_totext, text_ok->text_out), 0, + "line %d: '%s': " + "converts back to '%s', expected '%s'", + text_ok->lineno, text_ok->text_in, buf_totext, + text_ok->text_out); + /* + * Perform two-way conversion checks between uncompressed wire form and + * type-specific struct. + */ + check_struct_conversions(&rdata, structsize, text_ok->lineno); +} + +/* + * 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) +{ + isc_buffer_t source, target; + unsigned char buf[1024]; + dns_decompress_t dctx; + isc_result_t result; + dns_rdata_t rdata; + char hex[BUFSIZ]; + + /* + * Set up len-octet buffer pointing at data. + */ + isc_buffer_constinit(&source, wire_ok->data, wire_ok->len); + isc_buffer_add(&source, wire_ok->len); + isc_buffer_setactive(&source, wire_ok->len); + /* + * Initialize target structures. + */ + isc_buffer_init(&target, buf, sizeof(buf)); + dns_rdata_init(&rdata); + /* + * Try converting wire 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); + /* + * Check whether result is as expected. + */ + if (wire_ok->ok) { + ATF_REQUIRE_EQ_MSG(result, ISC_R_SUCCESS, + "line %d: %s (%lu): " + "expected success, got failure", + wire_ok->lineno, + dns_test_tohex(wire_ok->data, wire_ok->len, + hex, sizeof(hex)), + (unsigned long)wire_ok->len); + } else { + ATF_REQUIRE_MSG(result != ISC_R_SUCCESS, + "line %d: %s (%lu): " + "expected failure, got success", + wire_ok->lineno, + dns_test_tohex(wire_ok->data, wire_ok->len, + hex, sizeof(hex)), + (unsigned long)wire_ok->len); + } + /* + * If data was parsed correctly, perform two-way conversion checks + * between uncompressed wire form and type-specific struct. + */ + if (result == ISC_R_SUCCESS) { + check_struct_conversions(&rdata, structsize, wire_ok->lineno); + } +} + +/* + * 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); + size_t i; + + /* + * Check all entries in the supplied array. + */ + for (i = 0; wire_ok[i].len != 0; i++) { + check_wire_ok_single(&wire_ok[i], rdclass, type, structsize); + } + + /* + * Check empty wire data. + */ + check_wire_ok_single(&empty_wire, rdclass, type, structsize); +} + +/* + * Test whether supplied sets of text form and/or wire form RDATA are handled + * as expected. This is just a helper function which should be the only + * function called for a test case using it, due to the use of dns_test_begin() + * and dns_test_end(). + * + * 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, + bool empty_ok, dns_rdataclass_t rdclass, + dns_rdatatype_t type, size_t structsize) +{ + isc_result_t result; + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + 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); + } + + dns_test_end(); +} + +/***** + ***** Individual unit tests + *****/ + +/* + * 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. + */ +ATF_TC(csync); +ATF_TC_HEAD(csync, tc) { + atf_tc_set_md_var(tc, "descr", "CSYNC RDATA manipulations"); +} +ATF_TC_BODY(csync, tc) { + 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(tc); + + check_rdata(text_ok, wire_ok, false, dns_rdataclass_in, + dns_rdatatype_csync, sizeof(dns_rdata_csync_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. + */ +ATF_TC(doa); +ATF_TC_HEAD(doa, tc) { + atf_tc_set_md_var(tc, "descr", "DOA RDATA manipulations"); +} +ATF_TC_BODY(doa, tc) { + 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(tc); + + check_rdata(text_ok, wire_ok, false, dns_rdataclass_in, + dns_rdatatype_doa, sizeof(dns_rdata_doa_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). + */ +ATF_TC(edns_client_subnet); +ATF_TC_HEAD(edns_client_subnet, tc) { + atf_tc_set_md_var(tc, "descr", + "OPT RDATA with EDNS Client Subnet manipulations"); +} +ATF_TC_BODY(edns_client_subnet, tc) { + 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(tc); + + check_rdata(NULL, wire_ok, true, dns_rdataclass_in, + dns_rdatatype_opt, sizeof(dns_rdata_opt_t)); +} + +/* + * Successful load test. + */ +ATF_TC(hip); +ATF_TC_HEAD(hip, tc) { + atf_tc_set_md_var(tc, "descr", "that a oversized HIP record will " + "be rejected"); +} +ATF_TC_BODY(hip, tc) { + unsigned char hipwire[DNS_RDATA_MAXLENGTH] = { + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x04, 0x41, 0x42, 0x43, 0x44, 0x00 }; + unsigned char buf[1024*1024]; + isc_buffer_t source, target; + dns_rdata_t rdata; + dns_decompress_t dctx; + isc_result_t result; + size_t i; + + UNUSED(tc); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * 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; + } + + isc_buffer_init(&source, hipwire, sizeof(hipwire)); + isc_buffer_add(&source, sizeof(hipwire)); + isc_buffer_setactive(&source, i); + isc_buffer_init(&target, buf, sizeof(buf)); + dns_rdata_init(&rdata); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY); + result = dns_rdata_fromwire(&rdata, dns_rdataclass_in, + dns_rdatatype_hip, &source, &dctx, + 0, &target); + dns_decompress_invalidate(&dctx); + ATF_REQUIRE_EQ(result, DNS_R_FORMERR); + + dns_test_end(); +} + +/* + * 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. + */ +ATF_TC(isdn); +ATF_TC_HEAD(isdn, tc) { + atf_tc_set_md_var(tc, "descr", "ISDN RDATA manipulations"); +} +ATF_TC_BODY(isdn, tc) { + 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(tc); + + check_rdata(NULL, wire_ok, false, dns_rdataclass_in, + dns_rdatatype_isdn, sizeof(dns_rdata_isdn_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. + */ +ATF_TC(nsec); +ATF_TC_HEAD(nsec, tc) { + atf_tc_set_md_var(tc, "descr", "NSEC RDATA manipulations"); +} +ATF_TC_BODY(nsec, tc) { + 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_INVALID() + }; + + UNUSED(tc); + + check_rdata(text_ok, wire_ok, false, dns_rdataclass_in, + dns_rdatatype_nsec, sizeof(dns_rdata_nsec_t)); +} + +/* + * NSEC3 tests. + * + * RFC 5155. + */ +ATF_TC(nsec3); +ATF_TC_HEAD(nsec3, tc) { + atf_tc_set_md_var(tc, "descr", "NSEC3 RDATA manipulations"); +} +ATF_TC_BODY(nsec3, tc) { + 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 IMQ912BREQP1POLAH3RMONG;UED541AS A RRSIG"), + 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(tc); + + check_rdata(text_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_nsec3, sizeof(dns_rdata_nsec3_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. + */ +ATF_TC(wks); +ATF_TC_HEAD(wks, tc) { + atf_tc_set_md_var(tc, "descr", "WKS RDATA manipulations"); +} +ATF_TC_BODY(wks, tc) { + 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(tc); + + check_rdata(NULL, wire_ok, false, dns_rdataclass_in, + dns_rdatatype_wks, sizeof(dns_rdata_in_wks_t)); +} + +/***** + ***** Main + *****/ + +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, csync); + ATF_TP_ADD_TC(tp, doa); + ATF_TP_ADD_TC(tp, edns_client_subnet); + ATF_TP_ADD_TC(tp, hip); + ATF_TP_ADD_TC(tp, isdn); + ATF_TP_ADD_TC(tp, nsec); + ATF_TP_ADD_TC(tp, nsec3); + ATF_TP_ADD_TC(tp, wks); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/rdataset_test.c b/lib/dns/tests/rdataset_test.c new file mode 100644 index 0000000..b055846 --- /dev/null +++ b/lib/dns/tests/rdataset_test.c @@ -0,0 +1,125 @@ +/* + * 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 http://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 "dnstest.h" + + +/* + * Individual unit tests + */ + +/* Successful load test */ +ATF_TC(trimttl); +ATF_TC_HEAD(trimttl, tc) { + atf_tc_set_md_var(tc, "descr", "dns_master_loadfile() loads a " + "valid master file and returns success"); +} +ATF_TC_BODY(trimttl, tc) { + isc_result_t result; + dns_rdataset_t rdataset, sigrdataset; + dns_rdata_rrsig_t rrsig; + isc_stdtime_t ttltimenow, ttltimeexpire; + + ttltimenow = 10000000; + ttltimeexpire = ttltimenow + 800; + + UNUSED(tc); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&sigrdataset); + + result = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimeexpire; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + true); + ATF_REQUIRE_EQ(rdataset.ttl, 800); + ATF_REQUIRE_EQ(sigrdataset.ttl, 800); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + true); + ATF_REQUIRE_EQ(rdataset.ttl, 120); + ATF_REQUIRE_EQ(sigrdataset.ttl, 120); + + rdataset.ttl = 900; + sigrdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + false); + ATF_REQUIRE_EQ(rdataset.ttl, 0); + ATF_REQUIRE_EQ(sigrdataset.ttl, 0); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimeexpire; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + true); + ATF_REQUIRE_EQ(rdataset.ttl, 800); + ATF_REQUIRE_EQ(sigrdataset.ttl, 800); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + true); + ATF_REQUIRE_EQ(rdataset.ttl, 120); + ATF_REQUIRE_EQ(sigrdataset.ttl, 120); + + sigrdataset.ttl = 900; + rdataset.ttl = 1000; + rrsig.timeexpire = ttltimenow - 200; + rrsig.originalttl = 1000; + + dns_rdataset_trimttl(&rdataset, &sigrdataset, &rrsig, ttltimenow, + false); + ATF_REQUIRE_EQ(rdataset.ttl, 0); + ATF_REQUIRE_EQ(sigrdataset.ttl, 0); + + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, trimttl); + + return (atf_no_error()); +} + diff --git a/lib/dns/tests/rdatasetstats_test.c b/lib/dns/tests/rdatasetstats_test.c new file mode 100644 index 0000000..0c2d5c5 --- /dev/null +++ b/lib/dns/tests/rdatasetstats_test.c @@ -0,0 +1,172 @@ +/* + * 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 http://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 "dnstest.h" + +/* + * Helper functions + */ +static void +set_typestats(dns_stats_t *stats, dns_rdatatype_t type, + bool stale) +{ + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = 0; + if (stale) attributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); + + attributes = DNS_RDATASTATSTYPE_ATTR_NXRRSET; + if (stale) attributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + which = DNS_RDATASTATSTYPE_VALUE(type, attributes); + dns_rdatasetstats_increment(stats, which); +} + +static void +set_nxdomainstats(dns_stats_t *stats, bool stale) { + dns_rdatastatstype_t which; + unsigned int attributes; + + attributes = DNS_RDATASTATSTYPE_ATTR_NXDOMAIN; + if (stale) attributes |= DNS_RDATASTATSTYPE_ATTR_STALE; + which = DNS_RDATASTATSTYPE_VALUE(0, attributes); + dns_rdatasetstats_increment(stats, which); +} + +#define ATTRIBUTE_SET(y) ((attributes & (y)) != 0) +static void +checkit1(dns_rdatastatstype_t which, uint64_t value, void *arg) { + unsigned int attributes; +#if debug + unsigned int type; +#endif + + UNUSED(which); + UNUSED(arg); + + attributes = DNS_RDATASTATSTYPE_ATTR(which); +#if debug + type = DNS_RDATASTATSTYPE_BASE(which); + + fprintf(stderr, "%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_STALE) ? "#" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ", + type, (unsigned)value); +#endif + if ((attributes & DNS_RDATASTATSTYPE_ATTR_STALE) == 0) + ATF_REQUIRE_EQ(value, 1); + else + ATF_REQUIRE_EQ(value, 0); +} + +static void +checkit2(dns_rdatastatstype_t which, uint64_t value, void *arg) { + unsigned int attributes; +#if debug + unsigned int type; +#endif + + UNUSED(which); + UNUSED(arg); + + attributes = DNS_RDATASTATSTYPE_ATTR(which); +#if debug + type = DNS_RDATASTATSTYPE_BASE(which); + + fprintf(stderr, "%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_STALE) ? "#" : " ", + ATTRIBUTE_SET(DNS_RDATASTATSTYPE_ATTR_NXDOMAIN) ? "X" : " ", + type, (unsigned)value); +#endif + if ((attributes & DNS_RDATASTATSTYPE_ATTR_STALE) == 0) + ATF_REQUIRE_EQ(value, 0); + else + ATF_REQUIRE_EQ(value, 1); +} +/* + * Individual unit tests + */ + +ATF_TC(rdatasetstats); +ATF_TC_HEAD(rdatasetstats, tc) { + atf_tc_set_md_var(tc, "descr", "test that rdatasetstats counters are properly set"); +} +ATF_TC_BODY(rdatasetstats, tc) { + unsigned int i; + dns_stats_t *stats = NULL; + isc_result_t result; + + UNUSED(tc); + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_rdatasetstats_create(mctx, &stats); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* First 256 types. */ + for (i = 0; i <= 255; i++) + set_typestats(stats, (dns_rdatatype_t)i, false); + /* Specials */ + set_typestats(stats, dns_rdatatype_dlv, false); + set_typestats(stats, (dns_rdatatype_t)1000, false); + set_nxdomainstats(stats, false); + + /* + * Check that all counters are set to appropriately. + */ + dns_rdatasetstats_dump(stats, checkit1, NULL, 1); + + /* First 256 types. */ + for (i = 0; i <= 255; i++) + set_typestats(stats, (dns_rdatatype_t)i, true); + /* Specials */ + set_typestats(stats, dns_rdatatype_dlv, true); + set_typestats(stats, (dns_rdatatype_t)1000, true); + set_nxdomainstats(stats, true); + + /* + * Check that all counters are set to appropriately. + */ + dns_rdatasetstats_dump(stats, checkit2, NULL, 1); + + dns_stats_detach(&stats); + dns_test_end(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, rdatasetstats); + return (atf_no_error()); +} diff --git a/lib/dns/tests/resolver_test.c b/lib/dns/tests/resolver_test.c new file mode 100644 index 0000000..518622d --- /dev/null +++ b/lib/dns/tests/resolver_test.c @@ -0,0 +1,228 @@ +/* + * 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 http://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 "dnstest.h" + +static dns_dispatchmgr_t *dispatchmgr = NULL; +static dns_dispatch_t *dispatch = NULL; +static dns_view_t *view = NULL; + + +static void +setup(void) { + isc_result_t result; + isc_sockaddr_t local; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_dispatchmgr_create(mctx, NULL, &dispatchmgr); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_makeview("view", &view); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_sockaddr_any(&local); + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, &local, + 4096, 100, 100, 100, 500, 0, 0, + &dispatch); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); +} + +static void +teardown(void) { + dns_dispatch_detach(&dispatch); + dns_view_detach(&view); + dns_dispatchmgr_destroy(&dispatchmgr); + dns_test_end(); +} + + +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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); +} + +static void +destroy_resolver(dns_resolver_t **resolverp) { + dns_resolver_shutdown(*resolverp); + dns_resolver_detach(resolverp); +} + +ATF_TC(create); +ATF_TC_HEAD(create, tc) { + atf_tc_set_md_var(tc, "descr", "dns_resolver_create"); +} +ATF_TC_BODY(create, tc) { + dns_resolver_t *resolver = NULL; + + UNUSED(tc); + + setup(); + mkres(&resolver); + destroy_resolver(&resolver); + teardown(); +} + +ATF_TC(gettimeout); +ATF_TC_HEAD(gettimeout, tc) { + atf_tc_set_md_var(tc, "descr", "dns_resolver_gettimeout"); +} +ATF_TC_BODY(gettimeout, tc) { + dns_resolver_t *resolver = NULL; + unsigned int timeout; + + UNUSED(tc); + + setup(); + mkres(&resolver); + + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK(timeout > 0); + + destroy_resolver(&resolver); + teardown(); +} + +ATF_TC(settimeout); +ATF_TC_HEAD(settimeout, tc) { + atf_tc_set_md_var(tc, "descr", "dns_resolver_settimeout"); +} +ATF_TC_BODY(settimeout, tc) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(tc); + + setup(); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, default_timeout + 1); + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK(timeout == default_timeout + 1); + + destroy_resolver(&resolver); + teardown(); +} + +ATF_TC(settimeout_default); +ATF_TC_HEAD(settimeout_default, tc) { + atf_tc_set_md_var(tc, "descr", "dns_resolver_settimeout to default"); +} +ATF_TC_BODY(settimeout_default, tc) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(tc); + + setup(); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, default_timeout + 10); + + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK_EQ(timeout, default_timeout + 10); + + dns_resolver_settimeout(resolver, 0); + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK_EQ(timeout, default_timeout); + + destroy_resolver(&resolver); + teardown(); +} + +ATF_TC(settimeout_belowmin); +ATF_TC_HEAD(settimeout_belowmin, tc) { + atf_tc_set_md_var(tc, "descr", + "dns_resolver_settimeout below minimum"); +} +ATF_TC_BODY(settimeout_belowmin, tc) { + dns_resolver_t *resolver = NULL; + unsigned int default_timeout, timeout; + + UNUSED(tc); + + setup(); + + mkres(&resolver); + + default_timeout = dns_resolver_gettimeout(resolver); + dns_resolver_settimeout(resolver, 9); + + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK_EQ(timeout, default_timeout); + + destroy_resolver(&resolver); + teardown(); +} + +ATF_TC(settimeout_overmax); +ATF_TC_HEAD(settimeout_overmax, tc) { + atf_tc_set_md_var(tc, "descr", "dns_resolver_settimeout over maximum"); +} +ATF_TC_BODY(settimeout_overmax, tc) { + dns_resolver_t *resolver = NULL; + unsigned int timeout; + + UNUSED(tc); + + setup(); + + mkres(&resolver); + + dns_resolver_settimeout(resolver, 4000000); + timeout = dns_resolver_gettimeout(resolver); + ATF_CHECK(timeout < 4000000 && timeout > 0); + + destroy_resolver(&resolver); + teardown(); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, create); + ATF_TP_ADD_TC(tp, gettimeout); + ATF_TP_ADD_TC(tp, settimeout); + ATF_TP_ADD_TC(tp, settimeout_default); + ATF_TP_ADD_TC(tp, settimeout_belowmin); + ATF_TP_ADD_TC(tp, settimeout_overmax); + return (atf_no_error()); +} diff --git a/lib/dns/tests/rsa_test.c b/lib/dns/tests/rsa_test.c new file mode 100644 index 0000000..fb207ef --- /dev/null +++ b/lib/dns/tests/rsa_test.c @@ -0,0 +1,312 @@ +/* + * 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 http://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 "dnstest.h" + +#include "../dst_internal.h" + +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + +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 +}; + +#ifndef PK11_MD5_DISABLE +static unsigned char sigmd5[256] = { + 0xc0, 0x99, 0x90, 0xd6, 0xea, 0xc1, 0x5f, 0xc7, + 0x23, 0x60, 0xfc, 0x13, 0x3d, 0xcc, 0xda, 0x93, + 0x19, 0xf7, 0x22, 0xa9, 0x55, 0xbe, 0x70, 0x3c, + 0x87, 0x24, 0x8a, 0x7e, 0xa7, 0x59, 0x58, 0xd3, + 0x0e, 0x7c, 0x50, 0x3c, 0x81, 0x0f, 0x7a, 0x2b, + 0xb1, 0x94, 0x21, 0x87, 0xe4, 0x87, 0xcd, 0x2b, + 0xb9, 0xf1, 0xb8, 0x26, 0xc1, 0x02, 0xf4, 0x30, + 0x83, 0x41, 0x89, 0x61, 0xcc, 0x3d, 0xe3, 0x0f, + 0xec, 0x4a, 0x74, 0x95, 0x10, 0x65, 0xac, 0xd1, + 0xf5, 0x95, 0xe9, 0x99, 0xa8, 0x45, 0x98, 0x99, + 0xb5, 0xfd, 0x7a, 0x78, 0x80, 0xe5, 0x00, 0x33, + 0xa5, 0x54, 0xe5, 0xa3, 0xc0, 0x1b, 0x6c, 0xb9, + 0x77, 0x52, 0x6f, 0xe5, 0x85, 0xa8, 0xfa, 0x45, + 0x78, 0x49, 0x14, 0xa0, 0x10, 0x58, 0x40, 0x80, + 0x90, 0xc6, 0x55, 0x52, 0x6d, 0x46, 0x58, 0x50, + 0x3d, 0x5e, 0x40, 0x25, 0x51, 0x7c, 0xc4, 0x12, + 0x87, 0x2d, 0x7b, 0x10, 0xcd, 0x80, 0xec, 0x5d, + 0x27, 0x15, 0x09, 0x37, 0x1f, 0xa7, 0x86, 0x15, + 0xd1, 0xdd, 0xf1, 0x86, 0x1e, 0x42, 0x3a, 0xf9, + 0x5a, 0xed, 0x33, 0x07, 0xa9, 0x98, 0x08, 0x79, + 0xc5, 0xa4, 0x09, 0x95, 0x6e, 0x12, 0xfe, 0xee, + 0x49, 0x61, 0xe0, 0x99, 0xaa, 0x34, 0xa5, 0xca, + 0x82, 0xd3, 0x9b, 0x1c, 0x5b, 0x79, 0xf5, 0x0e, + 0x2c, 0x6c, 0x3b, 0x48, 0xd1, 0xbc, 0xd0, 0xda, + 0x73, 0xba, 0xe1, 0x81, 0x48, 0x27, 0x39, 0x2f, + 0x98, 0x77, 0x08, 0xb3, 0xf7, 0x38, 0x28, 0x6d, + 0x02, 0x56, 0xfa, 0x31, 0xbb, 0x14, 0x81, 0x6b, + 0x3c, 0x24, 0xa2, 0x68, 0x7a, 0x0a, 0x53, 0xbd, + 0x9d, 0x57, 0xd0, 0x99, 0x10, 0x28, 0x78, 0x69, + 0x31, 0x93, 0xa4, 0x73, 0x8d, 0x1a, 0xe4, 0xdc, + 0x0c, 0x15, 0xb8, 0x51, 0xd8, 0x66, 0x6a, 0x95, + 0x56, 0x17, 0x0a, 0x45, 0x72, 0xb5, 0xb8, 0xc4 +}; +#endif + +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 +}; + +ATF_TC(isc_rsa_verify); +ATF_TC_HEAD(isc_rsa_verify, tc) { + atf_tc_set_md_var(tc, "descr", "RSA verify"); +} +ATF_TC_BODY(isc_rsa_verify, tc) { + 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(tc); + + ret = dns_test_begin(NULL, false); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + 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); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + ret = dst_key_fromfile(name, 29235, DST_ALG_RSASHA1, + DST_TYPE_PUBLIC, "./", mctx, &key); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + /* RSASHA1 */ + + ret = dst_context_create3(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, &ctx); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = sigsha1; + r.length = 256; + ret = dst_context_verify(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + + /* RSAMD5 */ + +#ifndef PK11_MD5_DISABLE + key->key_alg = DST_ALG_RSAMD5; + + ret = dst_context_create3(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, &ctx); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = sigmd5; + r.length = 256; + ret = dst_context_verify(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); +#endif + + /* RSASHA256 */ + + key->key_alg = DST_ALG_RSASHA256; + + ret = dst_context_create3(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, &ctx); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = sigsha256; + r.length = 256; + ret = dst_context_verify(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + + /* RSASHA512 */ + + key->key_alg = DST_ALG_RSASHA512; + + ret = dst_context_create3(key, mctx, DNS_LOGCATEGORY_DNSSEC, + false, &ctx); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = d; + r.length = 10; + ret = dst_context_adddata(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + r.base = sigsha512; + r.length = 256; + ret = dst_context_verify(ctx, &r); + ATF_REQUIRE_EQ(ret, ISC_R_SUCCESS); + + dst_context_destroy(&ctx); + + + dst_key_free(&key); + dns_test_end(); +} +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping RSA test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("RSA not available"); +} +#endif +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + ATF_TP_ADD_TC(tp, isc_rsa_verify); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + return (atf_no_error()); +} + diff --git a/lib/dns/tests/sigs_test.c b/lib/dns/tests/sigs_test.c new file mode 100644 index 0000000..20fd4b6 --- /dev/null +++ b/lib/dns/tests/sigs_test.c @@ -0,0 +1,479 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include + +#include + +#if defined(OPENSSL) || defined(PKCS11CRYPTO) +#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" + +/*% + * 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, + const updatesigs_test_params_t *test, 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. + */ + ATF_CHECK_EQ_MSG(expected->op, found->op, + "test \"%s\": tuple %zu: " + "expected op %d, found %d", + test->description, index, + expected->op, found->op); + + /* + * Check owner name. + */ + expected_name = dns_fixedname_initname(&expected_fname); + result = dns_name_fromstring(expected_name, expected->owner, 0, mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + dns_name_format(&found->name, found_name, sizeof(found_name)); + ATF_CHECK_MSG(dns_name_equal(expected_name, &found->name), + "test \"%s\": tuple %zu: " + "expected owner \"%s\", found \"%s\"", + test->description, index, + expected->owner, found_name); + + /* + * Check TTL. + */ + ATF_CHECK_EQ_MSG(expected->ttl, found->ttl, + "test \"%s\": tuple %zu: " + "expected TTL %u, found %u", + test->description, index, + 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); + ATF_REQUIRE_EQ(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); + ATF_REQUIRE_EQ(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. + */ + ATF_CHECK_EQ_MSG(found->rdata.type, dns_rdatatype_rrsig, + "test \"%s\": tuple %zu: " + "expected type RRSIG, found %s", + test->description, index, + found_type); + 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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_buffer_init(&typebuf, found_covers, sizeof(found_covers)); + result = dns_rdatatype_totext(rrsig.covered, &typebuf); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ_MSG(expected_type, rrsig.covered, + "test \"%s\": tuple %zu: " + "expected RRSIG to cover %s, found covers %s", + test->description, index, + expected->type, found_covers); + break; + default: + /* + * Found tuple must be of type 'expected->type'. + */ + ATF_CHECK_EQ_MSG(expected_type, found->rdata.type, + "test \"%s\": tuple %zu: " + "expected type %s, found %s", + test->description, index, + expected->type, found_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); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Create a diff representing the supplied changes. + */ + result = dns_test_difffromchanges(&raw_diff, test->changes); + ATF_REQUIRE_EQ(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(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, now, + true, false, &zonediff); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, + "test \"%s\": expected success, got %s", + test->description, isc_result_totext(result)); + ATF_CHECK_MSG(ISC_LIST_EMPTY(raw_diff.tuples), + "test \"%s\": raw diff was not emptied", + test->description); + ATF_CHECK_MSG(!ISC_LIST_EMPTY(zone_diff.tuples), + "test \"%s\": zone diff was not created", + test->description); + + /* + * 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++; + } + + ATF_REQUIRE_EQ_MSG(tuples_expected, tuples_found, + "test \"%s\": " + "expected %zu tuples in output, found %zu", + test->description, + 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, test, 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); +} + +ATF_TC(updatesigs); +ATF_TC_HEAD(updatesigs, tc) { + atf_tc_set_md_var(tc, "descr", "dns__zone_updatesigs() tests"); +} +ATF_TC_BODY(updatesigs, tc) { + 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; + + result = dns_test_begin(NULL, true); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Prepare a zone along with its signing keys. + */ + + result = dns_test_makezone("example", &zone, NULL, false); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_test_loaddb(&db, dns_dbtype_zone, "example", + "testdata/master/master18.data"); + ATF_REQUIRE_EQ(result, DNS_R_SEENINCLUDE); + + result = dns_zone_setkeydirectory(zone, "testkeys"); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + isc_stdtime_get(&now); + result = dns__zone_findkeys(zone, db, NULL, now, mctx, DNS_MAXZONEKEYS, + zone_keys, &nkeys); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE_EQ(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); + + dns_test_end(); +} +#else +ATF_TC(untested); +ATF_TC_HEAD(untested, tc) { + atf_tc_set_md_var(tc, "descr", "skipping dns__zone_updatesigs() test"); +} +ATF_TC_BODY(untested, tc) { + UNUSED(tc); + atf_tc_skip("DNSSEC support not compiled in"); +} +#endif + +ATF_TP_ADD_TCS(tp) { +#if defined(OPENSSL) || defined(PKCS11CRYPTO) + ATF_TP_ADD_TC(tp, updatesigs); +#else + ATF_TP_ADD_TC(tp, untested); +#endif + + return (atf_no_error()); +} diff --git a/lib/dns/tests/testdata/db/data.db b/lib/dns/tests/testdata/db/data.db new file mode 100644 index 0000000..9ac043e --- /dev/null +++ b/lib/dns/tests/testdata/db/data.db @@ -0,0 +1,20 @@ +; 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 http://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..81c0abe --- /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 http://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..6eb87ab --- /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 http://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..d57d586 --- /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 http://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..65f12dd --- /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 http://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.+001+00002.key b/lib/dns/tests/testdata/dst/Ktest.+001+00002.key new file mode 100644 index 0000000..a8b4b4d --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+001+00002.key @@ -0,0 +1 @@ +test. IN DNSKEY 49152 2 1 diff --git a/lib/dns/tests/testdata/dst/Ktest.+001+54622.key b/lib/dns/tests/testdata/dst/Ktest.+001+54622.key new file mode 100644 index 0000000..b0277e3 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+001+54622.key @@ -0,0 +1 @@ +test. IN DNSKEY 257 3 1 AQPQjwSpaVzxIgRCpiUoozUQKGh2oX8NIFKDOvtxK+tn536OZg2cROKTlgGEHXJK9YHfW/6nzQULTVpb63P+SQMmjCCidb8IYyhItixRztVeJQ== diff --git a/lib/dns/tests/testdata/dst/Ktest.+001+54622.private b/lib/dns/tests/testdata/dst/Ktest.+001+54622.private new file mode 100644 index 0000000..c97ac30 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+001+54622.private @@ -0,0 +1,10 @@ +Private-key-format: v1.2 +Algorithm: 1 (RSA) +Modulus: 0I8EqWlc8SIEQqYlKKM1EChodqF/DSBSgzr7cSvrZ+d+jmYNnETik5YBhB1ySvWB31v+p80FC01aW+tz/kkDJowgonW/CGMoSLYsUc7VXiU= +PublicExponent: Aw== +PrivateExponent: iwoDG5uTS2wC1xluGxd4tXBFpGuqCMA3AidSS3Kc7++ptEQJEtiXC9kfCJMvZhGfQLaujft2OgrmkcuDVtPIbQWEENhyJhb4Lk82kFXbfus= +Prime1: /rSKuzcZY7R5cY2YWD4CiBNyj9WJMq1wWmBnb9+5M08nTl5E9NW5qQ== +Prime2: 0Z5shXQYd16E2Gs6e5WxtO0Oqlly2KkSqXohwTQWDWTb8Pw0WTZmHQ== +Exponent1: qc2x0iS7l82mS7O65X6sWrehtTkGIcj1kZWaSpUmIjTE3umDTePRGw== +Exponent2: i77zA6K6+j8DOvIm/Q52eJ4JxuZMkHC3G6bBK3gOs5iSoKgi5iREEw== +Coefficient: 3+wYZB0SJad7z2EsjzgbSlg6CawoaOvrROGSbwSiW5DCsMFROudOTw== diff --git a/lib/dns/tests/testdata/dst/Ktest.+003+23616.key b/lib/dns/tests/testdata/dst/Ktest.+003+23616.key new file mode 100644 index 0000000..958d585 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+003+23616.key @@ -0,0 +1 @@ +test. IN DNSKEY 16641 3 3 ANp1//lqDlEfTavcFI+cyudNfgEz73V/K7fSDvkA0eDYcGg/kSvEjAEO/oLWCERltkuC55ZcM/mSv17WF1d/wR6kww/pLI9eXwkjftAYqs5sNxk+mbEGl6zwve9wq5z7IoTY5/J4l7XLCKftg/wGvrzXQhggIkRvEh3myhxd+ouILcpfvTIthWlTKiH59tSJpmgmiSMTE7nDYaf10iVRWN6DMSprgejiH05/fpmyZAt44tyAh4m1wXS5u4tam1PXDJYJozn7EfQ8e2weIv1yC+t6PHSx diff --git a/lib/dns/tests/testdata/dst/Ktest.+003+23616.private b/lib/dns/tests/testdata/dst/Ktest.+003+23616.private new file mode 100644 index 0000000..5781c9d --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+003+23616.private @@ -0,0 +1,7 @@ +Private-key-format: v1.2 +Algorithm: 3 (DSA) +Prime(p): 73V/K7fSDvkA0eDYcGg/kSvEjAEO/oLWCERltkuC55ZcM/mSv17WF1d/wR6kww/pLI9eXwkjftAYqs5sNxk+mQ== +Subprime(q): 2nX/+WoOUR9Nq9wUj5zK501+ATM= +Base(g): sQaXrPC973CrnPsihNjn8niXtcsIp+2D/Aa+vNdCGCAiRG8SHebKHF36i4gtyl+9Mi2FaVMqIfn21ImmaCaJIw== +Private_value(x): Nky4tvIwg6xlcyeHXr4k2DEZg0E= +Public_value(y): ExO5w2Gn9dIlUVjegzEqa4Ho4h9Of36ZsmQLeOLcgIeJtcF0ubuLWptT1wyWCaM5+xH0PHtsHiL9cgvrejx0sQ== diff --git a/lib/dns/tests/testdata/dst/Ktest.+003+49667.key b/lib/dns/tests/testdata/dst/Ktest.+003+49667.key new file mode 100644 index 0000000..fb73f57 --- /dev/null +++ b/lib/dns/tests/testdata/dst/Ktest.+003+49667.key @@ -0,0 +1 @@ +test. IN DNSKEY 49152 2 3 diff --git a/lib/dns/tests/testdata/dst/test1.data b/lib/dns/tests/testdata/dst/test1.data new file mode 100644 index 0000000..b1a9bf5 --- /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). + + ::= | " " + + ::=